diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..3192b70d8 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +liberapay: FreshRSS diff --git a/.stylelintignore b/.stylelintignore new file mode 100644 index 000000000..83aaae275 --- /dev/null +++ b/.stylelintignore @@ -0,0 +1,4 @@ +# ignore SASS-generated CSS +p/themes/Ansum/*.css +p/themes/Mapco/*.css +p/themes/Swage/*.css diff --git a/.stylelintrc b/.stylelintrc new file mode 100644 index 000000000..c8efe2ca9 --- /dev/null +++ b/.stylelintrc @@ -0,0 +1,74 @@ +{ + "extends": "stylelint-config-recommended-scss", + "plugins": [ + "stylelint-order", + "stylelint-scss" + ], + "rules": { + "at-rule-empty-line-before": [ + "always", { + "ignoreAtRules": [ "after-comment", "else" ] + } + ], + "at-rule-name-space-after": [ + "always", { + "ignoreAtRules": [ "after-comment" ] + } + ], + "block-closing-brace-newline-after": [ + "always", { + "ignoreAtRules": [ "if", "else" ] + } + ], + "block-closing-brace-newline-before": "always-multi-line", + "block-opening-brace-newline-after": "always-multi-line", + "block-opening-brace-space-before": "always", + "color-hex-case": "lower", + "color-hex-length": "short", + "color-no-invalid-hex": true, + "declaration-colon-space-after": "always", + "declaration-colon-space-before": "never", + "indentation": "tab", + "no-descending-specificity": null, + "no-eol-whitespace": true, + "property-no-vendor-prefix": true, + "rule-empty-line-before": [ + "always", + "except": [ + "after-single-line-comment", + "first-nested" + ] + ], + "order/properties-order": [ + "margin", + "padding", + "background", + "display", + "float", + "max-width", + "width", + "max-height", + "height", + "color", + "font", + "font-family", + "font-size", + "border", + "border-top", + "border-top-color", + "border-right", + "border-right-color", + "border-bottom", + "border-bottom-color", + "border-left", + "border-left-color", + "border-radius", + "box-shadow" + ], + "scss/at-else-closing-brace-newline-after": "always-last-in-chain", + "scss/at-else-closing-brace-space-after": "always-intermediate", + "scss/at-else-empty-line-before": "never", + "scss/at-if-closing-brace-newline-after": "always-last-in-chain", + "scss/at-if-closing-brace-space-after": "always-intermediate" + } +} diff --git a/.travis.yml b/.travis.yml index 82f50f1ae..67889f166 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,6 @@ language: php php: - - 5.4 - - 5.5 - 5.6 - - 7.0 - - 7.1 - - 7.2 - 7.3 install: @@ -14,7 +9,7 @@ install: script: - phpenv rehash - - find . -not -path "./lib/JSON.php" -name \*.php -print0 | xargs -0 -n1 -P4 php -l 1>/dev/null 2>php-l-results + - find . -name \*.php -print0 | xargs -0 -n1 -P4 php -l 1>/dev/null 2>php-l-results - if [ -s php-l-results ]; then cat php-l-results; exit 1; fi - | if [[ $VALIDATE_STANDARD == yes ]]; then @@ -32,9 +27,6 @@ env: matrix: fast_finish: true include: - # PHP 5.3 only runs on Ubuntu 12.04 (precise), not 14.04 (trusty) - - php: "5.3" - dist: precise - php: "7.2" env: CHECK_TRANSLATION=yes VALIDATE_STANDARD=no - language: node_js @@ -45,12 +37,15 @@ matrix: env: - HADOLINT="$HOME/hadolint" install: - - npm install jshint + - npm install --save-dev jshint stylelint stylelint-order stylelint-scss stylelint-config-recommended-scss - curl -sLo "$HADOLINT" $(curl -s https://api.github.com/repos/hadolint/hadolint/releases/latest?access_token="$GITHUB_TOKEN" | jq -r '.assets | .[] | select(.name=="hadolint-Linux-x86_64") | .browser_download_url') && chmod 700 ${HADOLINT} script: - node_modules/jshint/bin/jshint . + # check SCSS separately + - stylelint --syntax scss "**/*.scss" + - stylelint "**/*.css" - bash tests/shellchecks.sh - git ls-files --exclude='*Dockerfile*' --ignored | xargs --max-lines=1 "$HADOLINT" allow_failures: - env: CHECK_TRANSLATION=yes VALIDATE_STANDARD=no - - dist: precise + diff --git a/CHANGELOG.md b/CHANGELOG.md index f33712f73..1070566dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,64 @@ # FreshRSS changelog +## 2019-10-31 FreshRSS 1.15.0 + +* CLI + * Command line to export/import any database to/from SQLite [#2496](https://github.com/FreshRSS/FreshRSS/pull/2496) +* Features + * New archiving method, including maximum number of articles per feed, and settings at feed, category, global levels [#2335](https://github.com/FreshRSS/FreshRSS/pull/2335) + * New option to control category sort order [#2592](https://github.com/FreshRSS/FreshRSS/pull/2592) + * New option to display article authors underneath the article title [#2487](https://github.com/FreshRSS/FreshRSS/pull/2487) + * Add e-mail capability [#2476](https://github.com/FreshRSS/FreshRSS/pull/2476), [#2481](https://github.com/FreshRSS/FreshRSS/pull/2481) + * Ability to define default user settings in `data/config-user.custom.php` [#2490](https://github.com/FreshRSS/FreshRSS/pull/2490) + * Including default feeds [#2515](https://github.com/FreshRSS/FreshRSS/pull/2515) + * Allow recreating users if they still exist in database [#2555](https://github.com/FreshRSS/FreshRSS/pull/2555) + * Add optional database connection URI parameters [#2549](https://github.com/FreshRSS/FreshRSS/issues/2549), [#2559](https://github.com/FreshRSS/FreshRSS/pull/2559) + * Allow longer articles with MySQL / MariaDB (up to 16MB compressed instead of 64kB) [#2448](https://github.com/FreshRSS/FreshRSS/issues/2448) + * Add support for terms of service [#2520](https://github.com/FreshRSS/FreshRSS/pull/2520) + * Add sharing with [Lemmy](https://github.com/dessalines/lemmy) [#2510](https://github.com/FreshRSS/FreshRSS/pull/2510) +* API + * Add support for [Reeder-4](https://www.reederapp.com/) client [#2513](https://github.com/FreshRSS/FreshRSS/issues/2513) +* Compatibility + * Require at least PHP 5.6+ [#2495](https://github.com/FreshRSS/FreshRSS/pull/2495), [#2527](https://github.com/FreshRSS/FreshRSS/pull/2527), [#2585](https://github.com/FreshRSS/FreshRSS/pull/2585) + * Require `php-json` and remove remove `JSON.php` fallback [#2528](https://github.com/FreshRSS/FreshRSS/pull/2528) + * Require at least PostgreSQL 9.5+ [#2554](https://github.com/FreshRSS/FreshRSS/pull/2554) +* Deployment + * Take advantage of `mod_authz_core` instead of `mod_access_compat` when running on Apache 2.4+ [#2461](https://github.com/FreshRSS/FreshRSS/pull/2461) + * Docker: Ubuntu image updated to 19.10 with PHP 7.3.8 and Apache 2.4.41 [#2577](https://github.com/FreshRSS/FreshRSS/pull/2577) + * Docker: Alpine image updated to 3.10 with PHP 7.3.11 and Apache 2.4.41 [#2238](https://github.com/FreshRSS/FreshRSS/pull/2238) + * Docker: Increase default PHP POST/upload size to ease importing ZIP files [#2563](https://github.com/FreshRSS/FreshRSS/pull/2563) + * New environment variable `COPY_LOG_TO_SYSLOG` to see all logs at once in e.g. `docker logs -f` [#2591](https://github.com/FreshRSS/FreshRSS/pull/2591) + * New environment variable `FRESHRSS_ENV` to control Minz development mode [#2508](https://github.com/FreshRSS/FreshRSS/pull/2508) + * Git ignore `themes/xTheme-*` [#2511](https://github.com/FreshRSS/FreshRSS/pull/2511) +* Bug fixing + * Fix missing PHP `opcache` package in Docker Alpine [#2498](https://github.com/FreshRSS/FreshRSS/pull/2498) + * Fix IE11 / Edge keyboard compatibility [#2507](https://github.com/FreshRSS/FreshRSS/pull/2507) + * Use `` instead of `` for RSS 2.0 outputs [#2542](https://github.com/FreshRSS/FreshRSS/pull/2542) + * Fix PostgreSQL and SQLite database size estimation [#2562](https://github.com/FreshRSS/FreshRSS/pull/2562) + * Fix broken SVG icons in Swage theme [#2568](https://github.com/FreshRSS/FreshRSS/issues/2568), [#2571](https://github.com/FreshRSS/FreshRSS/pull/2571) +* Security + * Fix referrer vulnerability when opening an article original link with a shortcut [#2506](https://github.com/FreshRSS/FreshRSS/pull/2506) + * Slight refactoring of access check [#2471](https://github.com/FreshRSS/FreshRSS/pull/2471) +* UI + * Optimize dynamic favicon for HiDPI screens [#2539](https://github.com/FreshRSS/FreshRSS/pull/2539) + * Hide the admin checkbox if user is not admin [#2531](https://github.com/FreshRSS/FreshRSS/pull/2531) +* I18n + * Add Slovak [#2497](https://github.com/FreshRSS/FreshRSS/pull/2497) + * Improve Dutch [#2503](https://github.com/FreshRSS/FreshRSS/pull/2503) + * Improve Occitan [#2519](https://github.com/FreshRSS/FreshRSS/pull/2519), [#2583](https://github.com/FreshRSS/FreshRSS/pull/2583), [#2603](https://github.com/FreshRSS/FreshRSS/pull/2603) +* Extensions + * Additional hooks [#2482](https://github.com/FreshRSS/FreshRSS/pull/2482) + * New call to change the layout [#2467](https://github.com/FreshRSS/FreshRSS/pull/2467) +* Misc. + * Make our JavaScript compatible with LibreJS [#2576](https://github.com/FreshRSS/FreshRSS/pull/2576) + * PDO (database) refactoring for code simplification [#2522](https://github.com/FreshRSS/FreshRSS/pull/2522) + * Automatic check of CSS syntax in Travis CI [#2477](https://github.com/FreshRSS/FreshRSS/pull/2477) + * Make our Travis greener by reducing redundant tests [#2589](https://github.com/FreshRSS/FreshRSS/pull/2589) + * Remove support for sharing with Google+ [#2464](https://github.com/FreshRSS/FreshRSS/pull/2464) + * Redirect connected users accessing registration page [#2530](https://github.com/FreshRSS/FreshRSS/pull/2530) + * Add Makefile [#2481](https://github.com/FreshRSS/FreshRSS/pull/2481) + + ## 2019-07-25 FreshRSS 1.14.3 * UI @@ -280,8 +339,8 @@ * API * Add support for Fever compatible API, enabling more clients [#1406](https://github.com/FreshRSS/FreshRSS/pull/1406) - * iOS: [Fiery Feeds](https://itunes.apple.com/app/fiery-feeds-rss-reader/id1158763303), [Unread](https://itunes.apple.com/app/unread-rss-reader/id1252376153) - * MacOS: [Readkit](https://itunes.apple.com/app/readkit/id588726889) + * iOS: [Fiery Feeds](https://apps.apple.com/app/fiery-feeds-rss-reader/id1158763303), [Unread](https://apps.apple.com/app/unread-rss-reader/id1252376153) + * MacOS: [Readkit](https://apps.apple.com/app/readkit/id588726889) * Features * Several per-feed options (implemented in JSON) [#1838](https://github.com/FreshRSS/FreshRSS/pull/1838) * Mark updated articles as read [#891](https://github.com/FreshRSS/FreshRSS/issues/891) @@ -478,7 +537,7 @@ * Simplified Chinese [#1541](https://github.com/FreshRSS/FreshRSS/pull/1541) * Improve English [#1465](https://github.com/FreshRSS/FreshRSS/pull/1465) * Improve Dutch [#1559](https://github.com/FreshRSS/FreshRSS/pull/1559) - * Added Spanish language [#1631] (https://github.com/FreshRSS/FreshRSS/pull/1631/) + * Added Spanish language [#1631] (https://github.com/FreshRSS/FreshRSS/pull/1631/) * Security * Do not require write access to check availability of new versions [#1450](https://github.com/FreshRSS/FreshRSS/issues/1450) * Misc. @@ -504,7 +563,7 @@ * New command `./cli/reconfigure.php` to update an existing installation [#1439](https://github.com/FreshRSS/FreshRSS/pull/1439) * Many CLI improvements [#1447](https://github.com/FreshRSS/FreshRSS/pull/1447) * More information (number of feeds, articles, etc.) in `./cli/user-info.php` - * Better idempotency of `./cli/do-install.php` and language parameter [#1449](https://github.com/FreshRSS/FreshRSS/issues/1449) + * Better idempotency of `./cli/do-install.php` and language parameter [#1449](https://github.com/FreshRSS/FreshRSS/issues/1449) * Bug fixing * Fix several CLI issues [#1445](https://github.com/FreshRSS/FreshRSS/issues/1445) * Fix CLI install bugs with SQLite [#1443](https://github.com/FreshRSS/FreshRSS/issues/1443), [#1448](https://github.com/FreshRSS/FreshRSS/issues/1448) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index df56fad76..ef00fb310 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,7 @@ If you have to create a new ticket, try to apply the following advices: - 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) + + Your storage system (SQLite, MySQL, MariaDB, PostgreSQL) + If possible, the related logs (PHP logs and FreshRSS logs under `data/users/your_user/log.txt`) ## Fix a bug diff --git a/CREDITS.md b/CREDITS.md index 81d647b84..d52d3c72c 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -13,6 +13,7 @@ People are sorted by name so please keep this order. * [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), [Web](https://amaury.carrade.eu/) * [Anton Smirnov](https://github.com/sandfoxme): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:sandfoxme), [Web](http://sandfox.me/) +* [ArthurHoaro](https://github.com/ArthurHoaro): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:ArthurHoaro), [Web](http://sandfox.me/) * [ASMfreaK](https://github.com/ASMfreaK): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=ASMfreaK) * [Benjamin Bouvier](https://github.com/bnjbvr): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:bnjbvr), [Web](https://benj.me/) * [chemical1979](https://github.com/chemical1979): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=chemical1979) @@ -26,6 +27,7 @@ People are sorted by name so please keep this order. * [ealdraed](https://github.com/ealdraed): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=ealdraed) * [Fake4d](https://github.com/Fake4d): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:Fake4d) * [Frans de Jonge](https://github.com/Frenzie): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=Frenzie), [Web](http://fransdejonge.com/) +* [Gaurav Thakur](https://github.com/notfoss): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:notfoss), [Web](https://blog.notfoss.com/) * [Gregor Nathanael Meyer](https://github.com/spackmat): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:spackmat), [Web](https://der-meyer.de) * [gsongsong](https://github.com/gsongsong): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:gsongsong) * [Guillaume Fillon](https://github.com/kokaz): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:kokaz), [Web](http://www.guillaume-fillon.com/) @@ -36,6 +38,7 @@ People are sorted by name so please keep this order. * [Jaussoin Timothée](https://github.com/edhelas): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=edhelas), [Web](http://edhelas.movim.eu/) * [jlefler](https://github.com/jlefler): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:jlefler) * [Jonas Östanbäck](https://github.com/cez81): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=cez81) +* [Joris Kinable](https://github.com/jkinable): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:jkinable) * [Julien Reichardt](https://github.com/j8r): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=j8r), [Web](https://blog.jrei.ch/) * [Kevin Papst](https://github.com/kevinpapst): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=kevinpapst), [Web](http://www.kevinpapst.de/) * [Leepic](https://github.com/Leepic): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:Leepic) @@ -52,16 +55,21 @@ People are sorted by name so please keep this order. * [Nicolas Elie](https://github.com/nicolaselie): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=nicolaselie) * [Nicolas Frandeboeuf](https://github.com/nicofrand): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=nicofrand), [Web](https://nicofrand.ey) * [Nicolas Lœuillet](https://github.com/nicosomb): [contributions](https://github.com/FreshRSS/documentation/commits?author=nicosomb), [Web](http://www.loeuillet.org/) +* [Offerel](https://github.com/Offerel): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:Offerel) * [Olivier Dossmann](https://github.com/blankoworld): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=blankoworld), [Web](https://olivier.dossmann.net) * [Patrick Crandol](https://github.com/pattems): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:pattems) * [Paulius Šukys](https://github.com/psukys): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:psukys), [Web](http://sukys.eu) * [perrinjerome](https://github.com/perrinjerome): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:perrinjerome) +* [Peter Stoinov](https://github.com/stoinov): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:stoinov), [Web](https://stoinov.com) +* [Pim Snel](https://github.com/mipmip): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is%3Apr+author%3Amipmip), [Web](https://www.pimsnel.com) * [plopoyop](https://github.com/plopoyop): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=plopoyop) * [primaeval](https://github.com/primaeval): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:primaeval) * [purexo](https://github.com/purexo): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:purexo), [Web](https://purexo.mom/) * [Quentin Dufour](https://github.com/superboum): [contributions](https://github.com/FreshRSS/documentation/commits?author=superboum), [Web](http://quentin.dufour.io/) * [Quentin Pagès](https://github.com/Quenty31): [contributions](https://github.com/FreshRSS/documentation/commits?author=Quenty31) * [Ramón Cutanda](https://github.com/rcutanda): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:rcutanda) +* [Robert Kaussow](https://github.com/xoxys): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:xoxys), [Web](https://geeklabor.de/) +* [rocka](https://github.com/rocka): [contributions](https://github.com/FreshRSS/FreshRss/commits/dev?author=rocka) * [romibi](https://github.com/romibi): [contributions](https://github.com/FreshRSS/FreshRSS/commits/dev?author=romibi) * [Rosemary Le Faive](https://github.com/rosiel): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:rosiel) * [Sandro Jäckel](https://github.com/SuperSandro2000): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:SuperSandro2000), [Web](https://supersandro.de/) @@ -72,7 +80,10 @@ People are sorted by name so please keep this order. * [Thomas Citharel](https://github.com/tcitworld): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:tomgue), [Web](https://www.tcit.fr/) * [Thomas Guesnon](https://github.com/patjennings): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:patjennings), [Web](http://www.thomasguesnon.fr/) * [thomas-gt](https://github.com/thomas-gt): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:thomas-gt) +* [Tibor Repček](https://github.com/tiborepcek): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:tiborepcek) * [tomgue](https://github.com/tomgue): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=tomgue) * [Twilek-de](https://github.com/Twilek-de): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:Twilek-de) * [Uncovery](https://github.com/uncovery): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:uncovery) * [Wanabo](https://github.com/Wanabo): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=Wanabo) +* [wtoscer](https://github.com/wtoscer): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:wtoscer) +* [Yamakuni](https://github.com/Yamakuni): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:Yamakuni), [Web](https://ofanch.me/) diff --git a/Docker/Dockerfile b/Docker/Dockerfile index 8693bb0f7..6dbebccb0 100644 --- a/Docker/Dockerfile +++ b/Docker/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:19.04 +FROM ubuntu:19.10 ENV TZ UTC SHELL ["/bin/bash", "-o", "pipefail", "-c"] @@ -43,12 +43,15 @@ RUN a2dismod -f alias autoindex negotiation status && \ RUN sed -r -i "/^\s*(CustomLog|ErrorLog|Listen) /s/^/#/" /etc/apache2/apache2.conf && \ sed -r -i "/^\s*Listen /s/^/#/" /etc/apache2/ports.conf && \ touch /var/www/FreshRSS/Docker/env.txt && \ - echo "17,47 * * * * . /var/www/FreshRSS/Docker/env.txt; \ + echo "7,37 * * * * . /var/www/FreshRSS/Docker/env.txt; \ su www-data -s /bin/sh -c 'php /var/www/FreshRSS/app/actualize_script.php' \ 2>> /proc/1/fd/2 > /tmp/FreshRSS.log" | crontab - +ENV COPY_LOG_TO_SYSLOG On ENV COPY_SYSLOG_TO_STDERR On ENV CRON_MIN '' +ENV FRESHRSS_ENV '' + ENTRYPOINT ["./Docker/entrypoint.sh"] EXPOSE 80 diff --git a/Docker/Dockerfile-Alpine b/Docker/Dockerfile-Alpine index ac5d74a43..4afad0949 100644 --- a/Docker/Dockerfile-Alpine +++ b/Docker/Dockerfile-Alpine @@ -5,7 +5,7 @@ SHELL ["/bin/ash", "-eo", "pipefail", "-c"] RUN apk add --no-cache \ apache2 php7-apache2 \ php7 php7-curl php7-gmp php7-intl php7-mbstring php7-xml php7-zip \ - php7-ctype php7-dom php7-fileinfo php7-iconv php7-json php7-session php7-simplexml php7-xmlreader php7-zlib \ + php7-ctype php7-dom php7-fileinfo php7-iconv php7-json php7-opcache php7-session php7-simplexml php7-xmlreader php7-zlib \ php7-pdo_sqlite php7-pdo_mysql php7-pdo_pgsql RUN mkdir -p /var/www/FreshRSS /run/apache2/ @@ -43,8 +43,11 @@ RUN rm -f /etc/apache2/conf.d/languages.conf /etc/apache2/conf.d/info.conf \ su apache -s /bin/sh -c 'php /var/www/FreshRSS/app/actualize_script.php' \ 2>> /proc/1/fd/2 > /tmp/FreshRSS.log" | crontab - +ENV COPY_LOG_TO_SYSLOG On ENV COPY_SYSLOG_TO_STDERR On ENV CRON_MIN '' +ENV FRESHRSS_ENV '' + ENTRYPOINT ["./Docker/entrypoint.sh"] EXPOSE 80 diff --git a/Docker/Dockerfile-QEMU-ARM b/Docker/Dockerfile-QEMU-ARM index 4d81a23f0..30b1e205b 100644 --- a/Docker/Dockerfile-QEMU-ARM +++ b/Docker/Dockerfile-QEMU-ARM @@ -1,7 +1,7 @@ # Only relevant for Docker Hub or QEMU multi-architecture builds. # Prefer the normal `Dockerfile` if you are building manually on the targeted architecture. -FROM arm32v7/ubuntu:19.04 +FROM arm32v7/ubuntu:19.10 # Requires ./hooks/* COPY ./Docker/qemu-arm-* /usr/bin/ @@ -59,8 +59,11 @@ RUN update-ca-certificates -f # Useful with the `--squash` build option RUN rm /usr/bin/qemu-* /var/www/FreshRSS/Docker/qemu-* +ENV COPY_LOG_TO_SYSLOG On ENV COPY_SYSLOG_TO_STDERR On ENV CRON_MIN '' +ENV FRESHRSS_ENV '' + ENTRYPOINT ["./Docker/entrypoint.sh"] EXPOSE 80 diff --git a/Docker/README.md b/Docker/README.md index c93178ff7..15510a220 100644 --- a/Docker/README.md +++ b/Docker/README.md @@ -17,7 +17,7 @@ sh get-docker.sh ## Create an isolated network ```sh -sudo docker network create freshrss-network +docker network create freshrss-network ``` ## Recommended: use [Træfik](https://traefik.io/) reverse proxy @@ -25,18 +25,18 @@ It is a good idea to use a reverse proxy on your host server, providing HTTPS. Here is the recommended configuration using automatic [Let’s Encrypt](https://letsencrypt.org/) HTTPS certificates and with a redirection from HTTP to HTTPS. See further below for alternatives. ```sh -sudo docker volume create traefik-letsencrypt -sudo docker volume create traefik-tmp +docker volume create traefik-letsencrypt +docker volume create traefik-tmp # Just change your e-mail address in the command below: -sudo docker run -d --restart unless-stopped --log-opt max-size=10m \ +docker run -d --restart unless-stopped --log-opt max-size=10m \ -v traefik-letsencrypt:/etc/traefik/acme \ -v traefik-tmp:/tmp \ -v /var/run/docker.sock:/var/run/docker.sock:ro \ --net freshrss-network \ -p 80:80 \ -p 443:443 \ - --name traefik traefik --docker \ + --name traefik traefik:1.7 --docker \ --loglevel=info \ --entryPoints='Name:http Address::80 Compress:true Redirect.EntryPoint:https' \ --entryPoints='Name:https Address::443 Compress:true TLS TLS.MinVersion:VersionTLS12 TLS.SniStrict:true TLS.CipherSuites:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA' \ @@ -48,17 +48,17 @@ sudo docker run -d --restart unless-stopped --log-opt max-size=10m \ See [more information about Docker and Let’s Encrypt in Træfik](https://docs.traefik.io/user-guide/docker-and-lets-encrypt/). -## Run FreshRSS +## Run FreshRSS Example using the built-in refresh cron job (see further below for alternatives). You must first chose a domain (DNS) or sub-domain, e.g. `freshrss.example.net`. > **N.B.:** Default images are for x64 (Intel, AMD) platforms. For ARM (e.g. Raspberry Pi), use the `*-arm` tags. For other platforms, see the section *Build Docker image* further below. ```sh -sudo docker volume create freshrss-data +docker volume create freshrss-data # Remember to replace freshrss.example.net by your server address in the command below: -sudo docker run -d --restart unless-stopped --log-opt max-size=10m \ +docker run -d --restart unless-stopped --log-opt max-size=10m \ -v freshrss-data:/var/www/FreshRSS/data \ -e 'CRON_MIN=4,34' \ -e TZ=Europe/Paris \ @@ -79,16 +79,16 @@ sudo docker run -d --restart unless-stopped --log-opt max-size=10m \ This already works with a built-in **SQLite** database (easiest), but more powerful databases are supported: -### [MySQL](https://hub.docker.com/_/mysql/) +### [MySQL](https://hub.docker.com/_/mysql/) or [MariaDB](https://hub.docker.com/_/mariadb) ```sh -# If you already have a MySQL instance running, just attach it to the FreshRSS network: -sudo docker network connect freshrss-network mysql +# If you already have a MySQL or MariaDB instance running, just attach it to the FreshRSS network: +docker network connect freshrss-network mysql # Otherwise, start a new MySQL instance, remembering to change the passwords: -sudo docker volume create mysql-data -sudo docker run -d --restart unless-stopped --log-opt max-size=10m \ +docker volume create mysql-data +docker run -d --restart unless-stopped --log-opt max-size=10m \ -v mysql-data:/var/lib/mysql \ - -e MYSQL_ROOT_PASSWORD=rootpass + -e MYSQL_ROOT_PASSWORD=rootpass \ -e MYSQL_DATABASE=freshrss \ -e MYSQL_USER=freshrss \ -e MYSQL_PASSWORD=pass \ @@ -99,11 +99,11 @@ sudo docker run -d --restart unless-stopped --log-opt max-size=10m \ ### [PostgreSQL](https://hub.docker.com/_/postgres/) ```sh # If you already have a PostgreSQL instance running, just attach it to the FreshRSS network: -sudo docker network connect freshrss-network postgres +docker network connect freshrss-network postgres # Otherwise, start a new PostgreSQL instance, remembering to change the passwords: -sudo docker volume create pgsql-data -sudo docker run -d --restart unless-stopped --log-opt max-size=10m \ +docker volume create pgsql-data +docker run -d --restart unless-stopped --log-opt max-size=10m \ -v pgsql-data:/var/lib/postgresql/data \ -e POSTGRES_DB=freshrss \ -e POSTGRES_USER=freshrss \ @@ -121,14 +121,14 @@ or use the command line described below. ```sh # Rebuild an image (see build section above) or get a new online version: -sudo docker pull freshrss/freshrss +docker pull freshrss/freshrss # And then -sudo docker stop freshrss -sudo docker rename freshrss freshrss_old +docker stop freshrss +docker rename freshrss freshrss_old # See the run section above for the full command -sudo docker run ... --name freshrss freshrss/freshrss +docker run ... --name freshrss freshrss/freshrss # If everything is working, delete the old container -sudo docker rm freshrss_old +docker rm freshrss_old ``` @@ -153,17 +153,16 @@ Note that prebuilt images are less recent and only available for x64 (Intel, AMD # First time only git clone https://github.com/FreshRSS/FreshRSS.git -cd ./FreshRSS/ +cd FreshRSS/ git pull -sudo docker pull ubuntu:18.10 -sudo docker build --tag freshrss/freshrss -f Docker/Dockerfile . +docker build --pull --tag freshrss/freshrss -f Docker/Dockerfile . ``` ## Command line ```sh -sudo docker exec --user apache -it freshrss php ./cli/list-users.php +docker exec --user apache -it freshrss php ./cli/list-users.php ``` See the [CLI documentation](../cli/) for all the other commands. @@ -173,14 +172,14 @@ See the [CLI documentation](../cli/) for all the other commands. ```sh # See FreshRSS data if you use Docker volume -sudo docker volume inspect freshrss-data +docker volume inspect freshrss-data sudo ls /var/lib/docker/volumes/freshrss-data/_data/ # See Web server logs -sudo docker logs -f freshrss +docker logs -f freshrss # Enter inside FreshRSS docker container -sudo docker exec -it freshrss sh +docker exec -it freshrss sh ## See FreshRSS root inside the container ls /var/www/FreshRSS/ ``` @@ -198,7 +197,7 @@ containing a valid cron minute definition such as `'13,43'` (recommended) or `'* Not passing the `CRON_MIN` environment variable – or setting it to empty string – will disable the cron daemon. ```sh -sudo docker run ... \ +docker run ... \ -e 'CRON_MIN=13,43' \ --name freshrss freshrss/freshrss ``` @@ -221,7 +220,7 @@ See cron option 1 for customising the cron schedule. #### For the Ubuntu image (default) ```sh -sudo docker run -d --restart unless-stopped --log-opt max-size=10m \ +docker run -d --restart unless-stopped --log-opt max-size=10m \ -v freshrss-data:/var/www/FreshRSS/data \ -e 'CRON_MIN=17,47' \ --net freshrss-network \ @@ -231,7 +230,7 @@ sudo docker run -d --restart unless-stopped --log-opt max-size=10m \ #### For the Alpine image ```sh -sudo docker run -d --restart unless-stopped --log-opt max-size=10m \ +docker run -d --restart unless-stopped --log-opt max-size=10m \ -v freshrss-data:/var/www/FreshRSS/data \ -e 'CRON_MIN=27,57' \ --net freshrss-network \ @@ -239,6 +238,22 @@ sudo docker run -d --restart unless-stopped --log-opt max-size=10m \ crond -f -d 6 ``` +## Development mode + +To contribute to FreshRSS development, you can use one of the Docker images to run and serve the PHP code, +while reading the source code from your local (git) directory, like the following example: + +```sh +cd /path-to-local/FreshRSS/ +docker run --rm -p 8080:80 -e TZ=Europe/Paris -e FRESHRSS_ENV=development \ + -v $(pwd):/var/www/FreshRSS \ + freshrss/freshrss:dev +``` + +This will start a server on port 8080, based on your local PHP code, which will show the logs directly in your terminal. +Press Control+c to exit. + +The `FRESHRSS_ENV=development` environment variable increases the level of logging and ensures that errors are displayed. ## More deployment options @@ -248,7 +263,7 @@ Changes in Apache `.htaccess` files are applied when restarting the container. In particular, if you want FreshRSS to use HTTP-based login (instead of the easier Web form login), you can mount your own `./FreshRSS/p/i/.htaccess`: ``` -sudo docker run ... +docker run ... -v /your/.htaccess:/var/www/FreshRSS/p/i/.htaccess \ -v /your/.htpasswd:/var/www/FreshRSS/data/.htpasswd \ ... @@ -276,7 +291,7 @@ A [docker-compose.yml](docker-compose.yml) file is given as an example, using Po You can then launch the stack (FreshRSS + PostgreSQL) with: ```sh -sudo docker-compose up -d +docker-compose up -d ``` ### Alternative reverse proxy using [nginx](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/) @@ -313,7 +328,7 @@ server { } location /freshrss/ { - proxy_pass http://freshrss/; + proxy_pass http://freshrss; add_header X-Frame-Options SAMEORIGIN; add_header X-XSS-Protection "1; mode=block"; proxy_redirect off; diff --git a/Docker/docker-compose.yml b/Docker/docker-compose.yml index 8a8ad270b..1c0a79ab8 100644 --- a/Docker/docker-compose.yml +++ b/Docker/docker-compose.yml @@ -1,38 +1,31 @@ -version: '2.3' +version: "3" services: - postgresql: - image: postgres:latest + freshrss_postgresql: + image: postgres restart: unless-stopped volumes: - - '/path/to/pgsql-data:/var/lib/postgresql/data' + - pgsql_data:/var/lib/postgresql/data environment: - - POSTGRES_USER=freshrss - - POSTGRES_PASSWORD=password - - POSTGRES_DB=freshrss + - POSTGRES_USER=freshrss + - POSTGRES_PASSWORD=freshrss + - POSTGRES_DB=freshrss freshrss: - image: freshrss/freshrss:latest + image: freshrss/freshrss restart: unless-stopped + ports: + - "8080:80" depends_on: - - postgresql - networks: - - web - - default + - freshrss_postgresql volumes: - - '/your/local/directory/data:/var/www/FreshRSS/data' - labels: - - "traefik.backend=freshrss" - - "traefik.docker.network=web" - - "traefik.frontend.rule=Host:rss.example.com" - - "traefik.enable=true" - - "traefik.default.protocol=http" - - "traefik.frontend.entryPoints=http,https" - - "traefik.port=80" + - freshrss_data:/var/www/FreshRSS/data environment: - CRON_MIN=*/20 + - TZ=Europe/Copenhagen + labels: + - "traefik.port=80" -networks: - web: - external: true - +volumes: + pgsql_data: + freshrss_data: diff --git a/Docker/entrypoint.sh b/Docker/entrypoint.sh index bb0e1bde0..02338c35e 100755 --- a/Docker/entrypoint.sh +++ b/Docker/entrypoint.sh @@ -6,10 +6,13 @@ chown -R :www-data . chmod -R g+r . && chmod -R g+w ./data/ find /etc/php*/ -name php.ini -exec sed -r -i "\\#^;?date.timezone#s#^.*#date.timezone = $TZ#" {} \; +find /etc/php*/ -name php.ini -exec sed -r -i "\\#^;?post_max_size#s#^.*#post_max_size = 32M#" {} \; +find /etc/php*/ -name php.ini -exec sed -r -i "\\#^;?upload_max_filesize#s#^.*#upload_max_filesize = 32M#" {} \; if [ -n "$CRON_MIN" ]; then ( echo "export TZ=$TZ" + echo "export COPY_LOG_TO_SYSLOG=$COPY_LOG_TO_SYSLOG" echo "export COPY_SYSLOG_TO_STDERR=$COPY_SYSLOG_TO_STDERR" ) >/var/www/FreshRSS/Docker/env.txt crontab -l | sed -r "\\#FreshRSS#s#^[^ ]+ #$CRON_MIN #" | crontab - diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..7239775f6 --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +.DEFAULT_GOAL := help + +ifndef TAG + TAG=dev-alpine +endif + +ifeq ($(findstring alpine,$(TAG)),alpine) + DOCKERFILE=Dockerfile-Alpine +else ifeq ($(findstring arm,$(TAG)),arm) + DOCKERFILE=Dockerfile-QEMU-ARM +else + DOCKERFILE=Dockerfile +endif + +.PHONY: build +build: ## Build a Docker image + docker build \ + --pull \ + --tag freshrss/freshrss:$(TAG) \ + -f Docker/$(DOCKERFILE) . + +.PHONY: start +start: ## Start the development environment (use Docker) + docker run \ + --rm \ + -v $(shell pwd):/var/www/FreshRSS:z \ + -p 8080:80 \ + -e FRESHRSS_ENV=development \ + --name freshrss-dev \ + freshrss/freshrss:$(TAG) + +.PHONY: stop +stop: ## Stop FreshRSS container if any + docker stop freshrss-dev + +.PHONY: help +help: + @grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' diff --git a/README.fr.md b/README.fr.md index 157979f93..889b72380 100644 --- a/README.fr.md +++ b/README.fr.md @@ -5,7 +5,7 @@ * [English version](README.md) # FreshRSS -FreshRSS est un agrégateur de flux RSS à auto-héberger à l’image de [Leed](http://leed.idleman.fr/) ou de [Kriss Feed](https://tontof.net/kriss/feed/). +FreshRSS est un agrégateur de flux RSS à auto-héberger à l’image de [Leed](https://github.com/LeedRSS/Leed) ou de [Kriss Feed](https://tontof.net/kriss/feed/). Il se veut léger et facile à prendre en main tout en étant un outil puissant et paramétrable. @@ -43,10 +43,10 @@ FreshRSS n’est fourni avec aucune garantie. * 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.8+ (PHP 5.4+ recommandé, et PHP 5.5+ pour les performances, ou PHP 7+ pour d’encore meilleures performances) - * Requis : [cURL](https://secure.php.net/curl), [DOM](https://secure.php.net/dom), [XML](https://secure.php.net/xml), [session](https://secure.php.net/session), [ctype](https://secure.php.net/ctype), et [PDO_MySQL](https://secure.php.net/pdo-mysql) ou [PDO_SQLite](https://secure.php.net/pdo-sqlite) ou [PDO_PGSQL](https://secure.php.net/pdo-pgsql) - * Recommandés : [JSON](https://secure.php.net/json), [GMP](https://secure.php.net/gmp) (pour accès API sur plateformes < 64 bits), [IDN](https://secure.php.net/intl.idn) (pour les noms de domaines internationalisés), [mbstring](https://secure.php.net/mbstring) (pour le texte Unicode), [iconv](https://secure.php.net/iconv) (pour conversion d’encodages), [ZIP](https://secure.php.net/zip) (pour import/export), [zlib](https://secure.php.net/zlib) (pour les flux compressés) -* MySQL 5.5.3+ (recommandé), ou SQLite 3.7.4+, ou PostgreSQL 9.2+ +* PHP 5.6+ (PHP 7+ recommandé pour de meilleures performances) + * Requis : [cURL](https://www.php.net/curl), [DOM](https://www.php.net/dom), [JSON](https://www.php.net/json), [XML](https://www.php.net/xml), [session](https://www.php.net/session), [ctype](https://www.php.net/ctype), et [PDO_MySQL](https://www.php.net/pdo-mysql) ou [PDO_SQLite](https://www.php.net/pdo-sqlite) ou [PDO_PGSQL](https://www.php.net/pdo-pgsql) + * Recommandés : [GMP](https://www.php.net/gmp) (pour accès API sur plateformes < 64 bits), [IDN](https://www.php.net/intl.idn) (pour les noms de domaines internationalisés), [mbstring](https://www.php.net/mbstring) (pour le texte Unicode), [iconv](https://www.php.net/iconv) (pour conversion d’encodages), [ZIP](https://www.php.net/zip) (pour import/export), [zlib](https://www.php.net/zlib) (pour les flux compressés) +* MySQL 5.5.3+ ou équivalent MariaDB, ou SQLite 3.7.4+, ou PostgreSQL 9.5+ # Téléchargement @@ -121,7 +121,7 @@ Voir la [documentation de la ligne de commande](cli/README.md) pour plus de dét ## 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.5+ recommandé) +* En utilisant l’identification par formulaire (requiert JavaScript) * 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](https://httpd.apache.org/docs/trunk/howto/auth.html) * Créer dans ce cas un fichier `./p/i/.htaccess` avec un fichier `.htpasswd` correspondant. @@ -187,8 +187,11 @@ Tout client supportant une API de type Google Reader ; Sélection : * [EasyRSS](https://github.com/Alkarex/EasyRSS) (Libre, [F-Droid](https://f-droid.org/fr/packages/org.freshrss.easyrss/)) * GNU/Linux * [FeedReader 2.0+](https://jangernert.github.io/FeedReader/) (Libre) +* iOS + * [Reeder](https://www.reederapp.com/) (Commercial) * MacOS * [Vienna RSS](http://www.vienna-rss.com/) (Libre) + * [Reeder](https://www.reederapp.com/) (Commercial) ## Via l’API compatible Fever @@ -199,11 +202,10 @@ Tout client supportant une API de type Fever ; Sélection : * Android * [Readably](https://play.google.com/store/apps/details?id=com.isaiasmatewos.readably) (Propriétaire) * iOS - * [Fiery Feeds](https://itunes.apple.com/app/fiery-feeds-rss-reader/id1158763303) (Propriétaire) - * [Unread](https://itunes.apple.com/app/unread-rss-reader/id1252376153) (Propriétaire) - * [Reeder-3](https://itunes.apple.com/app/reeder-3/id697846300) (Propriétaire) + * [Fiery Feeds](https://apps.apple.com/app/fiery-feeds-rss-reader/id1158763303) (Propriétaire) + * [Unread](https://apps.apple.com/app/unread-rss-reader/id1252376153) (Commercial) * MacOS - * [Readkit](https://itunes.apple.com/app/readkit/id588726889) (Propriétaire) + * [Readkit](https://apps.apple.com/app/readkit/id588726889) (Commercial) # Bibliothèques incluses @@ -213,12 +215,11 @@ Tout client supportant une API de type Fever ; Sélection : * [jQuery](https://jquery.com/) * [lib_opml](https://github.com/marienfressinaud/lib_opml) * [flotr2](http://www.humblesoftware.com/flotr2) +* [PHPMailer](https://github.com/PHPMailer/PHPMailer) ## Uniquement pour certaines options ou configurations * [bcrypt.js](https://github.com/dcodeIO/bcrypt.js) * [phpQuery](https://github.com/phpquery/phpquery) -* [Services_JSON](https://pear.php.net/pepr/pepr-proposal-show.php?id=198) -* [password_compat](https://github.com/ircmaxell/password_compat) diff --git a/README.md b/README.md index b91571e3e..54bc54c30 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ * [Version française](README.fr.md) # FreshRSS -FreshRSS is a self-hosted RSS feed aggregator like [Leed](http://leed.idleman.fr/) or [Kriss Feed](https://tontof.net/kriss/feed/). +FreshRSS is a self-hosted RSS feed aggregator like [Leed](https://github.com/LeedRSS/Leed) or [Kriss Feed](https://tontof.net/kriss/feed/). It is lightweight, easy to work with, powerful, and customizable. @@ -43,10 +43,10 @@ FreshRSS comes with absolutely no warranty. * 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.8+ (PHP 5.4+ recommended, and PHP 5.5+ for performance, or PHP 7 for even higher performance) - * Required extensions: [cURL](https://secure.php.net/curl), [DOM](https://secure.php.net/dom), [XML](https://secure.php.net/xml), [session](https://secure.php.net/session), [ctype](https://secure.php.net/ctype), and [PDO_MySQL](https://secure.php.net/pdo-mysql) or [PDO_SQLite](https://secure.php.net/pdo-sqlite) or [PDO_PGSQL](https://secure.php.net/pdo-pgsql) - * Recommended extensions: [JSON](https://secure.php.net/json), [GMP](https://secure.php.net/gmp) (for API access on 32-bit platforms), [IDN](https://secure.php.net/intl.idn) (for Internationalized Domain Names), [mbstring](https://secure.php.net/mbstring) (for Unicode strings), [iconv](https://secure.php.net/iconv) (for charset conversion), [ZIP](https://secure.php.net/zip) (for import/export), [zlib](https://secure.php.net/zlib) (for compressed feeds) -* MySQL 5.5.3+ (recommended), or SQLite 3.7.4+, or PostgreSQL 9.2+ +* PHP 5.6+ (PHP 7+ recommended for higher performance) + * Required extensions: [cURL](https://www.php.net/curl), [DOM](https://www.php.net/dom), [JSON](https://www.php.net/json), [XML](https://www.php.net/xml), [session](https://www.php.net/session), [ctype](https://www.php.net/ctype), and [PDO_MySQL](https://www.php.net/pdo-mysql) or [PDO_SQLite](https://www.php.net/pdo-sqlite) or [PDO_PGSQL](https://www.php.net/pdo-pgsql) + * Recommended extensions: [GMP](https://www.php.net/gmp) (for API access on 32-bit platforms), [IDN](https://www.php.net/intl.idn) (for Internationalized Domain Names), [mbstring](https://www.php.net/mbstring) (for Unicode strings), [iconv](https://www.php.net/iconv) (for charset conversion), [ZIP](https://www.php.net/zip) (for import/export), [zlib](https://www.php.net/zlib) (for compressed feeds) +* MySQL 5.5.3+ or MariaDB equivalent, or SQLite 3.7.4+, or PostgreSQL 9.5+ # Releases @@ -76,73 +76,6 @@ See the [list of releases](../../releases). More detailed information about installation and server configuration can be found in [our documentation](https://freshrss.github.io/FreshRSS/en/admins/02_Installation.html). -### 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 #Apache modules - -# Example for Ubuntu >= 16.04, Debian >= 9 Stretch -sudo apt install php php-curl php-gmp php-intl php-mbstring php-sqlite3 php-xml php-zip -sudo apt install libapache2-mod-php #For Apache -sudo apt install mysql-server mysql-client php-mysql #Optional MySQL database -sudo apt install postgresql php-pgsql #Optional PostgreSQL database - -# Restart Web server -sudo service apache2 restart - -# For FreshRSS itself (git is optional if you manually download the installation files) -cd /usr/share/ -sudo apt-get install git -sudo git clone https://github.com/FreshRSS/FreshRSS.git -cd FreshRSS - -# If you want to use the development version of FreshRSS -sudo git checkout -b dev origin/dev - -# Set the rights so that your Web server can access the files -sudo chown -R :www-data . && sudo chmod -R g+r . && sudo chmod -R g+w ./data/ -# If you would like to allow updates from the Web interface -sudo chmod -R g+w . - -# 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) -# or use the Command-Line Interface - -# Update to a newer version of FreshRSS with git -cd /usr/share/FreshRSS -sudo git pull -sudo chown -R :www-data . && sudo chmod -R g+r . && sudo chmod -R g+w ./data/ -``` - -See more commands and git commands in the [Command-Line Interface documentation](cli/README.md). - -## Access control -This is needed if you will be using the multi-user mode, to limit access to FreshRSS. Options Available: -* form authentication (needs JavaScript, and PHP 5.5+ recommended) -* HTTP authentication supported by your web server - * See [Apache documentation](https://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](https://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 is a good idea to run the cron job as the webserver user (often “www-data”). -For instance, if you want to run the script every hour: - -``` -9 * * * * php /usr/share/FreshRSS/app/actualize_script.php > /tmp/FreshRSS.log 2>&1 -``` - -### Example on Debian / Ubuntu -Create `/etc/cron.d/FreshRSS` with: - -``` -6,36 * * * * www-data php -f /usr/share/FreshRSS/app/actualize_script.php > /tmp/FreshRSS.log 2>&1 -``` - ## Advice * For better security, expose only the `./p/` folder to the Web. * Be aware that the `./data/` folder contains all personal data, so it is a bad idea to expose it. @@ -156,16 +89,6 @@ Create `/etc/cron.d/FreshRSS` with: * In particular, when importing a new feed, all of its articles will appear at the top of the feed list regardless of their declared date. -# Backup -* You need to keep `./data/config.php`, and `./data/users/*/config.php` files -* You can export your feed list in OPML format either from the Web interface, or from the [Command-Line Interface](cli/README.md) -* To save articles, you can use [phpMyAdmin](https://www.phpmyadmin.net) or MySQL tools: - -```bash -mysqldump --skip-comments --disable-keys --user= --password --host --result-file=freshrss.dump.sql --databases -``` - - # Extensions FreshRSS supports further customizations by adding extensions on top of its core functionality. See the [repository dedicated to those extensions](https://github.com/FreshRSS/Extensions). @@ -187,8 +110,11 @@ Supported clients are: * [EasyRSS](https://github.com/Alkarex/EasyRSS) (Open source, [F-Droid](https://f-droid.org/packages/org.freshrss.easyrss/)) * GNU/Linux * [FeedReader 2.0+](https://jangernert.github.io/FeedReader/) (Open source) +* iOS + * [Reeder](https://www.reederapp.com/) (Commercial) * MacOS * [Vienna RSS](http://www.vienna-rss.com/) (Open source) + * [Reeder](https://www.reederapp.com/) (Commercial) ## Fever API @@ -199,11 +125,10 @@ Supported clients are: * Android * [Readably](https://play.google.com/store/apps/details?id=com.isaiasmatewos.readably) (Closed source) * iOS - * [Fiery Feeds](https://itunes.apple.com/app/fiery-feeds-rss-reader/id1158763303) (Closed source) - * [Unread](https://itunes.apple.com/app/unread-rss-reader/id1252376153) (Closed source) - * [Reeder-3](https://itunes.apple.com/app/reeder-3/id697846300) (Closed source) + * [Fiery Feeds](https://apps.apple.com/app/fiery-feeds-rss-reader/id1158763303) (Closed source) + * [Unread](https://apps.apple.com/app/unread-rss-reader/id1252376153) (Commercial) * MacOS - * [Readkit](https://itunes.apple.com/app/readkit/id588726889) (Closed source) + * [Readkit](https://apps.apple.com/app/readkit/id588726889) (Commercial) # Included libraries @@ -213,12 +138,11 @@ Supported clients are: * [jQuery](https://jquery.com/) * [lib_opml](https://github.com/marienfressinaud/lib_opml) * [flotr2](http://www.humblesoftware.com/flotr2) +* [PHPMailer](https://github.com/PHPMailer/PHPMailer) ## Only for some options or configurations * [bcrypt.js](https://github.com/dcodeIO/bcrypt.js) * [phpQuery](https://github.com/phpquery/phpquery) -* [Services_JSON](https://pear.php.net/pepr/pepr-proposal-show.php?id=198) -* [password_compat](https://github.com/ircmaxell/password_compat) [travis-badge]:https://travis-ci.org/FreshRSS/FreshRSS.svg?branch=master [travis-link]:https://travis-ci.org/FreshRSS/FreshRSS diff --git a/app/.htaccess b/app/.htaccess index 9e768397d..32eca30f7 100644 --- a/app/.htaccess +++ b/app/.htaccess @@ -1,3 +1,11 @@ -Order Allow,Deny -Deny from all -Satisfy all +# Apache 2.2 + + Order Allow,Deny + Deny from all + Satisfy all + + +# Apache 2.4 + + Require all denied + diff --git a/app/Controllers/authController.php b/app/Controllers/authController.php index ca44b1a96..e2e1aaa22 100644 --- a/app/Controllers/authController.php +++ b/app/Controllers/authController.php @@ -169,10 +169,6 @@ class FreshRSS_auth_Controller extends Minz_ActionController { return; } - if (!function_exists('password_verify')) { - include_once(LIB_PATH . '/password_compat.php'); - } - $s = $conf->passwordHash; $ok = password_verify($password, $s); unset($password); @@ -203,12 +199,22 @@ class FreshRSS_auth_Controller extends Minz_ActionController { /** * This action gives possibility to a user to create an account. + * + * The user is redirected to the home if he's connected. + * + * A 403 is sent if max number of registrations is reached. */ public function registerAction() { + if (FreshRSS_Auth::hasAccess()) { + Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true); + } + if (max_registrations_reached()) { Minz_Error::error(403); } + $this->view->show_tos_checkbox = file_exists(join_path(DATA_PATH, 'tos.html')); + $this->view->show_email_field = FreshRSS_Context::$system_conf->force_email_validation; Minz_View::prependTitle(_t('gen.auth.registration.title') . ' · '); } } diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index 6d3c4dcce..b38d3289a 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -48,6 +48,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { 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->topline_display_authors = Minz_Request::param('topline_display_authors', 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); @@ -166,8 +167,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { * tab and up. */ public function shortcutAction() { - global $SHORTCUT_KEYS; - $this->view->list_keys = $SHORTCUT_KEYS; + $this->view->list_keys = SHORTCUT_KEYS; if (Minz_Request::isPost()) { FreshRSS_Context::$user_conf->shortcuts = validateShortcutList(Minz_Request::param('shortcuts')); @@ -196,9 +196,31 @@ class FreshRSS_configure_Controller extends Minz_ActionController { */ 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); + if (!Minz_Request::paramBoolean('enable_keep_max')) { + $keepMax = false; + } elseif (!$keepMax = Minz_Request::param('keep_max')) { + $keepMax = FreshRSS_Feed::ARCHIVING_RETENTION_COUNT_LIMIT; + } + if ($enableRetentionPeriod = Minz_Request::paramBoolean('enable_keep_period')) { + $keepPeriod = FreshRSS_Feed::ARCHIVING_RETENTION_PERIOD; + if (is_numeric(Minz_Request::param('keep_period_count')) && preg_match('/^PT?1[YMWDH]$/', Minz_Request::param('keep_period_unit'))) { + $keepPeriod = str_replace('1', Minz_Request::param('keep_period_count'), Minz_Request::param('keep_period_unit')); + } + } else { + $keepPeriod = false; + } + FreshRSS_Context::$user_conf->ttl_default = Minz_Request::param('ttl_default', FreshRSS_Feed::TTL_DEFAULT); + FreshRSS_Context::$user_conf->archiving = [ + 'keep_period' => $keepPeriod, + 'keep_max' => $keepMax, + 'keep_min' => Minz_Request::param('keep_min_default', 0), + 'keep_favourites' => Minz_Request::paramBoolean('keep_favourites'), + 'keep_labels' => Minz_Request::paramBoolean('keep_labels'), + 'keep_unreads' => Minz_Request::paramBoolean('keep_unreads'), + ]; + FreshRSS_Context::$user_conf->keep_history_default = null; //Legacy < FreshRSS 1.15 + FreshRSS_Context::$user_conf->old_entries = null; //Legacy < FreshRSS 1.15 FreshRSS_Context::$user_conf->save(); invalidateHttpCache(); @@ -206,7 +228,20 @@ class FreshRSS_configure_Controller extends Minz_ActionController { array('c' => 'configure', 'a' => 'archiving')); } - Minz_View::prependTitle(_t('conf.archiving.title') . ' · '); + $volatile = [ + 'enable_keep_period' => false, + 'keep_period_count' => '3', + 'keep_period_unit' => 'P1M', + ]; + $keepPeriod = FreshRSS_Context::$user_conf->archiving['keep_period']; + if (preg_match('/^PT?(?P\d+)[YMWDH]$/', $keepPeriod, $matches)) { + $volatile = [ + 'enable_keep_period' => true, + 'keep_period_count' => $matches['count'], + 'keep_period_unit' => str_replace($matches['count'], 1, $keepPeriod), + ]; + } + FreshRSS_Context::$user_conf->volatile = $volatile; $entryDAO = FreshRSS_Factory::createEntryDao(); $this->view->nb_total = $entryDAO->count(); @@ -217,6 +252,8 @@ class FreshRSS_configure_Controller extends Minz_ActionController { if (FreshRSS_Auth::hasAccess('admin')) { $this->view->size_total = $databaseDAO->size(true); } + + Minz_View::prependTitle(_t('conf.archiving.title') . ' · '); } /** @@ -292,15 +329,24 @@ class FreshRSS_configure_Controller extends Minz_ActionController { * configuration values then sends a notification to the user. * * The options available on the page are: + * - instance name (default: FreshRSS) + * - auto update URL (default: false) + * - force emails validation (default: false) * - user limit (default: 1) * - user category limit (default: 16384) * - user feed limit (default: 16384) * - user login duration for form auth (default: 2592000) + * + * The `force-email-validation` is ignored with PHP < 5.5 */ public function systemAction() { if (!FreshRSS_Auth::hasAccess('admin')) { Minz_Error::error(403); } + + $can_enable_email_validation = version_compare(PHP_VERSION, '5.5') >= 0; + $this->view->can_enable_email_validation = $can_enable_email_validation; + if (Minz_Request::isPost()) { $limits = FreshRSS_Context::$system_conf->limits; $limits['max_registrations'] = Minz_Request::param('max-registrations', 1); @@ -310,6 +356,9 @@ class FreshRSS_configure_Controller extends Minz_ActionController { 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); + if ($can_enable_email_validation) { + FreshRSS_Context::$system_conf->force_email_validation = Minz_Request::param('force-email-validation', false); + } FreshRSS_Context::$system_conf->save(); invalidateHttpCache(); diff --git a/app/Controllers/entryController.php b/app/Controllers/entryController.php index 9c5ee2616..7881cb3ec 100755 --- a/app/Controllers/entryController.php +++ b/app/Controllers/entryController.php @@ -17,7 +17,7 @@ class FreshRSS_entry_Controller extends Minz_ActionController { // If ajax request, we do not print layout $this->ajax = Minz_Request::param('ajax'); if ($this->ajax) { - $this->view->_useLayout(false); + $this->view->_layout(false); Minz_Request::_param('ajax'); } } @@ -181,32 +181,20 @@ class FreshRSS_entry_Controller extends Minz_ActionController { 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); - - $entryDAO = FreshRSS_Factory::createEntryDao(); $feedDAO = FreshRSS_Factory::createFeedDao(); $feeds = $feedDAO->listFeeds(); $nb_total = 0; invalidateHttpCache(); - foreach ($feeds as $feed) { - $feed_history = $feed->keepHistory(); - if (FreshRSS_Feed::KEEP_HISTORY_DEFAULT === $feed_history) { - $feed_history = FreshRSS_Context::$user_conf->keep_history_default; - } + $feedDAO->beginTransaction(); - if ($feed_history >= 0) { - $nb = $entryDAO->cleanOldEntries($feed->id(), $date_min, $feed_history); - if ($nb > 0) { - $nb_total += $nb; - Minz_Log::debug($nb . ' old entries cleaned in feed [' . $feed->url(false) . ']'); - } - } + foreach ($feeds as $feed) { + $nb_total += $feed->cleanOldEntries(); } $feedDAO->updateCachedValues(); + $feedDAO->commit(); $databaseDAO = FreshRSS_Factory::createDatabaseDAO(); $databaseDAO->minorDbMaintenance(); diff --git a/app/Controllers/extensionController.php b/app/Controllers/extensionController.php index 806e5a696..68bd90f4c 100644 --- a/app/Controllers/extensionController.php +++ b/app/Controllers/extensionController.php @@ -80,10 +80,10 @@ class FreshRSS_extension_Controller extends Minz_ActionController { */ public function configureAction() { if (Minz_Request::param('ajax')) { - $this->view->_useLayout(false); + $this->view->_layout(false); } else { $this->indexAction(); - $this->view->change_view('extension', 'index'); + $this->view->_path('extension/index.phtml'); } $ext_name = urldecode(Minz_Request::param('e')); diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 862bb10fb..aabeb80ff 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -267,10 +267,6 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $maxFeeds = 10; } - // 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); - // WebSub (PubSubHubbub) support $pubsubhubbubEnabledGeneral = FreshRSS_Context::$system_conf->pubsubhubbub_enabled; $pshbMinAge = time() - (3600 * 24); //TODO: Make a configuration. @@ -323,12 +319,6 @@ class FreshRSS_feed_Controller extends Minz_ActionController { continue; } - $feed_history = $feed->keepHistory(); - if ($isNewFeed) { - $feed_history = FreshRSS_Feed::KEEP_HISTORY_INFINITE; - } elseif (FreshRSS_Feed::KEEP_HISTORY_DEFAULT === $feed_history) { - $feed_history = FreshRSS_Context::$user_conf->keep_history_default; - } $needFeedCacheRefresh = false; // We want chronological order and SimplePie uses reverse order. @@ -376,15 +366,9 @@ class FreshRSS_feed_Controller extends Minz_ActionController { } $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 { $id = uTimeString(); $entry->_id($id); - if ($entry_date < $date_min) { - $entry->_isRead(true); //Old article that was not in database. Probably an error, so mark as read - } $entry->applyFilterActions(); @@ -413,23 +397,19 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $entryDAO->updateLastSeen($feed->id(), $oldGuids, $mtime); } - if ($feed_history >= 0 && mt_rand(0, 30) === 1) { - // TODO: move this function in web cron when available (see entry::purge) - // Remove old entries once in 30. + if (mt_rand(0, 30) === 1) { // Remove old entries once in 30. if (!$entryDAO->inTransaction()) { $entryDAO->beginTransaction(); } - - $nb = $entryDAO->cleanOldEntries($feed->id(), $date_min, max($feed_history, count($entries) + 10)); + $nb = $feed->cleanOldEntries(); if ($nb > 0) { $needFeedCacheRefresh = true; - Minz_Log::debug($nb . ' old entries cleaned in feed [' . $feed->url(false) . ']'); } } $feedDAO->updateLastUpdate($feed->id(), false, $mtime); if ($needFeedCacheRefresh) { - $feedDAO->updateCachedValue($feed->id()); + $feedDAO->updateCachedValues($feed->id()); } if ($entryDAO->inTransaction()) { $entryDAO->commit(); @@ -530,7 +510,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { ); Minz_Session::_param('notification', $notif); // No layout in ajax request. - $this->view->_useLayout(false); + $this->view->_layout(false); } else { // Redirect to the main page with correct notification. if ($updated_feeds === 1) { diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index 1d7176929..f2ae8238e 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -29,7 +29,25 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { Minz_View::prependTitle(_t('sub.import_export.title') . ' · '); } + private static function megabytes($size_str) { + switch (substr($size_str, -1)) { + case 'M': case 'm': return (int)$size_str; + case 'K': case 'k': return (int)$size_str / 1024; + case 'G': case 'g': return (int)$size_str * 1024; + } + return $size_str; + } + + private static function minimumMemory($mb) { + $mb = (int)$mb; + $ini = self::megabytes(ini_get('memory_limit')); + if ($ini < $mb) { + ini_set('memory_limit', $mb . 'M'); + } + } + public function importFile($name, $path, $username = null) { + self::minimumMemory(256); require_once(LIB_PATH . '/lib_opml.php'); $this->catDAO = new FreshRSS_CategoryDAO($username); @@ -709,8 +727,6 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $this->entryDAO = FreshRSS_Factory::createEntryDao($username); $this->feedDAO = FreshRSS_Factory::createFeedDao($username); - $this->entryDAO->disableBuffering(); - if ($export_feeds === true) { //All feeds $export_feeds = $this->feedDAO->listFeedsIds(); @@ -773,7 +789,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { if (!Minz_Request::isPost()) { Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true); } - $this->view->_useLayout(false); + $this->view->_layout(false); $nb_files = 0; try { diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index f536113dd..967029fd1 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -155,7 +155,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { // No layout for RSS output. $this->view->url = PUBLIC_TO_INDEX_PATH . '/' . (empty($_SERVER['QUERY_STRING']) ? '' : '?' . $_SERVER['QUERY_STRING']); $this->view->rss_title = FreshRSS_Context::$name . ' | ' . Minz_View::title(); - $this->view->_useLayout(false); + $this->view->_layout(false); header('Content-Type: application/rss+xml; charset=utf-8'); } @@ -173,7 +173,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { private function updateContext() { if (empty(FreshRSS_Context::$categories)) { $catDAO = FreshRSS_Factory::createCategoryDao(); - FreshRSS_Context::$categories = $catDAO->listCategories(); + FreshRSS_Context::$categories = $catDAO->listSortedCategories(); } // Update number of read / unread variables. @@ -259,6 +259,23 @@ class FreshRSS_index_Controller extends Minz_ActionController { Minz_View::prependTitle(_t('index.about.title') . ' · '); } + /** + * This action displays the EULA page of FreshRSS. + * This page is enabled only if admin created a data/tos.html file. + * The content of the page is the content of data/tos.html. + * It returns 404 if there is no EULA. + */ + public function tosAction() { + $terms_of_service = file_get_contents(join_path(DATA_PATH, 'tos.html')); + if (!$terms_of_service) { + Minz_Error::error(404); + } + + $this->view->terms_of_service = $terms_of_service; + $this->view->can_register = !max_registrations_reached(); + Minz_View::prependTitle(_t('index.tos.title') . ' · '); + } + /** * This action displays logs of FreshRSS for the current user. */ diff --git a/app/Controllers/javascriptController.php b/app/Controllers/javascriptController.php index d56da9cbb..c84e5483b 100755 --- a/app/Controllers/javascriptController.php +++ b/app/Controllers/javascriptController.php @@ -2,7 +2,7 @@ class FreshRSS_javascript_Controller extends Minz_ActionController { public function firstAction() { - $this->view->_useLayout(false); + $this->view->_layout(false); } public function actualizeAction() { diff --git a/app/Controllers/subscriptionController.php b/app/Controllers/subscriptionController.php index 79da39751..b4520c8e6 100644 --- a/app/Controllers/subscriptionController.php +++ b/app/Controllers/subscriptionController.php @@ -19,7 +19,7 @@ class FreshRSS_subscription_Controller extends Minz_ActionController { $catDAO->checkDefault(); $feedDAO->updateTTL(); - $this->view->categories = $catDAO->listCategories(false); + $this->view->categories = $catDAO->listSortedCategories(false); $this->view->default_category = $catDAO->getDefault(); } @@ -74,7 +74,7 @@ class FreshRSS_subscription_Controller extends Minz_ActionController { */ public function feedAction() { if (Minz_Request::param('ajax')) { - $this->view->_useLayout(false); + $this->view->_layout(false); } $feedDAO = FreshRSS_Factory::createFeedDao(); @@ -121,6 +121,32 @@ class FreshRSS_subscription_Controller extends Minz_ActionController { $feed->_attributes('timeout', null); } + if (Minz_Request::paramBoolean('use_default_purge_options')) { + $feed->_attributes('archiving', null); + } else { + if (!Minz_Request::paramBoolean('enable_keep_max')) { + $keepMax = false; + } elseif (!$keepMax = Minz_Request::param('keep_max')) { + $keepMax = FreshRSS_Feed::ARCHIVING_RETENTION_COUNT_LIMIT; + } + if ($enableRetentionPeriod = Minz_Request::paramBoolean('enable_keep_period')) { + $keepPeriod = FreshRSS_Feed::ARCHIVING_RETENTION_PERIOD; + if (is_numeric(Minz_Request::param('keep_period_count')) && preg_match('/^PT?1[YMWDH]$/', Minz_Request::param('keep_period_unit'))) { + $keepPeriod = str_replace(1, Minz_Request::param('keep_period_count'), Minz_Request::param('keep_period_unit')); + } + } else { + $keepPeriod = false; + } + $feed->_attributes('archiving', [ + 'keep_period' => $keepPeriod, + 'keep_max' => $keepMax, + 'keep_min' => intval(Minz_Request::param('keep_min', 0)), + 'keep_favourites' => Minz_Request::paramBoolean('keep_favourites'), + 'keep_labels' => Minz_Request::paramBoolean('keep_labels'), + 'keep_unreads' => Minz_Request::paramBoolean('keep_unreads'), + ]); + } + $feed->_filtersAction('read', preg_split('/[\n\r]+/', Minz_Request::param('filteractions_read', ''))); $values = array( @@ -132,7 +158,6 @@ class FreshRSS_subscription_Controller extends Minz_ActionController { 'pathEntries' => Minz_Request::param('path_entries', ''), 'priority' => intval(Minz_Request::param('priority', FreshRSS_Feed::PRIORITY_MAIN_STREAM)), 'httpAuth' => $httpAuth, - 'keep_history' => intval(Minz_Request::param('keep_history', FreshRSS_Feed::KEEP_HISTORY_DEFAULT)), 'ttl' => $ttl * ($mute ? -1 : 1), 'attributes' => $feed->attributes(), ); @@ -152,7 +177,7 @@ class FreshRSS_subscription_Controller extends Minz_ActionController { } public function categoryAction() { - $this->view->_useLayout(false); + $this->view->_layout(false); $categoryDAO = FreshRSS_Factory::createCategoryDao(); @@ -165,9 +190,39 @@ class FreshRSS_subscription_Controller extends Minz_ActionController { $this->view->category = $category; if (Minz_Request::isPost()) { - $values = array( + if (Minz_Request::paramBoolean('use_default_purge_options')) { + $category->_attributes('archiving', null); + } else { + if (!Minz_Request::paramBoolean('enable_keep_max')) { + $keepMax = false; + } elseif (!$keepMax = Minz_Request::param('keep_max')) { + $keepMax = FreshRSS_Feed::ARCHIVING_RETENTION_COUNT_LIMIT; + } + if ($enableRetentionPeriod = Minz_Request::paramBoolean('enable_keep_period')) { + $keepPeriod = FreshRSS_Feed::ARCHIVING_RETENTION_PERIOD; + if (is_numeric(Minz_Request::param('keep_period_count')) && preg_match('/^PT?1[YMWDH]$/', Minz_Request::param('keep_period_unit'))) { + $keepPeriod = str_replace(1, Minz_Request::param('keep_period_count'), Minz_Request::param('keep_period_unit')); + } + } else { + $keepPeriod = false; + } + $category->_attributes('archiving', [ + 'keep_period' => $keepPeriod, + 'keep_max' => $keepMax, + 'keep_min' => intval(Minz_Request::param('keep_min', 0)), + 'keep_favourites' => Minz_Request::paramBoolean('keep_favourites'), + 'keep_labels' => Minz_Request::paramBoolean('keep_labels'), + 'keep_unreads' => Minz_Request::paramBoolean('keep_unreads'), + ]); + } + + $position = Minz_Request::param('position'); + $category->_attributes('position', '' === $position ? null : (int) $position); + + $values = [ 'name' => Minz_Request::param('name', ''), - ); + 'attributes' => $category->attributes(), + ]; invalidateHttpCache(); diff --git a/app/Controllers/tagController.php b/app/Controllers/tagController.php index 106e0afa8..ba9efe2fc 100644 --- a/app/Controllers/tagController.php +++ b/app/Controllers/tagController.php @@ -16,7 +16,7 @@ class FreshRSS_tag_Controller extends Minz_ActionController { // If ajax request, we do not print layout $this->ajax = Minz_Request::param('ajax'); if ($this->ajax) { - $this->view->_useLayout(false); + $this->view->_layout(false); Minz_Request::_param('ajax'); } } @@ -70,7 +70,7 @@ class FreshRSS_tag_Controller extends Minz_ActionController { } public function getTagsForEntryAction() { - $this->view->_useLayout(false); + $this->view->_layout(false); header('Content-Type: application/json; charset=UTF-8'); header('Cache-Control: private, no-cache, no-store, must-revalidate'); $id_entry = Minz_Request::param('id_entry', 0); diff --git a/app/Controllers/updateController.php b/app/Controllers/updateController.php index 2be644c85..ebe5e4cc8 100644 --- a/app/Controllers/updateController.php +++ b/app/Controllers/updateController.php @@ -89,7 +89,7 @@ class FreshRSS_update_Controller extends Minz_ActionController { } public function checkAction() { - $this->view->change_view('update', 'index'); + $this->view->_path('update/index.phtml'); if (file_exists(UPDATE_FILENAME)) { // There is already an update file to apply: we don't need to check diff --git a/app/Controllers/userController.php b/app/Controllers/userController.php index 6d0fced5b..6afc91b4e 100644 --- a/app/Controllers/userController.php +++ b/app/Controllers/userController.php @@ -8,26 +8,7 @@ class FreshRSS_user_Controller extends Minz_ActionController { // so do not use a too high cost const BCRYPT_COST = 9; - /** - * This action is called before every other action in that class. It is - * the common boiler plate for every action. It is triggered by the - * underlying framework. - * - * @todo clean up the access condition. - */ - public function firstAction() { - if (!FreshRSS_Auth::hasAccess() && !( - Minz_Request::actionName() === 'create' && - !max_registrations_reached() - )) { - Minz_Error::error(403); - } - } - public static function hashPassword($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 @@ -52,12 +33,23 @@ class FreshRSS_user_Controller extends Minz_ActionController { return false; } - public static function updateUser($user, $passwordPlain, $apiPasswordPlain, $userConfigUpdated = array()) { + public static function updateUser($user, $email, $passwordPlain, $apiPasswordPlain, $userConfigUpdated = array()) { $userConfig = get_user_configuration($user); if ($userConfig === null) { return false; } + if ($email !== null && $userConfig->mail_login !== $email) { + $userConfig->mail_login = $email; + + if (FreshRSS_Context::$system_conf->force_email_validation) { + $salt = FreshRSS_Context::$system_conf->salt; + $userConfig->email_validation_token = sha1($salt . uniqid(mt_rand(), true)); + $mailer = new FreshRSS_User_Mailer(); + $mailer->send_email_need_validation($user, $userConfig); + } + } + if ($passwordPlain != '') { $passwordHash = self::hashPassword($passwordPlain); $userConfig->passwordHash = $passwordHash; @@ -103,7 +95,7 @@ class FreshRSS_user_Controller extends Minz_ActionController { $apiPasswordPlain = Minz_Request::param('apiPasswordPlain', '', true); $username = Minz_Request::param('username'); - $ok = self::updateUser($username, $passwordPlain, $apiPasswordPlain, array( + $ok = self::updateUser($username, null, $passwordPlain, $apiPasswordPlain, array( 'token' => Minz_Request::param('token', null), )); @@ -126,25 +118,63 @@ class FreshRSS_user_Controller extends Minz_ActionController { * This action displays the user profile page. */ public function profileAction() { + if (!FreshRSS_Auth::hasAccess()) { + Minz_Error::error(403); + } + + $email_not_verified = FreshRSS_Context::$user_conf->email_validation_token !== ''; + $this->view->disable_aside = false; + if ($email_not_verified) { + $this->view->_layout('simple'); + $this->view->disable_aside = true; + } + Minz_View::prependTitle(_t('conf.profile.title') . ' · '); Minz_View::appendScript(Minz_Url::display('/scripts/bcrypt.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js'))); if (Minz_Request::isPost()) { + $system_conf = FreshRSS_Context::$system_conf; + $user_config = FreshRSS_Context::$user_conf; + $old_email = $user_config->mail_login; + + $email = trim(Minz_Request::param('email', '')); $passwordPlain = Minz_Request::param('newPasswordPlain', '', true); Minz_Request::_param('newPasswordPlain'); //Discard plain-text password ASAP $_POST['newPasswordPlain'] = ''; $apiPasswordPlain = Minz_Request::param('apiPasswordPlain', '', true); - $ok = self::updateUser(Minz_Session::param('currentUser'), $passwordPlain, $apiPasswordPlain, array( + if ($system_conf->force_email_validation && empty($email)) { + Minz_Request::bad( + _t('user.email.feedback.required'), + array('c' => 'user', 'a' => 'profile') + ); + } + + if (!empty($email) && !validateEmailAddress($email)) { + Minz_Request::bad( + _t('user.email.feedback.invalid'), + array('c' => 'user', 'a' => 'profile') + ); + } + + $ok = self::updateUser( + Minz_Session::param('currentUser'), + $email, + $passwordPlain, + $apiPasswordPlain, + array( 'token' => Minz_Request::param('token', null), - )); + ) + ); Minz_Session::_param('passwordHash', FreshRSS_Context::$user_conf->passwordHash); if ($ok) { - if ($passwordPlain == '') { + if ($system_conf->force_email_validation && $email !== $old_email) { + Minz_Request::good(_t('feedback.profile.updated'), array('c' => 'user', 'a' => 'validateEmail')); + } elseif ($passwordPlain == '') { Minz_Request::good(_t('feedback.profile.updated'), array('c' => 'user', 'a' => 'profile')); } else { Minz_Request::good(_t('feedback.profile.updated'), array('c' => 'index', 'a' => 'index')); @@ -166,6 +196,7 @@ class FreshRSS_user_Controller extends Minz_ActionController { Minz_View::prependTitle(_t('admin.user.title') . ' · '); + $this->view->show_email_field = FreshRSS_Context::$system_conf->force_email_validation; $this->view->current_user = Minz_Request::param('u'); $this->view->nb_articles = 0; @@ -180,9 +211,19 @@ class FreshRSS_user_Controller extends Minz_ActionController { } } - public static function createUser($new_user_name, $passwordPlain, $apiPasswordPlain, $userConfig = array(), $insertDefaultFeeds = true) { - if (!is_array($userConfig)) { - $userConfig = array(); + public static function createUser($new_user_name, $email, $passwordPlain, $apiPasswordPlain = '', $userConfigOverride = [], $insertDefaultFeeds = true) { + $userConfig = []; + + $customUserConfigPath = join_path(DATA_PATH, 'config-user.custom.php'); + if (file_exists($customUserConfigPath)) { + $customUserConfig = include($customUserConfigPath); + if (is_array($customUserConfig)) { + $userConfig = $customUserConfig; + } + } + + if (is_array($userConfigOverride)) { + $userConfig = array_merge($userConfig, $userConfigOverride); } $ok = self::checkUsername($new_user_name); @@ -206,9 +247,9 @@ class FreshRSS_user_Controller extends Minz_ActionController { $ok &= (file_put_contents($configPath, "createUser($new_user_name, $userConfig['language'], $insertDefaultFeeds); - $ok &= self::updateUser($new_user_name, $passwordPlain, $apiPasswordPlain); + $newUserDAO = FreshRSS_Factory::createUserDao($new_user_name); + $ok &= $newUserDAO->createUser($insertDefaultFeeds); + $ok &= self::updateUser($new_user_name, $email, $passwordPlain, $apiPasswordPlain); } return $ok; } @@ -219,6 +260,7 @@ class FreshRSS_user_Controller extends Minz_ActionController { * Request parameters are: * - new_user_language * - new_user_name + * - new_user_email * - new_user_passwordPlain * - r (i.e. a redirection url, optional) * @@ -226,15 +268,43 @@ class FreshRSS_user_Controller extends Minz_ActionController { * @todo handle r redirection in Minz_Request::forward directly? */ public function createAction() { - if (Minz_Request::isPost() && ( - FreshRSS_Auth::hasAccess('admin') || - !max_registrations_reached() - )) { + if (!FreshRSS_Auth::hasAccess('admin') && max_registrations_reached()) { + Minz_Error::error(403); + } + + if (Minz_Request::isPost()) { + $system_conf = FreshRSS_Context::$system_conf; + $new_user_name = Minz_Request::param('new_user_name'); + $email = Minz_Request::param('new_user_email', ''); $passwordPlain = Minz_Request::param('new_user_passwordPlain', '', true); $new_user_language = Minz_Request::param('new_user_language', FreshRSS_Context::$user_conf->language); - $ok = self::createUser($new_user_name, $passwordPlain, '', array('language' => $new_user_language)); + $tos_enabled = file_exists(join_path(DATA_PATH, 'tos.html')); + $accept_tos = Minz_Request::param('accept_tos', false); + + if ($system_conf->force_email_validation && empty($email)) { + Minz_Request::bad( + _t('user.email.feedback.required'), + array('c' => 'auth', 'a' => 'register') + ); + } + + if (!empty($email) && !validateEmailAddress($email)) { + Minz_Request::bad( + _t('user.email.feedback.invalid'), + array('c' => 'auth', 'a' => 'register') + ); + } + + if ($tos_enabled && !$accept_tos) { + Minz_Request::bad( + _t('user.tos.feedback.invalid'), + array('c' => 'auth', 'a' => 'register') + ); + } + + $ok = self::createUser($new_user_name, $email, $passwordPlain, '', array('language' => $new_user_language)); Minz_Request::_param('new_user_passwordPlain'); //Discard plain-text password ASAP $_POST['new_user_passwordPlain'] = ''; invalidateHttpCache(); @@ -266,9 +336,6 @@ class FreshRSS_user_Controller extends Minz_ActionController { } public static function deleteUser($username) { - $db = FreshRSS_Context::$system_conf->db; - require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php'); - $ok = self::checkUsername($username); if ($ok) { $default_user = FreshRSS_Context::$system_conf->default_user; @@ -278,14 +345,130 @@ class FreshRSS_user_Controller extends Minz_ActionController { $ok &= is_dir($user_data); if ($ok) { self::deleteFeverKey($username); - $userDAO = new FreshRSS_UserDAO(); - $ok &= $userDAO->deleteUser($username); + $oldUserDAO = FreshRSS_Factory::createUserDao($username); + $ok &= $oldUserDAO->deleteUser(); $ok &= recursive_unlink($user_data); array_map('unlink', glob(PSHB_PATH . '/feeds/*/' . $username . '.txt')); } return $ok; } + /** + * This action validates an email address, based on the token sent by email. + * It also serves the main page when user is blocked. + * + * Request parameters are: + * - username + * - token + * + * This route works with GET requests since the URL is provided by email. + * The security risks (e.g. forged URL by an attacker) are not very high so + * it's ok. + * + * It returns 404 error if `force_email_validation` is disabled or if the + * user doesn't exist. + * + * It returns 403 if user isn't logged in and `username` param isn't passed. + */ + public function validateEmailAction() { + if (!FreshRSS_Context::$system_conf->force_email_validation) { + Minz_Error::error(404); + } + + Minz_View::prependTitle(_t('user.email.validation.title') . ' · '); + $this->view->_layout('simple'); + + $username = Minz_Request::param('username'); + $token = Minz_Request::param('token'); + + if ($username) { + $user_config = get_user_configuration($username); + } elseif (FreshRSS_Auth::hasAccess()) { + $user_config = FreshRSS_Context::$user_conf; + } else { + Minz_Error::error(403); + } + + if (!FreshRSS_UserDAO::exists($username) || $user_config === null) { + Minz_Error::error(404); + } + + if ($user_config->email_validation_token === '') { + Minz_Request::good( + _t('user.email.validation.feedback.unnecessary'), + array('c' => 'index', 'a' => 'index') + ); + } + + if ($token) { + if ($user_config->email_validation_token !== $token) { + Minz_Request::bad( + _t('user.email.validation.feedback.wrong_token'), + array('c' => 'user', 'a' => 'validateEmail') + ); + } + + $user_config->email_validation_token = ''; + if ($user_config->save()) { + Minz_Request::good( + _t('user.email.validation.feedback.ok'), + array('c' => 'index', 'a' => 'index') + ); + } else { + Minz_Request::bad( + _t('user.email.validation.feedback.error'), + array('c' => 'user', 'a' => 'validateEmail') + ); + } + } + } + + /** + * This action resends a validation email to the current user. + * + * It only acts on POST requests but doesn't require any param (except the + * CSRF token). + * + * It returns 403 error if the user is not logged in or 404 if request is + * not POST. Else it redirects silently to the index if user has already + * validated its email, or to the user#validateEmail route. + */ + public function sendValidationEmailAction() { + if (!FreshRSS_Auth::hasAccess()) { + Minz_Error::error(403); + } + + if (!Minz_Request::isPost()) { + Minz_Error::error(404); + } + + $username = Minz_Session::param('currentUser', '_'); + $user_config = FreshRSS_Context::$user_conf; + + if ($user_config->email_validation_token === '') { + Minz_Request::forward(array( + 'c' => 'index', + 'a' => 'index', + ), true); + } + + $mailer = new FreshRSS_User_Mailer(); + $ok = $mailer->send_email_need_validation($username, $user_config); + + $redirect_url = array('c' => 'user', 'a' => 'validateEmail'); + if ($ok) { + Minz_Request::good( + _t('user.email.validation.feedback.email_sent'), + $redirect_url + ); + } else { + Minz_Request::bad( + _t('user.email.validation.feedback.email_failed'), + $redirect_url + ); + } + } + /** * This action delete an existing user. * @@ -296,17 +479,18 @@ class FreshRSS_user_Controller extends Minz_ActionController { */ public function deleteAction() { $username = Minz_Request::param('username'); + $self_deletion = Minz_Session::param('currentUser', '_') === $username; + + if (!FreshRSS_Auth::hasAccess('admin') && !$self_deletion) { + Minz_Error::error(403); + } + $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 - )) { + if (Minz_Request::isPost()) { $ok = true; if ($ok && $self_deletion) { // We check the password if it's a self-destruction diff --git a/app/FreshRSS.php b/app/FreshRSS.php index 8f614c538..d472a2147 100644 --- a/app/FreshRSS.php +++ b/app/FreshRSS.php @@ -53,6 +53,10 @@ class FreshRSS extends Minz_FrontController { $ext_list = FreshRSS_Context::$user_conf->extensions_enabled; Minz_ExtensionManager::enableByList($ext_list); } + + self::checkEmailValidated(); + + Minz_ExtensionManager::callHook('freshrss_init'); } private static function initAuth() { @@ -142,4 +146,22 @@ class FreshRSS extends Minz_FrontController { FreshRSS_Share::load(join_path(APP_PATH, 'shares.php')); self::loadStylesAndScripts(); } + + private static function checkEmailValidated() { + $email_not_verified = FreshRSS_Auth::hasAccess() && FreshRSS_Context::$user_conf->email_validation_token !== ''; + $action_is_allowed = ( + Minz_Request::is('user', 'validateEmail') || + Minz_Request::is('user', 'sendValidationEmail') || + Minz_Request::is('user', 'profile') || + Minz_Request::is('user', 'delete') || + Minz_Request::is('auth', 'logout') || + Minz_Request::is('javascript', 'nonce') + ); + if ($email_not_verified && !$action_is_allowed) { + Minz_Request::forward(array( + 'c' => 'user', + 'a' => 'validateEmail', + ), true); + } + } } diff --git a/app/Mailers/UserMailer.php b/app/Mailers/UserMailer.php new file mode 100644 index 000000000..5a2d39f1a --- /dev/null +++ b/app/Mailers/UserMailer.php @@ -0,0 +1,31 @@ +view->_path('user_mailer/email_need_validation.txt'); + + $this->view->username = $username; + $this->view->site_title = FreshRSS_Context::$system_conf->title; + $this->view->validation_url = Minz_Url::display( + array( + 'c' => 'user', + 'a' => 'validateEmail', + 'params' => array( + 'username' => $username, + 'token' => $user_config->email_validation_token + ) + ), + 'txt', + true + ); + + $subject_prefix = '[' . FreshRSS_Context::$system_conf->title . ']'; + return $this->mail( + $user_config->mail_login, + $subject_prefix . ' ' ._t('user.mailer.email_need_validation.title') + ); + } +} diff --git a/app/Models/Auth.php b/app/Models/Auth.php index 6d079a01f..b7fb0e6d6 100644 --- a/app/Models/Auth.php +++ b/app/Models/Auth.php @@ -219,10 +219,6 @@ class FreshRSS_FormAuth { return false; } - if (!function_exists('password_verify')) { - include_once(LIB_PATH . '/password_compat.php'); - } - return password_verify($nonce . $hash, $challenge); } @@ -283,8 +279,7 @@ class FreshRSS_FormAuth { $cookie_duration = empty($limits['cookie_duration']) ? 2592000 : $limits['cookie_duration']; $oldest = time() - $cookie_duration; 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); + $extension = $file_info->getExtension(); if ($extension === 'txt' && $file_info->getMTime() < $oldest) { @unlink($file_info->getPathname()); } diff --git a/app/Models/Category.php b/app/Models/Category.php index fa711aa66..a195c88b3 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -8,6 +8,7 @@ class FreshRSS_Category extends Minz_Model { private $feeds = null; private $hasFeedsWithError = false; private $isDefault = false; + private $attributes = []; public function __construct($name = '', $feeds = null) { $this->_name($name); @@ -68,8 +69,19 @@ class FreshRSS_Category extends Minz_Model { return $this->hasFeedsWithError; } - public function _id($value) { - $this->id = $value; + public function attributes($key = '') { + if ($key == '') { + return $this->attributes; + } else { + return isset($this->attributes[$key]) ? $this->attributes[$key] : null; + } + } + + public function _id($id) { + $this->id = $id; + if ($id == FreshRSS_CategoryDAO::DEFAULTCATEGORYID) { + $this->_name(_t('gen.short.default_category')); + } } public function _name($value) { $this->name = trim($value); @@ -84,4 +96,19 @@ class FreshRSS_Category extends Minz_Model { $this->feeds = $values; } + + public function _attributes($key, $value) { + if ('' == $key) { + if (is_string($value)) { + $value = json_decode($value, true); + } + if (is_array($value)) { + $this->attributes = $value; + } + } elseif (null === $value) { + unset($this->attributes[$key]); + } else { + $this->attributes[$key] = $value; + } + } } diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php index 6535adae7..c1277751c 100644 --- a/app/Models/CategoryDAO.php +++ b/app/Models/CategoryDAO.php @@ -4,23 +4,92 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable const DEFAULTCATEGORYID = 1; + protected function addColumn($name) { + Minz_Log::warning(__method__ . ': ' . $name); + try { + if ('attributes' === $name) { //v1.15.0 + $ok = $this->pdo->exec('ALTER TABLE `_category` ADD COLUMN attributes TEXT') !== false; + + $stm = $this->pdo->query('SELECT * FROM `_feed`'); + $feeds = $stm->fetchAll(PDO::FETCH_ASSOC); + + $stm = $this->pdo->prepare('UPDATE `_feed` SET attributes = :attributes WHERE id = :id'); + foreach ($feeds as $feed) { + if (empty($feed['keep_history']) || empty($feed['id'])) { + continue; + } + $keepHistory = $feed['keep_history']; + $attributes = empty($feed['attributes']) ? [] : json_decode($feed['attributes'], true); + if (is_string($attributes)) { //Legacy risk of double-encoding + $attributes = json_decode($attributes, true); + } + if (!is_array($attributes)) { + $attributes = []; + } + if ($keepHistory > 0) { + $attributes['archiving']['keep_min'] = intval($keepHistory); + } elseif ($keepHistory == -1) { //Infinite + $attributes['archiving']['keep_period'] = false; + $attributes['archiving']['keep_max'] = false; + $attributes['archiving']['keep_min'] = false; + } else { + continue; + } + $stm->bindValue(':id', $feed['id'], PDO::PARAM_INT); + $stm->bindValue(':attributes', json_encode($attributes, JSON_UNESCAPED_SLASHES)); + $stm->execute(); + } + + if ($this->pdo->dbType() !== 'sqlite') { //SQLite does not support DROP COLUMN + $this->pdo->exec('ALTER TABLE `_feed` DROP COLUMN keep_history'); + } else { + $this->pdo->exec('DROP INDEX IF EXISTS feed_keep_history_index'); //SQLite at least drop index + } + return $ok; + } + } catch (Exception $e) { + Minz_Log::error(__method__ . ': ' . $e->getMessage()); + } + return false; + } + + protected function autoUpdateDb($errorInfo) { + if (isset($errorInfo[0])) { + if ($errorInfo[0] === FreshRSS_DatabaseDAO::ER_BAD_FIELD_ERROR || $errorInfo[0] === FreshRSS_DatabaseDAOPGSQL::UNDEFINED_COLUMN) { + foreach (['attributes'] as $column) { + if (stripos($errorInfo[2], $column) !== false) { + return $this->addColumn($column); + } + } + } + } + return false; + } + public function addCategory($valuesTmp) { - $sql = 'INSERT INTO `' . $this->prefix . 'category`(name) ' - . 'SELECT * FROM (SELECT TRIM(?)) c2 ' //TRIM() to provide a type hint as text for PostgreSQL - . 'WHERE NOT EXISTS (SELECT 1 FROM `' . $this->prefix . 'tag` WHERE name = TRIM(?))'; //No tag of the same name - $stm = $this->bd->prepare($sql); + $sql = 'INSERT INTO `_category`(name, attributes) ' + . 'SELECT * FROM (SELECT TRIM(?), ?) c2 ' //TRIM() to provide a type hint as text for PostgreSQL + . 'WHERE NOT EXISTS (SELECT 1 FROM `_tag` WHERE name = TRIM(?))'; //No tag of the same name + $stm = $this->pdo->prepare($sql); $valuesTmp['name'] = mb_strcut(trim($valuesTmp['name']), 0, FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE, 'UTF-8'); + if (!isset($valuesTmp['attributes'])) { + $valuesTmp['attributes'] = []; + } $values = array( $valuesTmp['name'], + is_string($valuesTmp['attributes']) ? $valuesTmp['attributes'] : json_encode($valuesTmp['attributes'], JSON_UNESCAPED_SLASHES), $valuesTmp['name'], ); if ($stm && $stm->execute($values)) { - return $this->bd->lastInsertId('"' . $this->prefix . 'category_id_seq"'); + return $this->pdo->lastInsertId('`_category_id_seq`'); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error addCategory: ' . $info[2]); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); + if ($this->autoUpdateDb($info)) { + return $this->addCategory($valuesTmp); + } + Minz_Log::error('SQL error addCategory: ' . json_encode($info)); return false; } } @@ -39,13 +108,17 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable } public function updateCategory($id, $valuesTmp) { - $sql = 'UPDATE `' . $this->prefix . 'category` SET name=? WHERE id=? ' - . 'AND NOT EXISTS (SELECT 1 FROM `' . $this->prefix . 'tag` WHERE name = ?)'; //No tag of the same name - $stm = $this->bd->prepare($sql); + $sql = 'UPDATE `_category` SET name=?, attributes=? WHERE id=? ' + . 'AND NOT EXISTS (SELECT 1 FROM `_tag` WHERE name = ?)'; //No tag of the same name + $stm = $this->pdo->prepare($sql); $valuesTmp['name'] = mb_strcut(trim($valuesTmp['name']), 0, FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE, 'UTF-8'); + if (!isset($valuesTmp['attributes'])) { + $valuesTmp['attributes'] = []; + } $values = array( $valuesTmp['name'], + is_string($valuesTmp['attributes']) ? $valuesTmp['attributes'] : json_encode($valuesTmp['attributes'], JSON_UNESCAPED_SLASHES), $id, $valuesTmp['name'], ); @@ -53,8 +126,11 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable 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]); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); + if ($this->autoUpdateDb($info)) { + return $this->updateCategory($valuesTmp); + } + Minz_Log::error('SQL error updateCategory: ' . json_encode($info)); return false; } } @@ -63,27 +139,42 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable if ($id <= self::DEFAULTCATEGORYID) { return false; } - $sql = 'DELETE FROM `' . $this->prefix . 'category` WHERE id=?'; - $stm = $this->bd->prepare($sql); - - $values = array($id); - - if ($stm && $stm->execute($values)) { + $sql = 'DELETE FROM `_category` WHERE id=:id'; + $stm = $this->pdo->prepare($sql); + $stm->bindParam(':id', $id, PDO::PARAM_INT); + if ($stm && $stm->execute()) { return $stm->rowCount(); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error deleteCategory: ' . $info[2]); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); + Minz_Log::error('SQL error deleteCategory: ' . json_encode($info)); return false; } } - public function searchById($id) { - $sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE id=?'; - $stm = $this->bd->prepare($sql); - - $values = array($id); + public function selectAll() { + $sql = 'SELECT id, name, attributes FROM `_category`'; + $stm = $this->pdo->query($sql); + if ($stm != false) { + while ($row = $stm->fetch(PDO::FETCH_ASSOC)) { + yield $row; + } + } else { + $info = $this->pdo->errorInfo(); + if ($this->autoUpdateDb($info)) { + foreach ($this->selectAll() as $category) { // `yield from` requires PHP 7+ + yield $category; + } + } + Minz_Log::error(__method__ . ' error: ' . json_encode($info)); + yield false; + } + } - $stm->execute($values); + public function searchById($id) { + $sql = 'SELECT * FROM `_category` WHERE id=:id'; + $stm = $this->pdo->prepare($sql); + $stm->bindParam(':id', $id, PDO::PARAM_INT); + $stm->execute(); $res = $stm->fetchAll(PDO::FETCH_ASSOC); $cat = self::daoToCategory($res); @@ -94,15 +185,15 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable } } public function searchByName($name) { - $sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE name=?'; - $stm = $this->bd->prepare($sql); - - $values = array($name); - - $stm->execute($values); + $sql = 'SELECT * FROM `_category` WHERE name=:name'; + $stm = $this->pdo->prepare($sql); + if ($stm == false) { + return false; + } + $stm->bindParam(':name', $name); + $stm->execute(); $res = $stm->fetchAll(PDO::FETCH_ASSOC); $cat = self::daoToCategory($res); - if (isset($cat[0])) { return $cat[0]; } else { @@ -110,30 +201,61 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable } } + public function listSortedCategories($prePopulateFeeds = true, $details = false) { + $categories = $this->listCategories($prePopulateFeeds, $details); + + if (!is_array($categories)) { + return $categories; + } + + uasort($categories, function ($a, $b) { + $aPosition = $a->attributes('position'); + $bPosition = $b->attributes('position'); + if ($aPosition === $bPosition) { + return ($a->name() < $b->name()) ? -1 : 1; + } elseif (null === $aPosition) { + return 1; + } elseif (null === $bPosition) { + return -1; + } + return ($aPosition < $bPosition) ? -1 : 1; + }); + + return $categories; + } + public function listCategories($prePopulateFeeds = true, $details = false) { if ($prePopulateFeeds) { - $sql = 'SELECT c.id AS c_id, c.name AS c_name, ' + $sql = 'SELECT c.id AS c_id, c.name AS c_name, c.attributes AS c_attributes, ' . ($details ? 'f.* ' : 'f.id, f.name, f.url, f.website, f.priority, f.error, f.`cache_nbEntries`, f.`cache_nbUnreads`, f.ttl ') - . 'FROM `' . $this->prefix . 'category` c ' - . 'LEFT OUTER JOIN `' . $this->prefix . 'feed` f ON f.category=c.id ' + . 'FROM `_category` c ' + . 'LEFT OUTER JOIN `_feed` f ON f.category=c.id ' . 'WHERE f.priority >= :priority_normal ' . 'GROUP BY f.id, c_id ' . 'ORDER BY c.name, f.name'; - $stm = $this->bd->prepare($sql); - $stm->execute(array(':priority_normal' => FreshRSS_Feed::PRIORITY_NORMAL)); - return self::daoToCategoryPrepopulated($stm->fetchAll(PDO::FETCH_ASSOC)); + $stm = $this->pdo->prepare($sql); + $values = [ ':priority_normal' => FreshRSS_Feed::PRIORITY_NORMAL ]; + if ($stm && $stm->execute($values)) { + return self::daoToCategoryPrepopulated($stm->fetchAll(PDO::FETCH_ASSOC)); + } else { + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); + if ($this->autoUpdateDb($info)) { + return $this->listCategories($prePopulateFeeds, $details); + } + Minz_Log::error('SQL error listCategories: ' . json_encode($info)); + return false; + } } else { - $sql = 'SELECT * FROM `' . $this->prefix . 'category` ORDER BY name'; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $sql = 'SELECT * FROM `_category` ORDER BY name'; + $stm = $this->pdo->query($sql); return self::daoToCategory($stm->fetchAll(PDO::FETCH_ASSOC)); } } public function getDefault() { - $sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE id=' . self::DEFAULTCATEGORYID; - $stm = $this->bd->prepare($sql); - + $sql = 'SELECT * FROM `_category` WHERE id=:id'; + $stm = $this->pdo->prepare($sql); + $stm->bindValue(':id', self::DEFAULTCATEGORYID, PDO::PARAM_INT); $stm->execute(); $res = $stm->fetchAll(PDO::FETCH_ASSOC); $cat = self::daoToCategory($res); @@ -155,12 +277,12 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable $cat = new FreshRSS_Category(_t('gen.short.default_category')); $cat->_id(self::DEFAULTCATEGORYID); - $sql = 'INSERT INTO `' . $this->prefix . 'category`(id, name) VALUES(?, ?)'; - if (parent::$sharedDbType === 'pgsql') { + $sql = 'INSERT INTO `_category`(id, name) VALUES(?, ?)'; + if ($this->pdo->dbType() === 'pgsql') { //Force call to nextval() - $sql .= ' RETURNING nextval(\'"' . $this->prefix . 'category_id_seq"\');'; + $sql .= " RETURNING nextval('`_category_id_seq`');"; } - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); $values = array( $cat->id(), @@ -168,9 +290,9 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable ); if ($stm && $stm->execute($values)) { - return $this->bd->lastInsertId('"' . $this->prefix . 'category_id_seq"'); + return $this->pdo->lastInsertId('`_category_id_seq`'); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error check default category: ' . json_encode($info)); return false; } @@ -179,31 +301,27 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable } public function count() { - $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'category`'; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $sql = 'SELECT COUNT(*) AS count FROM `_category`'; + $stm = $this->pdo->query($sql); $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); + $sql = 'SELECT COUNT(*) AS count FROM `_feed` WHERE category=:id'; + $stm = $this->pdo->prepare($sql); + $stm->bindParam(':id', $id, PDO::PARAM_INT); + $stm->execute(); $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); + $sql = 'SELECT COUNT(*) AS count FROM `_entry` e INNER JOIN `_feed` f ON e.id_feed=f.id WHERE category=:id AND e.is_read=0'; + $stm = $this->pdo->prepare($sql); + $stm->bindParam(':id', $id, PDO::PARAM_INT); + $stm->execute(); $res = $stm->fetchAll(PDO::FETCH_ASSOC); - return $res[0]['count']; } @@ -248,6 +366,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable $feedDao->daoToFeed($feedsDao, $previousLine['c_id']) ); $cat->_id($previousLine['c_id']); + $cat->_attributes('', $previousLine['c_attributes']); $list[$previousLine['c_id']] = $cat; $feedsDao = array(); //Prepare for next category @@ -264,6 +383,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable $feedDao->daoToFeed($feedsDao, $previousLine['c_id']) ); $cat->_id($previousLine['c_id']); + $cat->_attributes('', $previousLine['c_attributes']); $list[$previousLine['c_id']] = $cat; } @@ -282,6 +402,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable $dao['name'] ); $cat->_id($dao['id']); + $cat->_attributes('', isset($dao['attributes']) ? $dao['attributes'] : ''); $cat->_isDefault(static::DEFAULTCATEGORYID === intval($dao['id'])); $list[$key] = $cat; } diff --git a/app/Models/CategoryDAOSQLite.php b/app/Models/CategoryDAOSQLite.php new file mode 100644 index 000000000..e32545c90 --- /dev/null +++ b/app/Models/CategoryDAOSQLite.php @@ -0,0 +1,17 @@ +pdo->query("PRAGMA table_info('category')")) { + $columns = $tableInfo->fetchAll(PDO::FETCH_COLUMN, 1); + foreach (['attributes'] as $column) { + if (!in_array($column, $columns)) { + return $this->addColumn($column); + } + } + } + return false; + } + +} diff --git a/app/Models/ConfigurationSetter.php b/app/Models/ConfigurationSetter.php index ec6380df4..b1d271f41 100644 --- a/app/Models/ConfigurationSetter.php +++ b/app/Models/ConfigurationSetter.php @@ -79,11 +79,6 @@ class FreshRSS_ConfigurationSetter { $data['html5_notif_timeout'] = $value >= 0 ? $value : 0; } - private function _keep_history_default(&$data, $value) { - $value = intval($value); - $data['keep_history_default'] = $value >= FreshRSS_Feed::KEEP_HISTORY_INFINITE ? $value : 0; - } - // It works for system config too! private function _language(&$data, $value) { $value = strtolower($value); @@ -94,11 +89,6 @@ class FreshRSS_ConfigurationSetter { $data['language'] = $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 : ''; } @@ -257,6 +247,9 @@ class FreshRSS_ConfigurationSetter { private function _topline_read(&$data, $value) { $data['topline_read'] = $this->handleBool($value); } + private function _topline_display_authors(&$data, $value) { + $data['topline_display_authors'] = $this->handleBool($value); + } /** * The (not so long) list of setters for system configuration. @@ -386,4 +379,8 @@ class FreshRSS_ConfigurationSetter { $data['auto_update_url'] = $value; } + + private function _force_email_validation(&$data, $value) { + $data['force_email_validation'] = $this->handleBool($value); + } } diff --git a/app/Models/Context.php b/app/Models/Context.php index 95dc47c8c..e27330665 100644 --- a/app/Models/Context.php +++ b/app/Models/Context.php @@ -51,6 +51,24 @@ class FreshRSS_Context { // Init configuration. self::$system_conf = Minz_Configuration::get('system'); self::$user_conf = Minz_Configuration::get('user'); + + //Legacy + $oldEntries = (int)FreshRSS_Context::$user_conf->param('old_entries', 0); + $keepMin = (int)FreshRSS_Context::$user_conf->param('keep_history_default', -5); + if ($oldEntries > 0 || $keepMin > -5) { //Freshrss < 1.15 + $archiving = FreshRSS_Context::$user_conf->archiving; + $archiving['keep_max'] = false; + if ($oldEntries > 0) { + $archiving['keep_period'] = 'P' . $oldEntries . 'M'; + } + if ($keepMin > 0) { + $archiving['keep_min'] = $keepMin; + } elseif ($keepMin == -1) { //Infinite + $archiving['keep_period'] = false; + $archiving['keep_min'] = false; + } + FreshRSS_Context::$user_conf->archiving = $archiving; + } } /** diff --git a/app/Models/DatabaseDAO.php b/app/Models/DatabaseDAO.php index b331eccc3..a36b469b1 100644 --- a/app/Models/DatabaseDAO.php +++ b/app/Models/DatabaseDAO.php @@ -8,25 +8,50 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo { //MySQL error codes const ER_BAD_FIELD_ERROR = '42S22'; const ER_BAD_TABLE_ERROR = '42S02'; - const ER_TRUNCATED_WRONG_VALUE_FOR_FIELD = '1366'; + const ER_DATA_TOO_LONG = '1406'; //MySQL InnoDB maximum index length for UTF8MB4 //https://dev.mysql.com/doc/refman/8.0/en/innodb-restrictions.html const LENGTH_INDEX_UNICODE = 191; + public function create() { + require(APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php'); + $db = FreshRSS_Context::$system_conf->db; + + try { + $sql = sprintf($SQL_CREATE_DB, empty($db['base']) ? '' : $db['base']); + return $this->pdo->exec($sql) !== false; + } catch (PDOException $e) { + $_SESSION['bd_error'] = $e->getMessage(); + syslog(LOG_DEBUG, __method__ . ' warning: ' . $e->getMessage()); + return false; + } + } + + public function testConnection() { + try { + $sql = 'SELECT 1'; + $stm = $this->pdo->query($sql); + $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); + return $res != false; + } catch (PDOException $e) { + $_SESSION['bd_error'] = $e->getMessage(); + syslog(LOG_DEBUG, __method__ . ' warning: ' . $e->getMessage()); + return false; + } + } + public function tablesAreCorrect() { - $sql = 'SHOW TABLES'; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query('SHOW TABLES'); $res = $stm->fetchAll(PDO::FETCH_ASSOC); $tables = array( - $this->prefix . 'category' => false, - $this->prefix . 'feed' => false, - $this->prefix . 'entry' => false, - $this->prefix . 'entrytmp' => false, - $this->prefix . 'tag' => false, - $this->prefix . 'entrytag' => false, + $this->pdo->prefix() . 'category' => false, + $this->pdo->prefix() . 'feed' => false, + $this->pdo->prefix() . 'entry' => false, + $this->pdo->prefix() . 'entrytmp' => false, + $this->pdo->prefix() . 'tag' => false, + $this->pdo->prefix() . 'entrytag' => false, ); foreach ($res as $value) { $tables[array_pop($value)] = true; @@ -36,10 +61,8 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo { } public function getSchema($table) { - $sql = 'DESC ' . $this->prefix . $table; - $stm = $this->bd->prepare($sql); - $stm->execute(); - + $sql = 'DESC `_' . $table . '`'; + $stm = $this->pdo->query($sql); return $this->listDaoToSchema($stm->fetchAll(PDO::FETCH_ASSOC)); } @@ -63,7 +86,7 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo { public function feedIsCorrect() { return $this->checkTable('feed', array( 'id', 'url', 'category', 'name', 'website', 'description', 'lastUpdate', - 'priority', 'pathEntries', 'httpAuth', 'error', 'keep_history', 'ttl', 'attributes', + 'priority', 'pathEntries', 'httpAuth', 'error', 'ttl', 'attributes', 'cache_nbEntries', 'cache_nbUnreads', )); } @@ -119,9 +142,9 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo { $values = array($db['base']); if (!$all) { $sql .= ' AND table_name LIKE ?'; - $values[] = $this->prefix . '%'; + $values[] = $this->pdo->prefix() . '%'; } - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); $stm->execute($values); $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); return $res[0]; @@ -132,30 +155,23 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo { $tables = array('category', 'feed', 'entry', 'entrytmp', 'tag', 'entrytag'); foreach ($tables as $table) { - $sql = 'OPTIMIZE TABLE `' . $this->prefix . $table . '`'; //MySQL - $stm = $this->bd->prepare($sql); - $ok &= $stm != false; - if ($stm) { - $ok &= $stm->execute(); - } + $sql = 'OPTIMIZE TABLE `_' . $table . '`'; //MySQL + $ok &= ($this->pdo->exec($sql) !== false); } return $ok; } public function ensureCaseInsensitiveGuids() { $ok = true; - $db = FreshRSS_Context::$system_conf->db; - if ($db['type'] === 'mysql') { - include_once(APP_PATH . '/SQL/install.sql.mysql.php'); - if (defined('SQL_UPDATE_GUID_LATIN1_BIN')) { //FreshRSS 1.12 - try { - $sql = sprintf(SQL_UPDATE_GUID_LATIN1_BIN, $this->prefix); - $stm = $this->bd->prepare($sql); - $ok = $stm->execute(); - } catch (Exception $e) { - $ok = false; - Minz_Log::error('FreshRSS_DatabaseDAO::ensureCaseInsensitiveGuids error: ' . $e->getMessage()); - } + if ($this->pdo->dbType() === 'mysql') { + include(APP_PATH . '/SQL/install.sql.mysql.php'); + + $ok = false; + try { + $ok = $this->pdo->exec($SQL_UPDATE_GUID_LATIN1_BIN) !== false; //FreshRSS 1.12 + } catch (Exception $e) { + $ok = false; + Minz_Log::error(__METHOD__ . ' error: ' . $e->getMessage()); } } return $ok; @@ -164,4 +180,168 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo { public function minorDbMaintenance() { $this->ensureCaseInsensitiveGuids(); } + + private static function stdError($error) { + if (defined('STDERR')) { + fwrite(STDERR, $error . "\n"); + } + Minz_Log::error($error); + return false; + } + + const SQLITE_EXPORT = 1; + const SQLITE_IMPORT = 2; + + public function dbCopy($filename, $mode, $clearFirst = false) { + $error = ''; + + $userDAO = FreshRSS_Factory::createUserDao(); + $catDAO = FreshRSS_Factory::createCategoryDao(); + $feedDAO = FreshRSS_Factory::createFeedDao(); + $entryDAO = FreshRSS_Factory::createEntryDao(); + $tagDAO = FreshRSS_Factory::createTagDao(); + + switch ($mode) { + case self::SQLITE_EXPORT: + if (@filesize($filename) > 0) { + $error = 'Error: SQLite export file already exists: ' . $filename; + } + break; + case self::SQLITE_IMPORT: + if (!is_readable($filename)) { + $error = 'Error: SQLite import file is not readable: ' . $filename; + } elseif ($clearFirst) { + $userDAO->deleteUser(); + if ($this->pdo->dbType() === 'sqlite') { + //We cannot just delete the .sqlite file otherwise PDO gets buggy. + //SQLite is the only one with database-level optimization, instead of at table level. + $this->optimize(); + } + } else { + $nbEntries = $entryDAO->countUnreadRead(); + if (!empty($nbEntries['all'])) { + $error = 'Error: Destination database already contains some entries!'; + } + } + break; + default: + $error = 'Invalid copy mode!'; + break; + } + if ($error != '') { + return self::stdError($error); + } + + $sqlite = null; + + try { + $sqlite = new MinzPDOSQLite('sqlite:' . $filename); + } catch (Exception $e) { + $error = 'Error while initialising SQLite copy: ' . $e->getMessage(); + return self::stdError($error); + } + + Minz_ModelPdo::clean(); + $userDAOSQLite = new FreshRSS_UserDAO('', $sqlite); + $categoryDAOSQLite = new FreshRSS_CategoryDAOSQLite('', $sqlite); + $feedDAOSQLite = new FreshRSS_FeedDAOSQLite('', $sqlite); + $entryDAOSQLite = new FreshRSS_EntryDAOSQLite('', $sqlite); + $tagDAOSQLite = new FreshRSS_TagDAOSQLite('', $sqlite); + + switch ($mode) { + case self::SQLITE_EXPORT: + $userFrom = $userDAO; $userTo = $userDAOSQLite; + $catFrom = $catDAO; $catTo = $categoryDAOSQLite; + $feedFrom = $feedDAO; $feedTo = $feedDAOSQLite; + $entryFrom = $entryDAO; $entryTo = $entryDAOSQLite; + $tagFrom = $tagDAO; $tagTo = $tagDAOSQLite; + break; + case self::SQLITE_IMPORT: + $userFrom = $userDAOSQLite; $userTo = $userDAO; + $catFrom = $categoryDAOSQLite; $catTo = $catDAO; + $feedFrom = $feedDAOSQLite; $feedTo = $feedDAO; + $entryFrom = $entryDAOSQLite; $entryTo = $entryDAO; + $tagFrom = $tagDAOSQLite; $tagTo = $tagDAO; + break; + } + + $idMaps = []; + + if (defined('STDERR')) { + fwrite(STDERR, "Start SQL copy…\n"); + } + + $userTo->createUser(); + + $catTo->beginTransaction(); + foreach ($catFrom->selectAll() as $category) { + $cat = $catTo->searchByName($category['name']); //Useful for the default category + if ($cat != null) { + $catId = $cat->id(); + } else { + $catId = $catTo->addCategory($category); + if ($catId == false) { + $error = 'Error during SQLite copy of categories!'; + return self::stdError($error); + } + } + $idMaps['c' . $category['id']] = $catId; + } + foreach ($feedFrom->selectAll() as $feed) { + $feed['category'] = empty($idMaps['c' . $feed['category']]) ? FreshRSS_CategoryDAO::DEFAULTCATEGORYID : $idMaps['c' . $feed['category']]; + $feedId = $feedTo->addFeed($feed); + if ($feedId == false) { + $error = 'Error during SQLite copy of feeds!'; + return self::stdError($error); + } + $idMaps['f' . $feed['id']] = $feedId; + } + $catTo->commit(); + + $nbEntries = $entryFrom->count(); + $n = 0; + $entryTo->beginTransaction(); + foreach ($entryFrom->selectAll() as $entry) { + $n++; + if (!empty($idMaps['f' . $entry['id_feed']])) { + $entry['id_feed'] = $idMaps['f' . $entry['id_feed']]; + if (!$entryTo->addEntry($entry, false)) { + $error = 'Error during SQLite copy of entries!'; + return self::stdError($error); + } + } + if ($n % 100 === 1 && defined('STDERR')) { //Display progression + fwrite(STDERR, "\033[0G" . $n . '/' . $nbEntries); + } + } + if (defined('STDERR')) { + fwrite(STDERR, "\033[0G" . $n . '/' . $nbEntries . "\n"); + } + $entryTo->commit(); + $feedTo->updateCachedValues(); + + $idMaps = []; + + $tagTo->beginTransaction(); + foreach ($tagFrom->selectAll() as $tag) { + $tagId = $tagTo->addTag($tag); + if ($tagId == false) { + $error = 'Error during SQLite copy of tags!'; + return self::stdError($error); + } + $idMaps['t' . $tag['id']] = $tagId; + } + foreach ($tagFrom->selectEntryTag() as $entryTag) { + if (!empty($idMaps['t' . $entryTag['id_tag']])) { + $entryTag['id_tag'] = $idMaps['t' . $entryTag['id_tag']]; + if (!$tagTo->tagEntry($entryTag['id_tag'], $entryTag['id_entry'])) { + $error = 'Error during SQLite copy of entry-tags!'; + return self::stdError($error); + } + } + } + $tagTo->commit(); + + return true; + } } diff --git a/app/Models/DatabaseDAOPGSQL.php b/app/Models/DatabaseDAOPGSQL.php index 8582b5719..1a6b3599e 100644 --- a/app/Models/DatabaseDAOPGSQL.php +++ b/app/Models/DatabaseDAOPGSQL.php @@ -13,18 +13,18 @@ class FreshRSS_DatabaseDAOPGSQL extends FreshRSS_DatabaseDAOSQLite { $db = FreshRSS_Context::$system_conf->db; $dbowner = $db['user']; $sql = 'SELECT * FROM pg_catalog.pg_tables where tableowner=?'; - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); $values = array($dbowner); $stm->execute($values); $res = $stm->fetchAll(PDO::FETCH_ASSOC); $tables = array( - $this->prefix . 'category' => false, - $this->prefix . 'feed' => false, - $this->prefix . 'entry' => false, - $this->prefix . 'entrytmp' => false, - $this->prefix . 'tag' => false, - $this->prefix . 'entrytag' => false, + $this->pdo->prefix() . 'category' => false, + $this->pdo->prefix() . 'feed' => false, + $this->pdo->prefix() . 'entry' => false, + $this->pdo->prefix() . 'entrytmp' => false, + $this->pdo->prefix() . 'tag' => false, + $this->pdo->prefix() . 'entrytag' => false, ); foreach ($res as $value) { $tables[array_pop($value)] = true; @@ -35,8 +35,8 @@ class FreshRSS_DatabaseDAOPGSQL extends FreshRSS_DatabaseDAOSQLite { public function getSchema($table) { $sql = 'select column_name as field, data_type as type, column_default as default, is_nullable as null from INFORMATION_SCHEMA.COLUMNS where table_name = ?'; - $stm = $this->bd->prepare($sql); - $stm->execute(array($this->prefix . $table)); + $stm = $this->pdo->prepare($sql); + $stm->execute(array($this->pdo->prefix() . $table)); return $this->listDaoToSchema($stm->fetchAll(PDO::FETCH_ASSOC)); } @@ -49,12 +49,23 @@ class FreshRSS_DatabaseDAOPGSQL extends FreshRSS_DatabaseDAOSQLite { ); } - public function size($all = true) { - $db = FreshRSS_Context::$system_conf->db; - $sql = 'SELECT pg_size_pretty(pg_database_size(?))'; - $values = array($db['base']); - $stm = $this->bd->prepare($sql); - $stm->execute($values); + public function size($all = false) { + if ($all) { + $db = FreshRSS_Context::$system_conf->db; + $sql = 'SELECT pg_database_size(:base)'; + $stm = $this->pdo->prepare($sql); + $stm->bindParam(':base', $db['base']); + $stm->execute(); + } else { + $sql = "SELECT " + . "pg_total_relation_size('{$this->pdo->prefix()}category') + " + . "pg_total_relation_size('{$this->pdo->prefix()}feed') + " + . "pg_total_relation_size('{$this->pdo->prefix()}entry') + " + . "pg_total_relation_size('{$this->pdo->prefix()}entrytmp') + " + . "pg_total_relation_size('{$this->pdo->prefix()}tag') + " + . "pg_total_relation_size('{$this->pdo->prefix()}entrytag')"; + $stm = $this->pdo->query($sql); + } $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); return $res[0]; } @@ -64,12 +75,8 @@ class FreshRSS_DatabaseDAOPGSQL extends FreshRSS_DatabaseDAOSQLite { $tables = array('category', 'feed', 'entry', 'entrytmp', 'tag', 'entrytag'); foreach ($tables as $table) { - $sql = 'VACUUM `' . $this->prefix . $table . '`'; - $stm = $this->bd->prepare($sql); - $ok &= $stm != false; - if ($stm) { - $ok &= $stm->execute(); - } + $sql = 'VACUUM `_' . $table . '`'; + $ok &= ($this->pdo->exec($sql) !== false); } return $ok; } diff --git a/app/Models/DatabaseDAOSQLite.php b/app/Models/DatabaseDAOSQLite.php index a93a209b2..413e7ee09 100644 --- a/app/Models/DatabaseDAOSQLite.php +++ b/app/Models/DatabaseDAOSQLite.php @@ -6,17 +6,16 @@ class FreshRSS_DatabaseDAOSQLite extends FreshRSS_DatabaseDAO { public function tablesAreCorrect() { $sql = 'SELECT name FROM sqlite_master WHERE type="table"'; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); $res = $stm->fetchAll(PDO::FETCH_ASSOC); $tables = array( - 'category' => false, - 'feed' => false, - 'entry' => false, - 'entrytmp' => false, - 'tag' => false, - 'entrytag' => false, + $this->pdo->prefix() . 'category' => false, + $this->pdo->prefix() . 'feed' => false, + $this->pdo->prefix() . 'entry' => false, + $this->pdo->prefix() . 'entrytmp' => false, + $this->pdo->prefix() . 'tag' => false, + $this->pdo->prefix() . 'entrytag' => false, ); foreach ($res as $value) { $tables[$value['name']] = true; @@ -27,9 +26,7 @@ class FreshRSS_DatabaseDAOSQLite extends FreshRSS_DatabaseDAO { public function getSchema($table) { $sql = 'PRAGMA table_info(' . $table . ')'; - $stm = $this->bd->prepare($sql); - $stm->execute(); - + $stm = $this->pdo->query($sql); return $this->listDaoToSchema($stm->fetchAll(PDO::FETCH_ASSOC)); } @@ -57,15 +54,18 @@ class FreshRSS_DatabaseDAOSQLite extends FreshRSS_DatabaseDAO { } public function size($all = false) { - return @filesize(join_path(DATA_PATH, 'users', $this->current_user, 'db.sqlite')); + $sum = 0; + if ($all) { + foreach (glob(DATA_PATH . '/users/*/db.sqlite') as $filename) { + $sum += @filesize($filename); + } + } else { + $sum = @filesize(DATA_PATH . '/users/' . $this->current_user . '/db.sqlite'); + } + return $sum; } public function optimize() { - $sql = 'VACUUM'; - $stm = $this->bd->prepare($sql); - if ($stm) { - return $stm->execute(); - } - return false; + return $this->pdo->exec('VACUUM') !== false; } } diff --git a/app/Models/Entry.php b/app/Models/Entry.php index 3bb977283..d90f828bc 100644 --- a/app/Models/Entry.php +++ b/app/Models/Entry.php @@ -327,7 +327,7 @@ class FreshRSS_Entry extends Minz_Model { } $ch = curl_init(); - curl_setopt_array($ch, array( + curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_REFERER => SimplePie_Misc::url_remove_credentials($url), CURLOPT_HTTPHEADER => array('Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'), @@ -337,13 +337,9 @@ class FreshRSS_Entry extends Minz_Model { //CURLOPT_FAILONERROR => true; CURLOPT_MAXREDIRS => 4, CURLOPT_RETURNTRANSFER => true, - )); - if (version_compare(PHP_VERSION, '5.6.0') >= 0 || ini_get('open_basedir') == '') { - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); //Keep option separated for open_basedir PHP bug 65646 - } - if (defined('CURLOPT_ENCODING')) { - curl_setopt($ch, CURLOPT_ENCODING, ''); //Enable all encodings - } + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_ENCODING => '', //Enable all encodings + ]); curl_setopt_array($ch, $system_conf->curl_options); if (isset($attributes['ssl_verify'])) { curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $attributes['ssl_verify'] ? 2 : 0); diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index b47cd55ad..99e99f463 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -3,11 +3,11 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function isCompressed() { - return parent::$sharedDbType === 'mysql'; + return true; } public function hasNativeHex() { - return parent::$sharedDbType !== 'sqlite'; + return true; } public function sqlHexDecode($x) { @@ -19,106 +19,40 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } //TODO: Move the database auto-updates to DatabaseDAO - protected function addColumn($name) { - Minz_Log::warning('FreshRSS_EntryDAO::addColumn: ' . $name); - $hasTransaction = false; + protected function createEntryTempTable() { + $ok = false; + $hadTransaction = $this->pdo->inTransaction(); + if ($hadTransaction) { + $this->pdo->commit(); + } try { - $stm = null; - if ($name === 'lastSeen') { //v1.1.1 - if (!$this->bd->inTransaction()) { - $this->bd->beginTransaction(); - $hasTransaction = true; - } - $stm = $this->bd->prepare('ALTER TABLE `' . $this->prefix . 'entry` ADD COLUMN `lastSeen` INT(11) DEFAULT 0'); - if ($stm && $stm->execute()) { - $stm = $this->bd->prepare('CREATE INDEX entry_lastSeen_index ON `' . $this->prefix . 'entry`(`lastSeen`);'); //"IF NOT EXISTS" does not exist in MySQL 5.7 - if ($stm && $stm->execute()) { - if ($hasTransaction) { - $this->bd->commit(); - } - return true; - } - } - if ($hasTransaction) { - $this->bd->rollBack(); - } - } elseif ($name === 'hash') { //v1.1.1 - $stm = $this->bd->prepare('ALTER TABLE `' . $this->prefix . 'entry` ADD COLUMN hash BINARY(16)'); - return $stm && $stm->execute(); - } - } catch (Exception $e) { - Minz_Log::error('FreshRSS_EntryDAO::addColumn error: ' . $e->getMessage()); - if ($hasTransaction) { - $this->bd->rollBack(); - } + require(APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php'); + Minz_Log::warning('SQL CREATE TABLE entrytmp...'); + $ok = $this->pdo->exec($SQL_CREATE_TABLE_ENTRYTMP . $SQL_CREATE_INDEX_ENTRY_1) !== false; + } catch (Exception $ex) { + Minz_Log::error(__method__ . ' error: ' . $ex->getMessage()); } - return false; + if ($hadTransaction) { + $this->pdo->beginTransaction(); + } + return $ok; } - private $triedUpdateToUtf8mb4 = false; - - //TODO: Move the database auto-updates to DatabaseDAO - protected function updateToUtf8mb4() { - if ($this->triedUpdateToUtf8mb4) { + private function updateToMediumBlob() { + if ($this->pdo->dbType() !== 'mysql') { return false; } - $this->triedUpdateToUtf8mb4 = true; - $db = FreshRSS_Context::$system_conf->db; - if ($db['type'] === 'mysql') { - include_once(APP_PATH . '/SQL/install.sql.mysql.php'); - if (defined('SQL_UPDATE_UTF8MB4')) { - Minz_Log::warning('Updating MySQL to UTF8MB4...'); //v1.5.0 - $hadTransaction = $this->bd->inTransaction(); - if ($hadTransaction) { - $this->bd->commit(); - } - $ok = false; - try { - $sql = sprintf(SQL_UPDATE_UTF8MB4, $this->prefix, $db['base']); - $stm = $this->bd->prepare($sql); - $ok = $stm->execute(); - } catch (Exception $e) { - Minz_Log::error('FreshRSS_EntryDAO::updateToUtf8mb4 error: ' . $e->getMessage()); - } - if ($hadTransaction) { - $this->bd->beginTransaction(); - //NB: Transaction not starting. Why? (tested on PHP 7.0.8-0ubuntu and MySQL 5.7.13-0ubuntu) - } - return $ok; - } - } - return false; - } + Minz_Log::warning('Update MySQL table to use MEDIUMBLOB...'); - //TODO: Move the database auto-updates to DatabaseDAO - protected function createEntryTempTable() { - $ok = false; - $hadTransaction = $this->bd->inTransaction(); - if ($hadTransaction) { - $this->bd->commit(); - } + $sql = <<<'SQL' +ALTER TABLE `_entry` MODIFY `content_bin` MEDIUMBLOB; +ALTER TABLE `_entrytmp` MODIFY `content_bin` MEDIUMBLOB; +SQL; try { - $db = FreshRSS_Context::$system_conf->db; - require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php'); - Minz_Log::warning('SQL CREATE TABLE entrytmp...'); - if (defined('SQL_CREATE_TABLE_ENTRYTMP')) { - $sql = sprintf(SQL_CREATE_TABLE_ENTRYTMP, $this->prefix); - $stm = $this->bd->prepare($sql); - $ok = $stm && $stm->execute(); - } else { - global $SQL_CREATE_TABLE_ENTRYTMP; - $ok = !empty($SQL_CREATE_TABLE_ENTRYTMP); - foreach ($SQL_CREATE_TABLE_ENTRYTMP as $instruction) { - $sql = sprintf($instruction, $this->prefix); - $stm = $this->bd->prepare($sql); - $ok &= $stm && $stm->execute(); - } - } + $ok = $this->pdo->exec($sql) !== false; } catch (Exception $e) { - Minz_Log::error('FreshRSS_EntryDAO::createEntryTempTable error: ' . $e->getMessage()); - } - if ($hadTransaction) { - $this->bd->beginTransaction(); + $ok = false; + Minz_Log::error(__method__ . ' error: ' . $e->getMessage()); } return $ok; } @@ -126,14 +60,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { //TODO: Move the database auto-updates to DatabaseDAO protected function autoUpdateDb($errorInfo) { if (isset($errorInfo[0])) { - if ($errorInfo[0] === FreshRSS_DatabaseDAO::ER_BAD_FIELD_ERROR) { - //autoAddColumn - foreach (array('lastSeen', 'hash') as $column) { - if (stripos($errorInfo[2], $column) !== false) { - return $this->addColumn($column); - } - } - } elseif ($errorInfo[0] === FreshRSS_DatabaseDAO::ER_BAD_TABLE_ERROR) { + if ($errorInfo[0] === FreshRSS_DatabaseDAO::ER_BAD_TABLE_ERROR) { if (stripos($errorInfo[2], 'tag') !== false) { $tagDAO = FreshRSS_Factory::createTagDao(); return $tagDAO->createTagTable(); //v1.12.0 @@ -143,8 +70,10 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } } if (isset($errorInfo[1])) { - if ($errorInfo[1] == FreshRSS_DatabaseDAO::ER_TRUNCATED_WRONG_VALUE_FOR_FIELD) { - return $this->updateToUtf8mb4(); //v1.5.0 + if ($errorInfo[1] == FreshRSS_DatabaseDAO::ER_DATA_TOO_LONG) { + if (stripos($errorInfo[2], 'content_bin') !== false) { + return $this->updateToMediumBlob(); //v1.15.0 + } } } return false; @@ -152,9 +81,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { private $addEntryPrepared = null; - public function addEntry($valuesTmp) { + public function addEntry($valuesTmp, $useTmpTable = true) { if ($this->addEntryPrepared == null) { - $sql = 'INSERT INTO `' . $this->prefix . 'entrytmp` (id, guid, title, author, ' + $sql = 'INSERT INTO `_' . ($useTmpTable ? 'entrytmp' : 'entry') . '` (id, guid, title, author, ' . ($this->isCompressed() ? 'content_bin' : 'content') . ', link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags) ' . 'VALUES(:id, :guid, :title, :author, ' @@ -162,7 +91,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { . ', :link, :date, :last_seen, ' . $this->sqlHexDecode(':hash') . ', :is_read, :is_favorite, :id_feed, :tags)'; - $this->addEntryPrepared = $this->bd->prepare($sql); + $this->addEntryPrepared = $this->pdo->prepare($sql); } if ($this->addEntryPrepared) { $this->addEntryPrepared->bindParam(':id', $valuesTmp['id']); @@ -178,7 +107,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $valuesTmp['link'] = safe_ascii($valuesTmp['link']); $this->addEntryPrepared->bindParam(':link', $valuesTmp['link']); $this->addEntryPrepared->bindParam(':date', $valuesTmp['date'], PDO::PARAM_INT); - $valuesTmp['lastSeen'] = time(); + if (empty($valuesTmp['lastSeen'])) { + $valuesTmp['lastSeen'] = time(); + } $this->addEntryPrepared->bindParam(':last_seen', $valuesTmp['lastSeen'], PDO::PARAM_INT); $valuesTmp['is_read'] = $valuesTmp['is_read'] ? 1 : 0; $this->addEntryPrepared->bindParam(':is_read', $valuesTmp['is_read'], PDO::PARAM_INT); @@ -191,14 +122,14 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($this->hasNativeHex()) { $this->addEntryPrepared->bindParam(':hash', $valuesTmp['hash']); } else { - $valuesTmp['hashBin'] = pack('H*', $valuesTmp['hash']); //hex2bin() is PHP5.4+ + $valuesTmp['hashBin'] = hex2bin($valuesTmp['hash']); $this->addEntryPrepared->bindParam(':hash', $valuesTmp['hashBin']); } } if ($this->addEntryPrepared && $this->addEntryPrepared->execute()) { return true; } else { - $info = $this->addEntryPrepared == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $this->addEntryPrepared->errorInfo(); + $info = $this->addEntryPrepared == null ? $this->pdo->errorInfo() : $this->addEntryPrepared->errorInfo(); if ($this->autoUpdateDb($info)) { $this->addEntryPrepared = null; return $this->addEntry($valuesTmp); @@ -211,22 +142,26 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function commitNewEntries() { - $sql = 'SET @rank=(SELECT MAX(id) - COUNT(*) FROM `' . $this->prefix . 'entrytmp`); ' . //MySQL-specific - 'INSERT IGNORE INTO `' . $this->prefix . 'entry` - ( - id, guid, title, author, content_bin, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags - ) ' . - 'SELECT @rank:=@rank+1 AS id, guid, title, author, content_bin, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags - FROM `' . $this->prefix . 'entrytmp` - ORDER BY date; ' . - 'DELETE FROM `' . $this->prefix . 'entrytmp` WHERE id <= @rank;'; - $hadTransaction = $this->bd->inTransaction(); + $sql = <<<'SQL' +SET @rank=(SELECT MAX(id) - COUNT(*) FROM `_entrytmp`); + +INSERT IGNORE INTO `_entry` ( + id, guid, title, author, content_bin, link, date, `lastSeen`, + hash, is_read, is_favorite, id_feed, tags +) +SELECT @rank:=@rank+1 AS id, guid, title, author, content_bin, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags +FROM `_entrytmp` +ORDER BY date; + +DELETE FROM `_entrytmp` WHERE id <= @rank;'; +SQL; + $hadTransaction = $this->pdo->inTransaction(); if (!$hadTransaction) { - $this->bd->beginTransaction(); + $this->pdo->beginTransaction(); } - $result = $this->bd->exec($sql) !== false; + $result = $this->pdo->exec($sql) !== false; if (!$hadTransaction) { - $this->bd->commit(); + $this->pdo->commit(); } return $result; } @@ -239,7 +174,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } if ($this->updateEntryPrepared === null) { - $sql = 'UPDATE `' . $this->prefix . 'entry` ' + $sql = 'UPDATE `_entry` ' . 'SET title=:title, author=:author, ' . ($this->isCompressed() ? 'content_bin=COMPRESS(:content)' : 'content=:content') . ', link=:link, date=:date, `lastSeen`=:last_seen, ' @@ -247,7 +182,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { . ', ' . ($valuesTmp['is_read'] === null ? '' : 'is_read=:is_read, ') . 'tags=:tags ' . 'WHERE id_feed=:id_feed AND guid=:guid'; - $this->updateEntryPrepared = $this->bd->prepare($sql); + $this->updateEntryPrepared = $this->pdo->prepare($sql); } $valuesTmp['guid'] = substr($valuesTmp['guid'], 0, 760); @@ -273,14 +208,14 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($this->hasNativeHex()) { $this->updateEntryPrepared->bindParam(':hash', $valuesTmp['hash']); } else { - $valuesTmp['hashBin'] = pack('H*', $valuesTmp['hash']); //hex2bin() is PHP5.4+ + $valuesTmp['hashBin'] = hex2bin($valuesTmp['hash']); $this->updateEntryPrepared->bindParam(':hash', $valuesTmp['hashBin']); } if ($this->updateEntryPrepared && $this->updateEntryPrepared->execute()) { return true; } else { - $info = $this->updateEntryPrepared == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $this->updateEntryPrepared->errorInfo(); + $info = $this->updateEntryPrepared == null ? $this->pdo->errorInfo() : $this->updateEntryPrepared->errorInfo(); if ($this->autoUpdateDb($info)) { return $this->updateEntry($valuesTmp); } @@ -308,16 +243,16 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return 0; } FreshRSS_UserDAO::touch(); - $sql = 'UPDATE `' . $this->prefix . 'entry` ' + $sql = 'UPDATE `_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); + $stm = $this->pdo->prepare($sql); if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markFavorite: ' . $info[2]); return false; } @@ -335,11 +270,11 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { * @return boolean */ protected function updateCacheUnreads($catId = false, $feedId = false) { - $sql = 'UPDATE `' . $this->prefix . 'feed` f ' + $sql = 'UPDATE `_feed` f ' . 'LEFT OUTER JOIN (' . 'SELECT e.id_feed, ' . 'COUNT(*) AS nbUnreads ' - . 'FROM `' . $this->prefix . 'entry` e ' + . 'FROM `_entry` e ' . 'WHERE e.is_read=0 ' . 'GROUP BY e.id_feed' . ') x ON x.id_feed=f.id ' @@ -358,11 +293,11 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $sql .= ' f.category=?'; $values[] = $catId; } - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); if ($stm && $stm->execute($values)) { return true; } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error updateCacheUnreads: ' . $info[2]); return false; } @@ -392,14 +327,14 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return $affected; } - $sql = 'UPDATE `' . $this->prefix . 'entry` ' + $sql = 'UPDATE `_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); + $stm = $this->pdo->prepare($sql); if (!($stm && $stm->execute($values))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markRead: ' . $info[2]); return false; } @@ -409,16 +344,16 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } return $affected; } else { - $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id ' + $sql = 'UPDATE `_entry` e INNER JOIN `_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); + $stm = $this->pdo->prepare($sql); if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markRead: ' . $info[2]); return false; } @@ -453,7 +388,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { 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 ' + $sql = 'UPDATE `_entry` e INNER JOIN `_feed` f ON e.id_feed=f.id ' . 'SET e.is_read=? ' . 'WHERE e.is_read <> ? AND e.id <= ?'; if ($onlyFavorites) { @@ -465,9 +400,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { list($searchValues, $search) = $this->sqlListEntriesWhere('e.', $filters, $state); - $stm = $this->bd->prepare($sql . $search); + $stm = $this->pdo->prepare($sql . $search); if (!($stm && $stm->execute(array_merge($values, $searchValues)))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markReadEntries: ' . $info[2]); return false; } @@ -496,16 +431,16 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { 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 ' + $sql = 'UPDATE `_entry` e INNER JOIN `_feed` f ON e.id_feed=f.id ' . 'SET e.is_read=? ' . 'WHERE f.category=? AND e.is_read <> ? AND e.id <= ?'; $values = array($is_read ? 1 : 0, $id, $is_read ? 1 : 0, $idMax); list($searchValues, $search) = $this->sqlListEntriesWhere('e.', $filters, $state); - $stm = $this->bd->prepare($sql . $search); + $stm = $this->pdo->prepare($sql . $search); if (!($stm && $stm->execute(array_merge($values, $searchValues)))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markReadCat: ' . $info[2]); return false; } @@ -533,39 +468,39 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $idMax = time() . '000000'; Minz_Log::debug('Calling markReadFeed(0) is deprecated!'); } - $this->bd->beginTransaction(); + $this->pdo->beginTransaction(); - $sql = 'UPDATE `' . $this->prefix . 'entry` ' + $sql = 'UPDATE `_entry` ' . 'SET is_read=? ' . 'WHERE id_feed=? AND is_read <> ? AND id <= ?'; $values = array($is_read ? 1 : 0, $id_feed, $is_read ? 1 : 0, $idMax); list($searchValues, $search) = $this->sqlListEntriesWhere('', $filters, $state); - $stm = $this->bd->prepare($sql . $search); + $stm = $this->pdo->prepare($sql . $search); if (!($stm && $stm->execute(array_merge($values, $searchValues)))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markReadFeed: ' . $info[2] . ' with SQL: ' . $sql . $search); - $this->bd->rollBack(); + $this->pdo->rollBack(); return false; } $affected = $stm->rowCount(); if ($affected > 0) { - $sql = 'UPDATE `' . $this->prefix . 'feed` ' + $sql = 'UPDATE `_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(); + . ' WHERE id=:id'; + $stm = $this->pdo->prepare($sql); + $stm->bindParam(':id', $id_feed, PDO::PARAM_INT); + if (!($stm && $stm->execute())) { + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markReadFeed cache: ' . $info[2]); - $this->bd->rollBack(); + $this->pdo->rollBack(); return false; } } - $this->bd->commit(); + $this->pdo->commit(); return $affected; } @@ -582,7 +517,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { Minz_Log::debug('Calling markReadTag(0) is deprecated!'); } - $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'entrytag` et ON et.id_entry = e.id ' + $sql = 'UPDATE `_entry` e INNER JOIN `_entrytag` et ON et.id_entry = e.id ' . 'SET e.is_read = ? ' . 'WHERE ' . ($id == '' ? '' : 'et.id_tag = ? AND ') @@ -596,9 +531,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { list($searchValues, $search) = $this->sqlListEntriesWhere('e.', $filters, $state); - $stm = $this->bd->prepare($sql . $search); + $stm = $this->pdo->prepare($sql . $search); if (!($stm && $stm->execute(array_merge($values, $searchValues)))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markReadTag: ' . $info[2]); return false; } @@ -609,48 +544,86 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return $affected; } - public function cleanOldEntries($id_feed, $date_min, $keep = 15) { //Remember to call updateCachedValue($id_feed) or updateCachedValues() just after - $sql = 'DELETE FROM `' . $this->prefix . 'entry` ' - . 'WHERE id_feed=:id_feed AND id<=:id_max ' - . 'AND is_favorite=0 ' //Do not remove favourites - . 'AND `lastSeen` < (SELECT maxLastSeen FROM (SELECT (MAX(e3.`lastSeen`)-99) AS maxLastSeen FROM `' . $this->prefix . 'entry` e3 WHERE e3.id_feed=:id_feed) recent) ' //Do not remove the most newly seen articles, plus a few seconds of tolerance - . 'AND id NOT IN (SELECT id_entry FROM `' . $this->prefix . 'entrytag`) ' //Do not purge tagged entries - . 'AND id NOT IN (SELECT id FROM (SELECT e2.id FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=:id_feed ORDER BY id DESC LIMIT :keep) keep)'; //Double select: MySQL doesn't support 'LIMIT & IN/ALL/ANY/SOME subquery' - $stm = $this->bd->prepare($sql); + public function cleanOldEntries($id_feed, $options = []) { //Remember to call updateCachedValue($id_feed) or updateCachedValues() just after + $sql = 'DELETE FROM `_entry` WHERE id_feed = :id_feed1'; //No alias for MySQL / MariaDB + $params = []; + $params[':id_feed1'] = $id_feed; - if ($stm) { - $id_max = intval($date_min) . '000000'; - $stm->bindParam(':id_feed', $id_feed, PDO::PARAM_INT); - $stm->bindParam(':id_max', $id_max, PDO::PARAM_STR); - $stm->bindParam(':keep', $keep, PDO::PARAM_INT); + //==Exclusions== + if (!empty($options['keep_favourites'])) { + $sql .= ' AND is_favorite = 0'; + } + if (!empty($options['keep_unreads'])) { + $sql .= ' AND is_read = 1'; + } + if (!empty($options['keep_labels'])) { + $sql .= ' AND NOT EXISTS (SELECT 1 FROM `_entrytag` WHERE id_entry = id)'; + } + if (!empty($options['keep_min']) && $options['keep_min'] > 0) { + //Double SELECT for MySQL workaround ERROR 1093 (HY000) + $sql .= ' AND `lastSeen` < (SELECT `lastSeen`' + . ' FROM (SELECT e2.`lastSeen` FROM `_entry` e2 WHERE e2.id_feed = :id_feed2' + . ' ORDER BY e2.`lastSeen` DESC LIMIT 1 OFFSET :keep_min) last_seen2)'; + $params[':id_feed2'] = $id_feed; + $params[':keep_min'] = (int)$options['keep_min']; + } + //Keep at least the articles seen at the last refresh + $sql .= ' AND `lastSeen` < (SELECT maxlastseen' + . ' FROM (SELECT MAX(e3.`lastSeen`) AS maxlastseen FROM `_entry` e3 WHERE e3.id_feed = :id_feed3) last_seen3)'; + $params[':id_feed3'] = $id_feed; + + //==Inclusions== + $sql .= ' AND (1=0'; + if (!empty($options['keep_period'])) { + $sql .= ' OR `lastSeen` < :max_last_seen'; + $now = new DateTime('now'); + $now->sub(new DateInterval($options['keep_period'])); + $params[':max_last_seen'] = $now->format('U'); } + if (!empty($options['keep_max']) && $options['keep_max'] > 0) { + $sql .= ' OR `lastSeen` <= (SELECT `lastSeen`' + . ' FROM (SELECT e4.`lastSeen` FROM `_entry` e4 WHERE e4.id_feed = :id_feed4' + . ' ORDER BY e4.`lastSeen` DESC LIMIT 1 OFFSET :keep_max) last_seen4)'; + $params[':id_feed4'] = $id_feed; + $params[':keep_max'] = (int)$options['keep_max']; + } + $sql .= ')'; + + $stm = $this->pdo->prepare($sql); - if ($stm && $stm->execute()) { + if ($stm && $stm->execute($params)) { return $stm->rowCount(); } else { - $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); if ($this->autoUpdateDb($info)) { - return $this->cleanOldEntries($id_feed, $date_min, $keep); + return $this->cleanOldEntries($id_feed, $options); } - Minz_Log::error('SQL error cleanOldEntries: ' . $info[2]); + Minz_Log::error(__method__ . ' error:' . json_encode($info)); return false; } } + public function selectAll() { + $sql = 'SELECT id, guid, title, author, ' + . ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') + . ', link, date, `lastSeen`, ' . $this->sqlHexEncode('hash') . ' AS hash, is_read, is_favorite, id_feed, tags ' + . 'FROM `_entry`'; + $stm = $this->pdo->query($sql); + while ($row = $stm->fetch(PDO::FETCH_ASSOC)) { + yield $row; + } + } + 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); + . 'FROM `_entry` WHERE id_feed=:id_feed AND guid=:guid'; + $stm = $this->pdo->prepare($sql); + $stm->bindParam(':id_feed', $id_feed, PDO::PARAM_INT); + $stm->bindParam(':guid', $guid); + $stm->execute(); $res = $stm->fetchAll(PDO::FETCH_ASSOC); $entries = self::daoToEntries($res); return isset($entries[0]) ? $entries[0] : null; @@ -660,22 +633,21 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $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); + . 'FROM `_entry` WHERE id=:id'; + $stm = $this->pdo->prepare($sql); + $stm->bindParam(':id', $id, PDO::PARAM_INT); + $stm->execute(); $res = $stm->fetchAll(PDO::FETCH_ASSOC); $entries = self::daoToEntries($res); return isset($entries[0]) ? $entries[0] : null; } public function searchIdByGuid($id_feed, $guid) { - $sql = 'SELECT id FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND guid=?'; - $stm = $this->bd->prepare($sql); - $values = array($id_feed, $guid); - $stm->execute($values); + $sql = 'SELECT id FROM `_entry` WHERE id_feed=:id_feed AND guid=:guid'; + $stm = $this->pdo->prepare($sql); + $stm->bindParam(':id_feed', $id_feed, PDO::PARAM_INT); + $stm->bindParam(':guid', $guid); + $stm->execute(); $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); return isset($res[0]) ? $res[0] : null; } @@ -859,7 +831,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $where .= '1=1 '; break; case 'ST': //Starred or tagged - $where .= 'e.is_favorite=1 OR EXISTS (SELECT et2.id_tag FROM `' . $this->prefix . 'entrytag` et2 WHERE et2.id_entry = e.id) '; + $where .= 'e.is_favorite=1 OR EXISTS (SELECT et2.id_tag FROM `_entrytag` et2 WHERE et2.id_entry = e.id) '; break; default: throw new FreshRSS_EntriesGetter_Exception('Bad type in Entry->listByType: [' . $type . ']!'); @@ -870,9 +842,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return array(array_merge($values, $searchValues), 'SELECT ' . ($type === 'T' ? 'DISTINCT ' : '') - . 'e.id FROM `' . $this->prefix . 'entry` e ' - . 'INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' - . ($type === 't' || $type === 'T' ? 'INNER JOIN `' . $this->prefix . 'entrytag` et ON et.id_entry = e.id ' : '') + . 'e.id FROM `_entry` e ' + . 'INNER JOIN `_feed` f ON e.id_feed = f.id ' + . ($type === 't' || $type === 'T' ? 'INNER JOIN `_entrytag` et ON et.id_entry = e.id ' : '') . 'WHERE ' . $where . $search . 'ORDER BY e.id ' . $order @@ -885,17 +857,17 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $sql = 'SELECT e0.id, e0.guid, e0.title, e0.author, ' . ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') . ', e0.link, e0.date, e0.is_read, e0.is_favorite, e0.id_feed, e0.tags ' - . 'FROM `' . $this->prefix . 'entry` e0 ' + . 'FROM `_entry` e0 ' . 'INNER JOIN (' . $sql . ') e2 ON e2.id=e0.id ' . 'ORDER BY e0.id ' . $order; - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); if ($stm && $stm->execute($values)) { return $stm; } else { - $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error listWhereRaw: ' . $info[2]); return false; } @@ -918,11 +890,11 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $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` ' + . 'FROM `_entry` ' . 'WHERE id IN (' . str_repeat('?,', count($ids) - 1). '?) ' . 'ORDER BY id ' . $order; - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); $stm->execute($ids); return self::daoToEntries($stm->fetchAll(PDO::FETCH_ASSOC)); } @@ -930,7 +902,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function listIdsWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filters = null) { //For API list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filters); - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); $stm->execute($values); return $stm->fetchAll(PDO::FETCH_COLUMN, 0); @@ -941,8 +913,8 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return array(); } $guids = array_unique($guids); - $sql = 'SELECT guid, ' . $this->sqlHexEncode('hash') . ' AS hex_hash FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND guid IN (' . str_repeat('?,', count($guids) - 1). '?)'; - $stm = $this->bd->prepare($sql); + $sql = 'SELECT guid, ' . $this->sqlHexEncode('hash') . ' AS hex_hash FROM `_entry` WHERE id_feed=? AND guid IN (' . str_repeat('?,', count($guids) - 1). '?)'; + $stm = $this->pdo->prepare($sql); $values = array($id_feed); $values = array_merge($values, $guids); if ($stm && $stm->execute($values)) { @@ -953,7 +925,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } return $result; } else { - $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); if ($this->autoUpdateDb($info)) { return $this->listHashForFeedGuids($id_feed, $guids); } @@ -967,8 +939,8 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { 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); + $sql = 'UPDATE `_entry` SET `lastSeen`=? WHERE id_feed=? AND guid IN (' . str_repeat('?,', count($guids) - 1). '?)'; + $stm = $this->pdo->prepare($sql); if ($mtime <= 0) { $mtime = time(); } @@ -977,7 +949,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { - $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); if ($this->autoUpdateDb($info)) { return $this->updateLastSeen($id_feed, $guids); } @@ -988,65 +960,70 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } 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 f.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 f.priority > 0 AND e.is_read=0'; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $sql = 'SELECT COUNT(e.id) AS count FROM `_entry` e INNER JOIN `_feed` f ON e.id_feed=f.id WHERE f.priority > 0' + . ' UNION SELECT COUNT(e.id) AS count FROM `_entry` e INNER JOIN `_feed` f ON e.id_feed=f.id WHERE f.priority > 0 AND e.is_read=0'; + $stm = $this->pdo->query($sql); + if ($stm === false) { + return false; + } $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); rsort($res); $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'; + $sql = 'SELECT COUNT(e.id) AS count FROM `_entry` e'; if ($minPriority !== null) { - $sql .= ' INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id'; + $sql .= ' INNER JOIN `_feed` f ON e.id_feed=f.id'; $sql .= ' WHERE f.priority > ' . intval($minPriority); } - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); + if ($stm == false) { + return false; + } $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); return isset($res[0]) ? $res[0] : 0; } + public function countNotRead($minPriority = null) { - $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e'; + $sql = 'SELECT COUNT(e.id) AS count FROM `_entry` e'; if ($minPriority !== null) { - $sql .= ' INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id'; + $sql .= ' INNER JOIN `_feed` f ON e.id_feed=f.id'; } $sql .= ' WHERE e.is_read=0'; if ($minPriority !== null) { $sql .= ' AND f.priority > ' . intval($minPriority); } - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); return $res[0]; } public function countUnreadReadFavorites() { - $sql = <<prefix}entry` AS e1 - JOIN `{$this->prefix}feed` AS f1 ON e1.id_feed = f1.id - WHERE e1.is_favorite = 1 - AND f1.priority >= :priority_normal - UNION - SELECT COUNT(e2.id) AS c - , 2 AS o - FROM `{$this->prefix}entry` AS e2 - JOIN `{$this->prefix}feed` AS f2 ON e2.id_feed = f2.id - WHERE e2.is_favorite = 1 - AND e2.is_read = 0 - AND f2.priority >= :priority_normal - ) u + $sql = <<<'SQL' +SELECT c FROM ( + SELECT COUNT(e1.id) AS c, 1 AS o + FROM `_entry` AS e1 + JOIN `_feed` AS f1 ON e1.id_feed = f1.id + WHERE e1.is_favorite = 1 + AND f1.priority >= :priority_normal1 + UNION + SELECT COUNT(e2.id) AS c, 2 AS o + FROM `_entry` AS e2 + JOIN `_feed` AS f2 ON e2.id_feed = f2.id + WHERE e2.is_favorite = 1 + AND e2.is_read = 0 + AND f2.priority >= :priority_normal2 + ) u ORDER BY o SQL; - $stm = $this->bd->prepare($sql); - $stm->execute(array(':priority_normal' => FreshRSS_Feed::PRIORITY_NORMAL)); + $stm = $this->pdo->prepare($sql); + //Binding a value more than once is not standard and does not work with native prepared statements (e.g. MySQL) https://bugs.php.net/bug.php?id=40417 + $stm->bindValue(':priority_normal1', FreshRSS_Feed::PRIORITY_NORMAL, PDO::PARAM_INT); + $stm->bindValue(':priority_normal2', FreshRSS_Feed::PRIORITY_NORMAL, PDO::PARAM_INT); + $stm->execute(); $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); rsort($res); $all = empty($res[0]) ? 0 : $res[0]; diff --git a/app/Models/EntryDAOPGSQL.php b/app/Models/EntryDAOPGSQL.php index e571e457f..9afea279f 100644 --- a/app/Models/EntryDAOPGSQL.php +++ b/app/Models/EntryDAOPGSQL.php @@ -2,6 +2,10 @@ class FreshRSS_EntryDAOPGSQL extends FreshRSS_EntryDAOSQLite { + public function hasNativeHex() { + return true; + } + public function sqlHexDecode($x) { return 'decode(' . $x . ", 'hex')"; } @@ -31,25 +35,27 @@ class FreshRSS_EntryDAOPGSQL extends FreshRSS_EntryDAOSQLite { public function commitNewEntries() { $sql = 'DO $$ DECLARE -maxrank bigint := (SELECT MAX(id) FROM `' . $this->prefix . 'entrytmp`); -rank bigint := (SELECT maxrank - COUNT(*) FROM `' . $this->prefix . 'entrytmp`); +maxrank bigint := (SELECT MAX(id) FROM `_entrytmp`); +rank bigint := (SELECT maxrank - COUNT(*) FROM `_entrytmp`); BEGIN - INSERT INTO `' . $this->prefix . 'entry` (id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags) - (SELECT rank + row_number() OVER(ORDER BY date) AS id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags - FROM `' . $this->prefix . 'entrytmp` AS etmp + INSERT INTO `_entry` + (id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags) + (SELECT rank + row_number() OVER(ORDER BY date) AS id, guid, title, author, content, + link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags + FROM `_entrytmp` AS etmp WHERE NOT EXISTS ( - SELECT 1 FROM `' . $this->prefix . 'entry` AS ereal + SELECT 1 FROM `_entry` AS ereal WHERE (etmp.id = ereal.id) OR (etmp.id_feed = ereal.id_feed AND etmp.guid = ereal.guid)) ORDER BY date); - DELETE FROM `' . $this->prefix . 'entrytmp` WHERE id <= maxrank; + DELETE FROM `_entrytmp` WHERE id <= maxrank; END $$;'; - $hadTransaction = $this->bd->inTransaction(); + $hadTransaction = $this->pdo->inTransaction(); if (!$hadTransaction) { - $this->bd->beginTransaction(); + $this->pdo->beginTransaction(); } - $result = $this->bd->exec($sql) !== false; + $result = $this->pdo->exec($sql) !== false; if (!$hadTransaction) { - $this->bd->commit(); + $this->pdo->commit(); } return $result; } diff --git a/app/Models/EntryDAOSQLite.php b/app/Models/EntryDAOSQLite.php index f8cd14fe6..12e8f27e1 100644 --- a/app/Models/EntryDAOSQLite.php +++ b/app/Models/EntryDAOSQLite.php @@ -2,32 +2,32 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO { + public function isCompressed() { + return false; + } + + public function hasNativeHex() { + return false; + } + public function sqlHexDecode($x) { return $x; } protected function autoUpdateDb($errorInfo) { - if ($tableInfo = $this->bd->query("SELECT sql FROM sqlite_master where name='tag'")) { + if ($tableInfo = $this->pdo->query("SELECT sql FROM sqlite_master where name='tag'")) { $showCreate = $tableInfo->fetchColumn(); if (stripos($showCreate, 'tag') === false) { $tagDAO = FreshRSS_Factory::createTagDao(); return $tagDAO->createTagTable(); //v1.12.0 } } - if ($tableInfo = $this->bd->query("SELECT sql FROM sqlite_master where name='entrytmp'")) { + if ($tableInfo = $this->pdo->query("SELECT sql FROM sqlite_master where name='entrytmp'")) { $showCreate = $tableInfo->fetchColumn(); if (stripos($showCreate, 'entrytmp') === false) { return $this->createEntryTempTable(); //v1.7.0 } } - if ($tableInfo = $this->bd->query("SELECT sql FROM sqlite_master where name='entry'")) { - $showCreate = $tableInfo->fetchColumn(); - foreach (array('lastSeen', 'hash') as $column) { - if (stripos($showCreate, $column) === false) { - return $this->addColumn($column); - } - } - } return false; } @@ -36,27 +36,27 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO { DROP TABLE IF EXISTS `tmp`; CREATE TEMP TABLE `tmp` AS SELECT id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags - FROM `' . $this->prefix . 'entrytmp` + FROM `_entrytmp` ORDER BY date; -INSERT OR IGNORE INTO `' . $this->prefix . 'entry` +INSERT OR IGNORE INTO `_entry` (id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags) SELECT rowid + (SELECT MAX(id) - COUNT(*) FROM `tmp`) AS id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags FROM `tmp` ORDER BY date; -DELETE FROM `' . $this->prefix . 'entrytmp` WHERE id <= (SELECT MAX(id) FROM `tmp`); +DELETE FROM `_entrytmp` WHERE id <= (SELECT MAX(id) FROM `tmp`); DROP TABLE IF EXISTS `tmp`; '; - $hadTransaction = $this->bd->inTransaction(); + $hadTransaction = $this->pdo->inTransaction(); if (!$hadTransaction) { - $this->bd->beginTransaction(); + $this->pdo->beginTransaction(); } - $result = $this->bd->exec($sql) !== false; + $result = $this->pdo->exec($sql) !== false; if (!$result) { - Minz_Log::error('SQL error commitNewEntries: ' . json_encode($this->bd->errorInfo())); + Minz_Log::error('SQL error commitNewEntries: ' . json_encode($this->pdo->errorInfo())); } if (!$hadTransaction) { - $this->bd->commit(); + $this->pdo->commit(); } return $result; } @@ -66,10 +66,10 @@ DROP TABLE IF EXISTS `tmp`; } protected function updateCacheUnreads($catId = false, $feedId = false) { - $sql = 'UPDATE `' . $this->prefix . 'feed` ' + $sql = 'UPDATE `_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)'; + . 'SELECT COUNT(*) AS nbUnreads FROM `_entry` e ' + . 'WHERE e.id_feed=`_feed`.id AND e.is_read=0)'; $hasWhere = false; $values = array(); if ($feedId !== false) { @@ -84,11 +84,11 @@ DROP TABLE IF EXISTS `tmp`; $sql .= ' category=?'; $values[] = $catId; } - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); if ($stm && $stm->execute($values)) { return true; } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error updateCacheUnreads: ' . $info[2]); return false; } @@ -118,30 +118,30 @@ DROP TABLE IF EXISTS `tmp`; return $affected; } } else { - $this->bd->beginTransaction(); - $sql = 'UPDATE `' . $this->prefix . 'entry` SET is_read=? WHERE id=? AND is_read=?'; + $this->pdo->beginTransaction(); + $sql = 'UPDATE `_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); + $stm = $this->pdo->prepare($sql); if (!($stm && $stm->execute($values))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markRead 1: ' . $info[2]); - $this->bd->rollBack(); + $this->pdo->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=?)'; + $sql = 'UPDATE `_feed` SET `cache_nbUnreads`=`cache_nbUnreads`' . ($is_read ? '-' : '+') . '1 ' + . 'WHERE id=(SELECT e.id_feed FROM `_entry` e WHERE e.id=?)'; $values = array($ids); - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); if (!($stm && $stm->execute($values))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markRead 2: ' . $info[2]); - $this->bd->rollBack(); + $this->pdo->rollBack(); return false; } } - $this->bd->commit(); + $this->pdo->commit(); return $affected; } } @@ -174,19 +174,19 @@ DROP TABLE IF EXISTS `tmp`; Minz_Log::debug('Calling markReadEntries(0) is deprecated!'); } - $sql = 'UPDATE `' . $this->prefix . 'entry` SET is_read = ? WHERE is_read <> ? AND id <= ?'; + $sql = 'UPDATE `_entry` SET is_read = ? WHERE is_read <> ? 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) . ')'; + $sql .= ' AND id_feed IN (SELECT f.id FROM `_feed` f WHERE f.priority > ' . intval($priorityMin) . ')'; } $values = array($is_read ? 1 : 0, $is_read ? 1 : 0, $idMax); list($searchValues, $search) = $this->sqlListEntriesWhere('', $filters, $state); - $stm = $this->bd->prepare($sql . $search); + $stm = $this->pdo->prepare($sql . $search); if (!($stm && $stm->execute(array_merge($values, $searchValues)))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markReadEntries: ' . $info[2]); return false; } @@ -215,17 +215,17 @@ DROP TABLE IF EXISTS `tmp`; Minz_Log::debug('Calling markReadCat(0) is deprecated!'); } - $sql = 'UPDATE `' . $this->prefix . 'entry` ' + $sql = 'UPDATE `_entry` ' . 'SET is_read = ? ' . 'WHERE is_read <> ? AND id <= ? AND ' - . 'id_feed IN (SELECT f.id FROM `' . $this->prefix . 'feed` f WHERE f.category=?)'; + . 'id_feed IN (SELECT f.id FROM `_feed` f WHERE f.category=?)'; $values = array($is_read ? 1 : 0, $is_read ? 1 : 0, $idMax, $id); list($searchValues, $search) = $this->sqlListEntriesWhere('', $filters, $state); - $stm = $this->bd->prepare($sql . $search); + $stm = $this->pdo->prepare($sql . $search); if (!($stm && $stm->execute(array_merge($values, $searchValues)))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markReadCat: ' . $info[2]); return false; } @@ -249,10 +249,10 @@ DROP TABLE IF EXISTS `tmp`; Minz_Log::debug('Calling markReadTag(0) is deprecated!'); } - $sql = 'UPDATE `' . $this->prefix . 'entry` e ' + $sql = 'UPDATE `_entry` e ' . 'SET e.is_read = ? ' . 'WHERE e.is_read <> ? AND e.id <= ? AND ' - . 'e.id IN (SELECT et.id_entry FROM `' . $this->prefix . 'entrytag` et ' + . 'e.id IN (SELECT et.id_entry FROM `_entrytag` et ' . ($id == '' ? '' : 'WHERE et.id = ?') . ')'; $values = array($is_read ? 1 : 0, $is_read ? 1 : 0, $idMax); @@ -262,9 +262,9 @@ DROP TABLE IF EXISTS `tmp`; list($searchValues, $search) = $this->sqlListEntriesWhere('e.', $filters, $state); - $stm = $this->bd->prepare($sql . $search); + $stm = $this->pdo->prepare($sql . $search); if (!($stm && $stm->execute(array_merge($values, $searchValues)))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markReadTag: ' . $info[2]); return false; } diff --git a/app/Models/Factory.php b/app/Models/Factory.php index 1accb491c..69885c205 100644 --- a/app/Models/Factory.php +++ b/app/Models/Factory.php @@ -2,8 +2,18 @@ class FreshRSS_Factory { + public static function createUserDao($username = null) { + return new FreshRSS_UserDAO($username); + } + public static function createCategoryDao($username = null) { - return new FreshRSS_CategoryDAO($username); + $conf = Minz_Configuration::get('system'); + switch ($conf->db['type']) { + case 'sqlite': + return new FreshRSS_CategoryDAOSQLite($username); + default: + return new FreshRSS_CategoryDAO($username); + } } public static function createFeedDao($username = null) { diff --git a/app/Models/Feed.php b/app/Models/Feed.php index 89989236c..0a45a1f4c 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -7,8 +7,8 @@ class FreshRSS_Feed extends Minz_Model { const TTL_DEFAULT = 0; - const KEEP_HISTORY_DEFAULT = -2; - const KEEP_HISTORY_INFINITE = -1; + const ARCHIVING_RETENTION_COUNT_LIMIT = 10000; + const ARCHIVING_RETENTION_PERIOD = 'P3M'; private $id = 0; private $url; @@ -24,9 +24,8 @@ class FreshRSS_Feed extends Minz_Model { private $pathEntries = ''; private $httpAuth = ''; private $error = false; - private $keep_history = self::KEEP_HISTORY_DEFAULT; private $ttl = self::TTL_DEFAULT; - private $attributes = array(); + private $attributes = []; private $mute = false; private $hash = null; private $lockPath = ''; @@ -110,9 +109,6 @@ class FreshRSS_Feed extends Minz_Model { public function inError() { return $this->error; } - public function keepHistory() { - return $this->keep_history; - } public function ttl() { return $this->ttl; } @@ -153,18 +149,17 @@ class FreshRSS_Feed extends Minz_Model { return $this->nbNotRead; } public function faviconPrepare() { - global $favicons_dir; require_once(LIB_PATH . '/favicons.php'); $url = $this->website; if ($url == '') { $url = $this->url; } - $txt = $favicons_dir . $this->hash() . '.txt'; + $txt = FAVICONS_DIR . $this->hash() . '.txt'; if (!file_exists($txt)) { file_put_contents($txt, $url); } if (FreshRSS_Context::$isCli) { - $ico = $favicons_dir . $this->hash() . '.ico'; + $ico = FAVICONS_DIR . $this->hash() . '.ico'; $ico_mtime = @filemtime($ico); $txt_mtime = @filemtime($txt); if ($txt_mtime != false && @@ -231,12 +226,6 @@ class FreshRSS_Feed extends Minz_Model { public function _error($value) { $this->error = (bool)$value; } - public function _keepHistory($value) { - $value = intval($value); - $value = min($value, 1000000); - $value = max($value, self::KEEP_HISTORY_DEFAULT); - $this->keep_history = $value; - } public function _ttl($value) { $value = intval($value); $value = min($value, 100000000); @@ -470,6 +459,28 @@ class FreshRSS_Feed extends Minz_Model { $this->entries = $entries; } + public function cleanOldEntries() { //Remember to call updateCachedValue($id_feed) or updateCachedValues() just after + $archiving = $this->attributes('archiving'); + if ($archiving == null) { + $catDAO = FreshRSS_Factory::createCategoryDao(); + $category = $catDAO->searchById($this->category()); + $archiving = $category == null ? null : $category->attributes('archiving'); + if ($archiving == null) { + $archiving = FreshRSS_Context::$user_conf->archiving; + } + } + if (is_array($archiving)) { + $entryDAO = FreshRSS_Factory::createEntryDao(); + $nb = $entryDAO->cleanOldEntries($this->id(), $archiving); + if ($nb > 0) { + $needFeedCacheRefresh = true; + Minz_Log::debug($nb . ' entries cleaned in feed [' . $this->url(false) . '] with: ' . json_encode($archiving)); + } + return $nb; + } + return false; + } + protected function cacheFilename() { return CACHE_PATH . '/' . md5($this->url) . '.spc'; } @@ -701,7 +712,7 @@ class FreshRSS_Feed extends Minz_Model { file_put_contents($hubFilename, json_encode($hubJson)); } $ch = curl_init(); - curl_setopt_array($ch, array( + curl_setopt_array($ch, [ CURLOPT_URL => $hubJson['hub'], CURLOPT_RETURNTRANSFER => true, CURLOPT_POSTFIELDS => http_build_query(array( @@ -712,13 +723,9 @@ class FreshRSS_Feed extends Minz_Model { )), CURLOPT_USERAGENT => FRESHRSS_USERAGENT, CURLOPT_MAXREDIRS => 10, - )); - if (version_compare(PHP_VERSION, '5.6.0') >= 0 || ini_get('open_basedir') == '') { - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); //Keep option separated for open_basedir PHP bug 65646 - } - if (defined('CURLOPT_ENCODING')) { - curl_setopt($ch, CURLOPT_ENCODING, ''); //Enable all encodings - } + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_ENCODING => '', //Enable all encodings + ]); $response = curl_exec($ch); $info = curl_getinfo($ch); diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index c9c9f6301..fa0001df7 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -3,14 +3,13 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { protected function addColumn($name) { - Minz_Log::warning('FreshRSS_FeedDAO::addColumn: ' . $name); + Minz_Log::warning(__method__ . ': ' . $name); try { if ($name === 'attributes') { //v1.11.0 - $stm = $this->bd->prepare('ALTER TABLE `' . $this->prefix . 'feed` ADD COLUMN attributes TEXT'); - return $stm && $stm->execute(); + return $this->pdo->exec('ALTER TABLE `_feed` ADD COLUMN attributes TEXT') !== false; } } catch (Exception $e) { - Minz_Log::error('FreshRSS_FeedDAO::addColumn error: ' . $e->getMessage()); + Minz_Log::error(__method__ . ' error: ' . $e->getMessage()); } return false; } @@ -18,7 +17,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { protected function autoUpdateDb($errorInfo) { if (isset($errorInfo[0])) { if ($errorInfo[0] === FreshRSS_DatabaseDAO::ER_BAD_FIELD_ERROR || $errorInfo[0] === FreshRSS_DatabaseDAOPGSQL::UNDEFINED_COLUMN) { - foreach (array('attributes') as $column) { + foreach (['attributes'] as $column) { if (stripos($errorInfo[2], $column) !== false) { return $this->addColumn($column); } @@ -30,7 +29,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function addFeed($valuesTmp) { $sql = ' - INSERT INTO `' . $this->prefix . 'feed` + INSERT INTO `_feed` ( url, category, @@ -39,18 +38,24 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { description, `lastUpdate`, priority, + `pathEntries`, `httpAuth`, error, - keep_history, ttl, attributes ) VALUES - (?, ?, ?, ?, ?, ?, 10, ?, 0, ?, ?, ?)'; - $stm = $this->bd->prepare($sql); + (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; + $stm = $this->pdo->prepare($sql); $valuesTmp['url'] = safe_ascii($valuesTmp['url']); $valuesTmp['website'] = safe_ascii($valuesTmp['website']); + if (!isset($valuesTmp['pathEntries'])) { + $valuesTmp['pathEntries'] = ''; + } + if (!isset($valuesTmp['attributes'])) { + $valuesTmp['attributes'] = []; + } $values = array( substr($valuesTmp['url'], 0, 511), @@ -59,16 +64,18 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { substr($valuesTmp['website'], 0, 255), mb_strcut($valuesTmp['description'], 0, 1023, 'UTF-8'), $valuesTmp['lastUpdate'], + isset($valuesTmp['priority']) ? intval($valuesTmp['priority']) : FreshRSS_Feed::PRIORITY_MAIN_STREAM, + mb_strcut($valuesTmp['pathEntries'], 0, 511, 'UTF-8'), base64_encode($valuesTmp['httpAuth']), - FreshRSS_Feed::KEEP_HISTORY_DEFAULT, + isset($valuesTmp['error']) ? intval($valuesTmp['error']) : 0, isset($valuesTmp['ttl']) ? intval($valuesTmp['ttl']) : FreshRSS_Feed::TTL_DEFAULT, - isset($valuesTmp['attributes']) ? json_encode($valuesTmp['attributes']) : '', + is_string($valuesTmp['attributes']) ? $valuesTmp['attributes'] : json_encode($valuesTmp['attributes'], JSON_UNESCAPED_SLASHES), ); if ($stm && $stm->execute($values)) { - return $this->bd->lastInsertId('"' . $this->prefix . 'feed_id_seq"'); + return $this->pdo->lastInsertId('`_feed_id_seq`'); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); if ($this->autoUpdateDb($info)) { return $this->addFeed($valuesTmp); } @@ -129,13 +136,13 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($key === 'httpAuth') { $valuesTmp[$key] = base64_encode($v); } elseif ($key === 'attributes') { - $valuesTmp[$key] = json_encode($v); + $valuesTmp[$key] = is_string($valuesTmp[$key]) ? $valuesTmp[$key] : json_encode($valuesTmp[$key], JSON_UNESCAPED_SLASHES); } } $set = substr($set, 0, -2); - $sql = 'UPDATE `' . $this->prefix . 'feed` SET ' . $set . ' WHERE id=?'; - $stm = $this->bd->prepare($sql); + $sql = 'UPDATE `_feed` SET ' . $set . ' WHERE id=?'; + $stm = $this->pdo->prepare($sql); foreach ($valuesTmp as $v) { $values[] = $v; @@ -145,7 +152,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); if ($this->autoUpdateDb($info)) { return $this->updateFeed($id, $valuesTmp); } @@ -166,7 +173,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function updateLastUpdate($id, $inError = false, $mtime = 0) { //See also updateCachedValue() - $sql = 'UPDATE `' . $this->prefix . 'feed` ' + $sql = 'UPDATE `_feed` ' . 'SET `lastUpdate`=?, error=? ' . 'WHERE id=?'; $values = array( @@ -174,12 +181,12 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $inError ? 1 : 0, $id, ); - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error updateLastUpdate: ' . $info[2]); return false; } @@ -192,8 +199,8 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $newCat = $catDAO->getDefault(); } - $sql = 'UPDATE `' . $this->prefix . 'feed` SET category=? WHERE category=?'; - $stm = $this->bd->prepare($sql); + $sql = 'UPDATE `_feed` SET category=? WHERE category=?'; + $stm = $this->pdo->prepare($sql); $values = array( $newCat->id(), @@ -203,44 +210,54 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error changeCategory: ' . $info[2]); return false; } } public function deleteFeed($id) { - $sql = 'DELETE FROM `' . $this->prefix . 'feed` WHERE id=?'; - $stm = $this->bd->prepare($sql); + $sql = 'DELETE FROM `_feed` WHERE id=?'; + $stm = $this->pdo->prepare($sql); $values = array($id); if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error deleteFeed: ' . $info[2]); return false; } } public function deleteFeedByCategory($id) { - $sql = 'DELETE FROM `' . $this->prefix . 'feed` WHERE category=?'; - $stm = $this->bd->prepare($sql); + $sql = 'DELETE FROM `_feed` WHERE category=?'; + $stm = $this->pdo->prepare($sql); $values = array($id); if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error deleteFeedByCategory: ' . $info[2]); return false; } } + public function selectAll() { + $sql = 'SELECT id, url, category, name, website, description, `lastUpdate`, priority, ' + . '`pathEntries`, `httpAuth`, error, ttl, attributes ' + . 'FROM `_feed`'; + $stm = $this->pdo->query($sql); + while ($row = $stm->fetch(PDO::FETCH_ASSOC)) { + yield $row; + } + } + public function searchById($id) { - $sql = 'SELECT * FROM `' . $this->prefix . 'feed` WHERE id=?'; - $stm = $this->bd->prepare($sql); + $sql = 'SELECT * FROM `_feed` WHERE id=?'; + $stm = $this->pdo->prepare($sql); $values = array($id); @@ -255,8 +272,8 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } } public function searchByUrl($url) { - $sql = 'SELECT * FROM `' . $this->prefix . 'feed` WHERE url=?'; - $stm = $this->bd->prepare($sql); + $sql = 'SELECT * FROM `_feed` WHERE url=?'; + $stm = $this->pdo->prepare($sql); $values = array($url); @@ -272,25 +289,21 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function listFeedsIds() { - $sql = 'SELECT id FROM `' . $this->prefix . 'feed`'; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $sql = 'SELECT id FROM `_feed`'; + $stm = $this->pdo->query($sql); return $stm->fetchAll(PDO::FETCH_COLUMN, 0); } public function listFeeds() { - $sql = 'SELECT * FROM `' . $this->prefix . 'feed` ORDER BY name'; - $stm = $this->bd->prepare($sql); - $stm->execute(); - + $sql = 'SELECT * FROM `_feed` ORDER BY name'; + $stm = $this->pdo->query($sql); return self::daoToFeed($stm->fetchAll(PDO::FETCH_ASSOC)); } public function arrayFeedCategoryNames() { //For API - $sql = 'SELECT f.id, f.name, c.name as c_name FROM `' . $this->prefix . 'feed` f ' - . 'INNER JOIN `' . $this->prefix . 'category` c ON c.id = f.category'; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $sql = 'SELECT f.id, f.name, c.name as c_name FROM `_feed` f ' + . 'INNER JOIN `_category` c ON c.id = f.category'; + $stm = $this->pdo->query($sql); $res = $stm->fetchAll(PDO::FETCH_ASSOC); $feedCategoryNames = array(); foreach ($res as $line) { @@ -307,17 +320,18 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { */ public function listFeedsOrderUpdate($defaultCacheDuration = 3600, $limit = 0) { $this->updateTTL(); - $sql = 'SELECT id, url, name, website, `lastUpdate`, `pathEntries`, `httpAuth`, keep_history, ttl, attributes ' - . 'FROM `' . $this->prefix . 'feed` ' + $sql = 'SELECT id, url, name, website, `lastUpdate`, `pathEntries`, `httpAuth`, ttl, attributes ' + . 'FROM `_feed` ' . ($defaultCacheDuration < 0 ? '' : 'WHERE ttl >= ' . FreshRSS_Feed::TTL_DEFAULT - . ' AND `lastUpdate` < (' . (time() + 60) . '-(CASE WHEN ttl=' . FreshRSS_Feed::TTL_DEFAULT . ' THEN ' . intval($defaultCacheDuration) . ' ELSE ttl END)) ') + . ' AND `lastUpdate` < (' . (time() + 60) + . '-(CASE WHEN ttl=' . FreshRSS_Feed::TTL_DEFAULT . ' THEN ' . intval($defaultCacheDuration) . ' ELSE ttl END)) ') . 'ORDER BY `lastUpdate` ' . ($limit < 1 ? '' : 'LIMIT ' . intval($limit)); - $stm = $this->bd->prepare($sql); - if ($stm && $stm->execute()) { + $stm = $this->pdo->query($sql); + if ($stm !== false) { return self::daoToFeed($stm->fetchAll(PDO::FETCH_ASSOC)); } else { - $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); if ($this->autoUpdateDb($info)) { return $this->listFeedsOrderUpdate($defaultCacheDuration); } @@ -327,8 +341,8 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function listByCategory($cat) { - $sql = 'SELECT * FROM `' . $this->prefix . 'feed` WHERE category=? ORDER BY name'; - $stm = $this->bd->prepare($sql); + $sql = 'SELECT * FROM `_feed` WHERE category=? ORDER BY name'; + $stm = $this->pdo->prepare($sql); $values = array($cat); @@ -338,8 +352,8 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function countEntries($id) { - $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` WHERE id_feed=?'; - $stm = $this->bd->prepare($sql); + $sql = 'SELECT COUNT(*) AS count FROM `_entry` WHERE id_feed=?'; + $stm = $this->pdo->prepare($sql); $values = array($id); $stm->execute($values); $res = $stm->fetchAll(PDO::FETCH_ASSOC); @@ -348,8 +362,8 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function countNotRead($id) { - $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND is_read=0'; - $stm = $this->bd->prepare($sql); + $sql = 'SELECT COUNT(*) AS count FROM `_entry` WHERE id_feed=? AND is_read=0'; + $stm = $this->pdo->prepare($sql); $values = array($id); $stm->execute($values); $res = $stm->fetchAll(PDO::FETCH_ASSOC); @@ -357,62 +371,51 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return $res[0]['count']; } - public function updateCachedValue($id) { //For multiple feeds, call updateCachedValues() - $sql = 'UPDATE `' . $this->prefix . 'feed` ' //2 sub-requests with FOREIGN KEY(e.id_feed), INDEX(e.is_read) faster than 1 request with GROUP BY or CASE - . 'SET `cache_nbEntries`=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=`' . $this->prefix . 'feed`.id),' - . '`cache_nbUnreads`=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=`' . $this->prefix . 'feed`.id AND e2.is_read=0) ' - . 'WHERE id=?'; - $values = array($id); - $stm = $this->bd->prepare($sql); - - if ($stm && $stm->execute($values)) { - return $stm->rowCount(); - } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error updateCachedValue: ' . $info[2]); - return false; + public function updateCachedValues($id = null) { + //2 sub-requests with FOREIGN KEY(e.id_feed), INDEX(e.is_read) faster than 1 request with GROUP BY or CASE + $sql = 'UPDATE `_feed` ' + . 'SET `cache_nbEntries`=(SELECT COUNT(e1.id) FROM `_entry` e1 WHERE e1.id_feed=`_feed`.id),' + . '`cache_nbUnreads`=(SELECT COUNT(e2.id) FROM `_entry` e2 WHERE e2.id_feed=`_feed`.id AND e2.is_read=0)' + . ($id != null ? ' WHERE id=:id' : ''); + $stm = $this->pdo->prepare($sql); + if ($id != null) { + $stm->bindParam(':id', $id, PDO::PARAM_INT); } - } - public function updateCachedValues() { //For one single feed, call updateCachedValue($id) - $sql = 'UPDATE `' . $this->prefix . 'feed` ' - . 'SET `cache_nbEntries`=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=`' . $this->prefix . 'feed`.id),' - . '`cache_nbUnreads`=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=`' . $this->prefix . 'feed`.id AND e2.is_read=0)'; - $stm = $this->bd->prepare($sql); if ($stm && $stm->execute()) { return $stm->rowCount(); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error updateCachedValues: ' . $info[2]); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); + Minz_Log::error('SQL error updateCachedValue: ' . $info[2]); return false; } } public function truncate($id) { - $sql = 'DELETE FROM `' . $this->prefix . 'entry` WHERE id_feed=?'; - $stm = $this->bd->prepare($sql); - $values = array($id); - $this->bd->beginTransaction(); - if (!($stm && $stm->execute($values))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $sql = 'DELETE FROM `_entry` WHERE id_feed=:id'; + $stm = $this->pdo->prepare($sql); + $stm->bindParam(':id', $id, PDO::PARAM_INT); + $this->pdo->beginTransaction(); + if (!($stm && $stm->execute())) { + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error truncate: ' . $info[2]); - $this->bd->rollBack(); + $this->pdo->rollBack(); return false; } $affected = $stm->rowCount(); - $sql = 'UPDATE `' . $this->prefix . 'feed` ' - . 'SET `cache_nbEntries`=0, `cache_nbUnreads`=0 WHERE id=?'; - $values = array($id); - $stm = $this->bd->prepare($sql); - if (!($stm && $stm->execute($values))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $sql = 'UPDATE `_feed` ' + . 'SET `cache_nbEntries`=0, `cache_nbUnreads`=0 WHERE id=:id'; + $stm = $this->pdo->prepare($sql); + $stm->bindParam(':id', $id, PDO::PARAM_INT); + if (!($stm && $stm->execute())) { + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error truncate: ' . $info[2]); - $this->bd->rollBack(); + $this->pdo->rollBack(); return false; } - $this->bd->commit(); + $this->pdo->commit(); return $affected; } @@ -446,7 +449,6 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $myFeed->_pathEntries(isset($dao['pathEntries']) ? $dao['pathEntries'] : ''); $myFeed->_httpAuth(isset($dao['httpAuth']) ? base64_decode($dao['httpAuth']) : ''); $myFeed->_error(isset($dao['error']) ? $dao['error'] : 0); - $myFeed->_keepHistory(isset($dao['keep_history']) ? $dao['keep_history'] : FreshRSS_Feed::KEEP_HISTORY_DEFAULT); $myFeed->_ttl(isset($dao['ttl']) ? $dao['ttl'] : FreshRSS_Feed::TTL_DEFAULT); $myFeed->_attributes('', isset($dao['attributes']) ? $dao['attributes'] : ''); $myFeed->_nbNotRead(isset($dao['cache_nbUnreads']) ? $dao['cache_nbUnreads'] : 0); @@ -461,20 +463,16 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function updateTTL() { - $sql = <<prefix}feed` - SET ttl = :new_value - WHERE ttl = :old_value -SQL; - $stm = $this->bd->prepare($sql); + $sql = 'UPDATE `_feed` SET ttl=:new_value WHERE ttl=:old_value'; + $stm = $this->pdo->prepare($sql); if (!($stm && $stm->execute(array(':new_value' => FreshRSS_Feed::TTL_DEFAULT, ':old_value' => -2)))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL warning updateTTL 1: ' . $info[2] . ' ' . $sql); - $sql2 = 'ALTER TABLE `' . $this->prefix . 'feed` ADD COLUMN ttl INT NOT NULL DEFAULT ' . FreshRSS_Feed::TTL_DEFAULT; //v0.7.3 - $stm = $this->bd->prepare($sql2); - if (!($stm && $stm->execute())) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $sql2 = 'ALTER TABLE `_feed` ADD COLUMN ttl INT NOT NULL DEFAULT ' . FreshRSS_Feed::TTL_DEFAULT; //v0.7.3 + $stm = $this->pdo->query($sql2); + if ($stm === false) { + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error updateTTL 2: ' . $info[2] . ' ' . $sql2); } } else { diff --git a/app/Models/FeedDAOSQLite.php b/app/Models/FeedDAOSQLite.php index 3c203b378..0f685867a 100644 --- a/app/Models/FeedDAOSQLite.php +++ b/app/Models/FeedDAOSQLite.php @@ -3,9 +3,9 @@ class FreshRSS_FeedDAOSQLite extends FreshRSS_FeedDAO { protected function autoUpdateDb($errorInfo) { - if ($tableInfo = $this->bd->query("PRAGMA table_info('feed')")) { + if ($tableInfo = $this->pdo->query("PRAGMA table_info('feed')")) { $columns = $tableInfo->fetchAll(PDO::FETCH_COLUMN, 1); - foreach (array('attributes') as $column) { + foreach (['attributes'] as $column) { if (!in_array($column, $columns)) { return $this->addColumn($column); } diff --git a/app/Models/StatsDAO.php b/app/Models/StatsDAO.php index 67ada73f7..cbfa79c61 100644 --- a/app/Models/StatsDAO.php +++ b/app/Models/StatsDAO.php @@ -45,13 +45,11 @@ SELECT COUNT(1) AS total, COUNT(1) - SUM(e.is_read) AS count_unreads, SUM(e.is_read) AS count_reads, SUM(e.is_favorite) AS count_favorites -FROM `{$this->prefix}entry` AS e -, `{$this->prefix}feed` AS f +FROM `_entry` AS e, `_feed` AS f WHERE e.id_feed = f.id {$filter} SQL; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); $res = $stm->fetchAll(PDO::FETCH_ASSOC); return $res[0]; @@ -73,13 +71,12 @@ SQL; $sql = <<prefix}entry` +FROM `_entry` WHERE date >= {$oldest} AND date < {$midnight} GROUP BY day ORDER BY day ASC SQL; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); $res = $stm->fetchAll(PDO::FETCH_ASSOC); foreach ($res as $value) { @@ -143,14 +140,13 @@ SQL; $sql = <<prefix}entry` AS e +FROM `_entry` AS e {$restrict} GROUP BY period ORDER BY period ASC SQL; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); $res = $stm->fetchAll(PDO::FETCH_NAMED); $repartition = array(); @@ -207,11 +203,10 @@ SQL; SELECT COUNT(1) AS count , MIN(date) AS date_min , MAX(date) AS date_max -FROM `{$this->prefix}entry` AS e +FROM `_entry` AS e {$restrict} SQL; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); $res = $stm->fetch(PDO::FETCH_NAMED); $date_min = new \DateTime(); $date_min->setTimestamp($res['date_min']); @@ -251,14 +246,12 @@ SQL; $sql = <<prefix}category` AS c, -`{$this->prefix}feed` AS f +FROM `_category` AS c, `_feed` AS f WHERE c.id = f.category GROUP BY label ORDER BY data DESC SQL; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); $res = $stm->fetchAll(PDO::FETCH_ASSOC); return $res; @@ -274,16 +267,13 @@ SQL; $sql = <<prefix}category` AS c, -`{$this->prefix}feed` AS f, -`{$this->prefix}entry` AS e +FROM `_category` AS c, `_feed` AS f, `_entry` AS e WHERE c.id = f.category AND f.id = e.id_feed GROUP BY label ORDER BY data DESC SQL; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); $res = $stm->fetchAll(PDO::FETCH_ASSOC); return $res; @@ -300,17 +290,14 @@ SELECT f.id AS id , MAX(f.name) AS name , MAX(c.name) AS category , COUNT(e.id) AS count -FROM `{$this->prefix}category` AS c, -`{$this->prefix}feed` AS f, -`{$this->prefix}entry` AS e +FROM `_category` AS c, `_feed` AS f, `_entry` AS e WHERE c.id = f.category AND f.id = e.id_feed GROUP BY f.id ORDER BY count DESC LIMIT 10 SQL; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); return $stm->fetchAll(PDO::FETCH_ASSOC); } @@ -325,14 +312,12 @@ SELECT MAX(f.id) as id , MAX(f.name) AS name , MAX(date) AS last_date , COUNT(*) AS nb_articles -FROM `{$this->prefix}feed` AS f, -`{$this->prefix}entry` AS e +FROM `_feed` AS f, `_entry` AS e WHERE f.id = e.id_feed GROUP BY f.id ORDER BY name SQL; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); return $stm->fetchAll(PDO::FETCH_ASSOC); } diff --git a/app/Models/StatsDAOPGSQL.php b/app/Models/StatsDAOPGSQL.php index 1effbb64b..4a66068cb 100644 --- a/app/Models/StatsDAOPGSQL.php +++ b/app/Models/StatsDAOPGSQL.php @@ -47,14 +47,13 @@ class FreshRSS_StatsDAOPGSQL extends FreshRSS_StatsDAO { $sql = <<prefix}entry" AS e +FROM `_entry` AS e {$restrict} GROUP BY period ORDER BY period ASC SQL; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); $res = $stm->fetchAll(PDO::FETCH_NAMED); foreach ($res as $value) { diff --git a/app/Models/StatsDAOSQLite.php b/app/Models/StatsDAOSQLite.php index 6cfc20463..f96f8f479 100644 --- a/app/Models/StatsDAOSQLite.php +++ b/app/Models/StatsDAOSQLite.php @@ -15,14 +15,13 @@ class FreshRSS_StatsDAOSQLite extends FreshRSS_StatsDAO { $sql = <<prefix}entry` AS e +FROM `_entry` AS e {$restrict} GROUP BY period ORDER BY period ASC SQL; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); $res = $stm->fetchAll(PDO::FETCH_NAMED); $repartition = array(); diff --git a/app/Models/Tag.php b/app/Models/Tag.php index 3eb989cc1..0d50e356c 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -3,7 +3,7 @@ class FreshRSS_Tag extends Minz_Model { private $id = 0; private $name; - private $attributes = array(); + private $attributes = []; private $nbEntries = -1; private $nbUnread = -1; diff --git a/app/Models/TagDAO.php b/app/Models/TagDAO.php index 297d24c96..5882eee76 100644 --- a/app/Models/TagDAO.php +++ b/app/Models/TagDAO.php @@ -8,37 +8,24 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function createTagTable() { $ok = false; - $hadTransaction = $this->bd->inTransaction(); + $hadTransaction = $this->pdo->inTransaction(); if ($hadTransaction) { - $this->bd->commit(); + $this->pdo->commit(); } try { - $db = FreshRSS_Context::$system_conf->db; - require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php'); + require(APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php'); Minz_Log::warning('SQL ALTER GUID case sensitivity...'); $databaseDAO = FreshRSS_Factory::createDatabaseDAO(); $databaseDAO->ensureCaseInsensitiveGuids(); Minz_Log::warning('SQL CREATE TABLE tag...'); - if (defined('SQL_CREATE_TABLE_TAGS')) { - $sql = sprintf(SQL_CREATE_TABLE_TAGS, $this->prefix); - $stm = $this->bd->prepare($sql); - $ok = $stm && $stm->execute(); - } else { - global $SQL_CREATE_TABLE_TAGS; - $ok = !empty($SQL_CREATE_TABLE_TAGS); - foreach ($SQL_CREATE_TABLE_TAGS as $instruction) { - $sql = sprintf($instruction, $this->prefix); - $stm = $this->bd->prepare($sql); - $ok &= $stm && $stm->execute(); - } - } + $ok = $this->pdo->exec($SQL_CREATE_TABLE_TAGS) !== false; } catch (Exception $e) { Minz_Log::error('FreshRSS_EntryDAO::createTagTable error: ' . $e->getMessage()); } if ($hadTransaction) { - $this->bd->beginTransaction(); + $this->pdo->beginTransaction(); } return $ok; } @@ -55,22 +42,25 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function addTag($valuesTmp) { - $sql = 'INSERT INTO `' . $this->prefix . 'tag`(name, attributes) ' - . 'SELECT * FROM (SELECT TRIM(?), TRIM(?)) t2 ' //TRIM() to provide a type hint as text for PostgreSQL - . 'WHERE NOT EXISTS (SELECT 1 FROM `' . $this->prefix . 'category` WHERE name = TRIM(?))'; //No category of the same name - $stm = $this->bd->prepare($sql); + $sql = 'INSERT INTO `_tag`(name, attributes) ' + . 'SELECT * FROM (SELECT TRIM(?) as name, TRIM(?) as attributes) t2 ' //TRIM() gives a text type hint to PostgreSQL + . 'WHERE NOT EXISTS (SELECT 1 FROM `_category` WHERE name = TRIM(?))'; //No category of the same name + $stm = $this->pdo->prepare($sql); $valuesTmp['name'] = mb_strcut(trim($valuesTmp['name']), 0, 63, 'UTF-8'); + if (!isset($valuesTmp['attributes'])) { + $valuesTmp['attributes'] = []; + } $values = array( $valuesTmp['name'], - isset($valuesTmp['attributes']) ? json_encode($valuesTmp['attributes']) : '', + is_string($valuesTmp['attributes']) ? $valuesTmp['attributes'] : json_encode($valuesTmp['attributes'], JSON_UNESCAPED_SLASHES), $valuesTmp['name'], ); if ($stm && $stm->execute($values)) { - return $this->bd->lastInsertId('"' . $this->prefix . 'tag_id_seq"'); + return $this->pdo->lastInsertId('`_tag_id_seq`'); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error addTag: ' . $info[2]); return false; } @@ -89,14 +79,17 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function updateTag($id, $valuesTmp) { - $sql = 'UPDATE `' . $this->prefix . 'tag` SET name=?, attributes=? WHERE id=? ' - . 'AND NOT EXISTS (SELECT 1 FROM `' . $this->prefix . 'category` WHERE name = ?)'; //No category of the same name - $stm = $this->bd->prepare($sql); + $sql = 'UPDATE `_tag` SET name=?, attributes=? WHERE id=? ' + . 'AND NOT EXISTS (SELECT 1 FROM `_category` WHERE name = ?)'; //No category of the same name + $stm = $this->pdo->prepare($sql); $valuesTmp['name'] = mb_strcut(trim($valuesTmp['name']), 0, 63, 'UTF-8'); + if (!isset($valuesTmp['attributes'])) { + $valuesTmp['attributes'] = []; + } $values = array( $valuesTmp['name'], - isset($valuesTmp['attributes']) ? json_encode($valuesTmp['attributes']) : '', + is_string($valuesTmp['attributes']) ? $valuesTmp['attributes'] : json_encode($valuesTmp['attributes'], JSON_UNESCAPED_SLASHES), $id, $valuesTmp['name'], ); @@ -104,7 +97,7 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error updateTag: ' . $info[2]); return false; } @@ -125,23 +118,39 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($id <= 0) { return false; } - $sql = 'DELETE FROM `' . $this->prefix . 'tag` WHERE id=?'; - $stm = $this->bd->prepare($sql); + $sql = 'DELETE FROM `_tag` WHERE id=?'; + $stm = $this->pdo->prepare($sql); $values = array($id); if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error deleteTag: ' . $info[2]); return false; } } + public function selectAll() { + $sql = 'SELECT id, name, attributes FROM `_tag`'; + $stm = $this->pdo->query($sql); + while ($row = $stm->fetch(PDO::FETCH_ASSOC)) { + yield $row; + } + } + + public function selectEntryTag() { + $sql = 'SELECT id_tag, id_entry FROM `_entrytag`'; + $stm = $this->pdo->query($sql); + while ($row = $stm->fetch(PDO::FETCH_ASSOC)) { + yield $row; + } + } + public function searchById($id) { - $sql = 'SELECT * FROM `' . $this->prefix . 'tag` WHERE id=?'; - $stm = $this->bd->prepare($sql); + $sql = 'SELECT * FROM `_tag` WHERE id=?'; + $stm = $this->pdo->prepare($sql); $values = array($id); $stm->execute($values); $res = $stm->fetchAll(PDO::FETCH_ASSOC); @@ -150,8 +159,8 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function searchByName($name) { - $sql = 'SELECT * FROM `' . $this->prefix . 'tag` WHERE name=?'; - $stm = $this->bd->prepare($sql); + $sql = 'SELECT * FROM `_tag` WHERE name=?'; + $stm = $this->pdo->prepare($sql); $values = array($name); $stm->execute($values); $res = $stm->fetchAll(PDO::FETCH_ASSOC); @@ -162,20 +171,20 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function listTags($precounts = false) { if ($precounts) { $sql = 'SELECT t.id, t.name, count(e.id) AS unreads ' - . 'FROM `' . $this->prefix . 'tag` t ' - . 'LEFT OUTER JOIN `' . $this->prefix . 'entrytag` et ON et.id_tag = t.id ' - . 'LEFT OUTER JOIN `' . $this->prefix . 'entry` e ON et.id_entry = e.id AND e.is_read = 0 ' + . 'FROM `_tag` t ' + . 'LEFT OUTER JOIN `_entrytag` et ON et.id_tag = t.id ' + . 'LEFT OUTER JOIN `_entry` e ON et.id_entry = e.id AND e.is_read = 0 ' . 'GROUP BY t.id ' . 'ORDER BY t.name'; } else { - $sql = 'SELECT * FROM `' . $this->prefix . 'tag` ORDER BY name'; + $sql = 'SELECT * FROM `_tag` ORDER BY name'; } - $stm = $this->bd->prepare($sql); - if ($stm && $stm->execute()) { + $stm = $this->pdo->query($sql); + if ($stm !== false) { return self::daoToTag($stm->fetchAll(PDO::FETCH_ASSOC)); } else { - $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); if ($this->autoUpdateDb($info)) { return $this->listTags($precounts); } @@ -185,13 +194,13 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function count() { - $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'tag`'; - $stm = $this->bd->prepare($sql); - if ($stm && $stm->execute()) { + $sql = 'SELECT COUNT(*) AS count FROM `_tag`'; + $stm = $this->pdo->query($sql); + if ($stm !== false) { $res = $stm->fetchAll(PDO::FETCH_ASSOC); return $res[0]['count']; } else { - $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); if ($this->autoUpdateDb($info)) { return $this->count(); } @@ -201,8 +210,8 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function countEntries($id) { - $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entrytag` WHERE id_tag=?'; - $stm = $this->bd->prepare($sql); + $sql = 'SELECT COUNT(*) AS count FROM `_entrytag` WHERE id_tag=?'; + $stm = $this->pdo->prepare($sql); $values = array($id); $stm->execute($values); $res = $stm->fetchAll(PDO::FETCH_ASSOC); @@ -210,10 +219,10 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function countNotRead($id) { - $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entrytag` et ' - . 'INNER JOIN `' . $this->prefix . 'entry` e ON et.id_entry=e.id ' + $sql = 'SELECT COUNT(*) AS count FROM `_entrytag` et ' + . 'INNER JOIN `_entry` e ON et.id_entry=e.id ' . 'WHERE et.id_tag=? AND e.is_read=0'; - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); $values = array($id); $stm->execute($values); $res = $stm->fetchAll(PDO::FETCH_ASSOC); @@ -222,17 +231,17 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function tagEntry($id_tag, $id_entry, $checked = true) { if ($checked) { - $sql = 'INSERT ' . $this->sqlIgnore() . ' INTO `' . $this->prefix . 'entrytag`(id_tag, id_entry) VALUES(?, ?)'; + $sql = 'INSERT ' . $this->sqlIgnore() . ' INTO `_entrytag`(id_tag, id_entry) VALUES(?, ?)'; } else { - $sql = 'DELETE FROM `' . $this->prefix . 'entrytag` WHERE id_tag=? AND id_entry=?'; + $sql = 'DELETE FROM `_entrytag` WHERE id_tag=? AND id_entry=?'; } - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); $values = array($id_tag, $id_entry); if ($stm && $stm->execute($values)) { return true; } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error tagEntry: ' . $info[2]); return false; } @@ -240,11 +249,11 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function getTagsForEntry($id_entry) { $sql = 'SELECT t.id, t.name, et.id_entry IS NOT NULL as checked ' - . 'FROM `' . $this->prefix . 'tag` t ' - . 'LEFT OUTER JOIN `' . $this->prefix . 'entrytag` et ON et.id_tag = t.id AND et.id_entry=? ' + . 'FROM `_tag` t ' + . 'LEFT OUTER JOIN `_entrytag` et ON et.id_tag = t.id AND et.id_entry=? ' . 'ORDER BY t.name'; - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); $values = array($id_entry); if ($stm && $stm->execute($values)) { @@ -255,7 +264,7 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } return $lines; } else { - $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); if ($this->autoUpdateDb($info)) { return $this->getTagsForEntry($id_entry); } @@ -266,8 +275,8 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function getTagsForEntries($entries) { $sql = 'SELECT et.id_entry, et.id_tag, t.name ' - . 'FROM `' . $this->prefix . 'tag` t ' - . 'INNER JOIN `' . $this->prefix . 'entrytag` et ON et.id_tag = t.id'; + . 'FROM `_tag` t ' + . 'INNER JOIN `_entrytag` et ON et.id_tag = t.id'; $values = array(); if (is_array($entries) && count($entries) > 0) { @@ -286,12 +295,12 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } } } - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); if ($stm && $stm->execute($values)) { return $stm->fetchAll(PDO::FETCH_ASSOC); } else { - $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); if ($this->autoUpdateDb($info)) { return $this->getTagsForEntries($entries); } diff --git a/app/Models/TagDAOSQLite.php b/app/Models/TagDAOSQLite.php index b1deb6c65..ca0fce7ca 100644 --- a/app/Models/TagDAOSQLite.php +++ b/app/Models/TagDAOSQLite.php @@ -7,7 +7,7 @@ class FreshRSS_TagDAOSQLite extends FreshRSS_TagDAO { } protected function autoUpdateDb($errorInfo) { - if ($tableInfo = $this->bd->query("SELECT sql FROM sqlite_master where name='tag'")) { + if ($tableInfo = $this->pdo->query("SELECT sql FROM sqlite_master where name='tag'")) { $showCreate = $tableInfo->fetchColumn(); if (stripos($showCreate, 'tag') === false) { return $this->createTagTable(); //v1.12.0 diff --git a/app/Models/UserDAO.php b/app/Models/UserDAO.php index e9d3a7329..4e824cf01 100644 --- a/app/Models/UserDAO.php +++ b/app/Models/UserDAO.php @@ -1,83 +1,52 @@ db; - require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php'); - - $userPDO = new Minz_ModelPdo($username); - - $currentLanguage = Minz_Translate::language(); + public function createUser($insertDefaultFeeds = false) { + require(APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php'); try { - Minz_Translate::reset($new_user_language); - $ok = false; - $bd_prefix_user = $db['prefix'] . $username . '_'; - if (defined('SQL_CREATE_TABLES')) { //E.g. MySQL - $sql = sprintf(SQL_CREATE_TABLES . SQL_CREATE_TABLE_ENTRYTMP . SQL_CREATE_TABLE_TAGS, $bd_prefix_user, _t('gen.short.default_category')); - $stm = $userPDO->bd->prepare($sql); - $ok = $stm && $stm->execute(); - } else { //E.g. SQLite - global $SQL_CREATE_TABLES, $SQL_CREATE_TABLE_ENTRYTMP, $SQL_CREATE_TABLE_TAGS; - if (is_array($SQL_CREATE_TABLES)) { - $instructions = array_merge($SQL_CREATE_TABLES, $SQL_CREATE_TABLE_ENTRYTMP, $SQL_CREATE_TABLE_TAGS); - $ok = !empty($instructions); - foreach ($instructions as $instruction) { - $sql = sprintf($instruction, $bd_prefix_user, _t('gen.short.default_category')); - $stm = $userPDO->bd->prepare($sql); - $ok &= ($stm && $stm->execute()); - } - } - } + $sql = $SQL_CREATE_TABLES . $SQL_CREATE_TABLE_ENTRYTMP . $SQL_CREATE_TABLE_TAGS; + $ok = $this->pdo->exec($sql) !== false; //Note: Only exec() can take multiple statements safely. if ($ok && $insertDefaultFeeds) { - if (defined('SQL_INSERT_FEEDS')) { //E.g. MySQL - $sql = sprintf(SQL_INSERT_FEEDS, $bd_prefix_user); - $stm = $userPDO->bd->prepare($sql); - $ok &= $stm && $stm->execute(); - } else { //E.g. SQLite - global $SQL_INSERT_FEEDS; - if (is_array($SQL_INSERT_FEEDS)) { - foreach ($SQL_INSERT_FEEDS as $instruction) { - $sql = sprintf($instruction, $bd_prefix_user); - $stm = $userPDO->bd->prepare($sql); - $ok &= ($stm && $stm->execute()); - } - } + $default_feeds = FreshRSS_Context::$system_conf->default_feeds; + $stm = $this->pdo->prepare($SQL_INSERT_FEED); + foreach ($default_feeds as $feed) { + $parameters = [ + ':url' => $feed['url'], + ':name' => $feed['name'], + ':website' => $feed['website'], + ':description' => $feed['description'], + ]; + $ok &= ($stm && $stm->execute($parameters)); } } } catch (Exception $e) { - Minz_Log::error('Error while creating user: ' . $e->getMessage()); + Minz_Log::error('Error while creating database for user: ' . $e->getMessage()); } - Minz_Translate::reset($currentLanguage); - if ($ok) { return true; } else { - $info = empty($stm) ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error: ' . $info[2]); + $info = empty($stm) ? $this->pdo->errorInfo() : $stm->errorInfo(); + Minz_Log::error(__METHOD__ . ' error: ' . $info[2]); return false; } } - public function deleteUser($username) { - $db = FreshRSS_Context::$system_conf->db; - require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php'); + public function deleteUser() { + if (defined('STDERR')) { + fwrite(STDERR, 'Deleting SQL data for user “' . $this->current_user . "”…\n"); + } + + require(APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php'); + $ok = $this->pdo->exec($SQL_DROP_TABLES) !== false; - if ($db['type'] === 'sqlite') { - return unlink(USERS_PATH . '/' . $username . '/db.sqlite'); + if ($ok) { + return true; } else { - $userPDO = new Minz_ModelPdo($username); - - $sql = sprintf(SQL_DROP_TABLES, $db['prefix'] . $username . '_'); - $stm = $userPDO->bd->prepare($sql); - if ($stm && $stm->execute()) { - return true; - } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error : ' . $info[2]); - return false; - } + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); + Minz_Log::error(__METHOD__ . ' error: ' . $info[2]); + return false; } } diff --git a/app/SQL/install.sql.mysql.php b/app/SQL/install.sql.mysql.php index b3353ac95..1eabfae8b 100644 --- a/app/SQL/install.sql.mysql.php +++ b/app/SQL/install.sql.mysql.php @@ -1,20 +1,23 @@ ' . FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE . '; -ALTER TABLE `%1$scategory` MODIFY `name` VARCHAR(' . FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE . ') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL; -OPTIMIZE TABLE `%1$scategory`; +SQL; -ALTER TABLE `%1$sfeed` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -UPDATE `%1$sfeed` SET name=SUBSTRING(name,1,' . FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE . ') WHERE LENGTH(name) > ' . FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE . '; -ALTER TABLE `%1$sfeed` MODIFY `name` VARCHAR(' . FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE . ') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL; -ALTER TABLE `%1$sfeed` MODIFY `description` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -OPTIMIZE TABLE `%1$sfeed`; +$SQL_INSERT_FEED = <<<'SQL' +INSERT IGNORE INTO `_feed` (url, category, name, website, description, ttl) + VALUES(:url, 1, :name, :website, :description, 86400); +SQL; -ALTER TABLE `%1$sentry` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -ALTER TABLE `%1$sentry` MODIFY `title` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL; -ALTER TABLE `%1$sentry` MODIFY `author` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -ALTER TABLE `%1$sentry` MODIFY `tags` VARCHAR(1023) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -OPTIMIZE TABLE `%1$sentry`; -'); +$SQL_DROP_TABLES = <<<'SQL' +DROP TABLE IF EXISTS `_entrytag`, `_tag`, `_entrytmp`, `_entry`, `_feed`, `_category`; +SQL; -define('SQL_UPDATE_GUID_LATIN1_BIN', ' -- v1.12 -ALTER TABLE `%1$sentrytmp` MODIFY `guid` VARCHAR(760) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL; -ALTER TABLE `%1$sentry` MODIFY `guid` VARCHAR(760) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL; -'); +$SQL_UPDATE_GUID_LATIN1_BIN = <<<'SQL' +ALTER TABLE `_entrytmp` MODIFY `guid` VARCHAR(760) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL; -- v1.12 +ALTER TABLE `_entry` MODIFY `guid` VARCHAR(760) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL; +SQL; diff --git a/app/SQL/install.sql.pgsql.php b/app/SQL/install.sql.pgsql.php index e68e6f3be..53afc8a17 100644 --- a/app/SQL/install.sql.pgsql.php +++ b/app/SQL/install.sql.pgsql.php @@ -1,14 +1,16 @@ 'in seconds', //TODO - Translation 'number' => 'Duration to keep logged in', //TODO - Translation ), + 'force_email_validation' => 'Force email addresses validation', //TODO - Translation 'instance-name' => 'Instance name', //TODO - Translation 'max-categories' => 'Categories per user limit', //TODO - Translation 'max-feeds' => 'Feeds per user limit', //TODO - Translation diff --git a/app/i18n/cz/conf.php b/app/i18n/cz/conf.php index a2618e310..056e895a7 100644 --- a/app/i18n/cz/conf.php +++ b/app/i18n/cz/conf.php @@ -3,13 +3,21 @@ return array( 'archiving' => array( '_' => 'Archivace', - 'advanced' => 'Pokročilé', 'delete_after' => 'Smazat články starší než', + 'exception' => 'Purge exception', //TODO - Translation 'help' => 'Více možností je dostupných v nastavení jednotlivých kanálů', - 'keep_history_by_feed' => 'Zachovat tento minimální počet článků v každém kanálu', + 'keep_favourites' => 'Never delete favourites', //TODO - Translation + 'keep_min_by_feed' => 'Zachovat tento minimální počet článků v každém kanálu', + 'keep_labels' => 'Never delete labels', //TODO - Translation + 'keep_unreads' => 'Never delete unreads', //TODO - Translation + 'maintenance' => 'Maintenance', //TODO - Translation 'optimize' => 'Optimalizovat databázi', 'optimize_help' => 'Občasná údržba zmenší velikost databáze', + 'policy' => 'Purge policy', //TODO - Translation + 'policy_warning' => 'If no purge policy is selected, every article will be kept.', //TODO - Translation 'purge_now' => 'Vyčistit nyní', + 'keep_max' => 'Maximum number of articles to keep', //TODO - Translation + 'keep_period' => 'Maximum age of articles to keep', //TODO - Translation 'title' => 'Archivace', 'ttl' => 'Neaktualizovat častěji než', ), @@ -21,6 +29,7 @@ return array( 'publication_date' => 'Datum vydání', 'related_tags' => 'Související tagy', //TODO - Translation 'sharing' => 'Sdílení', + 'display_authors' => 'Authors', //TODO - Translation 'top_line' => 'Horní řádek', ), 'language' => 'Jazyk', @@ -45,6 +54,7 @@ return array( '_' => 'Smazání účtu', 'warn' => 'Váš účet bude smazán spolu se všemi souvisejícími daty', ), + 'email' => 'Email', 'password_api' => 'Password API
(tzn. pro mobilní aplikace)', 'password_form' => 'Heslo
(pro přihlášení webovým formulářem)', 'password_format' => 'Alespoň 7 znaků', @@ -133,7 +143,6 @@ return array( 'diaspora' => 'Diaspora*', 'email' => 'Email', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'more_information' => 'Více informací', 'print' => 'Tisk', 'remove' => 'Remove sharing method', //TODO - Translation diff --git a/app/i18n/cz/gen.php b/app/i18n/cz/gen.php index 08fce0280..de1456187 100644 --- a/app/i18n/cz/gen.php +++ b/app/i18n/cz/gen.php @@ -3,6 +3,7 @@ return array( 'action' => array( 'actualize' => 'Aktualizovat', + 'back' => '← Go back', //TODO - Translation 'back_to_rss_feeds' => '← Zpět na seznam RSS kanálů', 'cancel' => 'Zrušit', 'create' => 'Vytvořit', @@ -22,6 +23,7 @@ return array( 'update' => 'Update', //TODO - Translation ), 'auth' => array( + 'accept_tos' => 'I accept the Terms of Service.', // TODO - Translation 'email' => 'Email', 'keep_logged_in' => 'Zapamatovat přihlášení (%s dny)', 'login' => 'Login', @@ -160,15 +162,22 @@ return array( 'nothing_to_load' => 'Žádné nové články', 'previous' => 'Předchozí', ), + 'period' => array( + 'days' => 'days', //TODO - Translation + 'hours' => 'hours', //TODO - Translation + 'months' => 'months', //TODO - Translation + 'weeks' => 'weeks', //TODO - Translation + 'years' => 'years', //TODO - Translation + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', 'email' => 'Email', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'gnusocial' => 'GNU social', 'jdh' => 'Journal du hacker', 'Known' => 'Known based sites', + 'lemmy' => 'Lemmy', 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', diff --git a/app/i18n/cz/index.php b/app/i18n/cz/index.php index 00f424fe8..078ca5ca1 100644 --- a/app/i18n/cz/index.php +++ b/app/i18n/cz/index.php @@ -7,7 +7,7 @@ return array( 'bugs_reports' => 'Hlášení chyb', 'credits' => 'Poděkování', 'credits_content' => 'Některé designové prvky pocházejí z Bootstrap, FreshRSS ale tuto platformu nevyužívá. Ikony pocházejí z GNOME projektu. Font Open Sans vytvořil Steve Matteson. FreshRSS je založen na PHP framework Minz.', - 'freshrss_description' => 'FreshRSS je čtečka RSS kanálů určená k provozu na vlastním serveru, podobná Kriss Feed nebo Leed. Je to nenáročný a jednoduchý, zároveň ale mocný a konfigurovatelný nástroj.', + 'freshrss_description' => 'FreshRSS je čtečka RSS kanálů určená k provozu na vlastním serveru, podobná Kriss Feed nebo Leed. Je to nenáročný a jednoduchý, zároveň ale mocný a konfigurovatelný nástroj.', 'github' => 'na Github', 'license' => 'Licence', 'project_website' => 'Stránka projektu', @@ -15,6 +15,9 @@ return array( 'version' => 'Verze', 'website' => 'Webové stránka', ), + 'tos' => array( + 'title' => 'Terms of Service', // TODO - Translation + ), 'feed' => array( 'add' => 'Můžete přidat kanály.', 'empty' => 'Žádné články k zobrazení.', diff --git a/app/i18n/cz/sub.php b/app/i18n/cz/sub.php index b2bdf416b..f2c259d15 100644 --- a/app/i18n/cz/sub.php +++ b/app/i18n/cz/sub.php @@ -13,9 +13,12 @@ return array( 'category' => array( '_' => 'Kategorie', 'add' => 'Přidat kategorii', + 'archiving' => 'Archivace', 'empty' => 'Vyprázdit kategorii', 'information' => 'Informace', 'new' => 'Nová kategorie', + 'position' => 'Display position', //TODO - Translation + 'position_help' => 'To control category sort order', //TODO - Translation 'title' => 'Název', ), 'feed' => array( @@ -40,7 +43,7 @@ return array( 'help' => 'Write one search filter per line.', //TODO - Translation ), 'information' => 'Informace', - 'keep_history' => 'Zachovat tento minimální počet článků', + 'keep_min' => 'Zachovat tento minimální počet článků', 'moved_category_deleted' => 'Po smazání kategorie budou v ní obsažené kanály automaticky přesunuty do %s.', 'mute' => 'mute', //TODO - Translation 'no_selected' => 'Nejsou označeny žádné kanály.', @@ -72,6 +75,7 @@ return array( ), 'firefox' => array( 'documentation' => 'Follow the steps described here to add FreshRSS to Firefox feed reader list.',// TODO + 'obsolete_63' => 'From version 63 and onwards, Firefox has removed the ability to add your own subscription services that are not standalone programs.', //TODO - Translation 'title' => 'Firefox feed reader', //TODO - Translation ), 'import_export' => array( diff --git a/app/i18n/cz/user.php b/app/i18n/cz/user.php new file mode 100644 index 000000000..3a8343c11 --- /dev/null +++ b/app/i18n/cz/user.php @@ -0,0 +1,37 @@ + array( + 'feedback' => array( + 'invalid' => 'The email address is invalid.', //TODO - Translation + 'required' => 'The email address is required.', //TODO - Translation + ), + 'validation' => array( + 'change_email' => 'You can change your email address on the profile page.', //TODO - Translation + 'email_sent_to' => 'We sent you an email at %s, please follow its indications to validate your address.', //TODO - Translation + 'feedback' => array( + 'email_failed' => 'We couldn’t send you an email because of a misconfiguration of the server.', //TODO - Translation + 'email_sent' => 'An email has been sent to your address.', //TODO - Translation + 'error' => 'The email address failed to be validated.', //TODO - Translation + 'ok' => 'The email address has been validated.', //TODO - Translation + 'unneccessary' => 'The email address was already validated.', //TODO - Translation + 'wrong_token' => 'The email address failed to be validated due to a wrong token.', //TODO - Translation + ), + 'need_to' => 'You need to validate your email address before being able to use %s.', //TODO - Translation + 'resend_email' => 'Resend the email', //TODO - Translation + 'title' => 'Email address validation', //TODO - Translation + ), + ), + 'tos' => array( + 'feedback' => array( + 'invalid' => 'You must accept the Terms of Service to be able to register.', // TODO - Translation + ), + ), + 'mailer' => array( + 'email_need_validation' => array( + 'title' => 'You need to validate your account', //TODO - Translation + 'welcome' => 'Welcome %s,', //TODO - Translation + 'body' => 'You’ve just registered on %s but you still need to validate your email. For that, just follow the link:', //TODO - Translation + ), + ), +); diff --git a/app/i18n/de/admin.php b/app/i18n/de/admin.php index f0307dcab..d075bf28f 100644 --- a/app/i18n/de/admin.php +++ b/app/i18n/de/admin.php @@ -159,6 +159,7 @@ return array( 'system' => array( '_' => 'Systemeinstellungen', 'auto-update-url' => 'Auto-update URL', + 'force_email_validation' => 'Force email addresses validation', //TODO - Translation 'instance-name' => 'Dein Reader Name', 'max-categories' => 'Anzahl erlaubter Kategorien pro Benutzer', 'max-feeds' => 'Anzahl erlaubter Feeds pro Benutzer', diff --git a/app/i18n/de/conf.php b/app/i18n/de/conf.php index 40209576e..89bbfc10e 100644 --- a/app/i18n/de/conf.php +++ b/app/i18n/de/conf.php @@ -3,13 +3,21 @@ return array( 'archiving' => array( '_' => 'Archivierung', - 'advanced' => 'Erweitert', 'delete_after' => 'Entferne Artikel nach', + 'exception' => 'Purge exception', //TODO - Translation 'help' => 'Weitere Optionen sind in den Einstellungen der individuellen Feeds verfügbar.', - 'keep_history_by_feed' => 'Minimale Anzahl an Artikeln, die pro Feed behalten werden', + 'keep_favourites' => 'Never delete favourites', //TODO - Translation + 'keep_min_by_feed' => 'Minimale Anzahl an Artikeln, die pro Feed behalten werden', + 'keep_labels' => 'Never delete labels', //TODO - Translation + 'keep_unreads' => 'Never delete unreads', //TODO - Translation + 'maintenance' => 'Maintenance', //TODO - Translation 'optimize' => 'Datenbank optimieren', 'optimize_help' => 'Sollte gelegentlich durchgeführt werden, um die Größe der Datenbank zu reduzieren.', + 'policy' => 'Purge policy', //TODO - Translation + 'policy_warning' => 'If no purge policy is selected, every article will be kept.', //TODO - Translation 'purge_now' => 'Jetzt bereinigen', + 'keep_max' => 'Maximum number of articles to keep', //TODO - Translation + 'keep_period' => 'Maximum age of articles to keep', //TODO - Translation 'title' => 'Archivierung', 'ttl' => 'Aktualisiere automatisch nicht öfter als', ), @@ -21,6 +29,7 @@ return array( 'publication_date' => 'Datum der Veröffentlichung', 'related_tags' => 'Verwandte Tags', 'sharing' => 'Teilen', + 'display_authors' => 'Authors', //TODO - Translation 'top_line' => 'Kopfzeile', ), 'language' => 'Sprache', @@ -45,6 +54,7 @@ return array( '_' => 'Accountlöschung', 'warn' => 'Dein Account und alle damit bezogenen Daten werden gelöscht.', ), + 'email' => 'E-Mail-Adresse', 'password_api' => 'Passwort-API
(z. B. für mobile Anwendungen)', 'password_form' => 'Passwort
(für die Anmeldemethode per Webformular)', 'password_format' => 'mindestens 7 Zeichen', @@ -133,7 +143,6 @@ return array( 'diaspora' => 'Diaspora*', 'email' => 'E-Mail', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'more_information' => 'Weitere Informationen', 'print' => 'Drucken', 'remove' => 'Entferne Teilen-Dienst', diff --git a/app/i18n/de/gen.php b/app/i18n/de/gen.php index c02a55b2c..e2dd2a251 100644 --- a/app/i18n/de/gen.php +++ b/app/i18n/de/gen.php @@ -3,6 +3,7 @@ return array( 'action' => array( 'actualize' => 'Aktualisieren', + 'back' => '← Go back', //TODO - Translation 'back_to_rss_feeds' => '← Zurück zu Ihren RSS-Feeds gehen', 'cancel' => 'Abbrechen', 'create' => 'Erstellen', @@ -22,6 +23,7 @@ return array( 'update' => 'Aktualisieren', ), 'auth' => array( + 'accept_tos' => 'I accept the Terms of Service.', // TODO - Translation 'email' => 'E-Mail-Adresse', 'keep_logged_in' => 'Eingeloggt bleiben (%s Tage)', 'login' => 'Anmelden', @@ -160,15 +162,22 @@ return array( 'nothing_to_load' => 'Es gibt keine weiteren Artikel', 'previous' => 'Vorherige', ), + 'period' => array( + 'days' => 'days', //TODO - Translation + 'hours' => 'hours', //TODO - Translation + 'months' => 'months', //TODO - Translation + 'weeks' => 'weeks', //TODO - Translation + 'years' => 'years', //TODO - Translation + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', 'email' => 'E-Mail', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'gnusocial' => 'GNU social', 'jdh' => 'Journal du hacker', 'Known' => 'Known-Seite (https://withknown.com)', + 'lemmy' => 'Lemmy', 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', diff --git a/app/i18n/de/index.php b/app/i18n/de/index.php index 10172e6f5..85ab3bb26 100644 --- a/app/i18n/de/index.php +++ b/app/i18n/de/index.php @@ -7,7 +7,7 @@ return array( 'bugs_reports' => 'Fehlerberichte', 'credits' => 'Credits', 'credits_content' => 'Einige Designelemente stammen von Bootstrap, obwohl FreshRSS dieses Framework nicht nutzt. Icons stammen vom GNOME project. Open Sans Font wurde von Steve Matteson erstellt. FreshRSS basiert auf Minz, einem PHP-Framework.', - 'freshrss_description' => 'FreshRSS ist ein RSS-Feedsaggregator zum selbst hosten wie zum Beispiel Kriss Feed oder Leed. Er ist leicht und einfach zu handhaben und gleichzeitig ein leistungsstarkes und konfigurierbares Werkzeug.', + 'freshrss_description' => 'FreshRSS ist ein RSS-Feedsaggregator zum selbst hosten wie zum Beispiel Kriss Feed oder Leed. Er ist leicht und einfach zu handhaben und gleichzeitig ein leistungsstarkes und konfigurierbares Werkzeug.', 'github' => 'on Github', 'license' => 'Lizenz', 'project_website' => 'Projekt-Webseite', @@ -15,6 +15,9 @@ return array( 'version' => 'Version', 'website' => 'Webseite', ), + 'tos' => array( + 'title' => 'Terms of Service', // TODO - Translation + ), 'feed' => array( 'add' => 'Sie können Feeds hinzufügen.', 'empty' => 'Es gibt keinen Artikel zum Anzeigen.', diff --git a/app/i18n/de/sub.php b/app/i18n/de/sub.php index abc01b954..754ac0866 100644 --- a/app/i18n/de/sub.php +++ b/app/i18n/de/sub.php @@ -13,9 +13,12 @@ return array( 'category' => array( '_' => 'Kategorie', 'add' => 'Eine Kategorie hinzufügen', + 'archiving' => 'Archivierung', 'empty' => 'Leere Kategorie', 'information' => 'Information', 'new' => 'Neue Kategorie', + 'position' => 'Display position', //TODO - Translation + 'position_help' => 'To control category sort order', //TODO - Translation 'title' => 'Titel', ), 'feed' => array( @@ -40,7 +43,7 @@ return array( 'help' => 'Write one search filter per line.', //TODO - Translation ), 'information' => 'Information', - 'keep_history' => 'Minimale Anzahl an Artikeln, die behalten wird', + 'keep_min' => 'Minimale Anzahl an Artikeln, die behalten wird', 'moved_category_deleted' => 'Wenn Sie eine Kategorie entfernen, werden deren Feeds automatisch in die Kategorie %s eingefügt.', 'mute' => 'Stumm schalten', 'no_selected' => 'Kein Feed ausgewählt.', @@ -72,6 +75,7 @@ return array( ), 'firefox' => array( 'documentation' => 'Folge den hier beschriebenen Schritten um FreshRSS zu Deiner Firefox RSS-Reader Liste hinzuzufügen.', + 'obsolete_63' => 'From version 63 and onwards, Firefox has removed the ability to add your own subscription services that are not standalone programs.', //TODO - Translation 'title' => 'Firefox RSS-Reader', ), 'import_export' => array( diff --git a/app/i18n/de/user.php b/app/i18n/de/user.php new file mode 100644 index 000000000..3a8343c11 --- /dev/null +++ b/app/i18n/de/user.php @@ -0,0 +1,37 @@ + array( + 'feedback' => array( + 'invalid' => 'The email address is invalid.', //TODO - Translation + 'required' => 'The email address is required.', //TODO - Translation + ), + 'validation' => array( + 'change_email' => 'You can change your email address on the profile page.', //TODO - Translation + 'email_sent_to' => 'We sent you an email at %s, please follow its indications to validate your address.', //TODO - Translation + 'feedback' => array( + 'email_failed' => 'We couldn’t send you an email because of a misconfiguration of the server.', //TODO - Translation + 'email_sent' => 'An email has been sent to your address.', //TODO - Translation + 'error' => 'The email address failed to be validated.', //TODO - Translation + 'ok' => 'The email address has been validated.', //TODO - Translation + 'unneccessary' => 'The email address was already validated.', //TODO - Translation + 'wrong_token' => 'The email address failed to be validated due to a wrong token.', //TODO - Translation + ), + 'need_to' => 'You need to validate your email address before being able to use %s.', //TODO - Translation + 'resend_email' => 'Resend the email', //TODO - Translation + 'title' => 'Email address validation', //TODO - Translation + ), + ), + 'tos' => array( + 'feedback' => array( + 'invalid' => 'You must accept the Terms of Service to be able to register.', // TODO - Translation + ), + ), + 'mailer' => array( + 'email_need_validation' => array( + 'title' => 'You need to validate your account', //TODO - Translation + 'welcome' => 'Welcome %s,', //TODO - Translation + 'body' => 'You’ve just registered on %s but you still need to validate your email. For that, just follow the link:', //TODO - Translation + ), + ), +); diff --git a/app/i18n/en/admin.php b/app/i18n/en/admin.php index 004089ffc..c5ab183e8 100644 --- a/app/i18n/en/admin.php +++ b/app/i18n/en/admin.php @@ -159,6 +159,7 @@ return array( 'system' => array( '_' => 'System configuration', 'auto-update-url' => 'Auto-update server URL', + 'force_email_validation' => 'Force email addresses validation', 'instance-name' => 'Instance name', 'max-categories' => 'Categories per user limit', 'max-feeds' => 'Feeds per user limit', diff --git a/app/i18n/en/conf.php b/app/i18n/en/conf.php index fde78f5b5..2d4e06550 100644 --- a/app/i18n/en/conf.php +++ b/app/i18n/en/conf.php @@ -3,13 +3,21 @@ return array( 'archiving' => array( '_' => 'Archiving', - 'advanced' => 'Advanced', 'delete_after' => 'Remove articles after', + 'exception' => 'Purge exception', 'help' => 'More options are available in the individual feed settings', - 'keep_history_by_feed' => 'Minimum number of articles to keep by feed', + 'keep_favourites' => 'Never delete favourites', + 'keep_min_by_feed' => 'Minimum number of articles to keep by feed', + 'keep_labels' => 'Never delete labels', + 'keep_unreads' => 'Never delete unreads', + 'maintenance' => 'Maintenance', 'optimize' => 'Optimise database', 'optimize_help' => 'Do occasionally to reduce the size of the database', + 'policy' => 'Purge policy', + 'policy_warning' => 'If no purge policy is selected, every article will be kept.', 'purge_now' => 'Purge now', + 'keep_max' => 'Maximum number of articles to keep', + 'keep_period' => 'Maximum age of articles to keep', 'title' => 'Archiving', 'ttl' => 'Do not automatically refresh more often than', ), @@ -21,6 +29,7 @@ return array( 'publication_date' => 'Date of publication', 'related_tags' => 'Article tags', 'sharing' => 'Sharing', + 'display_authors' => 'Authors', 'top_line' => 'Top line', ), 'language' => 'Language', @@ -45,6 +54,7 @@ return array( '_' => 'Account deletion', 'warn' => 'Your account and all related data will be deleted.', ), + 'email' => 'Email address', 'password_api' => 'API password
(e.g., for mobile apps)', 'password_form' => 'Password
(for the Web-form login method)', 'password_format' => 'At least 7 characters', @@ -133,7 +143,6 @@ return array( 'diaspora' => 'Diaspora*', 'email' => 'Email', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'more_information' => 'More information', 'print' => 'Print', 'remove' => 'Remove sharing method', diff --git a/app/i18n/en/gen.php b/app/i18n/en/gen.php index 32f5ee02e..fc1bd587a 100644 --- a/app/i18n/en/gen.php +++ b/app/i18n/en/gen.php @@ -3,6 +3,7 @@ return array( 'action' => array( 'actualize' => 'Actualize', + 'back' => '← Go back', 'back_to_rss_feeds' => '← Go back to your RSS feeds', 'cancel' => 'Cancel', 'create' => 'Create', @@ -22,6 +23,7 @@ return array( 'update' => 'Update', ), 'auth' => array( + 'accept_tos' => 'I accept the Terms of Service.', 'email' => 'Email address', 'keep_logged_in' => 'Keep me logged in (%s days)', 'login' => 'Login', @@ -127,6 +129,7 @@ return array( 'oc' => 'Occitan', 'pt-br' => 'Português (Brasil)', 'ru' => 'Русский', + 'sk' => 'Slovenčina', 'tr' => 'Türkçe', 'zh-cn' => '简体中文', ), @@ -160,15 +163,22 @@ return array( 'nothing_to_load' => 'There are no more articles', 'previous' => 'Previous', ), + 'period' => array( + 'days' => 'days', + 'hours' => 'hours', + 'months' => 'months', + 'weeks' => 'weeks', + 'years' => 'years', + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', 'email' => 'Email', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'gnusocial' => 'GNU social', 'jdh' => 'Journal du hacker', 'Known' => 'Known based sites', + 'lemmy' => 'Lemmy', 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', diff --git a/app/i18n/en/index.php b/app/i18n/en/index.php index 46c415816..71bf8b53e 100644 --- a/app/i18n/en/index.php +++ b/app/i18n/en/index.php @@ -7,7 +7,7 @@ return array( 'bugs_reports' => 'Bugs reports', 'credits' => 'Credits', 'credits_content' => 'Some design elements come from Bootstrap although FreshRSS doesn’t use this framework. Icons come from GNOME project. Open Sans font police has been created by Steve Matteson. FreshRSS is based on Minz, a PHP framework.', - 'freshrss_description' => 'FreshRSS is a RSS feeds aggregator to self-host like Kriss Feed or Leed. It is light and easy to take in hand while being powerful and configurable tool.', + 'freshrss_description' => 'FreshRSS is a RSS feeds aggregator to self-host like Kriss Feed or Leed. It is light and easy to take in hand while being powerful and configurable tool.', 'github' => 'on Github', 'license' => 'License', 'project_website' => 'Project website', @@ -15,6 +15,9 @@ return array( 'version' => 'Version', 'website' => 'Website', ), + 'tos' => array( + 'title' => 'Terms of Service', + ), 'feed' => array( 'add' => 'You may add some feeds.', 'empty' => 'There is no article to show.', diff --git a/app/i18n/en/sub.php b/app/i18n/en/sub.php index fde01f9df..252177940 100644 --- a/app/i18n/en/sub.php +++ b/app/i18n/en/sub.php @@ -13,9 +13,12 @@ return array( 'category' => array( '_' => 'Category', 'add' => 'Add a category', + 'archiving' => 'Archiving', 'empty' => 'Empty category', 'information' => 'Information', 'new' => 'New category', + 'position' => 'Display position', + 'position_help' => 'To control category sort order', 'title' => 'Title', ), 'feed' => array( @@ -40,7 +43,7 @@ return array( 'help' => 'Write one search filter per line.', ), 'information' => 'Information', - 'keep_history' => 'Minimum number of articles to keep', + 'keep_min' => 'Minimum number of articles to keep', 'moved_category_deleted' => 'When you delete a category, its feeds are automatically classified under %s.', 'mute' => 'mute', 'no_selected' => 'No feed selected.', @@ -72,6 +75,7 @@ return array( ), 'firefox' => array( 'documentation' => 'Follow the steps described here to add FreshRSS to Firefox feed reader list.', + 'obsolete_63' => 'From version 63 and onwards, Firefox has removed the ability to add your own subscription services that are not standalone programs.', 'title' => 'Firefox feed reader', ), 'import_export' => array( diff --git a/app/i18n/en/user.php b/app/i18n/en/user.php new file mode 100644 index 000000000..54d8dfa4d --- /dev/null +++ b/app/i18n/en/user.php @@ -0,0 +1,37 @@ + array( + 'feedback' => array( + 'invalid' => 'The email address is invalid.', + 'required' => 'The email address is required.', + ), + 'validation' => array( + 'change_email' => 'You can change your email address on the profile page.', + 'email_sent_to' => 'We sent you an email at %s, please follow its indications to validate your address.', + 'feedback' => array( + 'email_failed' => 'We couldn’t send you an email because of a misconfiguration of the server.', + 'email_sent' => 'An email has been sent to your address.', + 'error' => 'The email address failed to be validated.', + 'ok' => 'The email address has been validated.', + 'unneccessary' => 'The email address was already validated.', + 'wrong_token' => 'The email address failed to be validated due to a wrong token.', + ), + 'need_to' => 'You need to validate your email address before being able to use %s.', + 'resend_email' => 'Resend the email', + 'title' => 'Email address validation', + ), + ), + 'tos' => array( + 'feedback' => array( + 'invalid' => 'You must accept the Terms of Service to be able to register.', + ), + ), + 'mailer' => array( + 'email_need_validation' => array( + 'title' => 'You need to validate your account', + 'welcome' => 'Welcome %s,', + 'body' => 'You’ve just registered on %s but you still need to validate your email. For that, just follow the link:', + ), + ), +); diff --git a/app/i18n/es/admin.php b/app/i18n/es/admin.php index 0ec8549bd..1af3bdcb2 100755 --- a/app/i18n/es/admin.php +++ b/app/i18n/es/admin.php @@ -159,6 +159,7 @@ return array( 'system' => array( '_' => 'Configuración del sistema', 'auto-update-url' => 'URL de auto-actualización', + 'force_email_validation' => 'Force email addresses validation', //TODO - Translation 'instance-name' => 'Nombre de la fuente', 'max-categories' => 'Límite de categorías por usuario', 'max-feeds' => 'Límite de fuentes por usuario', diff --git a/app/i18n/es/conf.php b/app/i18n/es/conf.php index b7d87f375..7a93a87de 100755 --- a/app/i18n/es/conf.php +++ b/app/i18n/es/conf.php @@ -3,13 +3,21 @@ return array( 'archiving' => array( '_' => 'Archivo', - 'advanced' => 'Avanzado', 'delete_after' => 'Eliminar artículos tras', + 'exception' => 'Purge exception', //TODO - Translation 'help' => 'Hay más opciones disponibles en los ajustes de la fuente', - 'keep_history_by_feed' => 'Número mínimo de artículos a conservar por fuente', + 'keep_favourites' => 'Never delete favourites', //TODO - Translation + 'keep_min_by_feed' => 'Número mínimo de artículos a conservar por fuente', + 'keep_labels' => 'Never delete labels', //TODO - Translation + 'keep_unreads' => 'Never delete unreads', //TODO - Translation + 'maintenance' => 'Maintenance', //TODO - Translation 'optimize' => 'Optimizar la base de datos', 'optimize_help' => 'Ejecuta la optimización de vez en cuando para reducir el tamaño de la base de datos', + 'policy' => 'Purge policy', //TODO - Translation + 'policy_warning' => 'If no purge policy is selected, every article will be kept.', //TODO - Translation 'purge_now' => 'Limpiar ahora', + 'keep_max' => 'Maximum number of articles to keep', //TODO - Translation + 'keep_period' => 'Maximum age of articles to keep', //TODO - Translation 'title' => 'Archivo', 'ttl' => 'No actualizar automáticamente más de', ), @@ -21,6 +29,7 @@ return array( 'publication_date' => 'Fecha de publicación', 'related_tags' => 'Etiquetas relacionadas', 'sharing' => 'Compartir', + 'display_authors' => 'Authors', //TODO - Translation 'top_line' => 'Línea superior', ), 'language' => 'Idioma', @@ -45,6 +54,7 @@ return array( '_' => 'Borrar cuenta', 'warn' => 'Tu cuenta y todos los datos asociados serán eliminados.', ), + 'email' => 'Correo electrónico', 'password_api' => 'Contraseña API
(para apps móviles, por ej.)', 'password_form' => 'Contraseña
(para el método de identificación por formulario web)', 'password_format' => 'Mínimo de 7 caracteres', @@ -133,7 +143,6 @@ return array( 'diaspora' => 'Diaspora*', 'email' => 'Email', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'more_information' => 'Más información', 'print' => 'Print', 'remove' => 'Remove sharing method', //TODO - Translation diff --git a/app/i18n/es/gen.php b/app/i18n/es/gen.php index db36e5f5b..538ddc8fe 100755 --- a/app/i18n/es/gen.php +++ b/app/i18n/es/gen.php @@ -3,6 +3,7 @@ return array( 'action' => array( 'actualize' => 'Actualizar', + 'back' => '← Go back', //TODO - Translation 'back_to_rss_feeds' => '← regresar a tus fuentes RSS', 'cancel' => 'Cancelar', 'create' => 'Crear', @@ -22,6 +23,7 @@ return array( 'update' => 'Update', //TODO - Translation ), 'auth' => array( + 'accept_tos' => 'I accept the Terms of Service.', // TODO - Translation 'email' => 'Correo electrónico', 'keep_logged_in' => 'Mantenerme identificado (%s días)', 'login' => 'Conectar', @@ -160,15 +162,22 @@ return array( 'nothing_to_load' => 'No hay más artículos', 'previous' => 'Anterior', ), + 'period' => array( + 'days' => 'days', //TODO - Translation + 'hours' => 'hours', //TODO - Translation + 'months' => 'months', //TODO - Translation + 'weeks' => 'weeks', //TODO - Translation + 'years' => 'years', //TODO - Translation + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', 'email' => 'Email', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'gnusocial' => 'GNU social', 'jdh' => 'Journal du hacker', 'Known' => 'Known based sites', + 'lemmy' => 'Lemmy', 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', diff --git a/app/i18n/es/index.php b/app/i18n/es/index.php index d7a42537b..8977ee70b 100755 --- a/app/i18n/es/index.php +++ b/app/i18n/es/index.php @@ -7,7 +7,7 @@ return array( 'bugs_reports' => 'Informe de fallos', 'credits' => 'Créditos', 'credits_content' => 'Aunque FreshRSS no usa ese entorno, algunos elementos del diseño están obtenidos de Bootstrap. Los Iconos han sido obtenidos del proyecto GNOME. La fuente Open Sans es una creación de Steve Matteson. FreshRSS usa el entorno PHP Minz.', - 'freshrss_description' => 'FreshRSS es un agregador de fuentes RSS de alojamiento privado al estilo de Kriss Feed o Leed. Es una herramienta potente, pero ligera y fácil de usar y configurar.', + 'freshrss_description' => 'FreshRSS es un agregador de fuentes RSS de alojamiento privado al estilo de Kriss Feed o Leed. Es una herramienta potente, pero ligera y fácil de usar y configurar.', 'github' => 'en Github', 'license' => 'Licencia', 'project_website' => 'Web del proyecto', @@ -15,6 +15,9 @@ return array( 'version' => 'Versión', 'website' => 'Web', ), + 'tos' => array( + 'title' => 'Terms of Service', // TODO - Translation + ), 'feed' => array( 'add' => 'Puedes añadir fuentes.', 'empty' => 'No hay artículos a mostrar.', diff --git a/app/i18n/es/sub.php b/app/i18n/es/sub.php index 7d33c59fa..f1640b76b 100755 --- a/app/i18n/es/sub.php +++ b/app/i18n/es/sub.php @@ -13,9 +13,12 @@ return array( 'category' => array( '_' => 'Categoría', 'add' => 'Añadir a la categoría', + 'archiving' => 'Archivo', 'empty' => 'Vaciar categoría', 'information' => 'Información', 'new' => 'Nueva categoría', + 'position' => 'Display position', //TODO - Translation + 'position_help' => 'To control category sort order', //TODO - Translation 'title' => 'Título', ), 'feed' => array( @@ -40,7 +43,7 @@ return array( 'help' => 'Write one search filter per line.', //TODO - Translation ), 'information' => 'Información', - 'keep_history' => 'Número mínimo de artículos a conservar', + 'keep_min' => 'Número mínimo de artículos a conservar', 'moved_category_deleted' => 'Al borrar una categoría todas sus fuentes pasan automáticamente a la categoría %s.', 'mute' => 'mute', //TODO - Translation 'no_selected' => 'No hay funentes seleccionadas.', @@ -72,6 +75,7 @@ return array( ), 'firefox' => array( 'documentation' => 'Follow the steps described here to add FreshRSS to Firefox feed reader list.', //TODO - Translation + 'obsolete_63' => 'From version 63 and onwards, Firefox has removed the ability to add your own subscription services that are not standalone programs.', //TODO - Translation 'title' => 'Firefox feed reader', //TODO - Translation ), 'import_export' => array( diff --git a/app/i18n/es/user.php b/app/i18n/es/user.php new file mode 100644 index 000000000..3a8343c11 --- /dev/null +++ b/app/i18n/es/user.php @@ -0,0 +1,37 @@ + array( + 'feedback' => array( + 'invalid' => 'The email address is invalid.', //TODO - Translation + 'required' => 'The email address is required.', //TODO - Translation + ), + 'validation' => array( + 'change_email' => 'You can change your email address on the profile page.', //TODO - Translation + 'email_sent_to' => 'We sent you an email at %s, please follow its indications to validate your address.', //TODO - Translation + 'feedback' => array( + 'email_failed' => 'We couldn’t send you an email because of a misconfiguration of the server.', //TODO - Translation + 'email_sent' => 'An email has been sent to your address.', //TODO - Translation + 'error' => 'The email address failed to be validated.', //TODO - Translation + 'ok' => 'The email address has been validated.', //TODO - Translation + 'unneccessary' => 'The email address was already validated.', //TODO - Translation + 'wrong_token' => 'The email address failed to be validated due to a wrong token.', //TODO - Translation + ), + 'need_to' => 'You need to validate your email address before being able to use %s.', //TODO - Translation + 'resend_email' => 'Resend the email', //TODO - Translation + 'title' => 'Email address validation', //TODO - Translation + ), + ), + 'tos' => array( + 'feedback' => array( + 'invalid' => 'You must accept the Terms of Service to be able to register.', // TODO - Translation + ), + ), + 'mailer' => array( + 'email_need_validation' => array( + 'title' => 'You need to validate your account', //TODO - Translation + 'welcome' => 'Welcome %s,', //TODO - Translation + 'body' => 'You’ve just registered on %s but you still need to validate your email. For that, just follow the link:', //TODO - Translation + ), + ), +); diff --git a/app/i18n/fr/admin.php b/app/i18n/fr/admin.php index 74605b5ee..6002617fc 100644 --- a/app/i18n/fr/admin.php +++ b/app/i18n/fr/admin.php @@ -159,6 +159,7 @@ return array( 'system' => array( '_' => 'Configuration du système', 'auto-update-url' => 'URL du service de mise à jour', + 'force_email_validation' => 'Forcer la validation des adresses email', 'instance-name' => 'Nom de l’instance', 'max-categories' => 'Limite de catégories par utilisateur', 'max-feeds' => 'Limite de flux par utilisateur', diff --git a/app/i18n/fr/conf.php b/app/i18n/fr/conf.php index ef29a360e..020c94085 100644 --- a/app/i18n/fr/conf.php +++ b/app/i18n/fr/conf.php @@ -3,13 +3,21 @@ return array( 'archiving' => array( '_' => 'Archivage', - 'advanced' => 'Avancé', 'delete_after' => 'Supprimer les articles après', + 'exception' => 'Exception de nettoyage', 'help' => 'D’autres options sont disponibles dans la configuration individuelle des flux.', - 'keep_history_by_feed' => 'Nombre minimum d’articles à conserver par flux', + 'keep_favourites' => 'Ne jamais supprimer les articles favoris', + 'keep_min_by_feed' => 'Nombre minimum d’articles à conserver par flux', + 'keep_labels' => 'Ne jamais supprimer les articles étiquetés', + 'keep_unreads' => 'Ne jamais supprimer les articles non lus', + 'maintenance' => 'Maintenance', 'optimize' => 'Optimiser la base de données', 'optimize_help' => 'À faire de temps en temps pour réduire la taille de la BDD', + 'policy' => 'Politique de nettoyage', + 'policy_warning' => 'Si aucune politique de nettoyage n’est sélectionnée, tous les articles seront conservés.', 'purge_now' => 'Purger maintenant', + 'keep_max' => 'Nombre maximum d’articles à conserver', + 'keep_period' => 'Âge maximum des articles à conserver', 'title' => 'Archivage', 'ttl' => 'Ne pas automatiquement rafraîchir plus souvent que', ), @@ -21,6 +29,7 @@ return array( 'publication_date' => 'Date de publication', 'related_tags' => 'Tags de l’article', 'sharing' => 'Partage', + 'display_authors' => 'Authors', //TODO - Translation 'top_line' => 'Ligne du haut', ), 'language' => 'Langue', @@ -45,6 +54,7 @@ return array( '_' => 'Suppression du compte', 'warn' => 'Le compte et toutes les données associées vont être supprimées.', ), + 'email' => 'Adresse email', 'password_api' => 'Mot de passe API
(ex. : pour applis mobiles)', 'password_form' => 'Mot de passe
(pour connexion par formulaire)', 'password_format' => '7 caractères minimum', @@ -133,7 +143,6 @@ return array( 'diaspora' => 'Diaspora*', 'email' => 'Courriel', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'more_information' => 'Plus d’informations', 'print' => 'Print', 'remove' => 'Supprimer la méthode de partage', diff --git a/app/i18n/fr/gen.php b/app/i18n/fr/gen.php index 86d8461e6..a6875dd05 100644 --- a/app/i18n/fr/gen.php +++ b/app/i18n/fr/gen.php @@ -3,6 +3,7 @@ return array( 'action' => array( 'actualize' => 'Actualiser', + 'back' => '← Retour', 'back_to_rss_feeds' => '← Retour à vos flux RSS', 'cancel' => 'Annuler', 'create' => 'Créer', @@ -22,6 +23,7 @@ return array( 'update' => 'Mettre à jour', ), 'auth' => array( + 'accept_tos' => 'Accepter les Conditions Générales d’Utilisation.', 'email' => 'Adresse courriel', 'keep_logged_in' => 'Rester connecté (%s jours)', 'login' => 'Connexion', @@ -160,15 +162,22 @@ return array( 'nothing_to_load' => 'Fin des articles', 'previous' => 'Précédent', ), + 'period' => array( + 'days' => 'jours', + 'hours' => 'heures', + 'months' => 'mois', + 'weeks' => 'semaines', + 'years' => 'années', + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', 'email' => 'Courriel', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'gnusocial' => 'GNU social', 'jdh' => 'Journal du hacker', 'Known' => 'Sites basés sur Known', + 'lemmy' => 'Lemmy', 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', diff --git a/app/i18n/fr/index.php b/app/i18n/fr/index.php index c9595e449..489de3849 100644 --- a/app/i18n/fr/index.php +++ b/app/i18n/fr/index.php @@ -7,7 +7,7 @@ return array( 'bugs_reports' => 'Rapports de bugs', 'credits' => 'Crédits', 'credits_content' => 'Des éléments de design sont issus du projet Bootstrap bien que FreshRSS n’utilise pas ce framework. Les icônes sont issues du projet GNOME. La police Open Sans utilisée a été créée par Steve Matteson. FreshRSS repose sur Minz, un framework PHP.', - 'freshrss_description' => 'FreshRSS est un agrégateur de flux RSS à auto-héberger à l’image de Kriss Feed ou Leed. Il se veut léger et facile à prendre en main tout en étant un outil puissant et paramétrable.', + 'freshrss_description' => 'FreshRSS est un agrégateur de flux RSS à auto-héberger à l’image de Kriss Feed ou Leed. Il se veut léger et facile à prendre en main tout en étant un outil puissant et paramétrable.', 'github' => 'sur Github', 'license' => 'Licence', 'project_website' => 'Site du projet', @@ -15,6 +15,9 @@ return array( 'version' => 'Version', 'website' => 'Site Internet', ), + 'tos' => array( + 'title' => 'Conditions Générales d’Utilisation', + ), 'feed' => array( 'add' => 'Vous pouvez ajouter des flux.', 'empty' => 'Il n’y a aucun article à afficher.', diff --git a/app/i18n/fr/sub.php b/app/i18n/fr/sub.php index df44150c2..e12444315 100644 --- a/app/i18n/fr/sub.php +++ b/app/i18n/fr/sub.php @@ -13,9 +13,12 @@ return array( 'category' => array( '_' => 'Catégorie', 'add' => 'Ajouter une catégorie', + 'archiving' => 'Archivage', 'empty' => 'Catégorie vide', 'information' => 'Informations', 'new' => 'Nouvelle catégorie', + 'position' => 'Position d’affichage', + 'position_help' => 'Pour contrôler l’ordre de tri des catégories', 'title' => 'Titre', ), 'feed' => array( @@ -40,7 +43,7 @@ return array( 'help' => 'Écrivez une recherche par ligne.', ), 'information' => 'Informations', - 'keep_history' => 'Nombre minimum d’articles à conserver', + 'keep_min' => 'Nombre minimum d’articles à conserver', 'moved_category_deleted' => 'Lors de la suppression d’une catégorie, ses flux seront automatiquement classés dans %s.', 'mute' => 'muet', 'no_selected' => 'Aucun flux sélectionné.', @@ -72,6 +75,7 @@ return array( ), 'firefox' => array( 'documentation' => 'Suivre les étapes décrites ici pour ajouter FreshRSS à la liste des lecteurs de flux dans Firefox.', + 'obsolete_63' => 'À partir de la version 63, Firefox ne supporte plus l’ajout de services d’abonnements.', 'title' => 'Lecteur de flux dans Firefox', ), 'import_export' => array( diff --git a/app/i18n/fr/user.php b/app/i18n/fr/user.php new file mode 100644 index 000000000..7b531c749 --- /dev/null +++ b/app/i18n/fr/user.php @@ -0,0 +1,37 @@ + array( + 'feedback' => array( + 'invalid' => 'L’adresse email est invalide.', + 'required' => 'L’adresse email est requise.', + ), + 'validation' => array( + 'change_email' => 'Vous pouvez changer votre adresse email dans votre profil.', + 'email_sent_to' => 'Nous venons d’envoyer un email à %s, veuillez suivre ses indications pour valider votre adresse.', + 'feedback' => array( + 'email_failed' => 'Nous n’avons pas pu vous envoyer d’email à cause d’une mauvaise configuration du serveur.', + 'email_sent' => 'Un email a été envoyé à votre adresse.', + 'error' => 'L’adresse email n’a pas pu être validée.', + 'ok' => 'L’adresse email a été validée.', + 'unnecessary' => 'L’adresse email a déjà été validée.', + 'wrong_token' => 'L’adresse email n’a pas pu être validée à cause d’un mauvais token.', + ), + 'need_to' => 'Vous devez valider votre adresse email avant de pouvoir utiliser %s.', + 'resend_email' => 'Renvoyer l’email', + 'title' => 'Validation de l’adresse email', + ), + ), + 'tos' => array( + 'feedback' => array( + 'invalid' => 'Vous devez accepter les conditions générales d’utilisation pour pouvoir vous inscrire.', + ), + ), + 'mailer' => array( + 'email_need_validation' => array( + 'title' => 'Vous devez valider votre compte', + 'welcome' => 'Bienvenue %s,', + 'body' => 'Vous venez de vous inscrire sur %s mais vous devez encore valider votre adresse email. Pour cela, veuillez cliquer sur ce lien :', + ), + ), +); diff --git a/app/i18n/he/admin.php b/app/i18n/he/admin.php index e0dfc405d..759b74e2a 100644 --- a/app/i18n/he/admin.php +++ b/app/i18n/he/admin.php @@ -163,6 +163,7 @@ return array( 'help' => 'in seconds', //TODO - Translation 'number' => 'Duration to keep logged in', //TODO - Translation ), + 'force_email_validation' => 'Force email addresses validation', //TODO - Translation 'instance-name' => 'Instance name', //TODO - Translation 'max-categories' => 'Categories per user limit', //TODO - Translation 'max-feeds' => 'Feeds per user limit', //TODO - Translation diff --git a/app/i18n/he/conf.php b/app/i18n/he/conf.php index 1eb447911..b987f21f4 100644 --- a/app/i18n/he/conf.php +++ b/app/i18n/he/conf.php @@ -3,13 +3,21 @@ return array( 'archiving' => array( '_' => 'ארכוב', - 'advanced' => 'מתקדם', 'delete_after' => 'מחיקת מאמרים לאחר', + 'exception' => 'Purge exception', //TODO - Translation 'help' => 'אפשרויות נוספות זמינות בזרמים ספציפיים', - 'keep_history_by_feed' => 'Minimum number of articles to keep by feed', //TODO - Translation + 'keep_favourites' => 'Never delete favourites', //TODO - Translation + 'keep_min_by_feed' => 'Minimum number of articles to keep by feed', + 'keep_labels' => 'Never delete labels', //TODO - Translation + 'keep_unreads' => 'Never delete unreads', //TODO - Translation + 'maintenance' => 'Maintenance', //TODO - Translation 'optimize' => 'מיטוב בסיס הנתונים', 'optimize_help' => 'ביצוע לעיתים קרובות על מנת למטב את בסיס הנתונים', + 'policy' => 'Purge policy', //TODO - Translation + 'policy_warning' => 'If no purge policy is selected, every article will be kept.', //TODO - Translation 'purge_now' => 'ניקוי עכשיו', + 'keep_max' => 'Maximum number of articles to keep', //TODO - Translation + 'keep_period' => 'Maximum age of articles to keep', //TODO - Translation 'title' => 'ארכוב', 'ttl' => 'אין לרענן אוטומטית יותר מ', ), @@ -21,6 +29,7 @@ return array( 'publication_date' => 'תאריך הפרסום', 'related_tags' => 'תגיות קשורות', //TODO - Translation 'sharing' => 'שיתוף', + 'display_authors' => 'Authors', //TODO - Translation 'top_line' => 'שורה עליונה', ), 'language' => 'שפה', @@ -45,6 +54,7 @@ return array( '_' => 'Account deletion', //TODO - Translation 'warn' => 'Your account and all related data will be deleted.', //TODO - Translation ), + 'email' => 'Email address', //TODO - Translation 'password_api' => 'סיסמת API
(לדוגמה ליישומים סלולריים)', 'password_form' => 'סיסמה
(לשימוש בטפוס ההרשמה)', 'password_format' => 'At least 7 characters', //TODO - Translation @@ -133,7 +143,6 @@ return array( 'diaspora' => 'Diaspora*', 'email' => 'דואר אלקטרוני', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'more_information' => 'מידע נוסף', 'print' => 'הדפסה', 'remove' => 'Remove sharing method', //TODO - Translation diff --git a/app/i18n/he/gen.php b/app/i18n/he/gen.php index cf4a1fcda..34e6d77de 100644 --- a/app/i18n/he/gen.php +++ b/app/i18n/he/gen.php @@ -3,6 +3,7 @@ return array( 'action' => array( 'actualize' => 'מימוש', + 'back' => '← Go back', //TODO - Translation 'back_to_rss_feeds' => '← חזרה להזנות הRSS שלך', 'cancel' => 'ביטול', 'create' => 'יצירה', @@ -22,6 +23,7 @@ return array( 'update' => 'Update', //TODO - Translation ), 'auth' => array( + 'accept_tos' => 'I accept the Terms of Service.', // TODO - Translation 'email' => 'Email address', //TODO - Translation 'keep_logged_in' => 'השאר מחובר חודש', 'login' => 'כניסה לחשבון', @@ -160,15 +162,22 @@ return array( 'nothing_to_load' => 'אין מאמרים נוספים', 'previous' => 'הקודם', ), + 'period' => array( + 'days' => 'days', //TODO - Translation + 'hours' => 'hours', //TODO - Translation + 'months' => 'months', //TODO - Translation + 'weeks' => 'weeks', //TODO - Translation + 'years' => 'years', //TODO - Translation + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', 'email' => 'דואר אלקטרוני', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'gnusocial' => 'GNU social', 'jdh' => 'Journal du hacker', 'Known' => 'Known based sites', + 'lemmy' => 'Lemmy', 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', diff --git a/app/i18n/he/index.php b/app/i18n/he/index.php index e01a02773..7cd1945e7 100644 --- a/app/i18n/he/index.php +++ b/app/i18n/he/index.php @@ -7,7 +7,7 @@ return array( 'bugs_reports' => 'דיווח באגים', 'credits' => 'קרדיטים', 'credits_content' => 'מאפייני עיצוב מסויימים הגיעו מ Bootstrap אף על פי ש FreshRSS אינו משתמש בתשתית הזו. סמלילים הגיעו מ פרוייקט GNOME . Open Sans הגופן police נוצר על ידי Steve Matteson. Favicons נאספים בעזרת getFavicon API. FreshRSS מבוסס על Minz, תשתית PHP.', - 'freshrss_description' => 'FreshRSS הוא קורא RSS לאחסון עצמי בדומה ל Kriss Feed או Leed. אינו צורך משאבים רבים, וקל לתפעול אך בו בזמן חזק וניתן להתאמה.', + 'freshrss_description' => 'FreshRSS הוא קורא RSS לאחסון עצמי בדומה ל Kriss Feed או Leed. אינו צורך משאבים רבים, וקל לתפעול אך בו בזמן חזק וניתן להתאמה.', 'github' => 'בגיטהאב', 'license' => 'רישיון', 'project_website' => 'אתר', @@ -15,6 +15,9 @@ return array( 'version' => 'גרסה', 'website' => 'אתר', ), + 'tos' => array( + 'title' => 'Terms of Service', // TODO - Translation + ), 'feed' => array( 'add' => 'ניתן להוסיף הזנות חדשות.', 'empty' => 'אין מאמר להצגה.', diff --git a/app/i18n/he/sub.php b/app/i18n/he/sub.php index 8a629defb..3fd0f267a 100644 --- a/app/i18n/he/sub.php +++ b/app/i18n/he/sub.php @@ -13,9 +13,12 @@ return array( 'category' => array( '_' => 'קטגוריה', 'add' => 'הוספת קטגוריה', + 'archiving' => 'ארכוב', 'empty' => 'Empty category', //TODO - Translation 'information' => 'מידע', 'new' => 'קטגוריה חדשה', + 'position' => 'Display position', //TODO - Translation + 'position_help' => 'To control category sort order', //TODO - Translation 'title' => 'כותרת', ), 'feed' => array( @@ -40,7 +43,7 @@ return array( 'help' => 'Write one search filter per line.', //TODO - Translation ), 'information' => 'מידע', - 'keep_history' => 'מסםר מינימלי של מאמרים לשמור', + 'keep_min' => 'מסםר מינימלי של מאמרים לשמור', 'moved_category_deleted' => 'כאשר הקטגוריה נמחקת ההזנות שבתוכה אוטומטית מקוטלגות תחת %s.', 'mute' => 'mute', //TODO - Translation 'no_selected' => 'אף הזנה לא נבחרה.', @@ -72,6 +75,7 @@ return array( ), 'firefox' => array( 'documentation' => 'Follow the steps described here to add FreshRSS to Firefox feed reader list.', //TODO - Translation + 'obsolete_63' => 'From version 63 and onwards, Firefox has removed the ability to add your own subscription services that are not standalone programs.', //TODO - Translation 'title' => 'Firefox feed reader', //TODO - Translation ), 'import_export' => array( diff --git a/app/i18n/he/user.php b/app/i18n/he/user.php new file mode 100644 index 000000000..3a8343c11 --- /dev/null +++ b/app/i18n/he/user.php @@ -0,0 +1,37 @@ + array( + 'feedback' => array( + 'invalid' => 'The email address is invalid.', //TODO - Translation + 'required' => 'The email address is required.', //TODO - Translation + ), + 'validation' => array( + 'change_email' => 'You can change your email address on the profile page.', //TODO - Translation + 'email_sent_to' => 'We sent you an email at %s, please follow its indications to validate your address.', //TODO - Translation + 'feedback' => array( + 'email_failed' => 'We couldn’t send you an email because of a misconfiguration of the server.', //TODO - Translation + 'email_sent' => 'An email has been sent to your address.', //TODO - Translation + 'error' => 'The email address failed to be validated.', //TODO - Translation + 'ok' => 'The email address has been validated.', //TODO - Translation + 'unneccessary' => 'The email address was already validated.', //TODO - Translation + 'wrong_token' => 'The email address failed to be validated due to a wrong token.', //TODO - Translation + ), + 'need_to' => 'You need to validate your email address before being able to use %s.', //TODO - Translation + 'resend_email' => 'Resend the email', //TODO - Translation + 'title' => 'Email address validation', //TODO - Translation + ), + ), + 'tos' => array( + 'feedback' => array( + 'invalid' => 'You must accept the Terms of Service to be able to register.', // TODO - Translation + ), + ), + 'mailer' => array( + 'email_need_validation' => array( + 'title' => 'You need to validate your account', //TODO - Translation + 'welcome' => 'Welcome %s,', //TODO - Translation + 'body' => 'You’ve just registered on %s but you still need to validate your email. For that, just follow the link:', //TODO - Translation + ), + ), +); diff --git a/app/i18n/it/admin.php b/app/i18n/it/admin.php index d4253e9ba..8bb6c7bfe 100644 --- a/app/i18n/it/admin.php +++ b/app/i18n/it/admin.php @@ -159,6 +159,7 @@ return array( 'system' => array( '_' => 'Configurazione di sistema', 'auto-update-url' => 'Auto-update server URL', //TODO - Translation + 'force_email_validation' => 'Force email addresses validation', //TODO - Translation 'instance-name' => 'Nome istanza', 'max-categories' => 'Limite categorie per utente', 'max-feeds' => 'Limite feeds per utente', diff --git a/app/i18n/it/conf.php b/app/i18n/it/conf.php index df4a5ebeb..4bdaad33d 100644 --- a/app/i18n/it/conf.php +++ b/app/i18n/it/conf.php @@ -3,13 +3,21 @@ return array( 'archiving' => array( '_' => 'Archiviazione', - 'advanced' => 'Avanzate', 'delete_after' => 'Rimuovi articoli dopo', + 'exception' => 'Purge exception', //TODO - Translation 'help' => 'Altre opzioni sono disponibili nelle impostazioni dei singoli feed', - 'keep_history_by_feed' => 'Numero minimo di articoli da mantenere per feed', + 'keep_favourites' => 'Never delete favourites', //TODO - Translation + 'keep_min_by_feed' => 'Numero minimo di articoli da mantenere per feed', + 'keep_labels' => 'Never delete labels', //TODO - Translation + 'keep_unreads' => 'Never delete unreads', //TODO - Translation + 'maintenance' => 'Maintenance', //TODO - Translation 'optimize' => 'Ottimizza database', 'optimize_help' => 'Da fare occasionalmente per ridurre le dimensioni del database', + 'policy' => 'Purge policy', //TODO - Translation + 'policy_warning' => 'If no purge policy is selected, every article will be kept.', //TODO - Translation 'purge_now' => 'Cancella ora', + 'keep_max' => 'Maximum number of articles to keep', //TODO - Translation + 'keep_period' => 'Maximum age of articles to keep', //TODO - Translation 'title' => 'Archiviazione', 'ttl' => 'Non effettuare aggiornamenti per più di', ), @@ -21,6 +29,7 @@ return array( 'publication_date' => 'Data di pubblicazione', 'related_tags' => 'Tags correlati', //TODO - Translation 'sharing' => 'Condivisione', + 'display_authors' => 'Authors', //TODO - Translation 'top_line' => 'Barra in alto', ), 'language' => 'Lingua', @@ -45,6 +54,7 @@ return array( '_' => 'Cancellazione account', 'warn' => 'Il tuo account e tutti i dati associati saranno cancellati.', ), + 'email' => 'Indirizzo email', 'password_api' => 'Password API
(e.g., per applicazioni mobili)', 'password_form' => 'Password
(per il login classico)', 'password_format' => 'Almeno 7 caratteri', @@ -133,7 +143,6 @@ return array( 'diaspora' => 'Diaspora*', 'email' => 'Email', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'more_information' => 'Ulteriori informazioni', 'print' => 'Stampa', 'remove' => 'Remove sharing method', //TODO - Translation diff --git a/app/i18n/it/gen.php b/app/i18n/it/gen.php index 9cc40ffe3..50d4b4e6c 100644 --- a/app/i18n/it/gen.php +++ b/app/i18n/it/gen.php @@ -3,6 +3,7 @@ return array( 'action' => array( 'actualize' => 'Aggiorna', + 'back' => '← Go back', //TODO - Translation 'back_to_rss_feeds' => '← Indietro', 'cancel' => 'Annulla', 'create' => 'Crea', @@ -22,6 +23,7 @@ return array( 'update' => 'Update', // TODO ), 'auth' => array( + 'accept_tos' => 'I accept the Terms of Service.', // TODO - Translation 'email' => 'Indirizzo email', 'keep_logged_in' => 'Ricorda i dati (%s giorni)', 'login' => 'Accedi', @@ -160,15 +162,22 @@ return array( 'nothing_to_load' => 'Non ci sono altri articoli', 'previous' => 'Precedente', ), + 'period' => array( + 'days' => 'days', //TODO - Translation + 'hours' => 'hours', //TODO - Translation + 'months' => 'months', //TODO - Translation + 'weeks' => 'weeks', //TODO - Translation + 'years' => 'years', //TODO - Translation + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', 'email' => 'Email', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'gnusocial' => 'GNU social', 'jdh' => 'Journal du hacker', 'Known' => 'Siti basati su Known', + 'lemmy' => 'Lemmy', 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', diff --git a/app/i18n/it/index.php b/app/i18n/it/index.php index 8162b1639..16c695a12 100644 --- a/app/i18n/it/index.php +++ b/app/i18n/it/index.php @@ -7,7 +7,7 @@ return array( 'bugs_reports' => 'Bugs', 'credits' => 'Crediti', 'credits_content' => 'Alcuni elementi di design provengono da Bootstrap sebbene FreshRSS non usi questo framework. Le icone provengono dal progetto GNOME. Il carattere Open Sans è stato creato da Steve Matteson. FreshRSS è basato su Minz, un framework PHP.', - 'freshrss_description' => 'FreshRSS è un aggregatore di feeds RSS da installare sul proprio host come Kriss Feed o Leed. Leggero e facile da mantenere pur essendo molto configurabile e potente.', + 'freshrss_description' => 'FreshRSS è un aggregatore di feeds RSS da installare sul proprio host come Kriss Feed o Leed. Leggero e facile da mantenere pur essendo molto configurabile e potente.', 'github' => 'su Github', 'license' => 'Licenza', 'project_website' => 'Sito del progetto', @@ -15,6 +15,9 @@ return array( 'version' => 'Versione', 'website' => 'Sito', ), + 'tos' => array( + 'title' => 'Terms of Service', // TODO - Translation + ), 'feed' => array( 'add' => 'Aggiungi un Feed RSS', 'empty' => 'Non ci sono articoli da mostrare.', diff --git a/app/i18n/it/sub.php b/app/i18n/it/sub.php index 50738d9e3..78db7b0a6 100644 --- a/app/i18n/it/sub.php +++ b/app/i18n/it/sub.php @@ -13,9 +13,12 @@ return array( 'category' => array( '_' => 'Categoria', 'add' => 'Aggiungi una categoria', + 'archiving' => 'Archiviazione', 'empty' => 'Categoria vuota', 'information' => 'Informazioni', 'new' => 'Nuova categoria', + 'position' => 'Display position', //TODO - Translation + 'position_help' => 'To control category sort order', //TODO - Translation 'title' => 'Titolo', ), 'feed' => array( @@ -40,7 +43,7 @@ return array( 'help' => 'Write one search filter per line.', //TODO - Translation ), 'information' => 'Informazioni', - 'keep_history' => 'Numero minimo di articoli da mantenere', + 'keep_min' => 'Numero minimo di articoli da mantenere', 'moved_category_deleted' => 'Cancellando una categoria i feed al suo interno verranno classificati automaticamente come %s.', 'mute' => 'mute', //TODO - Translation 'no_selected' => 'Nessun feed selezionato.', @@ -72,6 +75,7 @@ return array( ), 'firefox' => array( 'documentation' => 'Follow the steps described here to add FreshRSS to Firefox feed reader list.', //TODO - Translation + 'obsolete_63' => 'From version 63 and onwards, Firefox has removed the ability to add your own subscription services that are not standalone programs.', //TODO - Translation 'title' => 'Firefox feed reader', //TODO - Translation ), 'import_export' => array( diff --git a/app/i18n/it/user.php b/app/i18n/it/user.php new file mode 100644 index 000000000..3a8343c11 --- /dev/null +++ b/app/i18n/it/user.php @@ -0,0 +1,37 @@ + array( + 'feedback' => array( + 'invalid' => 'The email address is invalid.', //TODO - Translation + 'required' => 'The email address is required.', //TODO - Translation + ), + 'validation' => array( + 'change_email' => 'You can change your email address on the profile page.', //TODO - Translation + 'email_sent_to' => 'We sent you an email at %s, please follow its indications to validate your address.', //TODO - Translation + 'feedback' => array( + 'email_failed' => 'We couldn’t send you an email because of a misconfiguration of the server.', //TODO - Translation + 'email_sent' => 'An email has been sent to your address.', //TODO - Translation + 'error' => 'The email address failed to be validated.', //TODO - Translation + 'ok' => 'The email address has been validated.', //TODO - Translation + 'unneccessary' => 'The email address was already validated.', //TODO - Translation + 'wrong_token' => 'The email address failed to be validated due to a wrong token.', //TODO - Translation + ), + 'need_to' => 'You need to validate your email address before being able to use %s.', //TODO - Translation + 'resend_email' => 'Resend the email', //TODO - Translation + 'title' => 'Email address validation', //TODO - Translation + ), + ), + 'tos' => array( + 'feedback' => array( + 'invalid' => 'You must accept the Terms of Service to be able to register.', // TODO - Translation + ), + ), + 'mailer' => array( + 'email_need_validation' => array( + 'title' => 'You need to validate your account', //TODO - Translation + 'welcome' => 'Welcome %s,', //TODO - Translation + 'body' => 'You’ve just registered on %s but you still need to validate your email. For that, just follow the link:', //TODO - Translation + ), + ), +); diff --git a/app/i18n/kr/admin.php b/app/i18n/kr/admin.php index 6312bd3fe..4a8e425d5 100644 --- a/app/i18n/kr/admin.php +++ b/app/i18n/kr/admin.php @@ -159,6 +159,7 @@ return array( 'system' => array( '_' => '시스템 설정', 'auto-update-url' => '자동 업데이트 서버 URL', + 'force_email_validation' => 'Force email addresses validation', //TODO - Translation 'instance-name' => '인스턴스 이름', 'max-categories' => '사용자별 카테고리 개수 제한', 'max-feeds' => '사용자별 피드 개수 제한', diff --git a/app/i18n/kr/conf.php b/app/i18n/kr/conf.php index acd4c40c1..1e77d0098 100644 --- a/app/i18n/kr/conf.php +++ b/app/i18n/kr/conf.php @@ -3,13 +3,21 @@ return array( 'archiving' => array( '_' => '보관', - 'advanced' => '고급 설정', 'delete_after' => '다음 기간보다 오래된 글 삭제', + 'exception' => 'Purge exception', //TODO - Translation 'help' => '더 자세한 옵션은 개별 피드 설정에 있습니다', - 'keep_history_by_feed' => '피드별 최소 유지 글 개수', + 'keep_favourites' => 'Never delete favourites', //TODO - Translation + 'keep_min_by_feed' => '피드별 최소 유지 글 개수', + 'keep_labels' => 'Never delete labels', //TODO - Translation + 'keep_unreads' => 'Never delete unreads', //TODO - Translation + 'maintenance' => 'Maintenance', //TODO - Translation 'optimize' => '데이터베이스 최적화', 'optimize_help' => '데이터베이스 크기를 줄이기 위해 가끔씩 수행해주세요', + 'policy' => 'Purge policy', //TODO - Translation + 'policy_warning' => 'If no purge policy is selected, every article will be kept.', //TODO - Translation 'purge_now' => '지금 삭제', + 'keep_max' => 'Maximum number of articles to keep', //TODO - Translation + 'keep_period' => 'Maximum age of articles to keep', //TODO - Translation 'title' => '보관', 'ttl' => '다음 시간이 지나기 전에 새로고침 금지', ), @@ -21,6 +29,7 @@ return array( 'publication_date' => '발행일', 'related_tags' => '관련 태그', 'sharing' => '공유', + 'display_authors' => 'Authors', //TODO - Translation 'top_line' => '상단', ), 'language' => '언어', @@ -45,6 +54,7 @@ return array( '_' => '계정 삭제', 'warn' => '당신의 계정과 관련된 모든 데이터가 삭제됩니다.', ), + 'email' => '메일 주소', 'password_api' => 'API 암호
(예: 모바일 애플리케이션)', 'password_form' => '암호
(웹폼 로그인 방식 사용시)', 'password_format' => '7 글자 이상이어야 합니다', @@ -133,7 +143,6 @@ return array( 'diaspora' => 'Diaspora*', 'email' => '메일', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'more_information' => '자세한 정보', 'print' => '인쇄', 'remove' => '공유 방법 삭제', diff --git a/app/i18n/kr/gen.php b/app/i18n/kr/gen.php index f7855c499..fdc95d431 100644 --- a/app/i18n/kr/gen.php +++ b/app/i18n/kr/gen.php @@ -3,6 +3,7 @@ return array( 'action' => array( 'actualize' => '새 글 가져오기', + 'back' => '← Go back', //TODO - Translation 'back_to_rss_feeds' => '← RSS 피드로 돌아가기', 'cancel' => '취소', 'create' => '생성', @@ -22,6 +23,7 @@ return array( 'update' => '변경', ), 'auth' => array( + 'accept_tos' => 'I accept the Terms of Service.', // TODO - Translation 'email' => '메일 주소', 'keep_logged_in' => '로그인 유지 (%s 일)', 'login' => '로그인', @@ -160,15 +162,22 @@ return array( 'nothing_to_load' => '더 이상 글이 없습니다', 'previous' => '이전', ), + 'period' => array( + 'days' => 'days', //TODO - Translation + 'hours' => 'hours', //TODO - Translation + 'months' => 'months', //TODO - Translation + 'weeks' => 'weeks', //TODO - Translation + 'years' => 'years', //TODO - Translation + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', 'email' => '메일', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'gnusocial' => 'GNU social', 'jdh' => 'Journal du hacker', 'Known' => 'Known based sites', + 'lemmy' => 'Lemmy', 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', diff --git a/app/i18n/kr/index.php b/app/i18n/kr/index.php index bebc8bdec..6e582d906 100644 --- a/app/i18n/kr/index.php +++ b/app/i18n/kr/index.php @@ -7,7 +7,7 @@ return array( 'bugs_reports' => '버그 제보하기', 'credits' => '크레딧', 'credits_content' => 'FreshRSS는 Bootstrap 프레임워크를 사용하진 않지만, 일부 디자인 요소를 가져왔습니다. 아이콘들GNOME 프로젝트에서 가져왔습니다. Open Sans 글꼴은 Steve Matteson가 제작하였습니다. FreshRSS는 PHP 프레임워크인 Minz에 기반하고 있습니다.', - 'freshrss_description' => 'FreshRSS는 Kriss Feed 또는 Leed와 같은 셀프 호스팅 기반의 RSS 피드 수집기입니다. FreshRSS는 강력하고 다양한 설정을 할 수 있으면서 도 가볍고 사용하기 쉽습니다.', + 'freshrss_description' => 'FreshRSS는 Kriss Feed 또는 Leed와 같은 셀프 호스팅 기반의 RSS 피드 수집기입니다. FreshRSS는 강력하고 다양한 설정을 할 수 있으면서 도 가볍고 사용하기 쉽습니다.', 'github' => 'Github 저장소에 제보', 'license' => '라이센스', 'project_website' => '프로젝트 웹사이트', @@ -15,6 +15,9 @@ return array( 'version' => '버전', 'website' => '웹사이트', ), + 'tos' => array( + 'title' => 'Terms of Service', // TODO - Translation + ), 'feed' => array( 'add' => '피드를 추가하세요.', 'empty' => '글이 없습니다.', diff --git a/app/i18n/kr/sub.php b/app/i18n/kr/sub.php index f8eccfa27..ac45e4e73 100644 --- a/app/i18n/kr/sub.php +++ b/app/i18n/kr/sub.php @@ -13,9 +13,12 @@ return array( 'category' => array( '_' => '카테고리', 'add' => '카테고리 추가', + 'archiving' => '보관', 'empty' => '빈 카테고리', 'information' => '정보', 'new' => '새 카테고리', + 'position' => 'Display position', //TODO - Translation + 'position_help' => 'To control category sort order', //TODO - Translation 'title' => '제목', ), 'feed' => array( @@ -40,7 +43,7 @@ return array( 'help' => 'Write one search filter per line.', //TODO - Translation ), 'information' => '정보', - 'keep_history' => '최소 유지 글 개수', + 'keep_min' => '최소 유지 글 개수', 'moved_category_deleted' => '카테고리를 삭제하면, 해당 카테고리 아래에 있던 피드들은 자동적으로 %s 아래로 분류됩니다.', 'mute' => '무기한 새로고침 금지', 'no_selected' => '선택된 피드가 없습니다.', @@ -72,6 +75,7 @@ return array( ), 'firefox' => array( 'documentation' => 'FreshRSS를 Firefox 피드 리더에 추가하기 위해서는 여기의 설명을 따르세요.', + 'obsolete_63' => 'From version 63 and onwards, Firefox has removed the ability to add your own subscription services that are not standalone programs.', //TODO - Translation 'title' => 'Firefox 피드 리더', ), 'import_export' => array( diff --git a/app/i18n/kr/user.php b/app/i18n/kr/user.php new file mode 100644 index 000000000..3a8343c11 --- /dev/null +++ b/app/i18n/kr/user.php @@ -0,0 +1,37 @@ + array( + 'feedback' => array( + 'invalid' => 'The email address is invalid.', //TODO - Translation + 'required' => 'The email address is required.', //TODO - Translation + ), + 'validation' => array( + 'change_email' => 'You can change your email address on the profile page.', //TODO - Translation + 'email_sent_to' => 'We sent you an email at %s, please follow its indications to validate your address.', //TODO - Translation + 'feedback' => array( + 'email_failed' => 'We couldn’t send you an email because of a misconfiguration of the server.', //TODO - Translation + 'email_sent' => 'An email has been sent to your address.', //TODO - Translation + 'error' => 'The email address failed to be validated.', //TODO - Translation + 'ok' => 'The email address has been validated.', //TODO - Translation + 'unneccessary' => 'The email address was already validated.', //TODO - Translation + 'wrong_token' => 'The email address failed to be validated due to a wrong token.', //TODO - Translation + ), + 'need_to' => 'You need to validate your email address before being able to use %s.', //TODO - Translation + 'resend_email' => 'Resend the email', //TODO - Translation + 'title' => 'Email address validation', //TODO - Translation + ), + ), + 'tos' => array( + 'feedback' => array( + 'invalid' => 'You must accept the Terms of Service to be able to register.', // TODO - Translation + ), + ), + 'mailer' => array( + 'email_need_validation' => array( + 'title' => 'You need to validate your account', //TODO - Translation + 'welcome' => 'Welcome %s,', //TODO - Translation + 'body' => 'You’ve just registered on %s but you still need to validate your email. For that, just follow the link:', //TODO - Translation + ), + ), +); diff --git a/app/i18n/nl/admin.php b/app/i18n/nl/admin.php index e5d126eb8..1083c630b 100644 --- a/app/i18n/nl/admin.php +++ b/app/i18n/nl/admin.php @@ -159,6 +159,7 @@ return array( 'system' => array( '_' => 'Systeem configuratie', 'auto-update-url' => 'Automatische update server URL', + 'force_email_validation' => 'Emailadresvalidatie forceren', 'instance-name' => 'Voorbeeld naam', 'max-categories' => 'Categorielimiet per gebruiker', 'max-feeds' => 'Feedlimiet per gebruiker', diff --git a/app/i18n/nl/conf.php b/app/i18n/nl/conf.php index fa84ae184..ca6627cbb 100644 --- a/app/i18n/nl/conf.php +++ b/app/i18n/nl/conf.php @@ -1,15 +1,23 @@ array( '_' => 'Archivering', - 'advanced' => 'Geavanceerd', 'delete_after' => 'Verwijder artikelen na', + 'exception' => 'Zuiveringsuitzondering', 'help' => 'Meer opties zijn beschikbaar in de persoonlijke stroom instellingen', - 'keep_history_by_feed' => 'Minimum aantal te behouden artikelen in de feed', - 'optimize' => 'Optimaliseer database', + 'keep_favourites' => 'Favorieten nooit verwijderen', + 'keep_min_by_feed' => 'Minimum aantal te behouden artikelen in de feed', + 'keep_labels' => 'Labels nooit verwijderen', + 'keep_unreads' => 'Ongelezen artikels nooit verwijderen', + 'maintenance' => 'Onderhoud', + 'optimize' => 'Database optimaliseren', 'optimize_help' => 'Doe dit zo af en toe om de omvang van de database te verkleinen', + 'policy' => 'Zuiveringsbeleid', + 'policy_warning' => 'Zonder zuiveringsbeleid wordt elk artikel bewaard.', 'purge_now' => 'Schoon nu op', + 'keep_max' => 'Maximaal aantal artikelen om te behouden', + 'keep_period' => 'Maximumleeftijd artikelen om te behouden', 'title' => 'Archivering', 'ttl' => 'Vernieuw niet automatisch meer dan', ), @@ -21,6 +29,7 @@ return array( 'publication_date' => 'Publicatie datum', 'related_tags' => 'Gerelateerde labels', 'sharing' => 'Delen', + 'display_authors' => 'Auteurs', 'top_line' => 'Bovenaan', ), 'language' => 'Taal', @@ -45,6 +54,7 @@ return array( '_' => 'Account verwijderen', 'warn' => 'Uw account en alle gerelateerde gegvens worden verwijderd.', ), + 'email' => 'Email adres', 'password_api' => 'Wachtwoord API
(e.g., voor mobiele apps)', 'password_form' => 'Wachtwoord
(voor de Web-formulier log in methode)', 'password_format' => 'Ten minste 7 tekens', @@ -133,7 +143,6 @@ return array( 'diaspora' => 'Diaspora*', 'email' => 'Email', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'more_information' => 'Meer informatie', 'print' => 'Afdrukken', 'remove' => 'Deelmethode verwijderen', diff --git a/app/i18n/nl/feedback.php b/app/i18n/nl/feedback.php index 25378360b..97e1a71b8 100644 --- a/app/i18n/nl/feedback.php +++ b/app/i18n/nl/feedback.php @@ -75,7 +75,7 @@ return array( ), 'feed' => array( 'actualized' => '%s vernieuwd', - 'actualizeds' => 'RSS feeds vernieuwd', + 'actualizeds' => 'RSS-feeds vernieuwd', 'added' => 'RSS feed %s toegevoegd', 'already_subscribed' => 'Al geabonneerd op %s', 'deleted' => 'Feed verwijderd', diff --git a/app/i18n/nl/gen.php b/app/i18n/nl/gen.php index bdf2e0abd..fdbb866fc 100644 --- a/app/i18n/nl/gen.php +++ b/app/i18n/nl/gen.php @@ -3,6 +3,7 @@ return array( 'action' => array( 'actualize' => 'Actualiseren', + 'back' => '← Terug', 'back_to_rss_feeds' => '← Ga terug naar je RSS feeds', 'cancel' => 'Annuleren', 'create' => 'Opslaan', @@ -22,6 +23,7 @@ return array( 'update' => 'Updaten', ), 'auth' => array( + 'accept_tos' => 'Ik accepteer de gebruiksvoorwaarden.', 'email' => 'Email adres', 'keep_logged_in' => 'Ingelogd blijven voor (%s dagen)', 'login' => 'Log in', @@ -160,25 +162,16 @@ return array( 'nothing_to_load' => 'Er zijn geen artikelen meer', 'previous' => 'Vorige', ), + 'period' => array( + 'days' => 'dagen', + 'hours' => 'uren', + 'months' => 'maanden', + 'weeks' => 'weken', + 'years' => 'jaren', + ), 'share' => array( - 'blogotext' => 'Blogotext', - 'diaspora' => 'Diaspora*', 'email' => 'Email', - 'facebook' => 'Facebook', - 'g+' => 'Google+', - 'gnusocial' => 'GNU social', - 'jdh' => 'Journal du hacker', - 'Known' => 'Known based sites', - 'linkedin' => 'LinkedIn', - 'mastodon' => 'Mastodon', - 'movim' => 'Movim', - 'pinboard' => 'Pinboard', - 'pocket' => 'Pocket', - 'print' => 'Print', - 'shaarli' => 'Shaarli', - 'twitter' => 'Twitter', - 'wallabag' => 'wallabag v1', - 'wallabagv2' => 'wallabag v2', + 'Known' => 'Known-gebaseerde sites', ), 'short' => array( 'attention' => 'Attentie!', diff --git a/app/i18n/nl/index.php b/app/i18n/nl/index.php index d202b812a..22720f927 100644 --- a/app/i18n/nl/index.php +++ b/app/i18n/nl/index.php @@ -7,19 +7,22 @@ return array( 'bugs_reports' => 'Rapporteer fouten', 'credits' => 'Waarderingen', 'credits_content' => 'Sommige ontwerp elementen komen van Bootstrap alhoewel FreshRSS dit raamwerk niet gebruikt. Pictogrammen komen van het GNOME project. De Open Sans font police is gemaakt door Steve Matteson. FreshRSS is gebaseerd op Minz, een PHP raamwerk. Nederlandse vertaling door Wanabo, NieuwsKop.be. Link naar de Nederlandse vertaling, FreshRSS-Dutch-translation.', - 'freshrss_description' => 'FreshRSS is een RSS feed aggregator om zelf te hosten zoals Kriss Feed of Leed. Het gebruikt weinig systeembronnen en is makkelijk te administreren terwijl het een krachtig en makkelijk te configureren programma is.', + 'freshrss_description' => 'FreshRSS is een RSS-feed aggregator om zelf te hosten, net als Kriss Feed of Leed. Het gebruikt weinig systeembronnen en is makkelijk te beheren terwijl het een krachtig en makkelijk te configureren programma is.', 'github' => 'op Github', - 'license' => 'License', - 'project_website' => 'Project website', + 'license' => 'Licentie', + 'project_website' => 'Projectwebsite', 'title' => 'Over', 'version' => 'Versie', 'website' => 'Website', ), + 'tos' => array( + 'title' => 'Gebruiksvoorwaarden', + ), 'feed' => array( 'add' => 'U kunt wat feeds toevoegen.', 'empty' => 'Er is geen artikel om te laten zien.', - 'rss_of' => 'RSS feed van %s', - 'title' => 'Overzicht RSS feeds', + 'rss_of' => 'RSS-feed van %s', + 'title' => 'Overzicht RSS-feeds', 'title_global' => 'Globale weergave', 'title_fav' => 'Uw favorieten', ), @@ -48,7 +51,7 @@ return array( 'queries' => 'Gebruikers queries', 'read' => 'Laat alleen gelezen zien', 'reader_view' => 'Lees modus', - 'rss_view' => 'RSS feed', + 'rss_view' => 'RSS-feed', 'search_short' => 'Zoeken', 'starred' => 'Laat alleen favorieten zien', 'stats' => 'Statistieken', diff --git a/app/i18n/nl/sub.php b/app/i18n/nl/sub.php index b59515f42..8a3af6064 100644 --- a/app/i18n/nl/sub.php +++ b/app/i18n/nl/sub.php @@ -13,34 +13,37 @@ return array( 'category' => array( '_' => 'Categorie', 'add' => 'Voeg categorie toe', + 'archiving' => 'Archiveren', 'empty' => 'Lege categorie', 'information' => 'Informatie', 'new' => 'Nieuwe categorie', + 'position' => 'Weergavepositie', + 'position_help' => 'Om de categorieweergave-sorteervolgorde te controleren', 'title' => 'Titel', ), 'feed' => array( - 'add' => 'Voeg een RSS feed toe', + 'add' => 'Voeg een RSS-feed toe', 'advanced' => 'Geavanceerd', 'archiving' => 'Archiveren', 'auth' => array( 'configuration' => 'Log in', - 'help' => 'Verbinding toestaan toegang te krijgen tot HTTP beveiligde RSS feeds', + 'help' => 'Verbinding toestaan toegang te krijgen tot HTTP beveiligde RSS-feeds', 'http' => 'HTTP Authenticatie', 'password' => 'HTTP wachtwoord', 'username' => 'HTTP gebruikers naam', ), 'clear_cache' => 'Cache altijd leegmaken', - 'css_help' => 'Haalt verstoorde RSS feeds op (attentie, heeft meer tijd nodig!)', - 'css_path' => 'Artikelen CSS pad op originele website', + 'css_help' => 'Haalt onvolledige RSS-feeds op (attentie, heeft meer tijd nodig!)', + 'css_path' => 'CSS-pad van artikelen op originele website', 'description' => 'Omschrijving', 'empty' => 'Deze feed is leeg. Controleer of deze nog actueel is.', 'error' => 'Deze feed heeft problemen. Verifieer a.u.b het doeladres en actualiseer het.', 'filteractions' => array( - '_' => 'Filter actions', //TODO - Translation - 'help' => 'Write one search filter per line.', //TODO - Translation + '_' => 'Filteracties', + 'help' => 'Voer één zoekfilter per lijn in.', ), 'information' => 'Informatie', - 'keep_history' => 'Minimum aantal artikelen om te houden', + 'keep_min' => 'Minimum aantal artikelen om te houden', 'moved_category_deleted' => 'Als u een categorie verwijderd, worden de feeds automatisch geclassificeerd onder %s.', 'mute' => 'demp', 'no_selected' => 'Geen feed geselecteerd.', @@ -64,14 +67,15 @@ return array( 'think_to_add' => 'Voeg wat feeds toe.', 'timeout' => 'Time-out in seconden', 'title' => 'Titel', - 'title_add' => 'Voeg een RSS feed toe', + 'title_add' => 'Voeg een RSS-feed toe', 'ttl' => 'Vernieuw automatisch niet vaker dan', - 'url' => 'Feed URL', + 'url' => 'Feed-url', 'validator' => 'Controleer de geldigheid van de feed', - 'website' => 'Website URL', + 'website' => 'Website-url', ), 'firefox' => array( 'documentation' => 'Volg de stappen die hier beschreven worden om FreshRSS aan de Firefox-nieuwslezerlijst toe te voegen.', + 'obsolete_63' => 'Vanaf versie 63 en nieuwer, heeft Firefox de mogelijkheid om zelf niewslezers toe te voegen verwijderd voor online diensten.', 'title' => 'Firefox-nieuwslezer', ), 'import_export' => array( diff --git a/app/i18n/nl/user.php b/app/i18n/nl/user.php new file mode 100644 index 000000000..f98a6b2fd --- /dev/null +++ b/app/i18n/nl/user.php @@ -0,0 +1,37 @@ + array( + 'feedback' => array( + 'invalid' => 'Het emailadres is niet geldig.', + 'required' => 'Het emailadres is vereist.', + ), + 'validation' => array( + 'change_email' => 'Het emailadres kan worden gewijzigd op de profielpagina.', + 'email_sent_to' => 'Er is een email verzonden naar %s. Volg de instructies om het emailadres te valideren.', + 'feedback' => array( + 'email_failed' => 'Er kon geen email worden verzonden vanwege een incorrecte configuratie van de server.', + 'email_sent' => 'Er is een email naar het adres verzonden.', + 'error' => 'Het emailadres kon niet worden gevalideerd.', + 'ok' => 'Het emailadres is gevalideerd.', + 'unneccessary' => 'Het emailadres is al eerder gevalideerd.', + 'wrong_token' => 'Het emailadres kon niet worden gevalideerd vanwege een fout token.', + ), + 'need_to' => 'Het emailadres %1 moet worden gevalideerd voordat het kan worden gebruikt.', + 'resend_email' => 'Email opnieuw sturen', + 'title' => 'Emailadresvalidatie', + ), + ), + 'tos' => array( + 'feedback' => array( + 'invalid' => 'De gebruiksvoorwaarden moeten worden geaccepteerd om te kunnen registeren.', + ), + ), + 'mailer' => array( + 'email_need_validation' => array( + 'title' => 'Je account moet worden gevalideerd', + 'welcome' => 'Welkom %s,', + 'body' => 'Je hebt je net geregistreerd op %s, maar je moet je email nog valideren. Volg daarvoor de link:', + ), + ), +); diff --git a/app/i18n/oc/admin.php b/app/i18n/oc/admin.php index 2f8ede873..1fb8d5c3a 100644 --- a/app/i18n/oc/admin.php +++ b/app/i18n/oc/admin.php @@ -163,6 +163,7 @@ return array( 'help' => 'en segondas', 'number' => 'Durada de téner d’ésser connectat', ), + 'force_email_validation' => 'Forçar la validacion de las adreças electronicas', 'instance-name' => 'Nom de l’instància', 'max-categories' => 'Limita de categoria per utilizaire', 'max-feeds' => 'Limita de fluxes per utilizaire', diff --git a/app/i18n/oc/conf.php b/app/i18n/oc/conf.php index 1596950ea..e123c03c5 100644 --- a/app/i18n/oc/conf.php +++ b/app/i18n/oc/conf.php @@ -2,15 +2,24 @@ return array( 'archiving' => array( - '_' => 'Archivar', + '_' => 'Archius', 'advanced' => 'Avançat', 'delete_after' => 'Levar los articles aprèp', + 'exception' => 'Excepcion de purga', 'help' => 'Mai d’opcions son disponiblas dins la configuracion individuala dels fluxes', - 'keep_history_by_feed' => 'Nombre minimum d’articles de servar per flux', + 'keep_favourites' => 'Jamai suprimir los favorits', + 'keep_min_by_feed' => 'Nombre minimum d’articles de servar per flux', + 'keep_labels' => 'Jamai suprimir las etiquetas', + 'keep_unreads' => 'Jamai suprimir los pas legits', + 'maintenance' => 'Entreten', 'optimize' => 'Optimizar la basa de donada', 'optimize_help' => 'De far de temps en temps per redusir la talha de la basa de donadas', + 'policy' => 'Politica de purga', + 'policy_warning' => 'Se cap de politica de purga es pas seleccionada, totes los articles seràn gardats', 'purge_now' => 'Purgar ara', - 'title' => 'Archivar', + 'keep_max' => 'Nombre maximum d’articles de gardar', + 'keep_period' => 'Atge maximum dels articles de gardar', + 'title' => 'Archius', 'ttl' => 'Actualizar pas automaticament mai sovent que', ), 'display' => array( @@ -21,6 +30,7 @@ return array( 'publication_date' => 'Data de publicacion', 'related_tags' => 'Etiquetas ligadas', 'sharing' => 'Partatge', + 'display_authors' => 'Autors', 'top_line' => 'Linha amont', ), 'language' => 'Lenga', @@ -45,6 +55,7 @@ return array( '_' => 'Supression del compte', 'warn' => 'Lo compte e totas las donadas ligadas seràn suprimits.', ), + 'email' => 'Adreça de corrièl', 'password_api' => 'Senhal API
(ex. : per las aplicacions mobil)', 'password_form' => 'Senhal API
(ex. : per la connexion via formulari)', 'password_format' => 'Almens 7 caractèrs', @@ -133,7 +144,6 @@ return array( 'diaspora' => 'Diaspora*', 'email' => 'Corrièl', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'more_information' => 'Mai d’informacions', 'print' => 'Imprimir', 'remove' => 'Suprimir lo metòde de partatge', diff --git a/app/i18n/oc/gen.php b/app/i18n/oc/gen.php index 7f9793283..a5bd003c2 100644 --- a/app/i18n/oc/gen.php +++ b/app/i18n/oc/gen.php @@ -3,6 +3,7 @@ return array( 'action' => array( 'actualize' => 'Actualizar', + 'back' => '← Tornar', 'back_to_rss_feeds' => '← Tornar a vòstres fluxes RSS', 'cancel' => 'Anullar', 'create' => 'Crear', @@ -22,6 +23,7 @@ return array( 'update' => 'Actualizar', ), 'auth' => array( + 'accept_tos' => 'Accepti las condicions d’utilizacion.', 'email' => 'Adreça de corrièl', 'keep_logged_in' => 'Demorar connectat (%s jorns) ', 'login' => 'Connexion', @@ -49,7 +51,7 @@ return array( 'Aug' => '\\a\\g\\o\\s\\t', 'aug' => 'agost', 'august' => 'agost', - 'before_yesterday' => 'Abans ièr', + 'before_yesterday' => 'Anterior a ièr', 'Dec' => '\\d\\e\\c\\e\\m\\b\\r\\e', 'dec' => 'dec.', 'december' => 'decembre', @@ -160,15 +162,22 @@ return array( 'nothing_to_load' => 'I a pas mai d’articles', 'previous' => 'Precedent', ), + 'period' => array( + 'days' => 'jorns', + 'hours' => 'oras', + 'months' => 'meses', + 'weeks' => 'setmanas', + 'years' => 'ans', + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', 'email' => 'Corrièl', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'gnusocial' => 'GNU social', 'jdh' => 'Journal du hacker', 'Known' => 'Sites basats sus Known', + 'lemmy' => 'Lemmy', 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', diff --git a/app/i18n/oc/index.php b/app/i18n/oc/index.php index 5cc71c9a9..763d24139 100644 --- a/app/i18n/oc/index.php +++ b/app/i18n/oc/index.php @@ -7,7 +7,7 @@ return array( 'bugs_reports' => 'Senhalament de problèmas', 'credits' => 'Crèdits', 'credits_content' => 'Unes elements de l’estil venon del projècte Bootstrap encara que FreshRSS utilize pas aqueste framework. Las icònas venon del projècte GNOME. La polissa Open Sans utilizada foguèt creada per en Steve Matteson. FreshRSS es basat sus Minz, un framework PHP.', - 'freshrss_description' => 'FreshRSS es un agregador de fluxes RSS per l’auto-albergar tal coma Kriss Feed o Leed. Sa tòca es d’èsser leugièr e de bon utilizar de prima abòrd mas tanben d’èsser potent e parametrable.', + 'freshrss_description' => 'FreshRSS es un agregador de fluxes RSS per l’auto-albergar tal coma Kriss Feed o Leed. Sa tòca es d’èsser leugièr e de bon utilizar de prima abòrd mas tanben d’èsser potent e parametrable.', 'github' => 'on Github', 'license' => 'Licéncia', 'project_website' => 'Site del projècte', @@ -15,6 +15,9 @@ return array( 'website' => 'Site internet', 'version' => 'Version', ), + 'tos' => array( + 'title' => 'Condicions d’utilizacion', + ), 'feed' => array( 'add' => 'Podètz ajustar de fluxes.', 'empty' => 'I a pas cap de flux de mostrar.', diff --git a/app/i18n/oc/sub.php b/app/i18n/oc/sub.php index eae9dff29..98a7521eb 100644 --- a/app/i18n/oc/sub.php +++ b/app/i18n/oc/sub.php @@ -12,9 +12,12 @@ return array( 'category' => array( '_' => 'Categoria', 'add' => 'Ajustar una categoria', + 'archiving' => 'Archivar', 'empty' => 'Categoria voida', 'information' => 'Informacions', 'new' => 'Nòva categoria', + 'position' => 'Mostrar la posicion', + 'position_help' => 'Per contrarotlar l’òrdre de tria de la categoria', 'title' => 'Títol', ), 'feed' => array( @@ -39,7 +42,7 @@ return array( 'help' => 'Escrivètz una recèrca per linha.', ), 'information' => 'Informacions', - 'keep_history' => 'Nombre minimum d’articles de servar', + 'keep_min' => 'Nombre minimum d’articles de servar', 'moved_category_deleted' => 'Quand escafatz una categoria, sos fluxes son automaticament classats dins %s.', 'mute' => 'mut', 'no_selected' => 'Cap de flux pas seleccionat.', @@ -71,6 +74,7 @@ return array( ), 'firefox' => array( 'documentation' => 'Seguissètz las etapas descrichas aquí per ajustar FreshRSS a la lista dels lectors de flux de Firefox.', + 'obsolete_63' => 'A partir de la version 63 e las seguentas, Firefox permet pas mai d’ajustar vòstres pròpris servicis d’abonament.', 'title' => 'Lector de flux de Firefox', ), 'import_export' => array( diff --git a/app/i18n/oc/user.php b/app/i18n/oc/user.php new file mode 100644 index 000000000..655aa052c --- /dev/null +++ b/app/i18n/oc/user.php @@ -0,0 +1,37 @@ + array( + 'feedback' => array( + 'invalid' => 'L’adreça electronica es invalida.', + 'required' => 'L’adreça electronica es requesida.', + ), + 'validation' => array( + 'change_email' => 'Podètz cambiar l’adreça electronica sus la pagina de perfil.', + 'email_sent_to' => 'Vos avèm enviat un corrièl a %s, mercés de seguir las consignas per validar l’adreça electronica.', + 'feedback' => array( + 'email_failed' => 'Avèm pas pogut vos enviar un corrièl a causa d’una marrida configuracion del servidor.', + 'email_sent' => 'Avèm enviat un corrièl a vòstra adreça.', + 'error' => 'Fracàs de la validacion de l’adreça electronica.', + 'ok' => 'L’adreça electronica es estada validada.', + 'unneccessary' => 'L’adreça es ja estada validada.', + 'wrong_token' => 'Fracàs de la validacion de l’adreça a causa d’un marrit geton.', + ), + 'need_to' => 'Devèètz validar vòstra adreça electronica abans de poder utilizar %s.', + 'resend_email' => 'Tornar enviar lo corrièl', + 'title' => 'Validacion de l’adreça electronica', + ), + ), + 'tos' => array( + 'feedback' => array( + 'invalid' => 'Vos cal acceptar las condicions d’utilizacion per poder vos inscriure.', + ), + ), + 'mailer' => array( + 'email_need_validation' => array( + 'title' => 'Vos cal validar vòstra adreça electronica', + 'welcome' => 'La benvenguda %s,', + 'body' => 'Venètz de vos marcar sus %s mas vos cal encara validar l’adreça electronica. Per aquò far, seguissètz lo ligam :', + ), + ), +); diff --git a/app/i18n/pt-br/admin.php b/app/i18n/pt-br/admin.php index 82559c67b..cef6694c2 100644 --- a/app/i18n/pt-br/admin.php +++ b/app/i18n/pt-br/admin.php @@ -159,6 +159,7 @@ return array( 'system' => array( '_' => 'Configuração do sistema', 'auto-update-url' => 'URL do servidor para atualização automática', + 'force_email_validation' => 'Force email addresses validation', //TODO - Translation 'instance-name' => 'Nome da instância', 'max-categories' => 'Limite de categorias por usuário', 'max-feeds' => 'Limite de Feeds por usuário', diff --git a/app/i18n/pt-br/conf.php b/app/i18n/pt-br/conf.php index 8f5eb7746..5e43cc373 100644 --- a/app/i18n/pt-br/conf.php +++ b/app/i18n/pt-br/conf.php @@ -3,13 +3,21 @@ return array( 'archiving' => array( '_' => 'Arquivar', - 'advanced' => 'Avançado', 'delete_after' => 'Remover artigos depois', + 'exception' => 'Purge exception', //TODO - Translation 'help' => 'Mais opções estão disponíveis nas configurações individuais do feed', - 'keep_history_by_feed' => 'Número mínimo de artigos para deixar no feed', + 'keep_favourites' => 'Never delete favourites', //TODO - Translation + 'keep_min_by_feed' => 'Número mínimo de artigos para deixar no feed', + 'keep_labels' => 'Never delete labels', //TODO - Translation + 'keep_unreads' => 'Never delete unreads', //TODO - Translation + 'maintenance' => 'Maintenance', //TODO - Translation 'optimize' => 'Otimizar banco de dados', 'optimize_help' => 'Faça ocasionalmente para reduzir o tamanho do banco de dados', + 'policy' => 'Purge policy', //TODO - Translation + 'policy_warning' => 'If no purge policy is selected, every article will be kept.', //TODO - Translation 'purge_now' => 'Purge agora', + 'keep_max' => 'Maximum number of articles to keep', //TODO - Translation + 'keep_period' => 'Maximum age of articles to keep', //TODO - Translation 'title' => 'Arquivar', 'ttl' => 'Não atualize automaticamente mais frequente que', ), @@ -21,6 +29,7 @@ return array( 'publication_date' => 'Data da publicação', 'related_tags' => 'Tags relacionadas', //TODO - Translation 'sharing' => 'Compartilhar', + 'display_authors' => 'Authors', //TODO - Translation 'top_line' => 'Linha superior', ), 'language' => 'Ídioma', @@ -45,6 +54,7 @@ return array( '_' => 'Remover conta', 'warn' => 'Sua conta e todos os dados relacionados serão removidos.', ), + 'email' => 'Endereço de e-mail', 'password_api' => 'Senha da API
(p.s., para aplicativos móveis)', 'password_form' => 'Senha
(para o método de formulário web)', 'password_format' => 'Ao menos 7 caracteres', @@ -133,7 +143,6 @@ return array( 'diaspora' => 'Diaspora*', 'email' => 'Email', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'more_information' => 'Mais informação', 'print' => 'Imprimir', 'remove' => 'Remove sharing method', //TODO - Translation diff --git a/app/i18n/pt-br/gen.php b/app/i18n/pt-br/gen.php index 46ae53eb4..0e7f367ee 100644 --- a/app/i18n/pt-br/gen.php +++ b/app/i18n/pt-br/gen.php @@ -3,6 +3,7 @@ return array( 'action' => array( 'actualize' => 'Atualizar', + 'back' => '← Go back', //TODO - Translation 'back_to_rss_feeds' => '← Volte para o seu feeds RSS', 'cancel' => 'Cancelar', 'create' => 'Criar', @@ -22,6 +23,7 @@ return array( 'update' => 'Update', //TODO - Translation ), 'auth' => array( + 'accept_tos' => 'I accept the Terms of Service.', // TODO - Translation 'email' => 'Endereço de e-mail', 'keep_logged_in' => 'Mantenha logado por (%s days)', 'login' => 'Login', @@ -160,15 +162,22 @@ return array( 'nothing_to_load' => 'Não há mais artigos', 'previous' => 'Anterior', ), + 'period' => array( + 'days' => 'days', //TODO - Translation + 'hours' => 'hours', //TODO - Translation + 'months' => 'months', //TODO - Translation + 'weeks' => 'weeks', //TODO - Translation + 'years' => 'years', //TODO - Translation + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', 'email' => 'Email', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'gnusocial' => 'GNU social', 'jdh' => 'Journal du hacker', 'Known' => 'Known based sites', + 'lemmy' => 'Lemmy', 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', diff --git a/app/i18n/pt-br/index.php b/app/i18n/pt-br/index.php index e5807ed95..fac17e171 100644 --- a/app/i18n/pt-br/index.php +++ b/app/i18n/pt-br/index.php @@ -7,7 +7,7 @@ return array( 'bugs_reports' => 'Reportar Bugs', 'credits' => 'Créditos', 'credits_content' => 'Alguns elementos de design vieram do Bootstrap Embora FreshRRS não utiliza este framework. Ícones vieram do GNOME project. Open Sans font police foi criada por Steve Matteson. FreshRSS é baseado no Minz, um framework PHP.', - 'freshrss_description' => 'FreshRSS é um RSS feeds aggregator para um host próprio como o Kriss Feed ou Leed. É leve e fácil de utilizar enquanto é uma ferramenta poderosa e configurável. ', + 'freshrss_description' => 'FreshRSS é um RSS feeds aggregator para um host próprio como o Kriss Feed ou Leed. É leve e fácil de utilizar enquanto é uma ferramenta poderosa e configurável. ', 'github' => 'no Github', 'license' => 'licença', 'project_website' => 'Site do projeto', @@ -15,6 +15,9 @@ return array( 'version' => 'Versão', 'website' => 'Site', ), + 'tos' => array( + 'title' => 'Terms of Service', // TODO - Translation + ), 'feed' => array( 'add' => 'Você pode adicionar alguns feeds.', 'empty' => 'Não há nenhum artigo para mostrar.', diff --git a/app/i18n/pt-br/sub.php b/app/i18n/pt-br/sub.php index d4bea33c4..04e0c85ab 100644 --- a/app/i18n/pt-br/sub.php +++ b/app/i18n/pt-br/sub.php @@ -13,9 +13,12 @@ return array( 'category' => array( '_' => 'Categoria', 'add' => 'Adicionar uma categoria', + 'archiving' => 'Arquivar', 'empty' => 'Categoria vazia', 'information' => 'Informações', 'new' => 'Nova categoria', + 'position' => 'Display position', //TODO - Translation + 'position_help' => 'To control category sort order', //TODO - Translation 'title' => 'Título', ), 'feed' => array( @@ -40,7 +43,7 @@ return array( 'help' => 'Write one search filter per line.', //TODO - Translation ), 'information' => 'Informações', - 'keep_history' => 'Número mínimo de artigos para manter', + 'keep_min' => 'Número mínimo de artigos para manter', 'moved_category_deleted' => 'Quando você deleta uma categoria, seus feeds são automaticamente classificados como %s.', 'mute' => 'mute', //TODO - Translation 'no_selected' => 'Nenhum feed selecionado.', @@ -70,6 +73,11 @@ return array( 'validator' => 'Verifique a validade do feed', 'website' => 'URL do site', ), + 'firefox' => array( + 'documentation' => 'Follow the steps described here to add FreshRSS to Firefox feed reader list.',// TODO + 'obsolete_63' => 'From version 63 and onwards, Firefox has removed the ability to add your own subscription services that are not standalone programs.', //TODO - Translation + 'title' => 'Firefox feed reader', //TODO - Translation + ), 'import_export' => array( 'export' => 'Exportar', 'export_opml' => 'Exporta a lista dos feeds (OPML)', diff --git a/app/i18n/pt-br/user.php b/app/i18n/pt-br/user.php new file mode 100644 index 000000000..3a8343c11 --- /dev/null +++ b/app/i18n/pt-br/user.php @@ -0,0 +1,37 @@ + array( + 'feedback' => array( + 'invalid' => 'The email address is invalid.', //TODO - Translation + 'required' => 'The email address is required.', //TODO - Translation + ), + 'validation' => array( + 'change_email' => 'You can change your email address on the profile page.', //TODO - Translation + 'email_sent_to' => 'We sent you an email at %s, please follow its indications to validate your address.', //TODO - Translation + 'feedback' => array( + 'email_failed' => 'We couldn’t send you an email because of a misconfiguration of the server.', //TODO - Translation + 'email_sent' => 'An email has been sent to your address.', //TODO - Translation + 'error' => 'The email address failed to be validated.', //TODO - Translation + 'ok' => 'The email address has been validated.', //TODO - Translation + 'unneccessary' => 'The email address was already validated.', //TODO - Translation + 'wrong_token' => 'The email address failed to be validated due to a wrong token.', //TODO - Translation + ), + 'need_to' => 'You need to validate your email address before being able to use %s.', //TODO - Translation + 'resend_email' => 'Resend the email', //TODO - Translation + 'title' => 'Email address validation', //TODO - Translation + ), + ), + 'tos' => array( + 'feedback' => array( + 'invalid' => 'You must accept the Terms of Service to be able to register.', // TODO - Translation + ), + ), + 'mailer' => array( + 'email_need_validation' => array( + 'title' => 'You need to validate your account', //TODO - Translation + 'welcome' => 'Welcome %s,', //TODO - Translation + 'body' => 'You’ve just registered on %s but you still need to validate your email. For that, just follow the link:', //TODO - Translation + ), + ), +); diff --git a/app/i18n/ru/admin.php b/app/i18n/ru/admin.php index c9a7d6683..adf091df9 100644 --- a/app/i18n/ru/admin.php +++ b/app/i18n/ru/admin.php @@ -159,6 +159,7 @@ return array( 'system' => array( '_' => 'Системные настройки', 'auto-update-url' => 'Адрес сервера для автоматического обновления', + 'force_email_validation' => 'Force email addresses validation', //TODO - Translation 'instance-name' => 'Название этого сервера', 'max-categories' => 'Количество категорий на пользователя', 'max-feeds' => 'Количество статей на пользователя', diff --git a/app/i18n/ru/conf.php b/app/i18n/ru/conf.php index 841477964..7a80587f8 100644 --- a/app/i18n/ru/conf.php +++ b/app/i18n/ru/conf.php @@ -3,13 +3,21 @@ return array( 'archiving' => array( '_' => 'Архивация', - 'advanced' => 'Продвинутые настройки', 'delete_after' => 'Удалять статьи после', + 'exception' => 'Purge exception', //TODO - Translation 'help' => 'Каждую подписку можно настроить более гибко', - 'keep_history_by_feed' => 'Minimum number of articles to keep by feed', //TODO - Translation + 'keep_favourites' => 'Never delete favourites', //TODO - Translation + 'keep_min_by_feed' => 'Minimum number of articles to keep by feed', //TODO - Translation + 'keep_labels' => 'Never delete labels', //TODO - Translation + 'keep_unreads' => 'Never delete unreads', //TODO - Translation + 'maintenance' => 'Maintenance', //TODO - Translation 'optimize' => 'Оптимизировать базу данных', - 'optimize_help' => 'To do occasionally to reduce the size of the database', //TODO - Translation + 'optimize_help' => 'To do occasionally to reduce the size of the database', //TODO - Translation + 'policy' => 'Purge policy', //TODO - Translation + 'policy_warning' => 'If no purge policy is selected, every article will be kept.', //TODO - Translation 'purge_now' => 'Очистить сейчас', + 'keep_max' => 'Maximum number of articles to keep', //TODO - Translation + 'keep_period' => 'Maximum age of articles to keep', //TODO - Translation 'title' => 'Архивация', 'ttl' => 'Не обновлять чаще чем', ), @@ -21,6 +29,7 @@ return array( 'publication_date' => 'Date of publication', //TODO - Translation 'related_tags' => 'Related tags', //TODO - Translation 'sharing' => 'Sharing', //TODO - Translation + 'display_authors' => 'Authors', //TODO - Translation 'top_line' => 'Top line', //TODO - Translation ), 'language' => 'Язык', @@ -45,6 +54,7 @@ return array( '_' => 'Account deletion', //TODO - Translation 'warn' => 'Your account and all the related data will be deleted.', //TODO - Translation ), + 'email' => 'Email address', //TODO - Translation 'password_api' => 'Password API
(e.g., for mobile apps)', //TODO - Translation 'password_form' => 'Password
(for the Web-form login method)', //TODO - Translation 'password_format' => 'At least 7 characters', //TODO - Translation @@ -133,7 +143,6 @@ return array( 'diaspora' => 'Diaspora*', 'email' => 'Email', //TODO - Translation 'facebook' => 'Facebook', - 'g+' => 'Google+', 'more_information' => 'More information', //TODO - Translation 'print' => 'Print', //TODO - Translation 'remove' => 'Remove sharing method', //TODO - Translation diff --git a/app/i18n/ru/gen.php b/app/i18n/ru/gen.php index b55c6b667..5200a7005 100644 --- a/app/i18n/ru/gen.php +++ b/app/i18n/ru/gen.php @@ -3,6 +3,7 @@ return array( 'action' => array( 'actualize' => 'Actualize', //TODO - Translation + 'back' => '← Go back', //TODO - Translation 'back_to_rss_feeds' => '← Go back to your RSS feeds', //TODO - Translation 'cancel' => 'Cancel', //TODO - Translation 'create' => 'Create', //TODO - Translation @@ -22,6 +23,7 @@ return array( 'update' => 'Update', //TODO - Translation ), 'auth' => array( + 'accept_tos' => 'I accept the Terms of Service.', // TODO - Translation 'email' => 'Email address', //TODO - Translation 'keep_logged_in' => 'Keep me logged in (%s дней)', //TODO - Translation 'login' => 'Login', //TODO - Translation @@ -160,15 +162,22 @@ return array( 'nothing_to_load' => 'There are no more articles', //TODO - Translation 'previous' => 'Previous', //TODO - Translation ), + 'period' => array( + 'days' => 'days', //TODO - Translation + 'hours' => 'hours', //TODO - Translation + 'months' => 'months', //TODO - Translation + 'weeks' => 'weeks', //TODO - Translation + 'years' => 'years', //TODO - Translation + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', 'email' => 'Email', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'gnusocial' => 'GNU social', 'jdh' => 'Journal du hacker', 'Known' => 'Known based sites', + 'lemmy' => 'Lemmy', 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', diff --git a/app/i18n/ru/index.php b/app/i18n/ru/index.php index 977777178..b5f022cd2 100644 --- a/app/i18n/ru/index.php +++ b/app/i18n/ru/index.php @@ -7,7 +7,7 @@ return array( 'bugs_reports' => 'Bugs reports', //TODO - Translation 'credits' => 'Credits', //TODO - Translation 'credits_content' => 'Some design elements come from Bootstrap although FreshRSS doesn’t use this framework. Icons come from GNOME project. Open Sans font police has been created by Steve Matteson. FreshRSS is based on Minz, a PHP framework.', //TODO - Translation - 'freshrss_description' => 'FreshRSS is a RSS feeds aggregator to self-host like Kriss Feed or Leed. It is light and easy to take in hand while being powerful and configurable tool.', //TODO - Translation + 'freshrss_description' => 'FreshRSS is a RSS feeds aggregator to self-host like Kriss Feed or Leed. It is light and easy to take in hand while being powerful and configurable tool.', //TODO - Translation 'github' => 'on Github', //TODO - Translation 'license' => 'License', //TODO - Translation 'project_website' => 'Project website', //TODO - Translation @@ -15,6 +15,9 @@ return array( 'version' => 'Version', //TODO - Translation 'website' => 'Website', //TODO - Translation ), + 'tos' => array( + 'title' => 'Terms of Service', // TODO - Translation + ), 'feed' => array( 'add' => 'You may add some feeds.', //TODO - Translation 'empty' => 'There is no article to show.', //TODO - Translation diff --git a/app/i18n/ru/sub.php b/app/i18n/ru/sub.php index a2c4e4690..e8cdeb89d 100644 --- a/app/i18n/ru/sub.php +++ b/app/i18n/ru/sub.php @@ -13,9 +13,12 @@ return array( 'category' => array( '_' => 'Category', //TODO - Translation 'add' => 'Add a category', //TODO - Translation + 'archiving' => 'Archivage', //TODO - Translation 'empty' => 'Empty category', //TODO - Translation 'information' => 'Information', //TODO - Translation 'new' => 'New category', //TODO - Translation + 'position' => 'Display position', //TODO - Translation + 'position_help' => 'To control category sort order', //TODO - Translation 'title' => 'Title', //TODO - Translation ), 'feed' => array( @@ -40,7 +43,7 @@ return array( 'help' => 'Write one search filter per line.', //TODO - Translation ), 'information' => 'Information', //TODO - Translation - 'keep_history' => 'Minimum number of articles to keep', //TODO - Translation + 'keep_min' => 'Minimum number of articles to keep', //TODO - Translation 'moved_category_deleted' => 'When you delete a category, its feeds are automatically classified under %s.', //TODO - Translation 'mute' => 'mute', //TODO - Translation 'no_selected' => 'No feed selected.', //TODO - Translation @@ -72,6 +75,7 @@ return array( ), 'firefox' => array( 'documentation' => 'Follow the steps described here to add FreshRSS to Firefox feed reader list.', //TODO - Translation + 'obsolete_63' => 'From version 63 and onwards, Firefox has removed the ability to add your own subscription services that are not standalone programs.', //TODO - Translation 'title' => 'Firefox feed reader', //TODO - Translation ), 'import_export' => array( diff --git a/app/i18n/ru/user.php b/app/i18n/ru/user.php new file mode 100644 index 000000000..3a8343c11 --- /dev/null +++ b/app/i18n/ru/user.php @@ -0,0 +1,37 @@ + array( + 'feedback' => array( + 'invalid' => 'The email address is invalid.', //TODO - Translation + 'required' => 'The email address is required.', //TODO - Translation + ), + 'validation' => array( + 'change_email' => 'You can change your email address on the profile page.', //TODO - Translation + 'email_sent_to' => 'We sent you an email at %s, please follow its indications to validate your address.', //TODO - Translation + 'feedback' => array( + 'email_failed' => 'We couldn’t send you an email because of a misconfiguration of the server.', //TODO - Translation + 'email_sent' => 'An email has been sent to your address.', //TODO - Translation + 'error' => 'The email address failed to be validated.', //TODO - Translation + 'ok' => 'The email address has been validated.', //TODO - Translation + 'unneccessary' => 'The email address was already validated.', //TODO - Translation + 'wrong_token' => 'The email address failed to be validated due to a wrong token.', //TODO - Translation + ), + 'need_to' => 'You need to validate your email address before being able to use %s.', //TODO - Translation + 'resend_email' => 'Resend the email', //TODO - Translation + 'title' => 'Email address validation', //TODO - Translation + ), + ), + 'tos' => array( + 'feedback' => array( + 'invalid' => 'You must accept the Terms of Service to be able to register.', // TODO - Translation + ), + ), + 'mailer' => array( + 'email_need_validation' => array( + 'title' => 'You need to validate your account', //TODO - Translation + 'welcome' => 'Welcome %s,', //TODO - Translation + 'body' => 'You’ve just registered on %s but you still need to validate your email. For that, just follow the link:', //TODO - Translation + ), + ), +); diff --git a/app/i18n/sk/admin.php b/app/i18n/sk/admin.php new file mode 100644 index 000000000..347204f37 --- /dev/null +++ b/app/i18n/sk/admin.php @@ -0,0 +1,199 @@ + array( + 'allow_anonymous' => 'Povoliť čítanie článkov prednastaveného používateľa (%s) bez prihlásenia.', + 'allow_anonymous_refresh' => 'Povoliť obnovenie článkov bez prihlásenia', + 'api_enabled' => 'Povoliť prístup cez API (vyžadujú mobilné aplikácie)', + 'form' => 'Webový formulár (traditičný, vyžaduje JavaScript)', + 'http' => 'HTTP (pre pokročilých používateľov s HTTPS)', + 'none' => 'Žiadny (nebezpečné)', + 'title' => 'Prihlásenie', + 'title_reset' => 'Reset prihlásenia', + 'token' => 'Token prihlásenia', + 'token_help' => 'Povoliť prístup k výstupu RSS prednastaveného používateľa bez prihlásenia:', + 'type' => 'Spôsob prihlásenia', + 'unsafe_autologin' => 'Povoliť nebezpečné automatické prihlásenie pomocou webového formulára: ', + ), + 'check_install' => array( + 'cache' => array( + 'nok' => 'Overte prístupové práva priečinka ./data/cache. HTTP server musí mať právo doň zapisovať.', + 'ok' => 'Prístupové práva priečinka pre vyrovnávaciu pamäť sú OK.', + ), + 'categories' => array( + 'nok' => 'Tabuľka kategórií je nesprávne nastavená.', + 'ok' => 'Tabuľka kategórií je OK.', + ), + 'connection' => array( + 'nok' => 'Nepodarilo sa vytvoriť pripojenie k databáze.', + 'ok' => 'Pripojenie k databáze je OK.', + ), + 'ctype' => array( + 'nok' => 'Nepodarilo sa nájsť požadovanú knižnicu na kontrolu typu znakov (php-ctype).', + 'ok' => 'Našla sa požadovaná knižnica na kontrolu typu znakov (ctype).', + ), + 'curl' => array( + 'nok' => 'Nepodarilo sa nájsť knižnicu cURL (balík php-curl).', + 'ok' => 'Našla sa knižnica cURL.', + ), + 'data' => array( + 'nok' => 'Skontrolujte oprávnenia prístupu do priečinku ./data. HTTP server musí mať právo doň zapisovať.', + 'ok' => 'Oprávnenia prístupu do priečinku údajov sú OK.', + ), + 'database' => 'Inštalácia databázy', + 'dom' => array( + 'nok' => 'Nepodarilo sa nájsť požadovanú knižnicu na prehliadanie DOM.', + 'ok' => 'Našla sa požadovaná knižnica na prehliadanie DOM.', + ), + 'entries' => array( + 'nok' => 'Tabuľka článkov je nesprávne nastavená.', + 'ok' => 'Tabuľka článkov je OK.', + ), + 'favicons' => array( + 'nok' => 'Skontrolujte oprávnenia prístupu do priečinku ./data/favicons. HTTP server musí mať právo doň zapisovať.', + 'ok' => 'Oprávnenia prístupu do priečinku ikôn obľúbených sú OK.', + ), + 'feeds' => array( + 'nok' => 'Tabuľka kanálov je nesprávne nastavená.', + 'ok' => 'Tabuľka kanálov je OK.', + ), + 'fileinfo' => array( + 'nok' => 'Nepodarilo sa nájsť knižniuc PHP fileinfo (balík fileinfo).', + 'ok' => 'Našla sa knižnica fileinfo.', + ), + 'files' => 'Inštalácia súborov', + 'json' => array( + 'nok' => 'Nepodarilo sa nájsť požadovanú knižnicu na spracovanie formátu JSON.', + 'ok' => 'Našla sa požadovaná knižnica na spracovanie formátu JSON.', + ), + 'mbstring' => array( + 'nok' => 'Nepodarilo sa nájsť požadovanú knižnicu mbstring pre Unicode.', + 'ok' => 'Našla sa požadovaná knižnica mbstring pre Unicode.', + ), + 'minz' => array( + 'nok' => 'Nepodarilo sa nájsť framework Minz.', + 'ok' => 'Našiel sa framework Minz.', + ), + 'pcre' => array( + 'nok' => 'Nepodarilo sa nájsť požadovanú knižnicu pre regulárne výrazy (php-pcre).', + 'ok' => 'Našla sa požadovaná knižnica pre regulárne výrazy (PCRE).', + ), + 'pdo' => array( + 'nok' => 'Nepodarilo sa nájsť PDO alebo niektorý z podporovaných ovládačov (pdo_mysql, pdo_sqlite, pdo_pgsql).', + 'ok' => 'Našiel sa PDO a aspoň jeden z podporovaných ovládačov (pdo_mysql, pdo_sqlite, pdo_pgsql).', + ), + 'php' => array( + '_' => 'Inštalácia PHP', + 'nok' => 'Vaša verzia PHP je %s, ale FreshRSS vyžaduje minimálne verziu %s.', + 'ok' => 'Vaša verzia PHP %s je kompatibilná s FreshRSS.', + ), + 'tables' => array( + 'nok' => 'V databáze chýba jedna alebo viacero tabuliek.', + 'ok' => 'V databáze sa nachádzajú všetky potrebné tabuľky.', + ), + 'title' => 'Kontrola inštalácie', + 'tokens' => array( + 'nok' => 'Skontrolujte oprávnenia prístupu do priečinku ./data/tokens. HTTP server musí mať právo doň zapisovať.', + 'ok' => 'Oprávnenia prístupu do priečinku tokens sú OK.', + ), + 'users' => array( + 'nok' => 'Skontrolujte oprávnenia prístupu do priečinku ./data/users. HTTP server musí mať právo doň zapisovať.', + 'ok' => 'Oprávnenia prístupu do priečinku používateľov sú OK.', + ), + 'zip' => array( + 'nok' => 'Nepodarilo sa nájsť rozšírenie ZIP (balík php-zip).', + 'ok' => 'Rozšírenie ZIP sa našlo.', + ), + ), + 'extensions' => array( + 'author' => 'Autor', + 'community' => 'Rozšírenia od komunity', + 'description' => 'Popis', + 'disabled' => 'Zakázané', + 'empty_list' => 'Žiadne nainštalované rozšírenia', + 'enabled' => 'Povolené', + 'latest' => 'Nainštalované', + 'name' => 'Názov', + 'no_configure_view' => 'Toto rozšírenie nemá nastavenia.', + 'system' => array( + '_' => 'Systémové rozšírenia', + 'no_rights' => 'Systémové rozšírenie (nemáte oprávnenia)', + ), + 'title' => 'Rozšírenia', + 'update' => 'Sú dostupné aktualizácie', + 'user' => 'Používateľské rozšírenia', + 'version' => 'Verzia', + ), + 'stats' => array( + '_' => 'Štatistiky', + 'all_feeds' => 'Všetky kanály', + 'category' => 'Kategória', + 'entry_count' => 'Počet položiek', + 'entry_per_category' => 'Položiek v kategórii', + 'entry_per_day' => 'Položiek za deň (posledných 30 dní)', + 'entry_per_day_of_week' => 'Za deň v týždni (priemer: %.2f správy)', + 'entry_per_hour' => 'Za hodinu (priemer: %.2f správy)', + 'entry_per_month' => 'Za mesiac (priemer: %.2f správy)', + 'entry_repartition' => 'Rozdelenie článkov', + 'feed' => 'Kanál', + 'feed_per_category' => 'Kanálov v kategórii', + 'idle' => 'Neaktívne kanály', + 'main' => 'Hlavné štatistiky', + 'main_stream' => 'Všetky kanály', + 'menu' => array( + 'idle' => 'Neaktívne kanály', + 'main' => 'Hlavné štatistiky', + 'repartition' => 'Rozdelenie článkov', + ), + 'no_idle' => 'Žiadne neaktívne kanály!', + 'number_entries' => 'Počet článkov: %d', + 'percent_of_total' => 'Z celkového počtu: %%', + 'repartition' => 'Rozdelenie článkov', + 'status_favorites' => 'Obľúbené', + 'status_read' => 'Prečítané', + 'status_total' => 'Spolu', + 'status_unread' => 'Neprečítané', + 'title' => 'Štatistiky', + 'top_feed' => 'Top 10 kanálov', + ), + 'system' => array( + '_' => 'Nastavenia systému', + 'auto-update-url' => 'Odkaz na aktualizačný server', + 'instance-name' => 'Názov inštancie', + 'max-categories' => 'Limit počtu kategórií pre používateľa', + 'max-feeds' => 'Limit počtu kanálov pre používateľov', + 'cookie-duration' => array( + 'help' => 'v sekundách', + 'number' => 'Dobra, počas ktorej ste prihlásený', + ), + 'registration' => array( + 'help' => '0 znamená žiadny limit počtu účtov', + 'number' => 'Maximálny počt účtov', + ), + ), + 'update' => array( + '_' => 'Aktualizácia systému', + 'apply' => 'Použiť', + 'check' => 'Skontrolovať aktualizácie', + 'current_version' => 'Vaša aktuálna verzia FreshRSS: %s', + 'last' => 'Posledná kontrola: %s', + 'none' => 'Žiadna nová aktualizácia', + 'title' => 'Aktualizácia systému', + ), + 'user' => array( + 'articles_and_size' => '%s článkov (%s)', + 'create' => 'Vytvoriť nového používateľa', + 'delete_users' => 'Zmazať používateľa', + 'language' => 'Jazyk', + 'number' => 'Je vytvorený používateľ: %d', + 'numbers' => 'Je vytvorených používateľov: %d', + 'password_form' => 'Heslo
(pre spôsob prihlásenia cez webový formulár)', + 'password_format' => 'Minimálne 7 znakov', + 'selected' => 'Označený používateľ', + 'title' => 'Správa používateľov', + 'update_users' => 'Sktualizovať používateľov', + 'user_list' => 'Zoznam používateľov', + 'username' => 'Používateľské meno', + 'users' => 'Používatelia', + ), +); diff --git a/app/i18n/sk/conf.php b/app/i18n/sk/conf.php new file mode 100644 index 000000000..2e2289b79 --- /dev/null +++ b/app/i18n/sk/conf.php @@ -0,0 +1,188 @@ + array( + '_' => 'Archivovanie', + 'advanced' => 'Pokročilé', + 'delete_after' => 'Vymazať články po', + 'help' => 'Viac možností nájdete v nastaveniach kanála', + 'keep_min_by_feed' => 'Minimálny počet článkov kanála na zachovanie', + 'optimize' => 'Optimalizovať databázu', + 'optimize_help' => 'Občas vykonajte na zmenšenie veľkosti databázy', + 'purge_now' => 'Vyčistiť teraz', + 'title' => 'Archivovanie', + 'ttl' => 'Neaktualizovať častejšie ako', + ), + 'display' => array( + '_' => 'Zobrazenie', + 'icon' => array( + 'bottom_line' => 'Spodný riadok', + 'display_authors' => 'Autori', + 'entry' => 'Ikony článku', + 'publication_date' => 'Dátum zverejnenia', + 'related_tags' => 'Značky článku', + 'sharing' => 'Zdieľanie', + 'top_line' => 'Horný riadok', + ), + 'language' => 'Jazyk', + 'notif_html5' => array( + 'seconds' => 'sekundy (0 znamená bez limitu)', + 'timeout' => 'Limit HTML5 oznámenia', + ), + 'show_nav_buttons' => 'Zobraziť tlačidlá oznámenia', + 'theme' => 'Vzhľad', + 'title' => 'Zobraziť', + 'width' => array( + 'content' => 'Šírka obsahu', + 'large' => 'Veľká', + 'medium' => 'Stredná', + 'no_limit' => 'Bez obmedzenia', + 'thin' => 'Úzka', + ), + ), + 'profile' => array( + '_' => 'Správca profilu', + 'delete' => array( + '_' => 'Vymazanie účtu', + 'warn' => 'Váš účet a všetky údaje v ňom budú vymazané.', + ), + 'password_api' => 'Heslo API
(pre mobilné aplikácie)', + 'password_form' => 'Heslo
(pre spôsob prihlásenia cez webový formulár)', + 'password_format' => 'Najmenej 7 znakov', + 'title' => 'Profil', + ), + 'query' => array( + '_' => 'Dopyty používateľa', + 'deprecated' => 'Tento dopyt už nie je platný. Kategória alebo kanál boli vymazané.', + 'display' => 'Zobraziť výsledky dopytu používateľa', + 'filter' => 'Použitý filter:', + 'get_all' => 'Zobraziť všetky články', + 'get_category' => 'Zobraziť kategóriu "%s"', + 'get_favorite' => 'Zobraziť obľúbené články', + 'get_feed' => 'Zobraziť kanál "%s"', + 'no_filter' => 'Žiadny filter', + 'none' => 'Zatiaľ ste nevytvorili používateľský dopyt.', + 'number' => 'Dopyt číslo %d', + 'order_asc' => 'Zobraziť staršie články hore', + 'order_desc' => 'Zobraziť novšie články hore', + 'remove' => 'Vymazať dopyt používateľa', + 'search' => 'Vyhľadáva sa: "%s"', + 'state_0' => 'Zobraziť všetky články', + 'state_1' => 'Zobraziť prečítané články', + 'state_2' => 'Zobraziť neprečítané články', + 'state_3' => 'Zobraziť všetky články', + 'state_4' => 'Zobraziť obľúbené články', + 'state_5' => 'Zobraziť prečítané obľúbené články', + 'state_6' => 'Zobraziť neprečítané obľúbené články', + 'state_7' => 'Zobraziť obľúbené články', + 'state_8' => 'Zobraziť neobľúbené články', + 'state_9' => 'Zobraziť prečítané neobľúbené články', + 'state_10' => 'Zobraziť neprečítané neobľúbené články', + 'state_11' => 'Zobraziť neobľúbené články', + 'state_12' => 'Zobraziť všetky články', + 'state_13' => 'Zobraziť prečítané články', + 'state_14' => 'Zobraziť neprečítané články', + 'state_15' => 'Zobraziť všetky články', + 'title' => 'Používateľské dopyty', + ), + 'reading' => array( + '_' => 'Čítanie', + 'after_onread' => 'Po “Označiť všetko ako prečítané”,', + 'articles_per_page' => 'Počet článkov na jednu stranu', + 'auto_load_more' => 'Načítať ďalšie články dolu na stránke', + 'auto_remove_article' => 'Skryť články po prečítaní', + 'confirm_enabled' => 'Zobraziť potvrdzovací dialóg po kliknutí na “Označiť všetko ako prečítané”', + 'display_articles_unfolded' => 'Zobraziť články otvorené', + 'display_categories_unfolded' => 'Zobraziť kategórie otvorené', + 'hide_read_feeds' => 'Skryť kategórie a kanály s nulovým počtom neprečítaných článkov (nefunguje s nastaveným “Zobraziť všetky články”)', + 'img_with_lazyload' => 'Pre načítanie obrázkov použiť "lazy load"', + 'jump_next' => 'skočiť na ďalší neprečítaný (kanál ale kategóriu)', + 'mark_updated_article_unread' => 'Označiť aktualizované články ako neprečítané', + 'number_divided_when_reader' => 'V režime čítania predeliť na dve časti.', + 'read' => array( + 'article_open_on_website' => 'keď je článok otvorený na svojej webovej stránke', + 'article_viewed' => 'keď je článok zobrazený', + 'scroll' => 'počas skrolovania', + 'upon_reception' => 'po načítaní článku', + 'when' => 'Označiť článok ako prečítaný…', + ), + 'show' => array( + '_' => 'Článkov na zobrazenie', + 'adaptive' => 'Vyberte zobrazenie', + 'all_articles' => 'Zobraziť všetky články', + 'unread' => 'Zobraziť iba neprečítané', + ), + 'sides_close_article' => 'Po kliknutí mimo textu článku sa článok zatvorí', + 'sort' => array( + '_' => 'Poradie', + 'newer_first' => 'Novšie hore', + 'older_first' => 'Staršie hore', + ), + 'sticky_post' => 'Po otvorení posunúť článok hore', + 'title' => 'Čítanie', + 'view' => array( + 'default' => 'Prednastavené zobrazenie', + 'global' => 'Prehľadné zobrazenie', + 'normal' => 'Základné zobrazenie', + 'reader' => 'Zobrazenie na čítanie', + ), + ), + 'sharing' => array( + '_' => 'Zdieľanie', + 'add' => 'Pridať spôsob zdieľania', + 'blogotext' => 'Blogotext', + 'diaspora' => 'Diaspora*', + 'email' => 'E-mail', + 'facebook' => 'Facebook', + 'g+' => 'Google+', + 'more_information' => 'Viac informácií', + 'print' => 'Tlač', + 'remove' => 'Odstrániť spôsob zdieľania', + 'shaarli' => 'Shaarli', + 'share_name' => 'Meno pre zobrazenie', + 'share_url' => 'Zdieľaný odkaz', + 'title' => 'Zdieľanie', + 'twitter' => 'Twitter', + 'wallabag' => 'wallabag', + ), + 'shortcut' => array( + '_' => 'Skratky', + 'article_action' => 'Akcie článku', + 'auto_share' => 'Zdieľať', + 'auto_share_help' => 'Ak je nastavený iba jeden spôsob zdieľania, použije sa. Inak si spôsoby zdieľania vyberá používateľ podľa čísla.', + 'close_dropdown' => 'Zavrie menu', + 'collapse_article' => 'Zroluje článok', + 'first_article' => 'Otvorí prvý článok', + 'focus_search' => 'Vyhľadávanie', + 'global_view' => 'Prepne do prehľadného zobrazenia', + 'help' => 'Zobrazí dokumentáciu', + 'javascript' => 'JavaScript musí byť povolený, ak chcete používať skratky', + 'last_article' => 'Otvorí posledný článok', + 'load_more' => 'Načíta viac článkov', + 'mark_favorite' => 'O(d)značí ako obľúbené', + 'mark_read' => 'O(d)značí ako prečítané', + 'navigation' => 'Navigácia', + 'navigation_help' => 'Po stlačení skratky s klávesou "Shift", sa skratky navigácie vzťahujú na kanály.
Po stlačení skratky s klávesou "Alt", sa skratky navigácie vzťahujú na kategórie.', + 'navigation_no_mod_help' => 'Tieto skratky navigácie nepodporujú klávesy "Shift" a "Alt".', + 'next_article' => 'Otvorí ďalší článok', + 'normal_view' => 'Prepne do základného zobrazenia', + 'other_action' => 'Ostatné akcie', + 'previous_article' => 'Otvorí predošlý článok', + 'reading_view' => 'Prepne do zobrazenia na čítanie', + 'rss_view' => 'Otvorí zobrazenie RSS v novej záložke', + 'see_on_website' => 'Zobrazí na webovej stránke', + 'shift_for_all_read' => '+ shift na označenie všetkých článkov ako prečítaných', + 'skip_next_article' => 'Prejde na ďalší bez otvorenia', + 'skip_previous_article' => 'Prejde na predošlý bez otvorenia', + 'title' => 'Skratky', + 'user_filter' => 'Použiť používateľské filtre', + 'user_filter_help' => 'Ak je nastavený iba jeden spôsob zdieľania, použije sa. Inak si spôsoby zdieľania vyberá používateľ podľa čísla.', + 'views' => 'Zobrazenia', + ), + 'user' => array( + 'articles_and_size' => '%s článkov (%s)', + 'current' => 'Aktuálny používateľ', + 'is_admin' => 'je administrátor', + 'users' => 'Používatelia', + ), +); diff --git a/app/i18n/sk/feedback.php b/app/i18n/sk/feedback.php new file mode 100644 index 000000000..9aee79068 --- /dev/null +++ b/app/i18n/sk/feedback.php @@ -0,0 +1,116 @@ + array( + 'optimization_complete' => 'Optimalizácia dokončená', + ), + 'access' => array( + 'denied' => 'Na prístup k tejto stránke nemáte oprávnenie', + 'not_found' => 'Hľadáte stránku, ktorá neexistuje', + ), + 'auth' => array( + 'form' => array( + 'not_set' => 'Nastavl problém pri nastavovaní prihlasovacieho systému. Prosím, skúste to znova neskôr.', + 'set' => 'Webový formulár je teraz váš prednastavený prihlasovací spôsob.', + ), + 'login' => array( + 'invalid' => 'Nesprávne prihlasovacie údaje', + 'success' => 'Úspešne ste sa prihlásili', + ), + 'logout' => array( + 'success' => 'Boli ste odhlásený', + ), + 'no_password_set' => 'Heslo administrátora nebolo nastavené. Táto funkcia nie je dostupná.', + ), + 'conf' => array( + 'error' => 'Vyskytla sa chyba počas ukladania nastavaní', + 'query_created' => 'Dopyt "%s" bol vytvorený.', + 'shortcuts_updated' => 'Skratky boli aktualizované', + 'updated' => 'Nastavenia boli aktualizované', + ), + 'extensions' => array( + 'already_enabled' => '%s už je povolené', + 'disable' => array( + 'ko' => '%s sa nepodarilo nainštalovať. Prečítajte si záznamy FreshRSS, ak chcete poznať podrobnosti.', + 'ok' => '%s je teraz zakázaný', + ), + 'enable' => array( + 'ko' => '%s sa nepodarilo povoliť. Prečítajte si záznamy FreshRSS, ak chcete poznať podrobnosti.', + 'ok' => '%s je teraz povolený', + ), + 'no_access' => 'Nemáte prístup k %s', + 'not_enabled' => '%s nie je povolený', + 'not_found' => '%s neexistuje', + ), + 'import_export' => array( + 'export_no_zip_extension' => 'ZIP rozšírenie sa na vašom serveri nenachádza. Prosím, skúste exportovať súbory pojednom.', + 'feeds_imported' => 'Váš kanál bol importovaný a bude aktualizovaný', + 'feeds_imported_with_errors' => 'Vaše kanály boli importované, ale vyskytli sa chyby', + 'file_cannot_be_uploaded' => 'Súbor sa nepodarilo nahrať!', + 'no_zip_extension' => 'ZIP rozšírenie sa na vašom serveri nenachádza.', + 'zip_error' => 'Počas importovania ZIP sa vyskytla chyba.', + ), + 'profile' => array( + 'error' => 'Váš profil nie je možné upraviť', + 'updated' => 'Váš profil bol upravený', + ), + 'sub' => array( + 'actualize' => 'Aktualizácia', + 'articles' => array( + 'marked_read' => 'Vybraté články boli označené ako prečítané.', + 'marked_unread' => 'Články boli označené ako neprečítané.', + ), + 'category' => array( + 'created' => 'Kategória %s bola vytvorená.', + 'deleted' => 'Kategória bola odstránená.', + 'emptied' => 'Kategória bola vyprázdnená', + 'error' => 'Nepodarilo sa aktualizovať kategóriu', + 'name_exists' => 'Názov kategórie už existuje.', + 'no_id' => 'Musíte zadať ID kategórie.', + 'no_name' => 'Názov kategórie nemôže byť prázdny.', + 'not_delete_default' => 'Nemôžete odstrániť prednastavenú kategóriu!', + 'not_exist' => 'Kategória neexistuje!', + 'over_max' => 'Dosiahli ste limit počtu kategórií (%d)', + 'updated' => 'Kategória bola aktualizovaná.', + ), + 'feed' => array( + 'actualized' => '%s bol aktualizovaný', + 'actualizeds' => 'RSS kanál bol aktualizovaný', + 'added' => 'RSS kanál %s bol pridaný', + 'already_subscribed' => 'Tento RSS kanál už odoberáte: %s', + 'deleted' => 'Kanál bol vymazaný', + 'error' => 'Kanál sa nepodarilo aktualizovať', + 'internal_problem' => 'Kanál sa nepodarilo pridať. Prečítajte si záznamy FreshRSS, ak chcete poznať podrobnosti. Skúste pridať kanál pomocou #force_feed v odkaze (URL).', + 'invalid_url' => 'Odkaz %s je neplatný', + 'n_actualized' => 'Počet aktualizovaných kanálov: %d', + 'n_entries_deleted' => 'Počet vymazaných článkov: %d', + 'no_refresh' => 'Žiadny kanál sa neaktualizoval…', + 'not_added' => 'Kanál %s sa nepodarilo pridať', + 'over_max' => 'Dosiahli ste limit počtu kanálov (%d)', + 'updated' => 'Kanál bol aktualizovaný', + ), + 'purge_completed' => 'Čistenie ukončené. Počet vymazaných článkov: %d', + ), + 'update' => array( + 'can_apply' => 'FreshRSS sa teraz aktualizuje na verziu %s.', + 'error' => 'Počas aktualizácie sa vyskytla chyba: %s', + 'file_is_nok' => 'Je dostupná nová verzia %s, ale skontrolujte prístupové práva priečinka %s. HTTP server musí mať právo doň zapisovať.', + 'finished' => 'Aktualizácia prebehla úspešne!', + 'none' => 'Žiadne aktualizácie', + 'server_not_found' => 'Nepodarilo sa nájsť server s aktualizáciami. [%s]', + ), + 'user' => array( + 'created' => array( + '_' => 'Používateľ %s bol vytvorený', + 'error' => 'Používateľ %s nebol vytvorený', + ), + 'deleted' => array( + '_' => 'Používateľ %s bol vymazaný', + 'error' => 'Používateľ %s nebol vymazaný', + ), + 'updated' => array( + '_' => 'Používateľ %s bol aktualizovaný', + 'error' => 'Používateľ %s nebol aktualizovaný', + ), + ), +); diff --git a/app/i18n/sk/gen.php b/app/i18n/sk/gen.php new file mode 100644 index 000000000..7303ffa9f --- /dev/null +++ b/app/i18n/sk/gen.php @@ -0,0 +1,181 @@ + array( + 'actualize' => 'Aktualizovať', + 'back_to_rss_feeds' => '← Späť na vaše RSS kanály', + 'cancel' => 'Zrušiť', + 'create' => 'Vytvoriť', + 'disable' => 'Zakázať', + 'empty' => 'Vyprázdniť', + 'enable' => 'Povoliť', + 'export' => 'Exportovať', + 'filter' => 'Filtrovať', + 'import' => 'Importovať', + 'manage' => 'Spravovať', + 'mark_favorite' => 'Označiť ako obľúbené', + 'mark_read' => 'Označiť ako prečítané', + 'remove' => 'Odstrániť', + 'see_website' => 'Zobraziť webovú stránku', + 'submit' => 'Poslať', + 'truncate' => 'Vymazať všetky články', + 'update' => 'Aktualizovať', + ), + 'auth' => array( + 'accept_tos' => 'I accept the Terms of Service.', // TODO - Translation + 'email' => 'E-mailová adresa', + 'keep_logged_in' => 'Zostať prihlásený (počet dní: %s)', + 'login' => 'Prihlásiť', + 'logout' => 'Odhlásiť', + 'password' => array( + '_' => 'Heslo', + 'format' => 'Najmenej 7 znakov', + ), + 'registration' => array( + '_' => 'Nový účet', + 'ask' => 'Vytvoriť účet?', + 'title' => 'Vytvorenie účtu', + ), + 'reset' => 'Reset prihlásenia', + 'username' => array( + '_' => 'Používateľské meno', + 'admin' => 'Administrátorské používateľské meno', + 'format' => 'maximálne 16 alfanumerických znakov', + ), + ), + 'date' => array( + 'Apr' => '\\A\\p\\r\\í\\l', + 'apr' => 'Apr.', + 'april' => 'Apríl', + 'Aug' => '\\A\\u\\g\\u\\s\\t', + 'aug' => 'Aug.', + 'august' => 'August', + 'before_yesterday' => 'Predvčerom', + 'Dec' => '\\D\\e\\c\\e\\m\\b\\e\\r', + 'dec' => 'Dec.', + 'december' => 'December', + 'Feb' => '\\F\\e\\b\\r\\u\\á\\r', + 'feb' => 'Feb.', + 'february' => 'Február', + 'format_date' => '%s j\\<\\s\\u\\p\\>S\\<\\/\\s\\u\\p\\> Y', + 'format_date_hour' => '%s j\\<\\s\\u\\p\\>S\\<\\/\\s\\u\\p\\> Y \\a\\t H\\:i', + 'fri' => 'Pi', + 'Jan' => '\\J\\a\\n\\u\\á\\r', + 'jan' => 'Jan.', + 'january' => 'Január', + 'Jul' => '\\J\\ú\\l', + 'jul' => 'Júl', + 'july' => 'Júl', + 'Jun' => '\\J\\ú\\n', + 'jun' => 'Jún', + 'june' => 'Jún', + 'last_3_month' => 'Posledné 3 mesiace', + 'last_6_month' => 'Posledných 6 mesiacov', + 'last_month' => 'Posledný mesiac', + 'last_week' => 'Posledný týždeň', + 'last_year' => 'Posledný rok', + 'Mar' => '\\M\\a\\r\\e\\c', + 'mar' => 'Mar.', + 'march' => 'Marec', + 'May' => '\\M\\á\\j', + 'may' => 'Máj', + 'may_' => 'Máj', + 'mon' => 'Po', + 'month' => 'mesiace', + 'Nov' => '\\N\\o\\v\\e\\m\\b\\e\\r', + 'nov' => 'Nov.', + 'november' => 'November', + 'Oct' => '\\O\\k\\t\\ó\\b\\e\\r', + 'oct' => 'Okt.', + 'october' => 'Október', + 'Sep' => '\\S\\e\\p\\t\\e\\m\\b\\e\\r', + 'sat' => 'So', + 'sep' => 'Sept.', + 'september' => 'September', + 'sun' => 'Ne', + 'thu' => 'Št', + 'today' => 'Dnes', + 'tue' => 'Ut', + 'wed' => 'St', + 'yesterday' => 'Včera', + ), + 'freshrss' => array( + '_' => 'FreshRSS', + 'about' => 'O FreshRSS', + ), + 'js' => array( + 'category_empty' => 'Prázdna kategória', + 'confirm_action' => 'Určite chcete vykonať túto akciu? Zmeny budú nezvratné!', + 'confirm_action_feed_cat' => 'Určite chcete vykonať túto akciu? Prídete o súvisiace obľúbené a používateľské dopyty. Zmeny budú nezvratné!', + 'feedback' => array( + 'body_new_articles' => 'Počet nových článkov v čítačke FreshRSS: %%d', + 'request_failed' => 'Nepodarilo sa spracovať váš dopyt, pravdepodobne kvôli problému s pripojením do internetu.', + 'title_new_articles' => 'FreshRSS: nové články!', + ), + 'new_article' => 'Našli sa nové články. Kliknite na obnovenie stránky.', + 'should_be_activated' => 'Musíte povoliť JavaScript', + ), + 'menu' => array( + 'about' => 'O FreshRSS', + 'admin' => 'Administrácia', + 'archiving' => 'Archivácia', + 'authentication' => 'Prihlásenie', + 'check_install' => 'Kontroloa inštalácie', + 'configuration' => 'Nastavenia', + 'display' => 'Zobrazenie', + 'extensions' => 'Rozšírenia', + 'logs' => 'Záznamy', + 'queries' => 'Používateľské dopyty', + 'reading' => 'Čítanie', + 'search' => 'Hľadajte slová alebo #značky', + 'sharing' => 'Zdieľanie', + 'shortcuts' => 'Skratky', + 'stats' => 'Štatistiky', + 'system' => 'Nastavenie systému', + 'update' => 'Aktualizácia', + 'user_management' => 'Spravovať používateľov', + 'user_profile' => 'Profil', + ), + 'pagination' => array( + 'first' => 'Prvý', + 'last' => 'Posledný', + 'load_more' => 'Načítať viac článkov', + 'mark_all_read' => 'Označiť všetko prečítané', + 'next' => 'Ďalší', + 'nothing_to_load' => 'Žiadne nové články', + 'previous' => 'Predošlý', + ), + 'share' => array( + 'blogotext' => 'Blogotext', + 'diaspora' => 'Diaspora*', + 'email' => 'E-mail', + 'facebook' => 'Facebook', + 'g+' => 'Google+', + 'gnusocial' => 'GNU social', + 'jdh' => 'Journal du hacker', + 'Known' => 'Stránky založené na Known', + 'linkedin' => 'LinkedIn', + 'mastodon' => 'Mastodon', + 'movim' => 'Movim', + 'pinboard' => 'Pinboard', + 'pocket' => 'Pocket', + 'print' => 'Print', + 'shaarli' => 'Shaarli', + 'twitter' => 'Twitter', + 'wallabag' => 'wallabag v1', + 'wallabagv2' => 'wallabag v2', + ), + 'short' => array( + 'attention' => 'Upozornenie!', + 'blank_to_disable' => 'Ak chcete zakázať, ponechajte prázdne', + 'by_author' => 'Od:', + 'by_default' => 'Prednastavené', + 'damn' => 'Sakra!', + 'default_category' => 'Bez kategórie', + 'no' => 'Nie', + 'not_applicable' => 'Nie je k dispozícii', + 'ok' => 'OK', + 'or' => 'alebo', + 'yes' => 'Áno', + ), +); diff --git a/app/i18n/sk/index.php b/app/i18n/sk/index.php new file mode 100644 index 000000000..ae5a077b0 --- /dev/null +++ b/app/i18n/sk/index.php @@ -0,0 +1,66 @@ + array( + '_' => 'O FreshRSS', + 'agpl3' => 'AGPL 3', + 'bugs_reports' => 'Nahlásiť chybu', + 'credits' => 'Poďakovanie', + 'credits_content' => 'Niektoré časti vzhľadu pochádzajú z Bootstrapu, aj keď FreshRSS tento framework nepoužíva. Ikony sú z GNOME project. Font Open Sans zabezpečil Steve Matteson. FreshRSS je založený na PHP frameworku Minz.', + 'freshrss_description' => 'FreshRSS je čítačka RSS kanálov, ktorú môžete nasadiť na vlastný server podobne ako Kriss Feed alebo Leed. Ide o jednoduchý a zároveň dobre nastaviteľný nástroj.', + 'github' => 'na Githube', + 'license' => 'Licencia', + 'project_website' => 'Webová stránka projektu', + 'title' => 'O FreshRSS', + 'version' => 'Verzia', + 'website' => 'Webová stránka', + ), + 'tos' => array( + 'title' => 'Terms of Service', // TODO - Translation + ), + 'feed' => array( + 'add' => 'Môžete pridať kanály.', + 'empty' => 'Žiadne články.', + 'rss_of' => 'RSS kanál pre %s', + 'title' => 'Vaše RSS kanály', + 'title_global' => 'Prehľad', + 'title_fav' => 'Vaše obľúbené', + ), + 'log' => array( + '_' => 'Záznamy', + 'clear' => 'Vymazať záznamy', + 'empty' => 'Súbor záznamu je prázdny', + 'title' => 'Záznamy', + ), + 'menu' => array( + 'about' => 'O FreshRSS', + 'add_query' => 'Vytvoriť dopyt', + 'before_one_day' => 'Pred 1 dňom', + 'before_one_week' => 'Pred 1 týždňom', + 'favorites' => 'Obľúbené (%s)', + 'global_view' => 'Prehľad', + 'main_stream' => 'Všetky kanály', + 'mark_all_read' => 'Označiť všetko ako prečítané', + 'mark_cat_read' => 'Označiť kategóriu ako prečítanú', + 'mark_feed_read' => 'Označiť kanál ako prečítaný', + 'mark_selection_unread' => 'Označiť označené ako prečítané', + 'newer_first' => 'Novšie hore', + 'non-starred' => 'Zobraziť všetko okrem obľúbených', + 'normal_view' => 'Základné zobrazenie', + 'older_first' => 'Staršie hore', + 'queries' => 'Používateľské dopyty', + 'read' => 'Zobraziť prečítané', + 'reader_view' => 'Zobrazenie na čítanie', + 'rss_view' => 'RSS kanál', + 'search_short' => 'Hľadať', + 'starred' => 'Zobraziť obľúbené', + 'stats' => 'Štatistiky', + 'subscription' => 'Správca odberov', + 'tags' => 'Moje nálepky', + 'unread' => 'Zobraziť neprečítané', + ), + 'share' => 'Zdieľať', + 'tag' => array( + 'related' => 'Značky článku', + ), +); diff --git a/app/i18n/sk/install.php b/app/i18n/sk/install.php new file mode 100644 index 000000000..08fbfeef9 --- /dev/null +++ b/app/i18n/sk/install.php @@ -0,0 +1,123 @@ + array( + 'finish' => 'Dokončiť inštaláciu', + 'fix_errors_before' => 'Prosím, pred pokračovaním opravte chyby.', + 'keep_install' => 'Použiť predošlé nastavenia', + 'next_step' => 'Ďalší krok', + 'reinstall' => 'Preinštalovať FreshRSS', + ), + 'auth' => array( + 'form' => 'Webový formulár (tradičný, vyžaduje JavaScript)', + 'http' => 'HTTP (pre pokročilých používateľov s HTTPS)', + 'none' => 'Žiadny (nebezpečné)', + 'password_form' => 'Heslo
(pre prihlásenie cez webový formulár)', + 'password_format' => 'Najmenej 7 znakov', + 'type' => 'Spôsob prihlásenia', + ), + 'bdd' => array( + '_' => 'Databáza', + 'conf' => array( + '_' => 'Nastavenia databázy', + 'ko' => 'Skontrolovať vaše informácie o databáze.', + 'ok' => 'Nastavenia databázy boli uložené.', + ), + 'host' => 'Server', + 'password' => 'Heslo databázy', + 'prefix' => 'Predpona názvu tabuľky', + 'type' => 'Druh databázy', + 'username' => 'Používateľské meno databázy', + ), + 'check' => array( + '_' => 'Kontrola', + 'already_installed' => 'Zistilo sa, že FreshRSS je už nainštalovaný!', + 'cache' => array( + 'nok' => 'Skontrolujte oprávnenia prístupu do priečinku ./data/cache. HTTP server musí mať právo doň zapisovať.', + 'ok' => 'Oprávnenia prístupu do priečinku vyrovnávacej pamäte sú OK.', + ), + 'ctype' => array( + 'nok' => 'Nepodarilo sa nájsť požadovanú knižnicu na kontrolu typu znakov (php-ctype).', + 'ok' => 'Našla sa požadovaná knižnica na kontrolu typu znakov (ctype).', + ), + 'curl' => array( + 'nok' => 'Nepodarilo sa nájsť knižnicu cURL (balík php-curl).', + 'ok' => 'Našla sa knižnica cURL.', + ), + 'data' => array( + 'nok' => 'Skontrolujte oprávnenia prístupu do priečinku ./data. HTTP server musí mať právo doň zapisovať.', + 'ok' => 'Oprávnenia prístupu do priečinku údajov sú OK.', + ), + 'dom' => array( + 'nok' => 'Nepodarilo sa nájsť požadovanú knižnicu na prehliadanie DOM.', + 'ok' => 'Našla sa požadovaná knižnica na prehliadanie DOM.', + ), + 'favicons' => array( + 'nok' => 'Skontrolujte oprávnenia prístupu do priečinku ./data/favicons. HTTP server musí mať právo doň zapisovať.', + 'ok' => 'Oprávnenia prístupu do priečinku ikôn obľúbených sú OK.', + ), + 'fileinfo' => array( + 'nok' => 'Nepodarilo sa nájsť knižniuc PHP fileinfo (balík fileinfo).', + 'ok' => 'Našla sa knižnica fileinfo.', + ), + 'http_referer' => array( + 'nok' => 'Prosím, skontrolujte, či ste nezmenili váš HTTP REFERER.', + 'ok' => 'Váš HTTP REFERER je OK.', + ), + 'json' => array( + 'nok' => 'Nepodarilo sa nájsť požadovanú knižnicu na spracovanie formátu JSON.', + 'ok' => 'Našla sa požadovaná knižnica na spracovanie formátu JSON.', + ), + 'mbstring' => array( + 'nok' => 'Nepodarilo sa nájsť požadovanú knižnicu mbstring pre Unicode.', + 'ok' => 'Našla sa požadovaná knižnica mbstring pre Unicode.', + ), + 'minz' => array( + 'nok' => 'Nepodarilo sa nájsť framework Minz.', + 'ok' => 'Našiel sa framework Minz.', + ), + 'pcre' => array( + 'nok' => 'Nepodarilo sa nájsť požadovanú knižnicu pre regulárne výrazy (php-pcre).', + 'ok' => 'Našla sa požadovaná knižnica pre regulárne výrazy (PCRE).', + ), + 'pdo' => array( + 'nok' => 'Nepodarilo sa nájsť PDO alebo niektorý z podporovaných ovládačov (pdo_mysql, pdo_sqlite, pdo_pgsql).', + 'ok' => 'Našiel sa PDO a aspoň jeden z podporovaných ovládačov (pdo_mysql, pdo_sqlite, pdo_pgsql).', + ), + 'php' => array( + 'nok' => 'Vaša verzia PHP je %s, ale FreshRSS vyžaduje minimálne verziu %s.', + 'ok' => 'Vaša verzia PHP %s je kompatibilná s FreshRSS.', + ), + 'users' => array( + 'nok' => 'Skontrolujte oprávnenia prístupu do priečinku ./data/users. HTTP server musí mať právo doň zapisovať.', + 'ok' => 'Oprávnenia prístupu do priečinku používateľov sú OK.', + ), + 'xml' => array( + 'nok' => 'Nepodarilo sa nájsť požadovanú knižnicu na spracovanie formátu XML.', + 'ok' => 'Našla sa požadovaná knižnica na spracovanie formátu XML.', + ), + ), + 'conf' => array( + '_' => 'Hlavné nastavenia', + 'ok' => 'Hlavné nastavenia boli uložené.', + ), + 'congratulations' => 'Nastavenia!', + 'default_user' => 'Hlavné používateľské meno (najviac 16 alfanumerických znakov)', + 'delete_articles_after' => 'Vymazať články po', + 'fix_errors_before' => 'Prosím, pred pokračovaním opravte chyby.', + 'javascript_is_better' => 'FreshRSS si užijete viac, keď povolíte JavaScript', + 'js' => array( + 'confirm_reinstall' => 'Ak budete pokračovať v preinštalovaní FreshRSS, stratíte vaše predošlé nastavenia. Naozaj chcete pokračovať?', + ), + 'language' => array( + '_' => 'Jazyk', + 'choose' => 'Vyberte jazyk pre FreshRSS', + 'defined' => 'Jazyk bol nastavený.', + ), + 'not_deleted' => 'Niečo sa nepodarilo. Musíte ručne zmazať súbor %s.', + 'ok' => 'Inštalácia bola úspešná.', + 'step' => 'krok %d', + 'steps' => 'Kroky', + 'title' => 'Inštalácia · FreshRSS', + 'this_is_the_end' => 'Toto je koniec', +); diff --git a/app/i18n/sk/sub.php b/app/i18n/sk/sub.php new file mode 100644 index 000000000..3149c370b --- /dev/null +++ b/app/i18n/sk/sub.php @@ -0,0 +1,103 @@ + array( + 'documentation' => 'Skopírujte tento odkaz a použite ho v inom programe.', + 'title' => 'API', + ), + 'bookmarklet' => array( + 'documentation' => 'Presunte toto tlačidlo do vašich záložiek, alebo kliknite pravým a zvoľte "Uložiť odkaz do záložiek". Potom kliknite na tlačidlo "Odoberať" na ktorejkoľvek stránke, ktorú chcete odoberať.', + 'label' => 'Odoberať', + 'title' => 'Záložka', + ), + 'category' => array( + '_' => 'Kategória', + 'add' => 'Pridať kategóriu', + 'empty' => 'Prázdna kategória', + 'information' => 'Informácia', + 'new' => 'Nová kategória', + 'position' => 'Display position', //TODO - Translation + 'position_help' => 'To control category sort order', //TODO - Translation + 'title' => 'Názov', + ), + 'feed' => array( + 'add' => 'Pridať RSS kanál', + 'advanced' => 'Pokročilé', + 'archiving' => 'Archivovanie', + 'auth' => array( + 'configuration' => 'Prihlásenie', + 'help' => 'Povoliť prístup do kanálov chránených cez HTTP.', + 'http' => 'Prihlásenie cez HTTP', + 'password' => 'Heslo pre HTTP', + 'username' => 'Používateľské meno pre HTTP', + ), + 'clear_cache' => 'Vždy vymazať vyrovnávaciu pamäť', + 'css_help' => 'Stiahnuť skrátenú verziu RSS kanála (pozor, vyžaduje viac času!)', + 'css_path' => 'Pôvodný CSS súbor článku z webovej stránky', + 'description' => 'Popis', + 'empty' => 'Tento kanál je prázdny. Overte, prosím, či je ešte spravovaný autorom.', + 'error' => 'Vyskytol sa problém s týmto kanálom. Overte, prosím, či kanál stále existuje, potom ho obnovte.', + 'filteractions' => array( + '_' => 'Filtrovať akcie', + 'help' => 'Napíšte jeden výraz hľadania na riadok.', + ), + 'information' => 'Informácia', + 'keep_min' => 'Minimálny počet článkov na uchovanie', + 'moved_category_deleted' => 'Keď vymažete kategóriu, jej kanály sa automaticky zaradia pod %s.', + 'mute' => 'stíšiť', + 'no_selected' => 'Nevybrali ste kanál.', + 'number_entries' => 'Počet článkov: %d', + 'priority' => array( + '_' => 'Viditeľnosť', + 'archived' => 'Nezobrazovať (archivované)', + 'main_stream' => 'Zobraziť v prehľade kanálov', + 'normal' => 'Zobraziť vo svojej kategórii', + ), + 'websub' => 'Okamžité oznámenia cez WebSub', + 'show' => array( + 'all' => 'Zobraziť všetky kanály', + 'error' => 'Zobraziť iba kanály s chybou', + ), + 'showing' => array( + 'error' => 'Zobraziť iba kanály s chybou', + ), + 'ssl_verify' => 'Overiť bezpečnosť SSL', + 'stats' => 'Štatistiky', + 'think_to_add' => 'Mali by ste pridať kanály.', + 'timeout' => 'Doba platnosti dá v sekundách', + 'title' => 'Nadpis', + 'title_add' => 'Pridať kanál RSS', + 'ttl' => 'Automaticky neaktualizovať častejšie ako', + 'url' => 'Odkaz kanála', + 'validator' => 'Skontrolovať platnosť kanála', + 'website' => 'Odkaz webovej stránky', + ), + 'firefox' => array( + 'documentation' => 'Pridajte RSS kanály do Firefoxu pomocou tohto návodu.', + 'obsolete_63' => 'From version 63 and onwards, Firefox has removed the ability to add your own subscription services that are not standalone programs.', //TODO - Translation + 'title' => 'RSS čítačka vo Firefoxe', + ), + 'import_export' => array( + 'export' => 'Exportovať', + 'export_opml' => 'Exportovať zoznam kanálov (OPML)', + 'export_starred' => 'Exportovať vaše obľúbené', + 'export_labelled' => 'Exportovať vaše označené články', + 'feed_list' => 'Zoznam článkov %s', + 'file_to_import' => 'Súbor na import
(OPML, JSON alebo ZIP)', + 'file_to_import_no_zip' => 'Súbor na import
(OPML alebo JSON)', + 'import' => 'Importovať', + 'starred_list' => 'Zoznam obľúbených článkov', + 'title' => 'Import / export', + ), + 'menu' => array( + 'bookmark' => 'Odoberať (záložka FreshRSS)', + 'import_export' => 'Import / export', + 'subscription_management' => 'Správa odoberaných kanálov', + 'subscription_tools' => 'Nástroje na odoberanie kanálov', + ), + 'title' => array( + '_' => 'Správa odoberaných kanálov', + 'feed_management' => 'Správa RSS kanálov', + 'subscription_tools' => 'Nástroje na odoberanie kanálov', + ), +); diff --git a/app/i18n/sk/user.php b/app/i18n/sk/user.php new file mode 100644 index 000000000..3a8343c11 --- /dev/null +++ b/app/i18n/sk/user.php @@ -0,0 +1,37 @@ + array( + 'feedback' => array( + 'invalid' => 'The email address is invalid.', //TODO - Translation + 'required' => 'The email address is required.', //TODO - Translation + ), + 'validation' => array( + 'change_email' => 'You can change your email address on the profile page.', //TODO - Translation + 'email_sent_to' => 'We sent you an email at %s, please follow its indications to validate your address.', //TODO - Translation + 'feedback' => array( + 'email_failed' => 'We couldn’t send you an email because of a misconfiguration of the server.', //TODO - Translation + 'email_sent' => 'An email has been sent to your address.', //TODO - Translation + 'error' => 'The email address failed to be validated.', //TODO - Translation + 'ok' => 'The email address has been validated.', //TODO - Translation + 'unneccessary' => 'The email address was already validated.', //TODO - Translation + 'wrong_token' => 'The email address failed to be validated due to a wrong token.', //TODO - Translation + ), + 'need_to' => 'You need to validate your email address before being able to use %s.', //TODO - Translation + 'resend_email' => 'Resend the email', //TODO - Translation + 'title' => 'Email address validation', //TODO - Translation + ), + ), + 'tos' => array( + 'feedback' => array( + 'invalid' => 'You must accept the Terms of Service to be able to register.', // TODO - Translation + ), + ), + 'mailer' => array( + 'email_need_validation' => array( + 'title' => 'You need to validate your account', //TODO - Translation + 'welcome' => 'Welcome %s,', //TODO - Translation + 'body' => 'You’ve just registered on %s but you still need to validate your email. For that, just follow the link:', //TODO - Translation + ), + ), +); diff --git a/app/i18n/tr/admin.php b/app/i18n/tr/admin.php index b1d6671ca..2c7d0fd6d 100644 --- a/app/i18n/tr/admin.php +++ b/app/i18n/tr/admin.php @@ -159,6 +159,7 @@ return array( 'system' => array( '_' => 'Sistem yapılandırması', 'auto-update-url' => 'Otomatik güncelleme sunucu URL', + 'force_email_validation' => 'Force email addresses validation', //TODO - Translation 'instance-name' => 'Örnek isim', 'max-categories' => 'Kullanıcı başına kategori limiti', 'max-feeds' => 'Kullanıcı başına akış limiti', diff --git a/app/i18n/tr/conf.php b/app/i18n/tr/conf.php index 6c57d39da..c8ea78efa 100644 --- a/app/i18n/tr/conf.php +++ b/app/i18n/tr/conf.php @@ -3,13 +3,21 @@ return array( 'archiving' => array( '_' => 'Arşiv', - 'advanced' => 'Gelişmiş', 'delete_after' => 'Makelelerin tutulacağı süre', + 'exception' => 'Purge exception', //TODO - Translation 'help' => 'Akış ayarlarında daha çok ayar bulabilirsiniz', - 'keep_history_by_feed' => 'Akışta en az tutulacak makale sayısı', + 'keep_favourites' => 'Never delete favourites', //TODO - Translation + 'keep_min_by_feed' => 'Akışta en az tutulacak makale sayısı', + 'keep_labels' => 'Never delete labels', //TODO - Translation + 'keep_unreads' => 'Never delete unreads', //TODO - Translation + 'maintenance' => 'Maintenance', //TODO - Translation 'optimize' => 'Veritabanı optimize et', 'optimize_help' => 'Bu işlem bazen veritabanı boyutunu düşürmeye yardımcı olur', + 'policy' => 'Purge policy', //TODO - Translation + 'policy_warning' => 'If no purge policy is selected, every article will be kept.', //TODO - Translation 'purge_now' => 'Şimdi temizle', + 'keep_max' => 'Maximum number of articles to keep', //TODO - Translation + 'keep_period' => 'Maximum age of articles to keep', //TODO - Translation 'title' => 'Arşiv', 'ttl' => 'Şu süreden sık otomatik yenileme yapma', ), @@ -21,6 +29,7 @@ return array( 'publication_date' => 'Yayınlama Tarihi', 'related_tags' => 'İlgili etiketler', //TODO - Translation 'sharing' => 'Paylaşım', + 'display_authors' => 'Authors', //TODO - Translation 'top_line' => 'Üst çizgi', ), 'language' => 'Dil', @@ -45,6 +54,7 @@ return array( '_' => 'Hesap silme', 'warn' => 'Hesabınız ve tüm verileriniz silinecek.', ), + 'email' => 'Email adresleri', 'password_api' => 'API Şifresi
(ör. mobil uygulamalar için)', 'password_form' => 'Şifre
(Tarayıcı girişi için)', 'password_format' => 'En az 7 karakter', @@ -133,7 +143,6 @@ return array( 'diaspora' => 'Diaspora*', 'email' => 'Email', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'more_information' => 'Daha fazla bilgi', 'print' => 'Yazdır', 'remove' => 'Remove sharing method', //TODO - Translation diff --git a/app/i18n/tr/gen.php b/app/i18n/tr/gen.php index a84c39f20..ccc5b9ee6 100644 --- a/app/i18n/tr/gen.php +++ b/app/i18n/tr/gen.php @@ -3,6 +3,7 @@ return array( 'action' => array( 'actualize' => 'Yenile', + 'back' => '← Go back', //TODO - Translation 'back_to_rss_feeds' => '← RSS akışlarınız için geri gidin', 'cancel' => 'İptal', 'create' => 'Oluştur', @@ -22,6 +23,7 @@ return array( 'update' => 'Update', //TODO - Translation ), 'auth' => array( + 'accept_tos' => 'I accept the Terms of Service.', // TODO - Translation 'email' => 'Email adresleri', 'keep_logged_in' => '(%s günler) oturumu açık tut', 'login' => 'Giriş', @@ -160,15 +162,22 @@ return array( 'nothing_to_load' => 'Başka makale yok', 'previous' => 'Önceki', ), + 'period' => array( + 'days' => 'days', //TODO - Translation + 'hours' => 'hours', //TODO - Translation + 'months' => 'months', //TODO - Translation + 'weeks' => 'weeks', //TODO - Translation + 'years' => 'years', //TODO - Translation + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', 'email' => 'Email', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'gnusocial' => 'GNU social', 'jdh' => 'Journal du hacker', 'Known' => 'Known based sites', + 'lemmy' => 'Lemmy', 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', diff --git a/app/i18n/tr/index.php b/app/i18n/tr/index.php index d6db514dd..e284d78db 100644 --- a/app/i18n/tr/index.php +++ b/app/i18n/tr/index.php @@ -7,7 +7,7 @@ return array( 'bugs_reports' => 'Hata raporu', 'credits' => 'Tanıtım', 'credits_content' => 'Bu frameworkü kullanmamasına rağmen FreshRSS bazı tasarım ögelerini Bootstrap dan almıştır. İkonlar GNOME projesinden alınmıştır. Open Sans yazı tipi Steve Matteson tarafından oluşturulmuştur. FreshRSS bir PHP framework olan Minz i temel alır.', - 'freshrss_description' => 'FreshRSS Kriss Feed veya Leed gibi kendi hostunuzda çalışan bir RSS akış toplayıcısıdır. Güçlü ve yapılandırılabilir araçlarıyla basit ve kullanımı kolay bir uygulamadır.', + 'freshrss_description' => 'FreshRSS Kriss Feed veya Leed gibi kendi hostunuzda çalışan bir RSS akış toplayıcısıdır. Güçlü ve yapılandırılabilir araçlarıyla basit ve kullanımı kolay bir uygulamadır.', 'github' => 'Github sayfası', 'license' => 'Lisans', 'project_website' => 'Proje sayfası', @@ -15,6 +15,9 @@ return array( 'version' => 'Versiyon', 'website' => 'Website', ), + 'tos' => array( + 'title' => 'Terms of Service', // TODO - Translation + ), 'feed' => array( 'add' => 'Akış ekleyebilirsin.', 'empty' => 'Gösterilecek makale yok.', diff --git a/app/i18n/tr/sub.php b/app/i18n/tr/sub.php index 858d15758..ed2a7ce76 100644 --- a/app/i18n/tr/sub.php +++ b/app/i18n/tr/sub.php @@ -13,9 +13,12 @@ return array( 'category' => array( '_' => 'Kategori', 'add' => 'Kategori ekle', + 'archiving' => 'Arşiv', 'empty' => 'Boş kategori', 'information' => 'Bilgi', 'new' => 'Yeni kategori', + 'position' => 'Display position', //TODO - Translation + 'position_help' => 'To control category sort order', //TODO - Translation 'title' => 'Başlık', ), 'feed' => array( @@ -40,7 +43,7 @@ return array( 'help' => 'Write one search filter per line.', //TODO - Translation ), 'information' => 'Bilgi', - 'keep_history' => 'En az tutulacak makale sayısı', + 'keep_min' => 'En az tutulacak makale sayısı', 'moved_category_deleted' => 'Bir kategoriyi silerseniz, içerisindeki akışlar %s içerisine yerleşir.', 'mute' => 'mute', //TODO - Translation 'no_selected' => 'Hiçbir akış seçilmedi.', @@ -72,6 +75,7 @@ return array( ), 'firefox' => array( 'documentation' => 'Follow the steps described here to add FreshRSS to Firefox feed reader list.', //TODO - Translation + 'obsolete_63' => 'From version 63 and onwards, Firefox has removed the ability to add your own subscription services that are not standalone programs.', //TODO - Translation 'title' => 'Firefox feed reader', //TODO - Translation ), 'import_export' => array( diff --git a/app/i18n/tr/user.php b/app/i18n/tr/user.php new file mode 100644 index 000000000..3a8343c11 --- /dev/null +++ b/app/i18n/tr/user.php @@ -0,0 +1,37 @@ + array( + 'feedback' => array( + 'invalid' => 'The email address is invalid.', //TODO - Translation + 'required' => 'The email address is required.', //TODO - Translation + ), + 'validation' => array( + 'change_email' => 'You can change your email address on the profile page.', //TODO - Translation + 'email_sent_to' => 'We sent you an email at %s, please follow its indications to validate your address.', //TODO - Translation + 'feedback' => array( + 'email_failed' => 'We couldn’t send you an email because of a misconfiguration of the server.', //TODO - Translation + 'email_sent' => 'An email has been sent to your address.', //TODO - Translation + 'error' => 'The email address failed to be validated.', //TODO - Translation + 'ok' => 'The email address has been validated.', //TODO - Translation + 'unneccessary' => 'The email address was already validated.', //TODO - Translation + 'wrong_token' => 'The email address failed to be validated due to a wrong token.', //TODO - Translation + ), + 'need_to' => 'You need to validate your email address before being able to use %s.', //TODO - Translation + 'resend_email' => 'Resend the email', //TODO - Translation + 'title' => 'Email address validation', //TODO - Translation + ), + ), + 'tos' => array( + 'feedback' => array( + 'invalid' => 'You must accept the Terms of Service to be able to register.', // TODO - Translation + ), + ), + 'mailer' => array( + 'email_need_validation' => array( + 'title' => 'You need to validate your account', //TODO - Translation + 'welcome' => 'Welcome %s,', //TODO - Translation + 'body' => 'You’ve just registered on %s but you still need to validate your email. For that, just follow the link:', //TODO - Translation + ), + ), +); diff --git a/app/i18n/zh-cn/admin.php b/app/i18n/zh-cn/admin.php index 74f57b6e8..cdc8449a3 100644 --- a/app/i18n/zh-cn/admin.php +++ b/app/i18n/zh-cn/admin.php @@ -159,6 +159,7 @@ return array( 'system' => array( '_' => '系统配置', 'auto-update-url' => '自动升级服务器 URL', + 'force_email_validation' => 'Force email addresses validation', //TODO - Translation 'instance-name' => '实例名称', 'max-categories' => '每用户分类限制', 'max-feeds' => '每用户 RSS 源限制', diff --git a/app/i18n/zh-cn/conf.php b/app/i18n/zh-cn/conf.php index 216e4590a..a7404bc58 100644 --- a/app/i18n/zh-cn/conf.php +++ b/app/i18n/zh-cn/conf.php @@ -3,13 +3,21 @@ return array( 'archiving' => array( '_' => '存档', - 'advanced' => '高级', 'delete_after' => '文章保留', + 'exception' => 'Purge exception', //TODO - Translation 'help' => '详细选项位于单独的 RSS 源设置', - 'keep_history_by_feed' => '至少保存的文章数', + 'keep_favourites' => 'Never delete favourites', //TODO - Translation + 'keep_min_by_feed' => '至少保存的文章数', + 'keep_labels' => 'Never delete labels', //TODO - Translation + 'keep_unreads' => 'Never delete unreads', //TODO - Translation + 'maintenance' => 'Maintenance', //TODO - Translation 'optimize' => '优化数据库', 'optimize_help' => '偶尔执行优化可以减少数据库大小', + 'policy' => 'Purge policy', //TODO - Translation + 'policy_warning' => 'If no purge policy is selected, every article will be kept.', //TODO - Translation 'purge_now' => '立即清除', + 'keep_max' => 'Maximum number of articles to keep', //TODO - Translation + 'keep_period' => 'Maximum age of articles to keep', //TODO - Translation 'title' => '存档', 'ttl' => '最小自动更新时间', ), @@ -21,6 +29,7 @@ return array( 'publication_date' => '更新日期', 'related_tags' => '相关标签', 'sharing' => '分享', + 'display_authors' => 'Authors', //TODO - Translation 'top_line' => '顶栏', ), 'language' => '语言', @@ -45,6 +54,7 @@ return array( '_' => '账户删除', 'warn' => '你的帐户和所有相关数据都将被删除。', ), + 'email' => 'Email 地址', 'password_api' => 'API 密码
(例如,用于手机 APP)', 'password_form' => '密码
(用于 Web-form 登录方式)', 'password_format' => '至少 7 个字符', @@ -133,7 +143,6 @@ return array( 'diaspora' => 'Diaspora*', 'email' => 'Email', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'more_information' => '更多信息', 'print' => '打印', 'remove' => '删除分享方式', diff --git a/app/i18n/zh-cn/gen.php b/app/i18n/zh-cn/gen.php index 11d4efdb3..31817260e 100644 --- a/app/i18n/zh-cn/gen.php +++ b/app/i18n/zh-cn/gen.php @@ -3,6 +3,7 @@ return array( 'action' => array( 'actualize' => '获取', + 'back' => '← Go back', //TODO - Translation 'back_to_rss_feeds' => '← 返回', 'cancel' => '取消', 'create' => '创建', @@ -22,6 +23,7 @@ return array( 'update' => '更新', //TODO - Translation ), 'auth' => array( + 'accept_tos' => 'I accept the Terms of Service.', // TODO - Translation 'email' => 'Email 地址', 'keep_logged_in' => '自动登录(%s 天)', 'login' => '登录', @@ -160,15 +162,22 @@ return array( 'nothing_to_load' => '没有更多文章了', 'previous' => '上一页', ), + 'period' => array( + 'days' => 'days', //TODO - Translation + 'hours' => 'hours', //TODO - Translation + 'months' => 'months', //TODO - Translation + 'weeks' => 'weeks', //TODO - Translation + 'years' => 'years', //TODO - Translation + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', 'email' => 'Email', 'facebook' => 'Facebook', - 'g+' => 'Google+', 'gnusocial' => 'GNU social', 'jdh' => 'Journal du hacker', 'Known' => '基于 Known 的站点', + 'lemmy' => 'Lemmy', 'linkedin' => 'LinkedIn', 'mastodon' => 'Mastodon', 'movim' => 'Movim', diff --git a/app/i18n/zh-cn/index.php b/app/i18n/zh-cn/index.php index 018813c3e..3e448608e 100644 --- a/app/i18n/zh-cn/index.php +++ b/app/i18n/zh-cn/index.php @@ -7,7 +7,7 @@ return array( 'bugs_reports' => 'Bug 报告', 'credits' => '致谢', 'credits_content' => '某些设计元素来自于 Bootstrap ,尽管 FreshRSS 并没有使用此框架。图标 来自于 GNOME 项目Open Sans 字体出自 Steve Matteson 之手。FreshRSS 基于 PHP 框架 Minz。', - 'freshrss_description' => 'FreshRSS 是一个自托管的 RSS 聚合服务,类似于 Kriss FeedLeed。 它不仅轻快又易用,而且强大又易于配置。', + 'freshrss_description' => 'FreshRSS 是一个自托管的 RSS 聚合服务,类似于 Kriss FeedLeed。 它不仅轻快又易用,而且强大又易于配置。', 'github' => 'Github Issues', 'license' => '授权', 'project_website' => '项目网站', @@ -15,6 +15,9 @@ return array( 'version' => '版本', 'website' => '网站', ), + 'tos' => array( + 'title' => 'Terms of Service', // TODO - Translation + ), 'feed' => array( 'add' => '你可以添加一些 RSS 源。', 'empty' => '暂时没有文章可显示。', diff --git a/app/i18n/zh-cn/sub.php b/app/i18n/zh-cn/sub.php index bf517756b..944d6986f 100644 --- a/app/i18n/zh-cn/sub.php +++ b/app/i18n/zh-cn/sub.php @@ -13,9 +13,12 @@ return array( 'category' => array( '_' => '分类', 'add' => '添加分类', + 'archiving' => '存档', 'empty' => '空分类', 'information' => '信息', 'new' => '新分类', + 'position' => 'Display position', //TODO - Translation + 'position_help' => 'To control category sort order', //TODO - Translation 'title' => '标题', ), 'feed' => array( @@ -40,7 +43,7 @@ return array( 'help' => 'Write one search filter per line.', //TODO - Translation ), 'information' => '信息', - 'keep_history' => '至少保存的文章数', + 'keep_min' => '至少保存的文章数', 'moved_category_deleted' => '删除分类时,其中的 RSS 源会自动归类到 %s', 'mute' => '暂停', 'no_selected' => '未选择 RSS 源。', @@ -72,6 +75,7 @@ return array( ), 'firefox' => array( 'documentation' => '按照 这里 描述的步骤可将 FreshRSS 添加到 Firefox 阅读器列表', + 'obsolete_63' => 'From version 63 and onwards, Firefox has removed the ability to add your own subscription services that are not standalone programs.', //TODO - Translation 'title' => 'Firefox RSS 阅读器', ), 'import_export' => array( diff --git a/app/i18n/zh-cn/user.php b/app/i18n/zh-cn/user.php new file mode 100644 index 000000000..3a8343c11 --- /dev/null +++ b/app/i18n/zh-cn/user.php @@ -0,0 +1,37 @@ + array( + 'feedback' => array( + 'invalid' => 'The email address is invalid.', //TODO - Translation + 'required' => 'The email address is required.', //TODO - Translation + ), + 'validation' => array( + 'change_email' => 'You can change your email address on the profile page.', //TODO - Translation + 'email_sent_to' => 'We sent you an email at %s, please follow its indications to validate your address.', //TODO - Translation + 'feedback' => array( + 'email_failed' => 'We couldn’t send you an email because of a misconfiguration of the server.', //TODO - Translation + 'email_sent' => 'An email has been sent to your address.', //TODO - Translation + 'error' => 'The email address failed to be validated.', //TODO - Translation + 'ok' => 'The email address has been validated.', //TODO - Translation + 'unneccessary' => 'The email address was already validated.', //TODO - Translation + 'wrong_token' => 'The email address failed to be validated due to a wrong token.', //TODO - Translation + ), + 'need_to' => 'You need to validate your email address before being able to use %s.', //TODO - Translation + 'resend_email' => 'Resend the email', //TODO - Translation + 'title' => 'Email address validation', //TODO - Translation + ), + ), + 'tos' => array( + 'feedback' => array( + 'invalid' => 'You must accept the Terms of Service to be able to register.', // TODO - Translation + ), + ), + 'mailer' => array( + 'email_need_validation' => array( + 'title' => 'You need to validate your account', //TODO - Translation + 'welcome' => 'Welcome %s,', //TODO - Translation + 'body' => 'You’ve just registered on %s but you still need to validate your email. For that, just follow the link:', //TODO - Translation + ), + ), +); diff --git a/app/install.php b/app/install.php index 961a7c171..96bee34a1 100644 --- a/app/install.php +++ b/app/install.php @@ -17,24 +17,10 @@ if (isset($_GET['step'])) { define('STEP', 0); } -if (STEP === 3 && isset($_POST['type'])) { +if (STEP === 2 && isset($_POST['type'])) { $_SESSION['bd_type'] = $_POST['type']; } -if (isset($_SESSION['bd_type'])) { - switch ($_SESSION['bd_type']) { - case 'mysql': - include_once(APP_PATH . '/SQL/install.sql.mysql.php'); - break; - case 'sqlite': - include_once(APP_PATH . '/SQL/install.sql.sqlite.php'); - break; - case 'pgsql': - include_once(APP_PATH . '/SQL/install.sql.pgsql.php'); - break; - } -} - function param($key, $default = false) { if (isset($_POST[$key])) { return $_POST[$key]; @@ -43,7 +29,6 @@ function param($key, $default = false) { } } - // gestion internationalisation function initTranslate() { Minz_Translate::init(); @@ -61,7 +46,7 @@ function initTranslate() { } function get_best_language() { - $accept = $_SERVER['HTTP_ACCEPT_LANGUAGE']; + $accept = empty($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? '' : $_SERVER['HTTP_ACCEPT_LANGUAGE']; return strtolower(substr($accept, 0, 2)); } @@ -101,7 +86,6 @@ function saveStep1() { // Then, we set $_SESSION vars $_SESSION['title'] = $system_conf->title; $_SESSION['auth_type'] = $system_conf->auth_type; - $_SESSION['old_entries'] = $user_conf->old_entries; $_SESSION['default_user'] = $current_user; $_SESSION['passwordHash'] = $user_conf->passwordHash; @@ -119,68 +103,13 @@ function saveStep1() { } function saveStep2() { - $user_default_config = Minz_Configuration::get('default_user'); - if (!empty($_POST)) { - $system_default_config = Minz_Configuration::get('default_system'); - $_SESSION['title'] = $system_default_config->title; - $_SESSION['old_entries'] = param('old_entries', $user_default_config->old_entries); - $_SESSION['auth_type'] = param('auth_type', 'form'); - if (FreshRSS_user_Controller::checkUsername(param('default_user', ''))) { - $_SESSION['default_user'] = param('default_user', ''); - } - - $password_plain = param('passwordPlain', false); - if ($password_plain !== false && cryptAvailable()) { - $_SESSION['passwordHash'] = FreshRSS_user_Controller::hashPassword($password_plain); - } - - if (empty($_SESSION['old_entries']) || - empty($_SESSION['auth_type']) || - empty($_SESSION['default_user'])) { - return false; - } - - if ($_SESSION['auth_type'] === 'form' && empty($_SESSION['passwordHash'])) { - return false; - } - - $_SESSION['salt'] = generateSalt(); - if ((!ctype_digit($_SESSION['old_entries'])) ||($_SESSION['old_entries'] < 1)) { - $_SESSION['old_entries'] = $user_default_config->old_entries; - } - - $token = ''; - - $config_array = array( - 'language' => $_SESSION['language'], - 'theme' => $user_default_config->theme, - 'old_entries' => $_SESSION['old_entries'], - 'passwordHash' => $_SESSION['passwordHash'], - 'token' => $token, - ); - - // Create default user files but first, we delete previous data to - // avoid access right problems. - $user_dir = join_path(USERS_PATH, $_SESSION['default_user']); - $user_config_path = join_path($user_dir, 'config.php'); - - recursive_unlink($user_dir); - mkdir($user_dir); - file_put_contents($user_config_path, " $_SESSION['salt'], + $config_array = [ + 'salt' => generateSalt(), 'base_url' => $base_url, - 'title' => $_SESSION['title'], - 'default_user' => $_SESSION['default_user'], - 'auth_type' => $_SESSION['auth_type'], - 'db' => array( + 'default_user' => 'admin', + 'db' => [ 'type' => $_SESSION['bd_type'], 'host' => $_SESSION['bd_host'], 'user' => $_SESSION['bd_user'], 'password' => $_SESSION['bd_password'], 'base' => $_SESSION['bd_base'], 'prefix' => $_SESSION['bd_prefix'], - 'pdo_options' => array(), - ), + 'pdo_options' => [], + ], 'pubsubhubbub_enabled' => server_is_public($base_url), - ); + ]; + if (!empty($_SESSION['title'])) { + $config_array['title'] = $_SESSION['title']; + } + if (!empty($_SESSION['auth_type'])) { + $config_array['auth_type'] = $_SESSION['auth_type']; + } + + @unlink(DATA_PATH . '/config.php'); //To avoid access-rights problems + file_put_contents(DATA_PATH . '/config.php', "getMessage(); + $ok = false; + } if (!$ok) { @unlink(join_path(DATA_PATH, 'config.php')); } if ($ok) { $_SESSION['bd_error'] = ''; - header('Location: index.php?step=4'); - } else { - $_SESSION['bd_error'] = empty($config_array['db']['error']) ? 'Unknown error!' : $config_array['db']['error']; + header('Location: index.php?step=3'); + } elseif (empty($_SESSION['bd_error'])) { + $_SESSION['bd_error'] = 'Unknown error!'; } } invalidateHttpCache(); } +function saveStep3() { + $user_default_config = Minz_Configuration::get('default_user'); + if (!empty($_POST)) { + $system_default_config = Minz_Configuration::get('default_system'); + $_SESSION['title'] = $system_default_config->title; + $_SESSION['auth_type'] = param('auth_type', 'form'); + if (FreshRSS_user_Controller::checkUsername(param('default_user', ''))) { + $_SESSION['default_user'] = param('default_user', ''); + } + + if (empty($_SESSION['auth_type']) || + empty($_SESSION['default_user'])) { + return false; + } + + $password_plain = param('passwordPlain', false); + if ($_SESSION['auth_type'] === 'form' && $password_plain == '') { + return false; + } + + Minz_Configuration::register('system', DATA_PATH . '/config.php', FRESHRSS_PATH . '/config.default.php'); + FreshRSS_Context::$system_conf = Minz_Configuration::get('system'); + Minz_Translate::init($_SESSION['language']); + + FreshRSS_Context::$system_conf->default_user = $_SESSION['default_user']; + FreshRSS_Context::$system_conf->save(); + + // Create default user files but first, we delete previous data to + // avoid access right problems. + recursive_unlink(USERS_PATH . '/' . $_SESSION['default_user']); + + $ok = false; + try { + $ok = FreshRSS_user_Controller::createUser( + $_SESSION['default_user'], + '', //TODO: Add e-mail + $password_plain, + '', + [ + 'language' => $_SESSION['language'], + ] + ); + } catch (Exception $e) { + $_SESSION['bd_error'] = $e->getMessage(); + $ok = false; + } + if (!$ok) { + return false; + } + + header('Location: index.php?step=4'); + } +} /*** VÉRIFICATIONS ***/ function checkStep() { @@ -297,29 +289,6 @@ function freshrss_already_installed() { } function checkStep2() { - $conf = !empty($_SESSION['old_entries']) && - !empty($_SESSION['default_user']); - - $form = ( - isset($_SESSION['auth_type']) && - ($_SESSION['auth_type'] != 'form' || !empty($_SESSION['passwordHash'])) - ); - - $defaultUser = empty($_POST['default_user']) ? null : $_POST['default_user']; - if ($defaultUser === null) { - $defaultUser = empty($_SESSION['default_user']) ? '' : $_SESSION['default_user']; - } - $data = is_writable(join_path(USERS_PATH, $defaultUser, 'config.php')); - - return array( - 'conf' => $conf ? 'ok' : 'ko', - 'form' => $form ? 'ok' : 'ko', - 'data' => $data ? 'ok' : 'ko', - 'all' => $conf && $form && $data ? 'ok' : 'ko' - ); -} - -function checkStep3() { $conf = is_writable(join_path(DATA_PATH, 'config.php')); $bd = isset($_SESSION['bd_type']) && @@ -331,60 +300,52 @@ function checkStep3() { isset($_SESSION['bd_error']); $conn = empty($_SESSION['bd_error']); - return array( + return [ 'bd' => $bd ? 'ok' : 'ko', 'conn' => $conn ? 'ok' : 'ko', 'conf' => $conf ? 'ok' : 'ko', - 'all' => $bd && $conn && $conf ? 'ok' : 'ko' - ); + 'all' => $bd && $conn && $conf ? 'ok' : 'ko', + ]; } -function checkDbUser(&$dbOptions) { - $ok = false; - $str = $dbOptions['dsn']; - $driver_options = $dbOptions['options']; - try { - $c = new PDO($str, $dbOptions['user'], $dbOptions['password'], $driver_options); - if (defined('SQL_CREATE_TABLES')) { - $sql = sprintf(SQL_CREATE_TABLES . SQL_CREATE_TABLE_ENTRYTMP . SQL_CREATE_TABLE_TAGS . SQL_INSERT_FEEDS, - $dbOptions['prefix_user'], _t('gen.short.default_category')); - $stm = $c->prepare($sql); - $ok = $stm && $stm->execute(); - } else { - global $SQL_CREATE_TABLES, $SQL_CREATE_TABLE_ENTRYTMP, $SQL_CREATE_TABLE_TAGS, $SQL_INSERT_FEEDS; - $instructions = array_merge($SQL_CREATE_TABLES, $SQL_CREATE_TABLE_ENTRYTMP, $SQL_CREATE_TABLE_TAGS, $SQL_INSERT_FEEDS); - $ok = !empty($instructions); - foreach ($instructions as $instruction) { - $sql = sprintf($instruction, $dbOptions['prefix_user'], _t('gen.short.default_category')); - $stm = $c->prepare($sql); - $ok &= $stm && $stm->execute(); - } - } - } catch (PDOException $e) { - $ok = false; - $dbOptions['error'] = $e->getMessage(); +function checkStep3() { + $conf = !empty($_SESSION['default_user']); + + $form = isset($_SESSION['auth_type']); + + $defaultUser = empty($_POST['default_user']) ? null : $_POST['default_user']; + if ($defaultUser === null) { + $defaultUser = empty($_SESSION['default_user']) ? '' : $_SESSION['default_user']; } - return $ok; + $data = is_writable(join_path(USERS_PATH, $defaultUser, 'config.php')); + + return [ + 'conf' => $conf ? 'ok' : 'ko', + 'form' => $form ? 'ok' : 'ko', + 'data' => $data ? 'ok' : 'ko', + 'all' => $conf && $form && $data ? 'ok' : 'ko', + ]; } + /*** AFFICHAGE ***/ function printStep0() { $actual = Minz_Translate::language(); $languages = Minz_Translate::availableLanguages(); ?> -

+

- +
- +
@@ -393,10 +354,10 @@ function printStep0() {
- - + + - +
@@ -408,203 +369,135 @@ function printStep0() { function printStep1() { $res = checkRequirements(); ?> - + -

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

- - + + - + -

+

- -

- -

- - -
- - -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
-
- tabindex="5" /> - -
- - -
-
- -
-
- - - - - -
-
-
- - -

- -

+ +

+ +

-
- + +
- +
+
- +
- +
- +
- + +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+
+ +
+
+ + + + +
+ + + +

+ +

+ + +
+
- +
- +
- +
- +
+ +
+ +
+
+ tabindex="5" /> + +
+ + +
- - + + - +
@@ -681,14 +635,14 @@ function printStep3() { function printStep4() { ?> -

- +

+ -

+

- <?php echo _t('install.title'); ?> - - + <?= _t('install.title') ?> + +
-

-

+

+

@@ -775,6 +729,6 @@ case 5: ?>
- + diff --git a/app/layout/aside_configure.phtml b/app/layout/aside_configure.phtml index 94f5b1f6c..1267f747c 100644 --- a/app/layout/aside_configure.phtml +++ b/app/layout/aside_configure.phtml @@ -1,51 +1,54 @@ diff --git a/app/layout/aside_feed.phtml b/app/layout/aside_feed.phtml index 637acf4a4..e0c90282f 100644 --- a/app/layout/aside_feed.phtml +++ b/app/layout/aside_feed.phtml @@ -8,30 +8,30 @@ } ?> -
- +
+
- - + +
- + - +
@@ -45,9 +45,9 @@
@@ -56,16 +56,16 @@
- - + +
diff --git a/app/views/auth/register.phtml b/app/views/auth/register.phtml index 19e11ef76..4233f7fd4 100644 --- a/app/views/auth/register.phtml +++ b/app/views/auth/register.phtml @@ -1,22 +1,40 @@
-

+

-
- + +
- - + +
+ show_email_field) { ?> +
+ + +
+ +
- +
- +
- +
+ show_tos_checkbox) { ?> +
+ +
+ +
- - - + + +
-

+

diff --git a/app/views/configure/archiving.phtml b/app/views/configure/archiving.phtml index 09be55fd9..7d76e4dcc 100644 --- a/app/views/configure/archiving.phtml +++ b/app/views/configure/archiving.phtml @@ -1,34 +1,17 @@ partial('aside_configure'); ?>
- + -
- - -

+ + + +

- +
- -   -
-
-
- -
- () -
-
-
- -
- () + ?> () +
+
+ +

+ +

+ +
+ +
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
- - + +
-
- - + + +
- +
- nb_total), format_bytes($this->size_user)); ?> + nb_total), format_bytes($this->size_user)) ?>
-
-
- size_total); ?> +
+
-
+
+ +
- - + +
-
+ + +
+ +
+ size_total) ?> +
+
+
diff --git a/app/views/configure/display.phtml b/app/views/configure/display.phtml index 58c4e219a..c1cfecc2a 100644 --- a/app/views/configure/display.phtml +++ b/app/views/configure/display.phtml @@ -1,47 +1,47 @@ partial('aside_configure'); ?>
- + -
- - + + +
- +
- - +
- +
    themes); $i = 1; ?> themes as $theme) { ?> - theme === $theme['id']) {echo "checked";}?> value="" data-leave-validation="theme === $theme['id']) ? 1 : 0; ?>"/> + theme === $theme['id']) {echo "checked";}?> value="" data-leave-validation="theme === $theme['id']) ? 1 : 0 ?>"/>
  • - +
    -
    -
    -
    +
    +
    +
  • @@ -52,81 +52,84 @@ content_width; ?>
    - +
    - + - - -
    - + - - - - - - + + + + + + + - - - + + + - - + + + - - - - - - - + + + + + + + +
     
    topline_read ? ' checked="checked"' : ''; ?> data-leave-validation="topline_read; ?>"/>topline_favorite ? ' checked="checked"' : ''; ?> data-leave-validation="topline_favorite; ?>"/>topline_read ? ' checked="checked"' : '' ?> data-leave-validation="topline_read ?>"/>topline_favorite ? ' checked="checked"' : '' ?> data-leave-validation="topline_favorite ?>"/> topline_date ? ' checked="checked"' : ''; ?> data-leave-validation="topline_date; ?>"/>topline_link ? ' checked="checked"' : ''; ?> data-leave-validation="topline_link; ?>"/>topline_display_authors ? ' checked="checked"' : '' ?> data-leave-validation="topline_display_authors ?>"/>topline_date ? ' checked="checked"' : '' ?> data-leave-validation="topline_date ?>"/>topline_link ? ' checked="checked"' : '' ?> data-leave-validation="topline_link ?>"/>
    bottomline_read ? ' checked="checked"' : ''; ?> data-leave-validation="bottomline_read; ?>"/>bottomline_favorite ? ' checked="checked"' : ''; ?> data-leave-validation="bottomline_favorite; ?>"/>bottomline_tags ? ' checked="checked"' : ''; ?> data-leave-validation="bottomline_tags; ?>"/>bottomline_sharing ? ' checked="checked"' : ''; ?> data-leave-validation="bottomline_sharing; ?>"/>bottomline_date ? ' checked="checked"' : ''; ?> data-leave-validation="bottomline_date; ?>"/>bottomline_link ? ' checked="checked"' : ''; ?> data-leave-validation="bottomline_link; ?>"/>bottomline_read ? ' checked="checked"' : '' ?> data-leave-validation="bottomline_read ?>"/>bottomline_favorite ? ' checked="checked"' : '' ?> data-leave-validation="bottomline_favorite ?>"/>bottomline_tags ? ' checked="checked"' : '' ?> data-leave-validation="bottomline_tags ?>"/>bottomline_sharing ? ' checked="checked"' : '' ?> data-leave-validation="bottomline_sharing ?>"/>bottomline_date ? ' checked="checked"' : '' ?> data-leave-validation="bottomline_date ?>"/>bottomline_link ? ' checked="checked"' : '' ?> data-leave-validation="bottomline_link ?>"/>

    - +
    - +
    - - + +
    diff --git a/app/views/configure/queries.phtml b/app/views/configure/queries.phtml index baaf74954..a0f600b5d 100644 --- a/app/views/configure/queries.phtml +++ b/app/views/configure/queries.phtml @@ -1,70 +1,70 @@ partial('aside_configure'); ?>
    - + -
    - - + + + queries as $key => $query) { ?> -
    -
    '> - - + + sharing as $key => $share_options) { $share = FreshRSS_Share::get($share_options['type']); $share->update($share_options); ?> -
    +
    - - - + + +
    - + formType() === 'advanced') { ?> - + - + - +
    formType() === 'advanced') { ?> - +
    @@ -52,19 +52,19 @@
    - +
    - - + +
    diff --git a/app/views/configure/shortcut.phtml b/app/views/configure/shortcut.phtml index 412ea676d..4412266cc 100644 --- a/app/views/configure/shortcut.phtml +++ b/app/views/configure/shortcut.phtml @@ -1,181 +1,181 @@ partial('aside_configure'); ?>
    - + list_keys as $key) { ?> - shortcuts; ?> -
    - - + + + - + - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - + -

    +

    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    -

    +

    - +
    - +
    - +
    - +
    - +
    - +
    - - + +
    - +
    - +
    - +
    - +
    - +
    - - + +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - - + +
    - +
    - +
    - +
    - +
    - - + +
    diff --git a/app/views/configure/system.phtml b/app/views/configure/system.phtml index 9af4cc2c9..1a718e20f 100644 --- a/app/views/configure/system.phtml +++ b/app/views/configure/system.phtml @@ -1,31 +1,31 @@ partial('aside_configure'); ?>
    - + -
    - - + + +
    - +
    - +
    - +
    - +
    - +
    - - + +
    @@ -38,32 +38,50 @@
    + can_enable_email_validation) { ?> +
    +
    + +
    +
    + +
    - +
    - +
    - +
    - +
    - +
    - +
    - - + +
    - - + +
    diff --git a/app/views/error/index.phtml b/app/views/error/index.phtml index 8fd74e8bf..d5618d54c 100644 --- a/app/views/error/index.phtml +++ b/app/views/error/index.phtml @@ -1,9 +1,9 @@
    -

    code; ?>

    +

    code ?>

    - errorMessage, ENT_NOQUOTES, 'UTF-8'); ?>
    - + errorMessage, ENT_NOQUOTES, 'UTF-8') ?>
    +

    diff --git a/app/views/extension/index.phtml b/app/views/extension/index.phtml index 6439a0333..f5c5bf032 100644 --- a/app/views/extension/index.phtml +++ b/app/views/extension/index.phtml @@ -1,14 +1,14 @@ partial('aside_configure'); ?>
    - + -

    +

    - + extension_list['system'])) { ?> -

    +

    extension_list['system'] as $ext) { $this->ext_details = $ext; @@ -18,7 +18,7 @@ extension_list['user'])) { ?> -

    +

    extension_list['user'] as $ext) { $this->ext_details = $ext; @@ -30,34 +30,34 @@ if (empty($this->extension_list['system']) && empty($this->extension_list['user'])) { ?> -

    +

    available_extensions)) { ?> -

    +

    - - - - + + + + available_extensions as $ext) { ?> - - - + + + ":""),u=!0);var k=n[p],L=s.labelBoxWidth,A=s.labelBoxHeight;d=s.labelFormatter(k.label),v="background-color:"+(k.bars&&k.bars.show&&k.bars.fillColor&&k.bars.fill?k.bars.fillColor:k.color)+";",o.push('",'")}u&&o.push("");if(o.length>0){var O='
    - + extensions_installed[$ext['name']])) { ?> extensions_installed[$ext['name']], $ext['version']) >= 0) { ?> - + extensions_installed[$ext['name']] != $ext['version']) { ?> - + @@ -69,8 +69,8 @@ extension) ? ' class="active"' : ''; ?> -> -
    > +> +
    > extension)) { $this->renderHelper('extension/configure'); diff --git a/app/views/feed/add.phtml b/app/views/feed/add.phtml index 340970b25..e39f45a86 100644 --- a/app/views/feed/add.phtml +++ b/app/views/feed/add.phtml @@ -1,90 +1,90 @@ feed) { ?>
    -

    +

    load_ok) { ?> -

    +

    -
    - - + + + load_ok) { ?>
    - +
    - +
    feed->description(); if ($desc != '') { ?>
    - +
    - +
    - +
    - feed->website(); ?> - + feed->website() ?> +
    - +
    - - + +
    - +
    - +
    - + feed->httpAuth(false); ?>
    - +
    - +
    - +
    - +
    - +
    - - + +
    diff --git a/app/views/helpers/category/update.phtml b/app/views/helpers/category/update.phtml index a2ee3e2ef..9e55e613d 100644 --- a/app/views/helpers/category/update.phtml +++ b/app/views/helpers/category/update.phtml @@ -1,34 +1,160 @@
    -

    category->name(); ?>

    +

    category->name() ?>

    - +
    -
    - - + + +
    - +
    - + category->id() == FreshRSS_CategoryDAO::DEFAULTCATEGORYID ? 'disabled="disabled"' : ''; + ?> /> +
    +
    +
    + +
    + +
    - + + data-str-confirm="" + formaction="category->id()) ?>" + formmethod="post"> category->isDefault()): ?> + data-str-confirm="" + formaction="category->id()) ?>" + formmethod="post">
    + + + category->attributes('archiving'); + if (empty($archiving)) { + $archiving = [ 'default' => true ]; + } else { + $archiving['default'] = false; + } + $volatile = [ + 'enable_keep_period' => false, + 'keep_period_count' => '3', + 'keep_period_unit' => 'P1M', + ]; + if (!empty($archiving['keep_period'])) { + if (preg_match('/^PT?(?P\d+)[YMWDH]$/', $archiving['keep_period'], $matches)) { + $volatile['enable_keep_period'] = true; + $volatile['keep_period_count'] = $matches['count']; + $volatile['keep_period_unit'] = str_replace($matches['count'], '1', $archiving['keep_period']); + } + } + //Defaults + if (!isset($archiving['keep_max'])) { + $archiving['keep_max'] = false; + } + if (!isset($archiving['keep_favourites'])) { + $archiving['keep_favourites'] = true; + } + if (!isset($archiving['keep_labels'])) { + $archiving['keep_labels'] = true; + } + if (!isset($archiving['keep_unreads'])) { + $archiving['keep_unreads'] = false; + } + if (!isset($archiving['keep_min'])) { + $archiving['keep_min'] = 50; + } + ?> + +

    + +

    + +
    + +
    + +
    +
    + + + + + + +
    +
    + + +
    +
    diff --git a/app/views/helpers/export/articles.phtml b/app/views/helpers/export/articles.phtml index 2d1fcd133..0bbfb86ec 100644 --- a/app/views/helpers/export/articles.phtml +++ b/app/views/helpers/export/articles.phtml @@ -1,10 +1,7 @@ = 0) { - $options = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; -} +$options = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; $articles = array( 'id' => 'user/' . str_replace('/', '', $username) . '/state/org.freshrss/' . $this->type, diff --git a/app/views/helpers/extension/configure.phtml b/app/views/helpers/extension/configure.phtml index cde872aa0..cb6ebd6bb 100644 --- a/app/views/helpers/extension/configure.phtml +++ b/app/views/helpers/extension/configure.phtml @@ -1,19 +1,19 @@

    - extension->getName(); ?> (extension->getVersion(); ?>) — + extension->getName() ?> (extension->getVersion() ?>) — extension->isEnabled() ? _t('admin.extensions.enabled') : _t('admin.extensions.disabled'); ?>

    -

    extension->getDescription(); ?> — extension->getAuthor(); ?>

    +

    extension->getDescription() ?> — extension->getAuthor() ?>

    -

    +

    extension->getConfigureView(); if ($configure_view !== false) { echo $configure_view; } else { ?> -

    +

    diff --git a/app/views/helpers/extension/details.phtml b/app/views/helpers/extension/details.phtml index acba4e816..ed9674e3d 100644 --- a/app/views/helpers/extension/details.phtml +++ b/app/views/helpers/extension/details.phtml @@ -3,19 +3,19 @@ ext_details->getType() === 'user' || FreshRSS_Auth::hasAccess('admin')) { ?> ext_details->getName()); ?>
    - + ext_details->isEnabled()) { ?> - + - + - +
    - + -
  • ext_details->getName(); ?>
  • +
  • ext_details->getName() ?>
  • diff --git a/app/views/helpers/feed/update.phtml b/app/views/helpers/feed/update.phtml index 620806d7b..e5f186956 100644 --- a/app/views/helpers/feed/update.phtml +++ b/app/views/helpers/feed/update.phtml @@ -1,114 +1,214 @@
    -

    feed->name(); ?>

    +

    feed->name() ?>

    - - - + + +
    -

    feed->description(); ?>

    +

    feed->description() ?>

    feed->nbEntries(); ?> feed->inError()) { ?> -

    +

    -

    +

    -
    - - + + +
    - +
    - +
    - +
    - +
    - +
    - - + +
    - +
    - - + +
    - +
    - +
    - +
    - + + + data-str-confirm="" + formaction="feed->id()) ?>" + formmethod="post">
    - + + feed->attributes('archiving'); + if (empty($archiving)) { + $archiving = [ 'default' => true ]; + } else { + $archiving['default'] = false; + } + $volatile = [ + 'enable_keep_period' => false, + 'keep_period_count' => '3', + 'keep_period_unit' => 'P1M', + ]; + if (!empty($archiving['keep_period'])) { + if (preg_match('/^PT?(?P\d+)[YMWDH]$/', $archiving['keep_period'], $matches)) { + $volatile['enable_keep_period'] = true; + $volatile['keep_period_count'] = $matches['count']; + $volatile['keep_period_unit'] = str_replace($matches['count'], '1', $archiving['keep_period']); + } + } + //Defaults + if (!isset($archiving['keep_max'])) { + $archiving['keep_max'] = false; + } + if (!isset($archiving['keep_min'])) { + $archiving['keep_min'] = 50; + } + if (!isset($archiving['keep_favourites'])) { + $archiving['keep_favourites'] = true; + } + if (!isset($archiving['keep_labels'])) { + $archiving['keep_labels'] = true; + } + if (!isset($archiving['keep_unreads'])) { + $archiving['keep_unreads'] = false; + } + ?> + +

    + +

    +
    - +
    - + +
    +
    + + + + + + +
    - +
    - +
    - - + + +
    - + feed->httpAuth(false); ?>
    - +
    - - + +
    - +
    - +
    - - + +
    - +
    - +
    - - + +
    - +
    - +
    - +
    - feed->attributes('clear_cache') ? ' checked="checked"' : ''; ?> /> + feed->attributes('clear_cache') ? ' checked="checked"' : '' ?> />
    - +
    - +
    - +
    - +
    - +
    - +
    - - + +
    diff --git a/app/views/helpers/index/normal/entry_bottom.phtml b/app/views/helpers/index/normal/entry_bottom.phtml index c0edbdf7d..dd84ca346 100644 --- a/app/views/helpers/index/normal/entry_bottom.phtml +++ b/app/views/helpers/index/normal/entry_bottom.phtml @@ -19,7 +19,7 @@ if ($this->entry->isRead()) { $arUrl['params']['is_read'] = 0; } - ?>entry->isRead() ? 'read' : 'unread'); ?>entry->isFavorite()) { $arUrl['params']['is_favorite'] = 0; } - ?>entry->isFavorite() ? 'starred' : 'non-starred'); ?>
  • + +
  • @@ -70,10 +70,10 @@ ?>
  • currentPage > 1) { ?> - « + «
  • currentPage - 1; ?>
  • currentPage > 1) { ?> - +
  • @@ -24,9 +24,9 @@ 0 && $i <= $this->nbPage) { ?> currentPage) { ?> -
  • +
  • -
  • +
  • @@ -34,13 +34,13 @@ currentPage + 1; ?>
  • currentPage < $this->nbPage) { ?> - +
  • nbPage; ?>
  • currentPage < $this->nbPage) { ?> - » + »
  • diff --git a/app/views/helpers/pagination.phtml b/app/views/helpers/pagination.phtml index fc37ce3f5..b7d62ceab 100755 --- a/app/views/helpers/pagination.phtml +++ b/app/views/helpers/pagination.phtml @@ -18,26 +18,26 @@ ?>
    - +
    • - - + + -
      +
    • diff --git a/app/views/importExport/index.phtml b/app/views/importExport/index.phtml index 139e715c5..e5a24dbc4 100644 --- a/app/views/importExport/index.phtml +++ b/app/views/importExport/index.phtml @@ -1,14 +1,14 @@ partial('aside_subscription'); ?>
      - + - - - + + +
      @@ -17,30 +17,30 @@
      - +
      feeds) > 0) { ?> -
      - - + + +
      feeds)) .'" multiple="multiple"'; } ?> - size="10"> + ' ?> feeds as $feed) { ?> - +
      @@ -60,7 +60,7 @@
      - +
      diff --git a/app/views/index/about.phtml b/app/views/index/about.phtml index 649729952..320847886 100644 --- a/app/views/index/about.phtml +++ b/app/views/index/about.phtml @@ -1,26 +1,26 @@
      - + -

      +

      -
      -
      +
      +
      -
      -
      +
      +
      -
      -
      +
      +
      -
      -
      +
      +
      -

      +

      -

      -

      +

      +

      diff --git a/app/views/index/global.phtml b/app/views/index/global.phtml index 2f25b6dc2..a49e16525 100644 --- a/app/views/index/global.phtml +++ b/app/views/index/global.phtml @@ -9,7 +9,7 @@ } ?> -
      +
      -
      - +
      +
        nbEntries() === 0 ? ' empty' : ''; $url_base['params']['get'] = 'f_' . $feed->id(); ?> -
      • - ✇ - name(); ?> +
      • + ✇ + name() ?>
      @@ -51,7 +51,7 @@
      - +
      -
      display_posts ? '' : ' class="hide_posts"'; ?>> +
      display_posts ? '' : ' class="hide_posts"' ?>>
      diff --git a/app/views/index/logs.phtml b/app/views/index/logs.phtml index a88f89278..2deb1c315 100644 --- a/app/views/index/logs.phtml +++ b/app/views/index/logs.phtml @@ -1,11 +1,11 @@
      - + -

      -

      - +

      +

      + - +

      logsPaginator->items(); ?> @@ -15,12 +15,12 @@ logsPaginator->render('logs_pagination.phtml', 'page'); ?> -
      date())); ?>info(), ENT_NOQUOTES, 'UTF-8'); ?>
      +
      date())) ?>info(), ENT_NOQUOTES, 'UTF-8') ?>
      logsPaginator->render('logs_pagination.phtml','page'); ?>
      -

      +

      diff --git a/app/views/index/normal.phtml b/app/views/index/normal.phtml index ac2ea812d..f556df201 100644 --- a/app/views/index/normal.phtml +++ b/app/views/index/normal.phtml @@ -14,9 +14,9 @@ if (!empty($this->entries)) { $today = @strtotime('today'); ?> -
      ">
      - +
      entries as $item) { $this->entry = Minz_ExtensionManager::callHook('entry_before_display', $item); @@ -36,23 +36,23 @@ if (!empty($this->entries)) { if ($display_today && $this->entry->isDay(FreshRSS_Days::TODAY, $today)) { ?>
      entry->isDay(FreshRSS_Days::YESTERDAY, $today)) { ?>
      entry->isDay(FreshRSS_Days::BEFORE_YESTERDAY, $today)) { ?>
      entries)) { $this->renderHelper('index/normal/entry_header'); ?>
      -
      -

      entry->title(); ?>

      +
      +

      entry->title() ?>

      entry->authors(); if (is_array($authors)): @@ -75,7 +75,7 @@ if (!empty($this->entries)) { echo $first ? _t('gen.short.by_author') . ' ' : '· '; $first = false; ?> - +
      entries)) {
      -

      -

      +

      +

      diff --git a/app/views/index/reader.phtml b/app/views/index/reader.phtml index 129fae937..bbb6bd22b 100644 --- a/app/views/index/reader.phtml +++ b/app/views/index/reader.phtml @@ -9,16 +9,16 @@ if (!empty($this->entries)) {
      - +
      entries as $item) { $item = Minz_ExtensionManager::callHook('entry_before_display', $item); if (is_null($item)) { continue; } - ?>
      + ?>
      -
      +
      categories, $item->feed()); //We most likely already have the feed object in cache if (empty($feed)) $feed = $item->feed(true); @@ -31,16 +31,16 @@ if (!empty($this->entries)) { $readUrl['params']['is_read'] = 0; } ?> - - isRead() ? 'read' : 'unread'); ?> + + isRead() ? 'read' : 'unread') ?> - - isFavorite() ? 'starred' : 'non-starred'); ?> + + isFavorite() ? 'starred' : 'non-starred') ?> - - ✇ name(); ?> + + ✇ name() ?> -

      title(); ?>

      +

      title() ?>

      authors(); @@ -50,7 +50,7 @@ if (!empty($this->entries)) { echo $first ? _t('gen.short.by_author') . ' ' : '· '; $first = false; ?> - + entries)) { echo $item->date(); ?>
      - content(); ?> + content() ?>
      @@ -69,7 +69,7 @@ if (!empty($this->entries)) {
      -

      -

      +

      +

      diff --git a/app/views/index/rss.phtml b/app/views/index/rss.phtml index 104e03d15..00be01c28 100755 --- a/app/views/index/rss.phtml +++ b/app/views/index/rss.phtml @@ -1,23 +1,23 @@ -'; ?> +'; ?> - <?php echo $this->rss_title; ?> - - rss_title); ?> - - GMT - + <?= $this->rss_title ?> + + rss_title) ?> + + GMT + entries as $item) { ?> - <?php echo $item->title(); ?> - link(); ?> + <?= $item->title() ?> + link() ?> authors(); if (is_array($authors)) { foreach ($authors as $author) { - echo "\t\t\t" , '', $author, '', "\n"; + echo "\t\t\t" , '', $author, '', "\n"; } } $categories = $item->tags(); @@ -30,8 +30,8 @@ foreach ($this->entries as $item) { content(); ?>]]> - date(true)); ?> - id(); ?> + date(true)) ?> + id() ?> diff --git a/app/views/index/tos.phtml b/app/views/index/tos.phtml new file mode 100644 index 000000000..38dd0add6 --- /dev/null +++ b/app/views/index/tos.phtml @@ -0,0 +1,13 @@ +
      + can_register) { ?> + + + + + + + + + + terms_of_service ?> +
      diff --git a/app/views/stats/idle.phtml b/app/views/stats/idle.phtml index 88c78d465..9421893fb 100644 --- a/app/views/stats/idle.phtml +++ b/app/views/stats/idle.phtml @@ -1,9 +1,9 @@ partial('aside_stats'); ?>
      - + -

      +

      -

      +

      - +
      • - - - + + +
      • - () + ()
      @@ -43,7 +43,7 @@ if ($nothing) { ?>

      - +

      diff --git a/app/views/stats/index.phtml b/app/views/stats/index.phtml index 2ff3e6c52..4af197c5b 100644 --- a/app/views/stats/index.phtml +++ b/app/views/stats/index.phtml @@ -1,63 +1,63 @@ partial('aside_stats'); ?>
      - + -

      +

      -

      +

      - - + + - - - + + + - - - + + + - - - + + + - - - + + +
       
      repartition['main_stream']['total']); ?>repartition['all_feeds']['total']); ?>repartition['main_stream']['total']) ?>repartition['all_feeds']['total']) ?>
      repartition['main_stream']['count_reads']); ?>repartition['all_feeds']['count_reads']); ?>repartition['main_stream']['count_reads']) ?>repartition['all_feeds']['count_reads']) ?>
      repartition['main_stream']['count_unreads']); ?>repartition['all_feeds']['count_unreads']); ?>repartition['main_stream']['count_unreads']) ?>repartition['all_feeds']['count_unreads']) ?>
      repartition['main_stream']['count_favorites']); ?>repartition['all_feeds']['count_favorites']); ?>repartition['main_stream']['count_favorites']) ?>repartition['all_feeds']['count_favorites']) ?>
      -

      +

      - - - - + + + + topFeed as $feed) { ?> - - - - + + + + @@ -65,18 +65,18 @@
      -

      +

      -

      +

      -

      +

      @@ -90,4 +90,4 @@ echo htmlspecialchars(json_encode(array( 'entryByCategory' => $this->entryByCategory, ), JSON_UNESCAPED_UNICODE), ENT_NOQUOTES, 'UTF-8'); ?> - + diff --git a/app/views/stats/repartition.phtml b/app/views/stats/repartition.phtml index 4bce418c9..7b445a7cc 100644 --- a/app/views/stats/repartition.phtml +++ b/app/views/stats/repartition.phtml @@ -1,12 +1,12 @@ partial('aside_stats'); ?>
      - + -

      +

      feed) {?> - - + +
      repartition['all_feeds']['total'] * 100, 1);?>repartition['all_feeds']['total'] * 100, 1) ?>
      - - - - + + + + - - - - + + + +
      repartition['total']; ?>repartition['count_reads']; ?>repartition['count_unreads']; ?>repartition['count_favorites']; ?>repartition['total'] ?>repartition['count_reads'] ?>repartition['count_unreads'] ?>repartition['count_favorites'] ?>
      -

      averageHour); ?>

      +

      averageHour) ?>

      -

      averageDayOfWeek); ?>

      +

      averageDayOfWeek) ?>

      -

      averageMonth); ?>

      +

      averageMonth) ?>

      @@ -71,4 +71,4 @@ echo htmlspecialchars(json_encode(array( 'months' => $this->months, ), JSON_UNESCAPED_UNICODE), ENT_NOQUOTES, 'UTF-8'); ?> - + diff --git a/app/views/subscription/bookmarklet.phtml b/app/views/subscription/bookmarklet.phtml index 76ac700e0..e6f311f58 100644 --- a/app/views/subscription/bookmarklet.phtml +++ b/app/views/subscription/bookmarklet.phtml @@ -1,17 +1,20 @@ partial('aside_subscription'); ?>
      - + - -

      - + +

      + - -

      -
      browser.contentHandlers.types.number.uri →  'feed', 'a' => 'add'), 'html', true); ?>&url_rss=%s
      + +

      + default_category->name()) ?> +

      +

      +
      browser.contentHandlers.types.number.uri →  'feed', 'a' => 'add'), 'html', true) ?>&url_rss=%s
      - -

      -
       'feed', 'a' => 'add'), 'html', true); ?>&url_rss=%s
      + +

      +
       'feed', 'a' => 'add'), 'html', true) ?>&url_rss=%s
      \ No newline at end of file diff --git a/app/views/subscription/feed.phtml b/app/views/subscription/feed.phtml index 60664fdee..1a167777f 100644 --- a/app/views/subscription/feed.phtml +++ b/app/views/subscription/feed.phtml @@ -9,7 +9,7 @@ if ($this->feed) { } else { ?>
      - - + +
      diff --git a/app/views/subscription/index.phtml b/app/views/subscription/index.phtml index 20f72ad66..da7593a8d 100644 --- a/app/views/subscription/index.phtml +++ b/app/views/subscription/index.phtml @@ -1,77 +1,77 @@ partial('aside_subscription'); ?>
      - + -

      +

      - - + +
      - + - +

      - default_category->name()); ?> + default_category->name()) ?>

      onlyFeedsWithError): ?>

      - +

      -
      +
        -
        - -
      • -
      • + + +
      • +
      - - name(); ?> + + name() ?>
      -
        +
          inError() ? ' error' : ''; $empty = $feed->nbEntries() == 0 ? ' empty' : ''; ?> -
        • - - ✇ name(); ?> + + ✇ name() ?>
        • -
        • +
      @@ -111,16 +111,16 @@
        onlyFeedsWithError): ?> -
      • +
      • -
      • +
      displaySlider ? ' class="active"' : ''; ?> -> -
      > +> +
      > feed)) { $this->renderHelper('feed/update'); diff --git a/app/views/update/apply.phtml b/app/views/update/apply.phtml index 8221929ae..c5e6884db 100644 --- a/app/views/update/apply.phtml +++ b/app/views/update/apply.phtml @@ -1,9 +1,9 @@ partial('aside_configure'); ?>
      - + -

      +

      diff --git a/app/views/update/checkInstall.phtml b/app/views/update/checkInstall.phtml index 33d78cbe7..183f914c0 100644 --- a/app/views/update/checkInstall.phtml +++ b/app/views/update/checkInstall.phtml @@ -1,15 +1,15 @@ partial('aside_configure'); ?>
      - + -

      +

      status_php as $key => $status) { ?> -

      +

      -

      +

      status_files as $key => $status) { ?> -

      - +

      +

      +

      status_database as $key => $status) { ?> -

      - +

      +

      */ ?> diff --git a/app/views/update/index.phtml b/app/views/update/index.phtml index 0599d5b0d..041941e39 100644 --- a/app/views/update/index.phtml +++ b/app/views/update/index.phtml @@ -1,16 +1,16 @@ partial('aside_configure'); ?>
      - + -

      +

      - +

      - last_update_time); ?> + last_update_time) ?>

      message)) { ?> @@ -28,9 +28,9 @@ break; } ?> -

      - message['title']; ?> - message['body']; ?> +

      + message['title'] ?> + message['body'] ?>

      @@ -38,11 +38,11 @@ if (empty($this->message) || $this->message['status'] !== 'good') { ?>

      - +

      update_to_apply) { ?> - +
      diff --git a/app/views/user/manage.phtml b/app/views/user/manage.phtml index d0e5928ef..93d1008b5 100644 --- a/app/views/user/manage.phtml +++ b/app/views/user/manage.phtml @@ -1,98 +1,109 @@ partial('aside_configure'); ?>
      - + -
      - - + + +
      - +
      - +
      - +
      + show_email_field) { ?> +
      + +
      + +
      +
      + +
      - +
      - +
      - - + +
      - - + +
      -
      - - + + +
      - +
      - +
      - /> - + /> +
      - - + +
      - - + +
      -
      - - + + +
      - +
      @@ -104,7 +115,7 @@
      - +
      diff --git a/app/views/user/profile.phtml b/app/views/user/profile.phtml index 83140376d..b8bb5cee9 100644 --- a/app/views/user/profile.phtml +++ b/app/views/user/profile.phtml @@ -1,82 +1,100 @@ -partial('aside_configure'); ?> +disable_aside) { + $this->partial('aside_configure'); + } +?>
      - + -
      - - + + +
      - +
      - - + +
      +
      + + +
      +
      + +
      +
      + + +
      + +
      +
      - +
      - /> - + /> +
      - - + +
      api_enabled) { ?>
      - +
      - /> - + /> +
      - +
      - + token; ?>
      - data-leave-validation=""/> - - 'rss', 'params' => array('user' => Minz_Session::param('currentUser'), 'token' => $token, 'hours' => FreshRSS_Context::$user_conf->since_hours_posts_per_rss)), 'html', true); ?> + data-leave-validation=""/> + + 'rss', 'params' => array('user' => Minz_Session::param('currentUser'), 'token' => $token, 'hours' => FreshRSS_Context::$user_conf->since_hours_posts_per_rss)), 'html', true) ?>
      - - + +
      -
      - - + + + -

      +

      - +

      - +
      @@ -88,9 +106,9 @@ 'php', true )); ?> - - - + + +
      diff --git a/app/views/user/validateEmail.phtml b/app/views/user/validateEmail.phtml new file mode 100644 index 000000000..e525b1b6b --- /dev/null +++ b/app/views/user/validateEmail.phtml @@ -0,0 +1,22 @@ +
      +

      + title) ?> +

      + +

      + mail_login) ?> +

      + +
      + + +
      + +

      + + + +

      +
      diff --git a/app/views/user_mailer/email_need_validation.txt b/app/views/user_mailer/email_need_validation.txt new file mode 100644 index 000000000..c1f4d9911 --- /dev/null +++ b/app/views/user_mailer/email_need_validation.txt @@ -0,0 +1,5 @@ +username) ?> + +site_title) ?> + +validation_url ?> diff --git a/cli/.htaccess b/cli/.htaccess index 9e768397d..32eca30f7 100644 --- a/cli/.htaccess +++ b/cli/.htaccess @@ -1,3 +1,11 @@ -Order Allow,Deny -Deny from all -Satisfy all +# Apache 2.2 + + Order Allow,Deny + Deny from all + Satisfy all + + +# Apache 2.4 + + Require all denied + diff --git a/cli/README.md b/cli/README.md index e9e336439..89b440a39 100644 --- a/cli/README.md +++ b/cli/README.md @@ -35,7 +35,7 @@ cd /usr/share/FreshRSS ./cli/prepare.php # Ensure the needed directories in ./data/ -./cli/do-install.php --default_user admin ( --auth_type form --environment production --base_url https://rss.example.net --language en --title FreshRSS --allow_anonymous --api_enabled --db-type mysql --db-host localhost:3306 --db-user freshrss --db-password dbPassword123 --db-base freshrss --db-prefix freshrss ) +./cli/do-install.php --default_user admin [ --auth_type form --environment production --base_url https://rss.example.net --language en --title FreshRSS --allow_anonymous --api_enabled --db-type mysql --db-host localhost:3306 --db-user freshrss --db-password dbPassword123 --db-base freshrss --db-prefix freshrss ] # --auth_type can be: 'form' (default), 'http_auth' (using the Web server access control), 'none' (dangerous) # --db-type can be: 'sqlite' (default), 'mysql' (MySQL or MariaDB), 'pgsql' (PostgreSQL) # --base_url should be a public (routable) URL if possible, and is used for push (WebSub), for some API functions (e.g. favicons), and external URLs in FreshRSS. @@ -47,26 +47,20 @@ cd /usr/share/FreshRSS ./cli/reconfigure.php # Same parameters as for do-install.php. Used to update an existing installation. -./cli/create-user.php --user username ( --password 'password' --api_password 'api_password' --language en --email user@example.net --token 'longRandomString' --no_default_feeds --purge_after_months 3 --feed_min_articles_default 50 --feed_ttl_default 3600 --since_hours_posts_per_rss 168 --min_posts_per_rss 2 --max_posts_per_rss 400 ) +./cli/create-user.php --user username [ --password 'password' --api_password 'api_password' --language en --email user@example.net --token 'longRandomString' --no_default_feeds --purge_after_months 3 --feed_min_articles_default 50 --feed_ttl_default 3600 --since_hours_posts_per_rss 168 --min_posts_per_rss 2 --max_posts_per_rss 400 ] # --language can be: 'en' (default), 'fr', or one of the [supported languages](../app/i18n/) -./cli/update-user.php --user username ( ... ) +./cli/update-user.php --user username [ ... ] # Same options as create-user.php, except --no_default_feeds which is only available for create-user.php +./cli/actualize-user.php --user username +# Fetch feeds for the specified user + ./cli/delete-user.php --user username ./cli/list-users.php # Return a list of users, with the default/admin user first -./cli/actualize-user.php --user username - -./cli/import-for-user.php --user username --filename /path/to/file.ext -# The extension of the file { .json, .opml, .xml, .zip } is used to detect the type of import - -./cli/export-opml-for-user.php --user username > /path/to/file.opml.xml - -./cli/export-zip-for-user.php --user username ( --max-feed-entries 100 ) > /path/to/file.zip - ./cli/user-info.php -h --user username # -h is to use a human-readable format # --user can be a username, or '*' to loop on all users @@ -74,6 +68,20 @@ cd /usr/share/FreshRSS # 3) the date/time of last user action, 4) the size occupied, # and the number of: 5) categories, 6) feeds, 7) read articles, 8) unread articles, 9) favourites, and 10) tags +./cli/import-for-user.php --user username --filename /path/to/file.ext +# The extension of the file { .json, .opml, .xml, .zip } is used to detect the type of import + +./cli/export-sqlite-for-user.php --user username --filename /path/to/db.sqlite +# Export the user’s database to a new SQLite file. + +./cli/import-sqlite-for-user.php --user username [ --force-overwrite ] --filename /path/to/db.sqlite +# Import the user’s database from an SQLite file. +# --force-overwrite will clear the target user database before import (import only works on an empty user database) + +./cli/export-opml-for-user.php --user username > /path/to/file.opml.xml + +./cli/export-zip-for-user.php --user username [ --max-feed-entries 100 ] > /path/to/file.zip + ./cli/db-optimize.php --user username # Optimize database (reduces the size) for a given user (perform `OPTIMIZE TABLE` in MySQL, `VACUUM` in SQLite) ``` @@ -120,4 +128,4 @@ Example to get the number of feeds of a given user: # Install and updates If you want to administrate FreshRSS using git, please read our [installation docs](https://freshrss.github.io/FreshRSS/en/admins/02_Installation.html) -and [update guidelines](https://freshrss.github.io/FreshRSS/en/admins/03_Updating.html). +and [update guidelines](https://freshrss.github.io/FreshRSS/en/admins/03_Updating.html). diff --git a/cli/_update-or-create-user.php b/cli/_update-or-create-user.php index eda597f19..43b86a4a9 100644 --- a/cli/_update-or-create-user.php +++ b/cli/_update-or-create-user.php @@ -45,8 +45,8 @@ $values = array( 'language' => strParam('language'), 'mail_login' => strParam('email'), 'token' => strParam('token'), - 'old_entries' => intParam('purge_after_months'), - 'keep_history_default' => intParam('feed_min_articles_default'), + 'old_entries' => intParam('purge_after_months'), //TODO: Update with new mechanism + 'keep_history_default' => intParam('feed_min_articles_default'), //TODO: Update with new mechanism 'ttl_default' => intParam('feed_ttl_default'), 'since_hours_posts_per_rss' => intParam('since_hours_posts_per_rss'), 'min_posts_per_rss' => intParam('min_posts_per_rss'), diff --git a/cli/create-user.php b/cli/create-user.php index 29675fa53..7e0a031d9 100755 --- a/cli/create-user.php +++ b/cli/create-user.php @@ -16,11 +16,14 @@ if (preg_grep("/^$username$/i", $usernames)) { echo 'FreshRSS creating user “', $username, "”…\n"; -$ok = FreshRSS_user_Controller::createUser($username, +$ok = FreshRSS_user_Controller::createUser( + $username, + empty($options['mail_login']) ? '' : $options['mail_login'], empty($options['password']) ? '' : $options['password'], empty($options['api_password']) ? '' : $options['api_password'], $values, - !isset($options['no_default_feeds'])); + !isset($options['no_default_feeds']) +); if (!$ok) { fail('FreshRSS could not create user!'); diff --git a/cli/do-install.php b/cli/do-install.php index fd5aa4a3c..dea5d235e 100755 --- a/cli/do-install.php +++ b/cli/do-install.php @@ -3,7 +3,7 @@ require(__DIR__ . '/_cli.php'); if (!file_exists(DATA_PATH . '/do-install.txt')) { - fail('FreshRSS looks to be already installed! Please use `./cli/reconfigure.php` instead.'); + fail('FreshRSS seems to be already installed! Please use `./cli/reconfigure.php` instead.'); } $params = array( @@ -82,10 +82,9 @@ if (file_put_contents(join_path(DATA_PATH, 'config.php'), fail('FreshRSS could not write configuration file!: ' . join_path(DATA_PATH, 'config.php')); } -$config['db']['default_user'] = $config['default_user']; -if (!checkDb($config['db'])) { +if (!checkDb()) { @unlink(join_path(DATA_PATH, 'config.php')); - fail('FreshRSS database error: ' . (empty($config['db']['error']) ? 'Unknown error' : $config['db']['error'])); + fail('FreshRSS database error: ' . (empty($_SESSION['bd_error']) ? 'Unknown error' : $_SESSION['bd_error'])); } echo '• Remember to create the default user: ', $config['default_user'] , "\n", diff --git a/cli/export-sqlite-for-user.php b/cli/export-sqlite-for-user.php new file mode 100755 index 000000000..027d13f38 --- /dev/null +++ b/cli/export-sqlite-for-user.php @@ -0,0 +1,28 @@ +#!/usr/bin/php +dbCopy($filename, FreshRSS_DatabaseDAO::SQLITE_EXPORT); + +done($ok); diff --git a/cli/i18n/ignore/en.php b/cli/i18n/ignore/en.php index e231afdda..db5a06a5e 100644 --- a/cli/i18n/ignore/en.php +++ b/cli/i18n/ignore/en.php @@ -67,7 +67,6 @@ return array( 'conf.sharing.diaspora', 'conf.sharing.email', 'conf.sharing.facebook', - 'conf.sharing.g+', 'conf.sharing.print', 'conf.sharing.shaarli', 'conf.sharing.twitter', @@ -88,7 +87,6 @@ return array( 'gen.share.diaspora', 'gen.share.email', 'gen.share.facebook', - 'gen.share.g+', 'gen.share.movim', 'gen.share.print', 'gen.share.shaarli', @@ -98,6 +96,7 @@ return array( 'gen.share.jdh', 'gen.share.Known', 'gen.share.gnusocial', + 'gen.share.lemmy', 'index.menu.non-starred', 'index.menu.read', 'index.menu.starred', diff --git a/cli/i18n/ignore/fr.php b/cli/i18n/ignore/fr.php index 0ac2e8758..8ed8ffff1 100644 --- a/cli/i18n/ignore/fr.php +++ b/cli/i18n/ignore/fr.php @@ -8,7 +8,6 @@ return array( 'conf.sharing.blogotext', 'conf.sharing.diaspora', 'conf.sharing.facebook', - 'conf.sharing.g+', 'conf.sharing.print', 'conf.sharing.shaarli', 'conf.sharing.twitter', @@ -35,7 +34,6 @@ return array( 'gen.share.blogotext', 'gen.share.diaspora', 'gen.share.facebook', - 'gen.share.g+', 'gen.share.movim', 'gen.share.shaarli', 'gen.share.twitter', @@ -43,6 +41,7 @@ return array( 'gen.share.wallabagv2', 'gen.share.jdh', 'gen.share.gnusocial', + 'gen.share.lemmy', 'index.about.agpl3', 'index.about.version', 'index.log._', diff --git a/cli/i18n/ignore/kr.php b/cli/i18n/ignore/kr.php index bbccb2d7d..2fa867cbe 100644 --- a/cli/i18n/ignore/kr.php +++ b/cli/i18n/ignore/kr.php @@ -4,7 +4,6 @@ return array( 'conf.sharing.blogotext', 'conf.sharing.diaspora', 'conf.sharing.facebook', - 'conf.sharing.g+', 'conf.sharing.shaarli', 'conf.sharing.twitter', 'conf.sharing.wallabag', @@ -39,7 +38,6 @@ return array( 'gen.share.blogotext', 'gen.share.diaspora', 'gen.share.facebook', - 'gen.share.g+', 'gen.share.gnusocial', 'gen.share.jdh', 'gen.share.linkedin', @@ -51,6 +49,7 @@ return array( 'gen.share.twitter', 'gen.share.wallabag', 'gen.share.wallabagv2', + 'gen.share.lemmy', 'index.about.agpl3', 'sub.api.title', ); diff --git a/cli/i18n/ignore/nl.php b/cli/i18n/ignore/nl.php index 4013bc89e..dc4dd9356 100644 --- a/cli/i18n/ignore/nl.php +++ b/cli/i18n/ignore/nl.php @@ -1,15 +1,18 @@ dbCopy($filename, FreshRSS_DatabaseDAO::SQLITE_IMPORT, $clearFirst); +if (!$ok) { + echo 'If you would like to clear the user database first, use the option --force-overwrite', "\n"; +} +invalidateHttpCache($username); + +done($ok); diff --git a/cli/prepare.php b/cli/prepare.php index 81fb53f85..7e8ea051d 100755 --- a/cli/prepare.php +++ b/cli/prepare.php @@ -28,9 +28,17 @@ if (!is_file(DATA_PATH . '/config.php')) { } file_put_contents(DATA_PATH . '/.htaccess', -"Order Allow,Deny\n" . -"Deny from all\n" . -"Satisfy all\n" +"# Apache 2.2\n" . +"\n" . +" Order Allow,Deny\n" . +" Deny from all\n" . +" Satisfy all\n" . +"\n" . +"\n" . +"# Apache 2.4\n" . +"\n" . +" Require all denied\n" . +"\n" ); accessRights(); diff --git a/cli/update-user.php b/cli/update-user.php index 7eb3e81ff..8067dadd3 100755 --- a/cli/update-user.php +++ b/cli/update-user.php @@ -9,6 +9,7 @@ echo 'FreshRSS updating user “', $username, "”…\n"; $ok = FreshRSS_user_Controller::updateUser( $username, + empty($options['mail_login']) ? null : $options['mail_login'], empty($options['password']) ? '' : $options['password'], empty($options['api_password']) ? '' : $options['api_password'], $values); diff --git a/config-user.default.php b/config-user.default.php index 08f3af720..5b49d689a 100644 --- a/config-user.default.php +++ b/config-user.default.php @@ -1,11 +1,21 @@ 'en', - 'old_entries' => 3, - 'keep_history_default' => 50, + 'archiving' => [ + 'keep_period' => 'P3M', + 'keep_max' => 200, + 'keep_min' => 50, + 'keep_favourites' => true, + 'keep_labels' => true, + 'keep_unreads' => false, + ], 'ttl_default' => 3600, 'mail_login' => '', + 'email_validation_token' => '', 'token' => '', 'passwordHash' => '', 'apiPasswordHash' => '', @@ -67,6 +77,7 @@ return array ( ), 'topline_read' => true, 'topline_favorite' => true, + 'topline_display_authors' => false, 'topline_date' => true, 'topline_link' => true, 'bottomline_read' => true, diff --git a/config.default.php b/config.default.php index 49f0cf1af..d885a8dea 100644 --- a/config.default.php +++ b/config.default.php @@ -33,6 +33,13 @@ return array( # Name of the user that has administration rights. 'default_user' => '_', + # Force users to validate their email address. If `true`, an email with a + # validation URL is sent during registration, and users cannot access their + # feed if they didn't access this URL. + # Note: it is recommended to not enable it with PHP < 5.5 (emails cannot be + # sent). + 'force_email_validation' => false, + # Allow or not visitors without login to see the articles # of the default user. 'allow_anonymous' => false, @@ -116,32 +123,65 @@ return array( //CURLOPT_PROXYUSERPWD => 'user:password', ), - 'db' => array( + 'db' => [ - # Type of database: `sqlite` or `mysql`. + # Type of database: `sqlite` or `mysql` or 'pgsql' 'type' => 'sqlite', - # MySQL host. + # Database server 'host' => 'localhost', - # MySQL user. + # Database user 'user' => '', - # MySQL password. + # Database password 'password' => '', - # MySQL database. + # Database name 'base' => '', - # MySQL table prefix. + # Tables prefix (useful if you use the same database for multiple things) 'prefix' => 'freshrss_', - 'pdo_options' => array( + # Additional connection string parameters, such as PostgreSQL 'sslmode=??;sslrootcert=??' + # https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS + 'connection_uri_params' => '', + + # Additional PDO parameters, such as offered by MySQL https://php.net/ref.pdo-mysql + 'pdo_options' => [ //PDO::MYSQL_ATTR_SSL_KEY => '/path/to/client-key.pem', //PDO::MYSQL_ATTR_SSL_CERT => '/path/to/client-cert.pem', //PDO::MYSQL_ATTR_SSL_CA => '/path/to/ca-cert.pem', + ], + + ], + + # Configure the default feeds to which users will automatically be subscribed. + 'default_feeds' => array( + array( + 'url' => 'https://github.com/FreshRSS/FreshRSS/releases.atom', + 'name' => 'FreshRSS releases', + 'website' => 'https://github.com/FreshRSS/FreshRSS/', + 'description' => 'FreshRSS releases @ GitHub', ), + ), + # Configuration to send emails. Be aware that PHP < 5.5 are not supported. + # These options are basically a mapping of the PHPMailer class attributes + # from the PHPMailer library. + # + # See http://phpmailer.github.io/PHPMailer/classes/PHPMailer.PHPMailer.PHPMailer.html#properties + 'mailer' => 'mail', // 'mail' or 'smtp' + 'smtp' => array( + 'hostname' => '', // the domain used in the Message-ID header + 'host' => 'localhost', // the SMTP server address + 'port' => 25, + 'auth' => false, + 'auth_type' => '', // 'CRAM-MD5', 'LOGIN', 'PLAIN', 'XOAUTH2' or '' + 'username' => '', + 'password' => '', + 'secure' => '', // '', 'ssl' or 'tls' + 'from' => 'root@localhost', ), # List of enabled FreshRSS extensions. diff --git a/constants.php b/constants.php index 2a31f71d5..1228e5c29 100644 --- a/constants.php +++ b/constants.php @@ -2,7 +2,7 @@ //NB: Do not edit; use ./constants.local.php instead. // -define('FRESHRSS_VERSION', '1.14.3'); +define('FRESHRSS_VERSION', '1.15.0'); define('FRESHRSS_WEBSITE', 'https://freshrss.org'); define('FRESHRSS_WIKI', 'https://freshrss.github.io/FreshRSS/'); @@ -32,6 +32,7 @@ safe_define('FRESHRSS_USERAGENT', 'FreshRSS/' . FRESHRSS_VERSION . ' (' . PHP_OS // PHP text output compression http://php.net/ob_gzhandler (better to do it at Web server level) safe_define('PHP_COMPRESSION', false); +safe_define('COPY_LOG_TO_STDERR', filter_var(getenv('COPY_LOG_TO_STDERR'), FILTER_VALIDATE_BOOLEAN)); // For cases when syslog is not available safe_define('COPY_SYSLOG_TO_STDERR', filter_var(getenv('COPY_SYSLOG_TO_STDERR'), FILTER_VALIDATE_BOOLEAN)); diff --git a/data/.gitignore b/data/.gitignore index 0410f3797..862bf4ad1 100644 --- a/data/.gitignore +++ b/data/.gitignore @@ -1,7 +1,9 @@ .htpasswd config.php config.php.bak.php +config-user.custom.php force-https.txt last_update.txt no-cache.txt update.php +tos.html diff --git a/data/.htaccess b/data/.htaccess index 9e768397d..32eca30f7 100644 --- a/data/.htaccess +++ b/data/.htaccess @@ -1,3 +1,11 @@ -Order Allow,Deny -Deny from all -Satisfy all +# Apache 2.2 + + Order Allow,Deny + Deny from all + Satisfy all + + +# Apache 2.4 + + Require all denied + diff --git a/data/tos.example.html b/data/tos.example.html new file mode 100644 index 000000000..370302b16 --- /dev/null +++ b/data/tos.example.html @@ -0,0 +1,56 @@ +

      Terms of Service

      + +

      Article 1: Lorem ipsum

      + +

      + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed fringilla, + magna non luctus lobortis, ipsum quam bibendum ipsum, ac rhoncus ipsum + velit in dolor. Vivamus quis ultrices sapien. Maecenas imperdiet massa + felis, ut ullamcorper arcu fermentum at. Mauris vitae tempor mi. Nullam ut + fermentum tortor, a luctus neque. Aenean imperdiet, leo vel blandit + molestie, lorem risus suscipit sem, et facilisis tellus elit ut magna. Ut + mattis blandit quam vitae interdum. Vestibulum quis pharetra ex, nec + efficitur erat. Vivamus hendrerit quam non facilisis lacinia. Sed tincidunt + consectetur maximus. Quisque eleifend risus vel felis hendrerit vulputate. + Nulla quis dapibus sapien, eget egestas quam. Nulla facilisi. Pellentesque + tincidunt elementum ultricies. Praesent mauris ante, ultrices quis dui ut, + ornare lacinia lectus. Etiam neque massa, congue in gravida et, mollis + egestas eros. +

      + +

      Article 2: Nunc Vel Diam

      + +

      + Nunc vel diam sollicitudin turpis pharetra lobortis. Donec id mattis ipsum, + non porta sem. Maecenas fermentum velit quis sem lacinia egestas. Praesent + vulputate nulla in lectus tempus, maximus suscipit felis molestie. Sed ac + suscipit mi. Maecenas at pretium nibh, a elementum elit. Ut pretium turpis + tempor maximus ullamcorper. Vivamus ipsum velit, volutpat vitae varius ut, + dignissim eget mi. Nunc id erat facilisis, vestibulum lacus in, ultricies + purus. Mauris id ligula tempor, venenatis dolor at, mattis magna. Duis + luctus dui ut est porttitor, id pulvinar enim tempus. +

      + +

      Article 3: Ut Non Leo Commodo

      + +

      + Ut non leo commodo, tempus mauris ac, pulvinar nunc. Fusce facilisis purus + et est ornare, eget tempor purus porttitor. Donec ut leo in diam sodales + ullamcorper. Sed efficitur nisl sit amet ante euismod, id dapibus dui + mollis. In sagittis eget dolor id pharetra. Suspendisse pellentesque + ultricies volutpat. Pellentesque pretium quam quis ligula lobortis + convallis. Donec vel erat elementum, varius metus a, egestas turpis. + Aliquam porttitor ut dolor et volutpat. Aliquam pretium, enim quis suscipit + bibendum, risus diam convallis tellus, et cursus odio felis quis nibh. + Vivamus fringilla hendrerit massa, eget varius odio ultrices vestibulum. Ut + vehicula eget tortor quis sodales. +

      + +

      + Pellentesque pulvinar ex vel metus volutpat, a pretium libero aliquet. In + fringilla nisi ac lorem hendrerit, non volutpat elit condimentum. + Pellentesque congue luctus purus id porta. Suspendisse nec elementum urna. + Maecenas sollicitudin a tortor nec finibus. Donec malesuada, lectus + blandit sodales egestas, tortor ipsum cursus eros, eget pellentesque erat + nunc ut nulla. Vivamus porttitor consectetur felis at luctus. +

      diff --git a/docs/assets/css/style.scss b/docs/assets/css/style.scss index f7fff902b..17ced012f 100644 --- a/docs/assets/css/style.scss +++ b/docs/assets/css/style.scss @@ -1,13 +1,10 @@ ---- ---- - @import "{{ site.theme }}"; .page-header .project-name a { - color: #fff; + color: #fff; - &:hover { - text-decoration: none; - opacity: .7; - } + &:hover { + text-decoration: none; + opacity: .7; + } } diff --git a/docs/en/admins/01_Index.md b/docs/en/admins/01_Index.md index 45ed02c0f..49cd247d5 100644 --- a/docs/en/admins/01_Index.md +++ b/docs/en/admins/01_Index.md @@ -1,9 +1,19 @@ -# FreshRSS administration +# FreshRSS Administration -Learn how to install, update and backup FreshRSS and how to use the command line tools. +Learn how to install, update, and backup FreshRSS, as well as how to use the command line tools. -* [Install FreshRSS](02_Installation.md) on your server -* [Update your installation](03_Updating.md) to the latest stable or dev version -* [The command line interface](https://github.com/FreshRSS/FreshRSS/tree/master/cli) can be used to administrate feeds and users -* [Automatic feed updates](https://github.com/FreshRSS/FreshRSS#automatic-feed-update) using cron is the preferred way to get the latest feeds entries +1. [Prerequisites](02_Prerequisites.md): What you'll need to run FreshRSS +2. [General installation instructions](03_Installation.md) for FreshRSS +3. [Update your installation](04_Updating.md) to the latest stable or development version + +## Tutorials and Examples + +* [Backing up FreshRSS](05_Backup.md) +* [Installation on Debian 9/Ubuntu 16.04](06_LinuxInstall.md) +* [Updating on Debian 9/Ubuntu 16.04](07_LinuxUpdate.md) +* [Setting Up Automatic Feed Updating](08_FeedUpdates.md) +* [Access Control](09_AccessControl.md) +* [Apache/Nginx configuration files](10_ServerConfig.md) +* [Using the command line interface (CLI)](https://github.com/FreshRSS/FreshRSS/tree/master/cli) +* [Configuring the email address validation](05_Configuring_email_validation.md) * [Frequently asked questions](04_Frequently_Asked_Questions.md) diff --git a/docs/en/admins/02_Installation.md b/docs/en/admins/02_Installation.md deleted file mode 100644 index 8cb15edb4..000000000 --- a/docs/en/admins/02_Installation.md +++ /dev/null @@ -1,147 +0,0 @@ -# Server requirements - -FreshRSS is a web application. This means you’ll need a web server to run it. FreshRSS requirements are really low, so it could run on most shared host servers. - -You need to verify that your server can run FreshRSS before installing it. If your server has the proper requirements and FreshRSS does not work, please contact us to find a solution. - -| Software | Recommended | Works also with | -| ----------- | ---------------- | ----------------------------- | -| Web server | **Apache 2** | Nginx | -| PHP | **PHP 5.5+** | PHP 5.3.8+ | -| PHP modules | Required: libxml, cURL, PDO_MySQL, PCRE and ctype.
      Required (32-bit only): GMP
      Recommanded: JSON, Zlib, mbstring, iconv, ZipArchive
      *For the whole modules list see [Dockerfile](https://github.com/FreshRSS/FreshRSS/blob/744a9e8cf00aef7dec0acfa5f90f0dcfa2ef8837/Docker/Dockerfile-Alpine#L7-L9)* | | -| Database | **MySQL 5.5.3+** | SQLite 3.7.4+ | -| Browser | **Firefox** | Chrome, Opera, Safari, or IE11+ | - -## Important notice - -FreshRSS **CAN** work with PHP 5.3.8+. To do so, we are using specific functions available in the [''password_compat'' library](https://github.com/ircmaxell/password_compat#requirements) for the form authentication. - - -# Getting the appropriate version of FreshRSS - -FreshRSS has three different releases or branches. Each branch has its own release frequency. So it is better if you spend some time to understand the purpose of each release. - -## Stable release - -[Download](https://github.com/FreshRSS/FreshRSS/archive/master.zip) - -This release is done when we consider that our goal concerning the new features and the stability is reached. It could happen that we make two releases in a really short time if we have a really good coding pace. In reality, we are all working on our spare time, so we release every few months. But this version is really stable, tested thoroughly and you should not face any major bugs. - -## Development release - -[Download](https://github.com/FreshRSS/FreshRSS/archive/dev.zip) - -As its name suggests, it is the working release for developers. **This release is unstable!** If you want to keep track of enhancements on a daily basis, you can use it. But keep in mind that you need to follow the branch activity on Github (via [the branch RSS feed](https://github.com/FreshRSS/FreshRSS/commits/dev.atom) for instance). Some say that the main developers use it on a daily basis without problem. They may know what they are doing… - -# Apache installation - -This is an example Apache virtual hosts configuration file. It covers HTTP and HTTPS configuration. - -``` - - DocumentRoot /var/www/html/ - - #Default site... - - ErrorLog ${APACHE_LOG_DIR}/error.default.log - CustomLog ${APACHE_LOG_DIR}/access.default.log vhost_combined - - - - ServerName rss.example.net - DocumentRoot /path/to/FreshRSS/p/ - - - AllowOverride AuthConfig FileInfo Indexes Limit - Require all granted - - - ErrorLog ${APACHE_LOG_DIR}/freshrss_error.log - CustomLog ${APACHE_LOG_DIR}/freshrss_access.log combined - - AllowEncodedSlashes On - - - - - ServerName rss.example.net - DocumentRoot /path/to/FreshRSS/p/ - - - AllowOverride AuthConfig FileInfo Indexes Limit - Require all granted - - - ErrorLog ${APACHE_LOG_DIR}/freshrss_error.log - CustomLog ${APACHE_LOG_DIR}/freshrss_access.log combined - - - Protocols h2 http/1.1 - - - # For the API - AllowEncodedSlashes On - - SSLEngine on - SSLCompression off - SSLCertificateFile /path/to/server.crt - SSLCertificateKeyFile /path/to/server.key - # Additional SSL configuration, e.g. with LetsEncrypt - - -``` - -# Nginx installation - -This is an example nginx configuration file. It covers HTTP, HTTP, and php-fpm configuration. - -_You can find simpler config file but they may be incompatible with FreshRSS API._ - -``` -server { - listen 80; - listen 443 ssl; - - # HTTPS configuration - ssl on; - ssl_certificate /etc/nginx/server.crt; - ssl_certificate_key /etc/nginx/server.key; - - # your server’s URL(s) - server_name rss.example.net; - - # the folder p of your FreshRSS installation - root /srv/FreshRSS/p/; - - index index.php index.html index.htm; - - # nginx log files - access_log /var/log/nginx/rss.access.log; - error_log /var/log/nginx/rss.error.log; - - # php files handling - # this regex is mandatory because of the API - location ~ ^.+?\.php(/.*)?$ { - fastcgi_pass unix:/var/run/php/php7.0-fpm.sock; - fastcgi_split_path_info ^(.+\.php)(/.*)$; - # By default, the variable PATH_INFO is not set under PHP-FPM - # But FreshRSS API greader.php need it. If you have a “Bad Request” error, double check this var! - fastcgi_param PATH_INFO $fastcgi_path_info; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - } - - location / { - try_files $uri $uri/ index.php; - } -} -``` - -A step-by-step tutorial is available [in French](http://www.pihomeserver.fr/2013/05/08/raspberry-pi-home-server-installer-un-agregateur-de-flux-rss-pour-remplacer-google-reader/). - -# Security - -Make sure to expose only the `./p/` folder on the web, the other directories contain personal and sensitive data. -See the Apache and nginx config examples above. - -**TODO** diff --git a/docs/en/admins/02_Prerequisites.md b/docs/en/admins/02_Prerequisites.md new file mode 100644 index 000000000..0fc0c9a11 --- /dev/null +++ b/docs/en/admins/02_Prerequisites.md @@ -0,0 +1,40 @@ +# Server Requirements + +FreshRSS is a web application. This means you’ll need a web server to run it. FreshRSS requirements are really low, so it should run on most shared host servers, or any old computer you happen to have on hand. + +You need to verify that your server can run FreshRSS before installing it. If your server has the proper requirements and FreshRSS does not work, please contact us to find a solution. + +| Software | Recommended | Also Works With | +| ------------- | ----------------------- | ----------------------- | +| Web server | **Apache 2** | Nginx | +| PHP | **PHP 7+** | PHP 5.6+ | +| PHP modules | Required: libxml, cURL, JSON, PDO\_MySQL, PCRE and ctype.
      Required (32-bit only): GMP
      Recommanded: Zlib, mbstring, iconv, ZipArchive
      *For the whole modules list see [Dockerfile](https://github.com/FreshRSS/FreshRSS/blob/master/Docker/Dockerfile-Alpine#L7-L9)* | | +| Database | **MySQL 5.5.3+** | SQLite 3.7.4+, PostgreSQL 9.5+ | +| Browser | **Firefox** | Chrome, Opera, Safari, or IE11/Edge[^1] | + + +# Getting the appropriate version of FreshRSS + +FreshRSS has three different releases or branches. Each branch has its own release frequency. So it is better if you spend some time to understand the purpose of each release. + +## Stable release + +[Download](https://github.com/FreshRSS/FreshRSS/archive/master.zip) + +This version is really stable, tested thoroughly, and you should not face any major bugs. + +Stable releases are not released on a set schedule. Rather, they are released whenever we consider that our goal for new features is reached, and the software is stable. + +It could happen that we make two releases in a short span of time if we have a really good coding pace. In reality, we are all working on the project in our spare time, so a new release usually occurs every few months. + +## Development version + +[Download](https://github.com/FreshRSS/FreshRSS/archive/dev.zip) + +As its name suggests, the development version is the working codebase, intended for developers. **This release may be unstable!** + +If you want to keep track of the most recent enhancements or help the developers with bug reports, this is the branch for you. If you use this version, please keep in mind that you need to follow the branch activity on Github (via [the branch RSS feed](https://github.com/FreshRSS/FreshRSS/commits/dev.atom), for instance), and manually pull new commits. + +Some say that the main developers use this branch on a daily basis without problem. They may know what they are doing… + +[^1]: IE11/Edge may not support all features found in FreshRSS on other browsers diff --git a/docs/en/admins/03_Installation.md b/docs/en/admins/03_Installation.md new file mode 100644 index 000000000..e999a69ad --- /dev/null +++ b/docs/en/admins/03_Installation.md @@ -0,0 +1,23 @@ +# General Installation Instructions + +These instructions are intended as general guidelines for installing FreshRSS. You may wish to consult the [Step-by-step Tutorial for installing FreshRSS on Debian 9/Ubuntu 16.04](06_LinuxInstall.md) if you don't currently have a web server and don't have experience setting one up. + +Before you begin, make sure that you've read the [prerequisites](02_Prerequisites.md) for running FreshRSS. As shorthand, `.` refers to the directory to which your FreshRSS installation lives. + +1. If the computer you're running on is not currently running a web server, you'll first need to install and configure a web server, a version of PHP, and an appropriate database, as listed in the prerequisites. [Example Apache and Nginx configuration files can be found here](10_ServerConfig.md). + +2. Download your chosen version of FreshRSS, or fetch it via git. It's advisable that you put FreshRSS in `/usr/share/`, and symlink the `./p/` folder to the root of your web server.[^1] + +3. Give ownership of the FreshRSS folder to your web server user (often `www-data`). Give group read permissions to all files in `.`[^2], and group write permissions to `./data/`. + +4. Install needed PHP modules. + +5. Create a database for FreshRSS to use. Note the username and password for this database, as it will be needed during installation! + +6. Using your supported web browser of choice, navigate to the address you've installed your server to complete the installation from the GUI.[^3] + +[^1]: Make sure to expose only the `./p/` folder to the Web, as the other directories contain personal and sensitive data. + +[^2]: If you wish to allow updates from the web interface, also give group write permissions to this folder. + +[^3]: Assuming your server is `http://example.net`, this address could be `http://example.net/p/` if you didn't follow our previous advice about not exposing the `./p/` folder. diff --git a/docs/en/admins/03_Updating.md b/docs/en/admins/03_Updating.md deleted file mode 100644 index 4e1fdfa5d..000000000 --- a/docs/en/admins/03_Updating.md +++ /dev/null @@ -1,90 +0,0 @@ - -First things first: we recommend to create a backup before updating: - -```sh -# Perform all commands below in your FreshRSS directory: -cd /usr/share/FreshRSS - -tar -czvf FreshRSS-backup.tgz . -``` - -The update process depends on your installation type, see below: - - -## Using the web admin panel - -Change to your installation at http://localhost/FreshRSS/p/i/?c=update and hit the "Check for new updates" button. - -If there is a new version you will be prompted again. - - -## Using git - -If you manage FreshRSS via command line, then installing and updating FreshRSS can be done via git: - -```sh -# If your local user does not have write access, prefix all commands by sudo: -sudo ... - -# Perform all commands below in your FreshRSS directory: -cd /usr/share/FreshRSS - -# Use the development version of FreshRSS -git checkout -b dev origin/dev - -# Check out a specific version of FreshRSS -# See release names on https://github.com/FreshRSS/FreshRSS/releases -# You will then need to manually change version -# or checkout master or dev branch to get new versions -git checkout 1.7.0 - -# Verify what branch is used -git branch - -# Check whether there is a new version of FreshRSS, -# assuming you are on the /master or /dev branch -git fetch --all -git status - -# Discard manual changes (do a backup before) -git reset --hard -# Then re-delete the file forcing the setup wizard -rm data/do-install.txt - -# Delete manual additions (do a backup before) -git clean -f -d - -# Update to a newer version of FreshRSS, -# assuming you are on the /master or /dev branch -git pull - -# Set the rights so that your Web server can access the files -# (Example for Debian / Ubuntu) -chown -R :www-data . && chmod -R g+r . && chmod -R g+w ./data/ -``` - - -## Using the zip archive - -Perform all commands in your FreshRSS directory: -```sh -cd /usr/share/FreshRSS -``` - -Commands intended to be executed in order (you can c/p the whole block if desired): - -```sh -wget https://github.com/FreshRSS/FreshRSS/archive/master.zip -unzip master.zip -cp -R FreshRSS-master/* . -chown -R :www-data . && chmod -R g+r . && chmod -R g+w ./data/ -rm -f master.zip -rm -f data/do-install.txt -rm -rf FreshRSS-master/ -``` - -Short explanation of the commands above: -* Download the latest version and unzip it -* Overwrite all your existing files with the new ones -* Fix possible permission issues -* Cleanup by deleting the downloaded zip, the file forcing the setup wizard and the temporary directory diff --git a/docs/en/admins/04_Frequently_Asked_Questions.md b/docs/en/admins/04_Frequently_Asked_Questions.md index 8a2ead73e..cbabc3867 100644 --- a/docs/en/admins/04_Frequently_Asked_Questions.md +++ b/docs/en/admins/04_Frequently_Asked_Questions.md @@ -1,3 +1,5 @@ +# Frequently Asked Questions + We may not have answered all of your questions in the previous sections. The FAQ contains some questions that have not been answered elsewhere. ## Promoting a user to admin diff --git a/docs/en/admins/04_Updating.md b/docs/en/admins/04_Updating.md new file mode 100644 index 000000000..30a6ba1f7 --- /dev/null +++ b/docs/en/admins/04_Updating.md @@ -0,0 +1,31 @@ +# Backing Up and Updating FreshRSS + +The following is general procedure; [specific commands for Linux may be found here](07_LinuxUpdate.md). + +## Backing Up + +Before you update to a new version of FreshRSS, it's always a good idea to backup your current installation. Simply make an archive of your FreshRSS directory, which can be restored if needed by following the "Updating from a Zip Archive" section at the bottom of this document. + +## Updating From the Web + +If you enabled web updates from your installation (see footnote 2 in [installation](03_Installation.md)), you can log into your admin account, select the update option under Administration in the Settings dropdown found on the top right of the webpage, and press the "Check for new updates" button. Alternatively, this page can be reached directly at `http:///i/?c=update`. + +This will check for and apply a new Stable version, if available. + +## Updating Using git + +If you installed FreshRSS using git, you can update, change branches, or switch to a specific version from the command line. + +Generally, the update procedure via git works as follows: + +1. Making sure you're in your FreshRSS install directory, and fetch updates. +2. Checkout the branch you wish to use. +3. Perform a hard reset to discard local changes. +4. Delete manual additions. Be sure to move your backup out of the directory before doing this step! +5. Pull the new version. +6. Remove `./data/do-install.txt`. +7. Re-set group read (and write, if you wish) permissions on all files in `.`, and group write permissions on `./data/`. + +## Updating from a Zip Archive + +Updating to a new version from a zip archive is always an option. Begin by unzipping the archive into your FreshRSS directory, overwriting old files, remove `./data/do-install.txt`, and finally re-set group read (and write, if you wish) permissions on all files in `.` and group write permissions on `./data/`. diff --git a/docs/en/admins/05_Backup.md b/docs/en/admins/05_Backup.md new file mode 100644 index 000000000..f3ead0cca --- /dev/null +++ b/docs/en/admins/05_Backup.md @@ -0,0 +1,55 @@ +# Backup + +This tutorial demonstrates commands for backing up FreshRSS. It assumes that your main FreshRSS directory is `/usr/share/FreshRSS`; If you've installed it somewhere else, substitute your path as necessary. + +## Installation Backup + +Do this before an upgrade. + +### Creating a Backup + +First, Enter the directory you wish to save your backup to. Here, for example, we'll save the backup to the user home directory +``` +cd ~ +``` + +Next, we'll create a gzipped tar archive of the FreshRSS directory. The following command will archive the entire contents of your FreshRSS installation in it's current state. +``` +tar -czf FreshRSS-backup.tgz -C /usr/share/FreshRSS/ . +``` + +And you're done! + +### Restoring from a Backup + +First, copy the backup previously made into your FreshRSS directory +``` +cp ~/FreshRSS-backup.tgz /usr/share/FreshRSS/ +``` + +Next, change to your FreshRSS directory +``` +cd /usr/share/FreshRSS/ +``` + +Extract the backup +``` +tar -xzf FreshRSS-backup.tgz +``` + +And optionally, as cleanup, remove the copy of your backup from the FreshRSS directory +``` +rm FreshRSS-backup.tgz +``` + +## Backing up Feeds + +### Feed list Export +You can export your feed list in OPML format either from the web interface, or from the [Command-Line Interface](https://github.com/FreshRSS/FreshRSS/blob/master/cli/README.md). + +### Saving Articles + +To save articles, you can use [phpMyAdmin](https://www.phpmyadmin.net/) or MySQL tools, where `` is your database username, `` is the hostname of your web server containing your FreshRSS database, and `` is the database used by FreshRSS: +``` +mysqldump --skip-comments --disable-keys --user= --password --host --result-file=freshrss.dump.sql --databases +``` diff --git a/docs/en/admins/05_Configuring_email_validation.md b/docs/en/admins/05_Configuring_email_validation.md new file mode 100644 index 000000000..fe073545c --- /dev/null +++ b/docs/en/admins/05_Configuring_email_validation.md @@ -0,0 +1,70 @@ +# Configuring the email address validation + +FreshRSS can verify that users give a valid email address. It is not configured +by default so you'll have to follow these few steps to verify email addresses. + +It is intended to administrators who host users and want to be sure to be able +to contact them. + +## Force email validation + +In your `data/config.php` file, you'll find a `force_email_validation` item: +set it to `true`. An email field now appears on the registration page and +emails are sent when users change their email. + +You can also enable this feature directly in FreshRSS: `Administration` > +`System configuration` > check `Force email addresses validation`. + +## Configure the SMTP server + +By default, FreshRSS will attempt to send emails with the [`mail`](https://www.php.net/manual/en/function.mail.php) +function of PHP. It is the simpler solution but it might not work as expected. +For example, we don't support (yet?) sending emails from inside our official +Docker images. We recommend to use a proper SMTP server. + +To configure a SMTP server, you'll have to modify the `data/config.php` file. + +First, change the `mailer` item to `smtp` (instead of the default `mail`). + +Then, you should change the `smtp` options like you would do with a regular +email client. You can find the full list of options in the [`config.default.php` file](/config.default.php). +If you're not sure to what each item is corresponding, you may find useful [the +PHPMailer documentation](http://phpmailer.github.io/PHPMailer/classes/PHPMailer.PHPMailer.PHPMailer.html#properties) +(which is used by FreshRSS under the hood). + +## Check your SMTP server is correctly configured + +To do so, once you've enabled the `force_email_validation` option, you only +need to change your email address on the profile page and check that an email +arrives on the new address. + +If it fails, you can change the environment (in `data/config.php` file, change +`production` to `development`). PHPMailer will become more verbose and you'll +be able to see what happens in the PHP logs. If something's wrong here, you'll +probably better served by asking to your favorite search engine than asking us. +If you think that something's wrong in FreshRSS code, don't hesitate to open a +ticket though. + +Also, make sure the email didn't arrive in your spam. + +Once you're done, don't forget to reconfigure your environment to `production`. + +## Access the validation URL during development + +You might find painful to configure a SMTP server when you're developping and +`mail` function will not work on your local machine. For the moment, there is +no easy way to access the validation URL unless forging it. You'll need to +information: + +- the username of the user to validate (you should know it) +- its validation token, that you'll find in its configuration file: + +```console +$ # For instance, for a user called `alice` +$ grep email_validation_token data/users/alice/config.php | cut -d \' -f 4 - +3d75042a4471994a0346e18ae87602f19220a795 +``` + +Then, the validation URL should be `http://localhost:8080/i/?c=user&a=validateEmail&username=alice&token=3d75042a4471994a0346e18ae87602f19220a795` + +Don't forget to adapt this URL with the correct port, username and token. diff --git a/docs/en/admins/06_LinuxInstall.md b/docs/en/admins/06_LinuxInstall.md new file mode 100644 index 000000000..d31758556 --- /dev/null +++ b/docs/en/admins/06_LinuxInstall.md @@ -0,0 +1,116 @@ +# Installation on Debian 9/Ubuntu 16.04 + +This tutorial will give you step-by-step commands to install the latest stable release of FreshRSS with Apache and MySQL using git. It's always recommended that you [backup your installation](05_Backup.md) before updating + +Please note: Commands need to be run as an administrator; either perform the following from a sudo shell (`sudo -s`) or use an administrator account. + +## Part 1: Setting up and configuring the LAMP stack +Begin by installing Apache, and enable Apache modules needed for FreshRSS +``` +apt install apache2 +a2enmod headers expires rewrite ssl +``` + +Then, you have to configure Apache. You can create a file in `/etc/apache2/sites-available`, based on [our example configuration file](10_ServerConfig.md). Once you're done, create a symbolic link from this file to the `sites-enabled` folder: + +``` +ln -s /etc/apache2/sites-available/freshrss.conf /etc/apache2/sites-enabled/freshrss.conf +``` + +Next, install PHP and the necessary modules +``` +apt install php php-curl php-gmp php-intl php-mbstring php-sqlite3 php-xml php-zip +``` + +Install the PHP module for Apache +``` +apt install libapache2-mod-php +``` + +Next, we'll need to install and configure MySQL. Install MySQL components like so: +``` +sudo apt install mysql-server mysql-client php-mysql +``` + +MySQL must now be started: +``` +service mysql-server start +``` + +We'll need to configure MySQL. +**Note:** As you've just installed mysql, there will be no root password; simply hit enter on the first step +``` +mysql_secure_installation +``` + +And restart it +``` +service mysql-server restart +``` + +Finally, restart MySQL and the web server +``` + +service apache2 restart +``` + +## Part 2: Installing FreshRSS + +Begin by installing git, if you don't already have it installed. +``` +apt install git +``` + +Next, change to the install directory and download FreshRSS using git +``` +cd /usr/share/ +git clone https://github.com/FreshRSS/FreshRSS.git +``` + +Change to the new FreshRSS directory, and set the permissions so that your Web server can access the files +``` +cd FreshRSS +chown -R :www-data . +sudo chmod -R g+r . +``` +We'll also need to allow the data folder to be written to, like so: +``` +chmod -R g+w ./data/ +``` + +Optional: If you would like to allow updates from the Web interface, set write permissions +``` +chmod -R g+w . +``` + +Finally, symlink the public folder to the root of your web directory +``` +ln -s /usr/share/FreshRSS/p /var/www/html/ +``` + +## Part 3: Creating a Database for FreshRSS + +Start a MySQL session. running this command will ask you for the MySQL password you set earler, and then put you into a prompt that should look like `MariaDB [(none)]>` +``` +mysql -u root -p +``` + +From the MySQL prompt (`MariaDB [(none)]>`), run the following commands, substituting ``, ``, and `` for real values. +``` +CREATE USER ''@'localhost' IDENTIFIED BY ''; +CREATE DATABASE `databaseName`; +GRANT ALL privileges ON `databaseName`.* TO 'userName'@localhost; +FLUSH PRIVILEGES; +QUIT; +``` + +A brief explanation of the previous command block: +* You first create a database user for FreshRSS to use. +* Then you create a database for FreshRSS to store data in. +* You grant permissions for the user you created to read, write, and modify the database. +* Flushing privileges reloads the permissions, which makes the previous command take effect. + +## Part 4: Finishing the Installation + +You can now finish the installation from a web browser by navigating to to `http:///p` and following the graphical prompts. +Alternatively, you can finish the installation using [the cli](https://github.com/FreshRSS/FreshRSS/tree/master/cli) diff --git a/docs/en/admins/07_LinuxUpdate.md b/docs/en/admins/07_LinuxUpdate.md new file mode 100644 index 000000000..5c91fa3ca --- /dev/null +++ b/docs/en/admins/07_LinuxUpdate.md @@ -0,0 +1,97 @@ +# Updating on Debian 9/Ubuntu 16.04 + +This tutorial demonstrates commands for updating FreshRSS. It assumes that your main FreshRSS directory is `/usr/share/FreshRSS`; If you've installed it somewhere else, substitute your path as necessary. + +## Using git + +**You must have used git to install FreshRSS to use this update method.** + +If your local user doesn't have write access to the FreshRSS folder, use a sudo shell (`sudo -s`), prefix the following commands with `sudo `, or switch to an account that does have write access to the folder. + +1. Change to your FreshRSS directory +``` +cd /usr/share/FreshRSS/ +``` + +2. Verify the branch you're currently on. For stable releases, this should be `master`. +``` +git branch +``` + + +3. Fetch the most recent code from the FreshRSS github Page +``` +git fetch --all +``` + +Note: If you wish to switch to a specific version of FreshRSS, or switch to/from the dev branch, this is the time to do that. Example commands for switching branches are found below, in "Switching Branches" + +4. Check for an update +``` +git status +``` + +If there's not an update, you're done! If there is, continue the following steps: + +5. Discard manual changes and delete manual additions +``` +get reset --hard +git clean -f -d +``` + +6. Delete the file that triggers the install wizard +``` +rm data/do-install.txt +``` + +7. Update to the new version of FreshRSS +``` +git pull +``` + +8. Re-set correct permissions so that your web server can access the files +``` +chown -R :www-data . && chmod -R g+r . && chmod -R g+w ./data/ +``` + +### Switching Branches + +Any command listed here should be run between steps 3 and 4 in the previous section. + +To switch from stable to dev (if you haven't before) use the following command: `git checkout -b dev origin/dev` + +If you've checked out dev and want to go back to master, the command would be `git checkout master`. After the first time you check out the dev branch, you can use this syntax to switch between the two main branches at will. + +If you wish to switch to [a specific release of FreshRSS](https://github.com/FreshRSS/FreshRSS/releases), you would use the command `git checkout `, where is the specific release number you wish to check out (for example, `git checkout 1.12.0`). Be aware that checking out a specific release will leave you in a state where you can't automatically update; you'll need to run `git checkout master` or `git checkout dev` before you'll be able to pull updates from git automatically. + +## Using the zip Archive + +If your local user doesn't have write access to the FreshRSS folder, use a sudo shell (`sudo -s`), prefix the following commands with `sudo `, or switch to an account that does have write access to the folder. + +1. Change to your FreshRSS directory +``` +cd /usr/share/FreshRSS/ +``` + +2. Download and unzip the update file +``` +wget https://github.com/FreshRSS/FreshRSS/archive/master.zip +unzip master.zip +``` + +3. Overwrite all your existing files with the new ones +``` +cp -R FreshRSS-master/* . +``` + +4. Re-set permissions +``` +chown -R :www-data . && chmod -R g+r . && chmod -R g+w ./data/ +``` + +5. Clean up the FreshRSS directory by deleting the downloaded zip, the file forcing the setup wizard and the temporary directory +``` +rm -f master.zip +rm -f data/do-install.txt +rm -rf FreshRSS-master/ +``` diff --git a/docs/en/admins/08_FeedUpdates.md b/docs/en/admins/08_FeedUpdates.md new file mode 100644 index 000000000..981bdbb01 --- /dev/null +++ b/docs/en/admins/08_FeedUpdates.md @@ -0,0 +1,56 @@ +# Setting Up Automatic Feed Updating + +FreshRSS is updated by the `./app/actualize_script.php` script. Knowing this, we can periodically trigger it to ensure up-to-date feeds. + +**Note:** the update script won't update any particular feed more often than once every twenty minutes, so it doesn't make sense to trigger it much more frequently than that. + +**Note:** the following examples assume that FreshRSS is installed to `/usr/share/FreshRSS`. You'll need to modify the FreshRSS path to reflect your own system. + +## Cron as a trigger + +You'll need to check the Cron documentation for your specific distribution ([Debian/Ubuntu](https://help.ubuntu.com/community/CronHowto), [Red Hat/Fedora/CentOS](https://fedoraproject.org/wiki/Administration_Guide_Draft/Cron), [Slackware](https://docs.slackware.com/fr:slackbook:process_control?#cron), [Gentoo](https://wiki.gentoo.org/wiki/Cron), [Arch Linux](https://wiki.archlinux.org/index.php/Cron) ...) to make sure you set the Cron job correctly. + +It's advisable that you run the Cron job as your Web server user (often `www-data`). + +### Example on Debian/Ubuntu +To run the updater script every hour, and 10 minutes past the hour: + +Run `sudo crontab -e` and copy the following line into the crontab: +``` +10 * * * * www-data php -f /usr/share/FreshRSS/app/actualize_script.php > /tmp/FreshRSS.log 2>&1 +``` + +## Systemd as a trigger + +Some systems can't use a Cron job, but they can use systemd. It's easy to configure it to mimic Cron's features. + +First you need to add a `freshrss.timer` file in `/etc/systemd/system/` with the following content: + +``` +[Unit] +Description=FreshRSS get new content + +[Timer] +OnBootSec=30s +OnCalendar=*:0/20 + +[Install] +WantedBy=timers.target +``` + +This timer will start 30 seconds after boot and it will trigger the service every 20 minutes. Feel free to change the configuration to better suit your needs. + +Then you need to add a `freshrss.service` file in the same directory. This will be the description of the service triggered by the aforementioned timer. + +``` +[Unit] +Description=FreshRSS get new content +Wants=freshrss.timer + +[Service] +User=www-data +Type=simple +ExecStart=/usr/bin/php /usr/share/FreshRSS/app/actualize_script.php +``` + +Finally, you need to enable the timer with `systemctl enable freshrss.timer` and reload the systemd configuration with `systemctl daeamon-reload`. diff --git a/docs/en/admins/09_AccessControl.md b/docs/en/admins/09_AccessControl.md new file mode 100644 index 000000000..c17ad1fc3 --- /dev/null +++ b/docs/en/admins/09_AccessControl.md @@ -0,0 +1,24 @@ +# Access Control + +FreshRSS offers three methods of Access control: Form Authentication using Javascript, HTTP based Authentication, or an uncontrolled state with no authentication required. + +## Form Authentication + +Form Authentication requires the use of Javascript. It will work on any supported version of PHP, but version 5.5 or newer is recommended (see footnote 1 in [prerequisites](02_Prerequisites.md) for the reason why). + +This option requires nothing more than selecting Form Authentication during installation. + +## HTTP Authentication + +You may also choose to use HTTP Authentication provided by your web server.[^1] + +If you choose to use this option, create a `./p/i/.htaccess` file with a matching `.htpasswd` file. + +## No Authentication +Not using authentication on your server is dangerous, as anyone with access to your server would be able to make changes as an admin. It is never advisable to not use any form of authentication, but **never** chose this option on a server that is able to be accessed outside of your home network. + +## Hints + +You can switch your authentication method at any time by editing the `./data/config.php` file, on the line that begins `'auth_type'`. + +[^1]: See [the Apache documentation](https://httpd.apache.org/docs/trunk/howto/auth.html) diff --git a/docs/en/admins/10_ServerConfig.md b/docs/en/admins/10_ServerConfig.md new file mode 100644 index 000000000..88387274a --- /dev/null +++ b/docs/en/admins/10_ServerConfig.md @@ -0,0 +1,103 @@ +# Apache/Nginx Configuration Files + +## Apache configuration +This is an example Apache virtual hosts configuration file. It covers HTTP and HTTPS configuration. + +``` + + DocumentRoot /var/www/html/ + + #Default site... + + ErrorLog ${APACHE_LOG_DIR}/error.default.log + CustomLog ${APACHE_LOG_DIR}/access.default.log vhost_combined + + + + ServerName rss.example.net + DocumentRoot /path/to/FreshRSS/p/ + + + AllowOverride AuthConfig FileInfo Indexes Limit + Require all granted + + + ErrorLog ${APACHE_LOG_DIR}/freshrss_error.log + CustomLog ${APACHE_LOG_DIR}/freshrss_access.log combined + + AllowEncodedSlashes On + + + + + ServerName rss.example.net + DocumentRoot /path/to/FreshRSS/p/ + + + AllowOverride AuthConfig FileInfo Indexes Limit + Require all granted + + + ErrorLog ${APACHE_LOG_DIR}/freshrss_error.log + CustomLog ${APACHE_LOG_DIR}/freshrss_access.log combined + + + Protocols h2 http/1.1 + + + # For the API + AllowEncodedSlashes On + + SSLEngine on + SSLCompression off + SSLCertificateFile /path/to/server.crt + SSLCertificateKeyFile /path/to/server.key + # Additional SSL configuration, e.g. with LetsEncrypt + + +``` + +## Nginx configuration + +This is an example nginx configuration file. It covers HTTP, HTTPS, and php-fpm configuration. + +You can find simpler config file but they may be incompatible with FreshRSS API. +``` +server { + listen 80; + listen 443 ssl; + + # HTTPS configuration + ssl on; + ssl_certificate /etc/nginx/server.crt; + ssl_certificate_key /etc/nginx/server.key; + + # your server’s URL(s) + server_name rss.example.net; + + # the folder p of your FreshRSS installation + root /srv/FreshRSS/p/; + + index index.php index.html index.htm; + + # nginx log files + access_log /var/log/nginx/rss.access.log; + error_log /var/log/nginx/rss.error.log; + + # php files handling + # this regex is mandatory because of the API + location ~ ^.+?\.php(/.*)?$ { + fastcgi_pass unix:/var/run/php/php7.0-fpm.sock; + fastcgi_split_path_info ^(.+\.php)(/.*)$; + # By default, the variable PATH_INFO is not set under PHP-FPM + # But FreshRSS API greader.php need it. If you have a “Bad Request” error, double check this var! + fastcgi_param PATH_INFO $fastcgi_path_info; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + } + + location / { + try_files $uri $uri/ index.php; + } +} +``` diff --git a/docs/en/contributing.md b/docs/en/contributing.md index 19f9cb9b1..870e0c14f 100644 --- a/docs/en/contributing.md +++ b/docs/en/contributing.md @@ -20,7 +20,7 @@ If you have to create a new ticket, try to apply the following advices: - 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 / PostgreSQL or SQLite) + + Your storage system (SQLite, MySQL, MariaDB, PostgreSQL) + If possible, the related logs (PHP logs and FreshRSS logs under `data/users/your_user/log.txt`) ## Fix a bug @@ -52,5 +52,5 @@ We are working on a better way to handle internationalization but don't hesitate ## 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. +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 in the main repositories [docs directory](https://github.com/FreshRSS/FreshRSS/tree/master/docs)! diff --git a/docs/en/developers/01_First_steps.md b/docs/en/developers/01_First_steps.md index adca4495b..28c249be4 100644 --- a/docs/en/developers/01_First_steps.md +++ b/docs/en/developers/01_First_steps.md @@ -1,6 +1,59 @@ -# Environment configuration +# Environment configuration (Docker) -**TODO** +FreshRSS is built with PHP and uses a homemade framework, Minz. The dependencies are directly included in the source code, so you don't need Composer. + +There are various ways to configure your development environment. The easiest and most supported method is based on Docker, which is the solution documented below. If you already have a working PHP environment, you probably don't need it. + +We assume here that you use a GNU/Linux distribution, capable of running Docker. Otherwise, you'll have to adapt the commands accordingly. + +The commands that follow have to be executed in a console. They start by `$` when commands need to be executed as normal user, and by `#` when they need to be executed as root user. You don't have to type these characters. A path may be indicated before these characters to help you identify where they need to be executed. For instance, `app$ echo 'Hello World'` indicates that you have to execute `echo` command in the `app/` directory. + +First, you need to install [Docker](https://docs.docker.com/install/linux/docker-ce/ubuntu/). + +Once you're done, clone the repository with: + +```console +$ git clone https://github.com/FreshRSS/FreshRSS.git +$ cd FreshRSS +``` + +Note that, if you want to contribute, you have to fork the repository first and clone your fork instead of the "root" one. Adapt the commands in consequence. + +Then, the only command you need to know is the following: + +```console +$ make start +``` + +This might take some time while Docker downloads the image. If your user isn't in the `docker` group, you'll need to prepend the command with `sudo`. + +**You can now access FreshRSS at [http://localhost:8080](http://localhost:8080).** Just follow the install process and select the SQLite database. + +You can stop the containers by typing Control + c or with the following command, in another terminal: + +```console +$ make stop +``` + +If you're interested in the configuration, the `make` commands are defined in the [`Makefile`](/Makefile). + +If you need to use a different tag image (default is `dev-alpine`), you can set the `TAG` environment variable: + +```console +$ TAG=dev-arm make start +``` + +You can find the full list of available tags [on the Docker hub](https://hub.docker.com/r/freshrss/freshrss/tags). + +You might want to rebuild the Docker image locally. You can do it with: + +```console +$ make build +$ # or +$ TAG=dev-arm make build +``` + +The `TAG` variable can be anything (e.g. `dev-local`). You can target a specific architecture by adding `-alpine` or `-arm` at the end of the tag (e.g. `dev-local-arm`). # Project architecture @@ -8,7 +61,7 @@ # Extensions -If you want to create your own FreshRSS extension, take a look at the [extension documentation](03_Backend/05_Extensions.md). +If you want to create your own FreshRSS extension, take a look at the [extension documentation](03_Backend/05_Extensions.md). # Coding style @@ -57,7 +110,7 @@ There is a space before and after every operator. ```php if ($a == 10) { - // do something + // do something } echo $a ? 1 : 0; @@ -69,11 +122,11 @@ There is no spaces in the brackets. There is no space before the opening bracket ```php if ($a == 10) { - // do something + // do something } if ((int)$a == 10) { - // do something + // do something } ``` @@ -84,16 +137,16 @@ It happens most of the time in Javascript files. When there is chained functions ```javascript // First instruction shortcut.add(shortcuts.mark_read, function () { - //... - }, { - 'disable_in_input': true - }); + //... + }, { + 'disable_in_input': true + }); // Second instruction shortcut.add("shift+" + shortcuts.mark_read, function () { - //... - }, { - 'disable_in_input': true - }); + //... + }, { + 'disable_in_input': true + }); ``` ## Line length @@ -105,7 +158,7 @@ With functions, parameters can be declared on different lines. ```php function my_function($param_1, $param_2, $param_3, $param_4) { - // do something + // do something } ``` @@ -120,7 +173,7 @@ They must follow the "snake case" convention. ```php // a function function function_name() { - // do something + // do something } // a variable $variable_name; @@ -132,7 +185,7 @@ They must follow the "lower camel case" convention. ```php private function methodName() { - // do something + // do something } ``` @@ -148,26 +201,9 @@ abstract class ClassName {} Files must be encoded with UTF-8 character set. -## PHP 5.3 compatibility - -Do not get an array item directly from a function or a method. Use a variable. - -```php -// code with PHP 5.3 compatibility -$my_variable = function_returning_an_array(); -echo $my_variable[0]; -// code without PHP 5.3 compatibility -echo function_returning_an_array()[0]; -``` - -Do not use short array declaration. +## PHP compatibility -```php -// code with PHP 5.3 compatibility -$variable = array(); -// code without PHP 5.3 compatibility -$variable = []; -``` +Ensure that your code is working with a PHP version as old as what FreshRSS officially supports. ## Miscellaneous @@ -177,7 +213,7 @@ They must be at the end of the line if a condition runs on more than one line. ```php if ($a == 10 || $a == 20) { - // do something + // do something } ``` @@ -190,9 +226,9 @@ If the file contains only PHP code, the PHP closing tag must be omitted. If an array declaration runs on more than one line, each element must be followed by a comma even the last one. ```php -$variable = array( - "value 1", - "value 2", - "value 3", -); +$variable = [ + "value 1", + "value 2", + "value 3", +]; ``` diff --git a/docs/en/developers/03_Backend/05_Extensions.md b/docs/en/developers/03_Backend/05_Extensions.md index ae304f6e0..4610e4b90 100644 --- a/docs/en/developers/03_Backend/05_Extensions.md +++ b/docs/en/developers/03_Backend/05_Extensions.md @@ -48,13 +48,13 @@ Code example: view->a_variable = 'FooBar'; - } + public function indexAction() { + $this->view->a_variable = 'FooBar'; + } - public function worldAction() { - $this->view->a_variable = 'Hello World!'; - } + public function worldAction() { + $this->view->a_variable = 'Hello World!'; + } } ?> @@ -74,7 +74,7 @@ As explained above, the views consist of HTML mixed with PHP. Code example: ```html

      - This is a parameter passed from the controller: a_variable; ?> + This is a parameter passed from the controller: a_variable ?>

      ``` @@ -119,7 +119,7 @@ To take full advantage of the Minz routing system, it is strongly discouraged to ```html

      - Go to page Hello world! + Go to page Hello world!

      ``` @@ -130,13 +130,13 @@ So use the `Minz_Url` class and its `display()` method instead. `Minz_Url::displ ```php 'hello', - 'a' => 'world', - 'params' => array( - 'foo' => 'bar', - ) -); +$url_array = [ + 'c' => 'hello', + 'a' => 'world', + 'params' => [ + 'foo' => 'bar', + ], +]; // Show something like .?c=hello&a=world&foo=bar echo Minz_Url::display($url_array); @@ -166,10 +166,10 @@ Code example: ```php 'hello', - 'a' => 'world' -); +$url_array = [ + 'c' => 'hello', + 'a' => 'world', +]; // Tells Minz to redirect the user to the hello / world page. // Note that this is a redirection in the Minz sense of the term, not a redirection that the browser will have to manage (HTTP code 301 or 302) @@ -188,10 +188,10 @@ It is very common to want display a message to the user while performing a redir ```php 'hello', - 'a' => 'world' -); +$url_array = [ + 'c' => 'hello', + 'a' => 'world', +]; $feedback_good = 'Tout s\'est bien passé !'; $feedback_bad = 'Oups, quelque chose n\'a pas marché.'; @@ -226,18 +226,18 @@ The translation files are quite simple: it is only a matter of returning a PHP t array( - 'actualize' => 'Actualiser', - 'back_to_rss_feeds' => '← Retour à vos flux RSS', - 'cancel' => 'Annuler', - 'create' => 'Créer', - 'disable' => 'Désactiver', - ), - 'freshrss' => array( - '_' => 'FreshRSS', - 'about' => 'À propos de FreshRSS', - ), -); + 'action' => [ + 'actualize' => 'Actualiser', + 'back_to_rss_feeds' => '← Retour à vos flux RSS', + 'cancel' => 'Annuler', + 'create' => 'Créer', + 'disable' => 'Désactiver', + ), + 'freshrss' => array( + '_' => 'FreshRSS', + 'about' => 'À propos de FreshRSS', + ), +]; ?> ``` @@ -247,9 +247,9 @@ Code example: ```html

      - - - + + +

      ``` @@ -267,8 +267,8 @@ An extension allows you to add functionality easily to FreshRSS without having t ### Basic files and folders -The first thing to note is that **all** extensions **must** be located in the `extensions` directory, at the base of the FreshRSS tree. -An extension is a directory containing a set of mandatory (and optional) files and subdirectories. +The first thing to note is that **all** extensions **must** be located in the `extensions` directory, at the base of the FreshRSS tree. +An extension is a directory containing a set of mandatory (and optional) files and subdirectories. The convention requires that the main directory name be preceded by an "x" to indicate that it is not an extension included by default in FreshRSS. The main directory of an extension must contain at least two **mandatory** files: @@ -276,16 +276,16 @@ The main directory of an extension must contain at least two **mandatory** files - A `metadata.json` file that contains a description of the extension. This file is written in JSON. - An `extension.php` file containing the entry point of the extension (which is a class that inherits Minz_Extension). -Please note that there is a not a required link between the directory name of the extension and the name of the class inside `extension.php`, -but you should follow our best practice: +Please note that there is a not a required link between the directory name of the extension and the name of the class inside `extension.php`, +but you should follow our best practice: If you want to write a `HelloWorld` extension, the directory name should be `xExtension-HelloWorld` and the base class name `HelloWorldExtension`. In the file `freshrss/extensions/xExtension-HelloWorld/extension.php` you need the structure: ```html class HelloWorldExtension extends Minz_Extension { - public function init() { - // your code here - } + public function init() { + // your code here + } } ``` There is an example HelloWorld extension that you can download from [our GitHub repo](https://github.com/FreshRSS/xExtension-HelloWorld). @@ -315,14 +315,14 @@ Only the `name` and` entrypoint` fields are required. ### Choose between « system » or « user » -A __user__ extension can be enabled by some users and not by others (typically for user preferences). +A __user__ extension can be enabled by some users and not by others (typically for user preferences). A __system__ extension in comparison is enabled for every account. ### Writing your own extension.php -This file is the entry point of your extension. It must contain a specific class to function. -As mentioned above, the name of the class must be your `entrypoint` suffixed by` Extension` (`HelloWorldExtension` for example). +This file is the entry point of your extension. It must contain a specific class to function. +As mentioned above, the name of the class must be your `entrypoint` suffixed by` Extension` (`HelloWorldExtension` for example). In addition, this class must be inherited from the `Minz_Extension` class to benefit from extensions-specific methods. Your class will benefit from four methods to redefine: @@ -351,25 +351,31 @@ You can register at the FreshRSS event system in an extensions `init()` method, ```html class HelloWorldExtension extends Minz_Extension { - public function init() { - $this->registerHook('entry_before_display', array($this, 'renderEntry')); - } - public function renderEntry($entry) { - $entry->_content('

      Hello World

      ' . $entry->content()); - return $entry; - } -} + public function init() { + $this->registerHook('entry_before_display', array($this, 'renderEntry')); + } + public function renderEntry($entry) { + $entry->_content('

      Hello World

      ' . $entry->content()); + return $entry; + } +} ``` + The following events are available: -- `entry_before_display` (`function($entry) -> Entry | null`) : will be executed every time an entry is rendered. The entry itself (instance of FreshRSS_Entry) will be passed as parameter. -- `entry_before_insert` (`function($entry) -> Entry | null`) : will be executed when a feed is refreshed and new entries will be imported into the database. The new entry (instance of FreshRSS_Entry) will be passed as parameter. -- `feed_before_insert` (`function($feed) -> Feed | null`) : will be executed when a new feed is imported into the database. The new feed (instance of FreshRSS_Feed) will be passed as parameter. -- `post_update` (`function(none) -> none`) : **TODO** add documentation -- `simplepie_before_init` (`function($simplePie, $feed) -> none`) : **TODO** add documentation +- `entry_before_display` (`function($entry) -> Entry | null`): will be executed every time an entry is rendered. The entry itself (instance of FreshRSS\_Entry) will be passed as parameter. +- `entry_before_insert` (`function($entry) -> Entry | null`): will be executed when a feed is refreshed and new entries will be imported into the database. The new entry (instance of FreshRSS\_Entry) will be passed as parameter. +- `feed_before_insert` (`function($feed) -> Feed | null`): will be executed when a new feed is imported into the database. The new feed (instance of FreshRSS\_Feed) will be passed as parameter. +- `freshrss_init` (`function() -> none`): will be executed at the end of the initialization of FreshRSS, useful to initialize components or to do additional access checks +- `menu_admin_entry` (`function() -> string`): add an entry at the end of the "Administration" menu, the returned string must be valid HTML (e.g. `
    • New entry
    • `) +- `menu_configuration_entry` (`function() -> string`): add an entry at the end of the "Configuration" menu, the returned string must be valid HTML (e.g. `
    • New entry
    • `) +- `menu_other_entry` (`function() -> string`): add an entry at the end of the header dropdown menu (i.e. after the "About" entry), the returned string must be valid HTML (e.g. `
    • New entry
    • `) +- `nav_reading_modes` (`function($reading_modes) -> array | null`): **TODO** add documentation +- `post_update` (`function(none) -> none`): **TODO** add documentation +- `simplepie_before_init` (`function($simplePie, $feed) -> none`): **TODO** add documentation ### Writing your own configure.phtml -When you want to support user configurations for your extension or simply display some information, you have to create the `configure.phtml` file. +When you want to support user configurations for your extension or simply display some information, you have to create the `configure.phtml` file. **TODO** diff --git a/docs/en/users/03_Main_view.md b/docs/en/users/03_Main_view.md index 59d051e7e..c6c3e3b50 100644 --- a/docs/en/users/03_Main_view.md +++ b/docs/en/users/03_Main_view.md @@ -32,20 +32,20 @@ Here is an example to trigger article update every hour. Special parameters to configure the script - all parameters can be combined: -- Parameter "force" -https://freshrss.example.net/i/?c=feed&a=actualize&force=1 +- Parameter "force" +https://freshrss.example.net/i/?c=feed&a=actualize&force=1 If *force* is set to 1 all feeds will be refreshed at once. -- Parameter "ajax" -https://freshrss.example.net/i/?c=feed&a=actualize&ajax=1 +- Parameter "ajax" +https://freshrss.example.net/i/?c=feed&a=actualize&ajax=1 Only a status site is returned and not a complete website. Example: "OK" -- Parameter "maxFeeds" -https://freshrss.example.net/i/?c=feed&a=actualize&maxFeeds=30 +- Parameter "maxFeeds" +https://freshrss.example.net/i/?c=feed&a=actualize&maxFeeds=30 If *maxFeeds* is set the configured amount of feeds is refreshed at once. The default setting is "10". -- Parameter "token" -https://freshrss.example.net/i/?c=feed&a=actualize&token=542345872345734 +- Parameter "token" +https://freshrss.example.net/i/?c=feed&a=actualize&token=542345872345734 Security parameter to prevent unauthorized refreshes. For detailed Documentation see "Form authentication". ### Online cron diff --git a/docs/en/users/05_Configuration.md b/docs/en/users/05_Configuration.md index 225c1e5f9..f635f9d5e 100644 --- a/docs/en/users/05_Configuration.md +++ b/docs/en/users/05_Configuration.md @@ -9,7 +9,7 @@ the missing bits or add a new language, please check how you can [contribute to There are parts of FreshRSS that are not translated and are not intended to be translated. For now, the logs visible in the application as well as the one generated by automatic update scripts are part of it. -Available languages are: cz, de, en, es, fr, he, it, kr, nl, oc, pt-br, ru, tr, zh-cn. +Available languages are: cz, de, en, es, fr, he, it, kr, nl, oc, pt-br, ru, tr, zh-cn. ## Theme diff --git a/docs/en/users/06_Fever_API.md b/docs/en/users/06_Fever_API.md index b895d4eae..b1cf6cb21 100644 --- a/docs/en/users/06_Fever_API.md +++ b/docs/en/users/06_Fever_API.md @@ -21,15 +21,15 @@ Then point your mobile application to the URL of `fever.php` (e.g. `https://fres Tested with: - Android - - [Readably](https://play.google.com/store/apps/details?id=com.isaiasmatewos.readably) + - [Readably](https://play.google.com/store/apps/details?id=com.isaiasmatewos.readably) (Closed source) - iOS - - [Fiery Feeds](https://itunes.apple.com/app/fiery-feeds-rss-reader/id1158763303) - - [Unread](https://itunes.apple.com/app/unread-rss-reader/id1252376153) - - [Reeder-4](https://itunes.apple.com/app/reeder-4/id1449412357) + - [Fiery Feeds](https://apps.apple.com/app/fiery-feeds-rss-reader/id1158763303) (Closed source) + - [Unread](https://apps.apple.com/app/unread-rss-reader/id1252376153) (Commercial) + - [Reeder](https://www.reederapp.com/) (Commercial) (Use its Google Reader API / native FreshRSS option when possible) - MacOS - - [Readkit](https://itunes.apple.com/app/readkit/id588726889) + - [ReadKit](https://apps.apple.com/app/readkit/id588726889) (Commercial) ## Features @@ -75,7 +75,7 @@ Add a body to your POST request encoded as `form-data` and one key named `api_ke curl -s -F "api_key=$api_key" 'https://freshrss.example.net/api/fever.php?api' ``` -This shoud give: +This should give: ```json { "api_version": 3, @@ -83,7 +83,7 @@ This shoud give: "last_refreshed_on_time": "1520013061" } ``` -Perfect, you are authenticated and can now start testing the more advanced features. Therefor change the URL and append the possible API actions to your request parameters. Check the [original Fever documentation](https://feedafever.com/api) for more infos. +Perfect, you are authenticated and can now start testing the more advanced features. Therefor change the URL and append the possible API actions to your request parameters. Check the [original Fever documentation](https://feedafever.com/api) for more information. Some basic calls are: diff --git a/docs/en/users/06_Mobile_access.md b/docs/en/users/06_Mobile_access.md index 13dba828d..f5f1520e9 100644 --- a/docs/en/users/06_Mobile_access.md +++ b/docs/en/users/06_Mobile_access.md @@ -29,7 +29,7 @@ See the [page about the Fever compatible API](06_Fever_API.md) for another possi * If you get *Service Unavailable!*, then check from step 1 again. * With __Apache__: * If you get *FAIL getallheaders!*, the combination of your PHP version and your Web server does not provide access to [`getallheaders`](http://php.net/getallheaders) - * Update to PHP 5.4+, or use PHP as module instead of CGI. Otherwise turn on Apache `mod_setenvif` (often enabled by default), or `mod_rewrite` with the following procedure: + * Turn on Apache `mod_setenvif` (often enabled by default), or `mod_rewrite` with the following procedure: * Allow [`FileInfo` in `.htaccess`](http://httpd.apache.org/docs/trunk/mod/core.html#allowoverride): see the [server setup](../admins/02_Installation.md) again. * Enable [`mod_rewrite`](http://httpd.apache.org/docs/trunk/mod/mod_rewrite.html): * With Debian / Ubuntu: `sudo a2enmod rewrite` @@ -43,7 +43,7 @@ See the [page about the Fever compatible API](06_Fever_API.md) for another possi # Compatible clients -6. On the same FreshRSS API page, note the adress given under “Your API address”, like `https://freshrss.example.net/api/greader.php` +6. On the same FreshRSS API page, note the address given under “Your API address”, like `https://freshrss.example.net/api/greader.php` * You will type it in a client, together with your FreshRSS username, and the corresponding special API password. 7. Pick a client supporting a Google Reader-like API. Selection: @@ -55,6 +55,9 @@ See the [page about the Fever compatible API](06_Fever_API.md) for another possi * [FeedReader 2.0+](https://jangernert.github.io/FeedReader/) (Open source) * MacOS * [Vienna RSS](http://www.vienna-rss.com/) (Open source) + * [Reeder](https://www.reederapp.com/) (Commercial) + * iOS + * [Reeder](https://www.reederapp.com/) (Commercial) * Firefox * [FreshRSS-Notify](https://addons.mozilla.org/firefox/addon/freshrss-notify-webextension/) (Open source) diff --git a/docs/en/users/07_Frequently_Asked_Questions.md b/docs/en/users/07_Frequently_Asked_Questions.md index 42156b1a9..fd3db4bea 100644 --- a/docs/en/users/07_Frequently_Asked_Questions.md +++ b/docs/en/users/07_Frequently_Asked_Questions.md @@ -47,7 +47,7 @@ For more information on that matter, there is a [dedicated documentation](../../ ## Permissions under SELinux -Some Linux distribution like Fedora or RedHat Enterprise Linux have SELinux system enabled. This acts like a firewall application, so all applications cannot write/modify files under certain conditions. While installing FreshRSS, step 2 can fail if the httpd process cannot write to some data sub-directories, the following command should be executed as root : +Some Linux distribution like Fedora or RedHat Enterprise Linux have SELinux system enabled. This acts like a firewall application, so all applications cannot write/modify files under certain conditions. While installing FreshRSS, step 2 can fail if the httpd process cannot write to some data sub-directories, the following command should be executed as root : ```sh semanage fcontext -a -t httpd_sys_rw_content_t '/usr/share/FreshRSS/data(/.*)?' restorecon -Rv /usr/share/FreshRSS/data diff --git a/docs/fr/developers/01_First_steps.md b/docs/fr/developers/01_First_steps.md index d2bf9d315..df3fa65f2 100644 --- a/docs/fr/developers/01_First_steps.md +++ b/docs/fr/developers/01_First_steps.md @@ -57,7 +57,7 @@ Chaque opérateur est entouré d'espaces. ```php if ($a == 10) { - // faire quelque chose + // faire quelque chose } echo $a ? 1 : 0; @@ -69,11 +69,11 @@ Il n'y a pas d'espaces entre des parenthèses. Il n'y a pas d'espaces avant une ```php if ($a == 10) { - // faire quelque chose + // faire quelque chose } if ((int)$a == 10) { - // faire quelque chose + // faire quelque chose } ``` @@ -84,16 +84,16 @@ Ce cas se présente le plus souvent en Javascript. Quand on a des fonctions chai ```javascript // Première instruction shortcut.add(shortcuts.mark_read, function () { - //... - }, { - 'disable_in_input': true - }); + //... + }, { + 'disable_in_input': true + }); // Deuxième instruction shortcut.add("shift+" + shortcuts.mark_read, function () { - //... - }, { - 'disable_in_input': true - }); + //... + }, { + 'disable_in_input': true + }); ``` ## Longueur des lignes @@ -105,7 +105,7 @@ Dans le cas des fonctions, les paramètres peuvent être déclarés sur plusieur ```php function ma_fonction($param_1, $param_2, $param_3, $param_4) { - // faire quelque chose + // faire quelque chose } ``` @@ -120,7 +120,7 @@ Les fonctions et les variables doivent suivre la convention "snake case". ```php // une fontion function nom_de_la_fontion() { - // faire quelque chose + // faire quelque chose } // une variable $nom_de_la_variable; @@ -132,7 +132,7 @@ Les méthodes doivent suivre la convention "lower camel case". ```php private function nomDeLaMethode() { - // faire quelque chose + // faire quelque chose } ``` @@ -148,26 +148,9 @@ abstract class NomDeLaClasse {} Les fichiers doivent être encodés en UTF-8. -## Compatibilité avec PHP 5.3 +## Compatibilité PHP -Il ne faut pas demander l'indice d'un tableau qui est retourné par une fonction ou une méthode. Il faut passer par une variable intermédiaire. - -```php -// code compatible avec PHP 5.3 -$ma_variable = fonction_qui_retourne_un_tableau(); -echo $ma_variable[0]; -// code incompatible avec PHP 5.3 -echo fonction_qui_retourne_un_tableau()[0]; -``` - -Il ne faut pas utiliser la déclaration raccourcie des tableaux. - -```php -// code compatible avec PHP 5.3 -$variable = array(); -// code incompatible avec PHP 5.3 -$variable = []; -``` +Assurez-vous que votre code fonctionne avec une version de PHP aussi ancienne que celle que FreshRSS supporte officiellement. ## Divers @@ -177,7 +160,7 @@ Les opérateurs doivent être en fin de ligne dans le cas de conditions sur plus ```php if ($a == 10 || $a == 20) { - // faire quelque chose + // faire quelque chose } ``` @@ -190,9 +173,9 @@ Si le fichier ne contient que du PHP, il ne doit pas comporter de balise fermant Lors de l'écriture de tableaux sur plusieurs lignes, tous les éléments doivent être suivis d'une virgule (même le dernier). ```php -$variable = array( - "valeur 1", - "valeur 2", - "valeur 3", -); +$variable = [ + "valeur 1", + "valeur 2", + "valeur 3", +]; ``` diff --git a/docs/fr/developers/02_Github.md b/docs/fr/developers/02_Github.md index b4fa7b301..686b69ec4 100644 --- a/docs/fr/developers/02_Github.md +++ b/docs/fr/developers/02_Github.md @@ -63,7 +63,7 @@ Pensez à donner les informations suivantes si vous les connaissez : 1. Quel navigateur ? Quelle version ? 2. Quel serveur : Apache, Nginx ? Quelle version ? 3. Quelle version de PHP ? - 4. MySQL ou SQLite ? Quelle version ? + 4. Quelle base de données : SQLite, MySQL, MariaDB, PostgreSQL ? Quelle version ? 5. Quelle distribution sur le serveur ? Et… quelle version ? ---- diff --git a/docs/fr/developers/03_Backend/05_Extensions.md b/docs/fr/developers/03_Backend/05_Extensions.md index 2ee81b781..37a4340af 100644 --- a/docs/fr/developers/03_Backend/05_Extensions.md +++ b/docs/fr/developers/03_Backend/05_Extensions.md @@ -49,13 +49,13 @@ Exemple de code : view->a_variable = 'FooBar'; - } + public function indexAction() { + $this->view->a_variable = 'FooBar'; + } - public function worldAction() { - $this->view->a_variable = 'Hello World!'; - } + public function worldAction() { + $this->view->a_variable = 'Hello World!'; + } } ?> @@ -75,7 +75,7 @@ Comme expliqué plus haut, les vues sont du code HTML mixé à du PHP. Exemple d ```html

      - Phrase passée en paramètre : a_variable; ?> + Phrase passée en paramètre : a_variable ?>

      ``` @@ -119,7 +119,7 @@ Pour profiter pleinement du système de routage de Minz, il est fortement décon ```html

      - Accéder à la page Hello world! + Accéder à la page Hello world!

      ``` @@ -130,13 +130,13 @@ Préférez donc l'utilisation de la classe `Minz_Url` et de sa méthode `display ```php 'hello', - 'a' => 'world', - 'params' => array( - 'foo' => 'bar', - ) -); +$url_array = [ + 'c' => 'hello', + 'a' => 'world', + 'params' => [ + 'foo' => 'bar', + ], +]; // Affichera quelque chose comme .?c=hello&a=world&foo=bar echo Minz_Url::display($url_array); @@ -166,10 +166,10 @@ Exemple de code : ```php 'hello', - 'a' => 'world' -); +$url_array = [ + 'c' => 'hello', + 'a' => 'world', +]; // Indique à Minz de rediriger l'utilisateur vers la page hello/world. // Notez qu'il s'agit d'une redirection au sens Minz du terme, pas d'une redirection que le navigateur va avoir à gérer (code HTTP 301 ou 302) @@ -188,10 +188,10 @@ Il est très fréquent de vouloir effectuer une redirection tout en affichant un ```php 'hello', - 'a' => 'world' -); +$url_array = [ + 'c' => 'hello', + 'a' => 'world', +]; $feedback_good = 'Tout s\'est bien passé !'; $feedback_bad = 'Oups, quelque chose n\'a pas marché.'; @@ -225,19 +225,19 @@ Les fichiers de traduction sont assez simples : il s'agit seulement de retourne ```php array( - 'actualize' => 'Actualiser', - 'back_to_rss_feeds' => '← Retour à vos flux RSS', - 'cancel' => 'Annuler', - 'create' => 'Créer', - 'disable' => 'Désactiver', - ), - 'freshrss' => array( - '_' => 'FreshRSS', - 'about' => 'À propos de FreshRSS', - ), -); +return [ + 'action' => [ + 'actualize' => 'Actualiser', + 'back_to_rss_feeds' => '← Retour à vos flux RSS', + 'cancel' => 'Annuler', + 'create' => 'Créer', + 'disable' => 'Désactiver', + ], + 'freshrss' => [ + '_' => 'FreshRSS', + 'about' => 'À propos de FreshRSS', + ], +]; ?> ``` @@ -246,9 +246,9 @@ Pour accéder à ces traductions, `Minz_Translate` va nous aider à l'aide de sa ```html

      - - - + + +

      ``` diff --git a/docs/fr/users/01_Installation.md b/docs/fr/users/01_Installation.md index a6495d2fd..9c5022f17 100644 --- a/docs/fr/users/01_Installation.md +++ b/docs/fr/users/01_Installation.md @@ -7,15 +7,11 @@ Il est toutefois de votre responsabilité de vérifier que votre hébergement pe | Logiciel | Recommandé | Fonctionne aussi avec | | -------- | ----------- | --------------------- | | Serveur web | **Apache 2** | Nginx | - | PHP | **PHP 5.5+** | PHP 5.3.8+ | - | Modules PHP | Requis : libxml, cURL, PDO_MySQL, PCRE et ctype
      Requis (32 bits seulement) : GMP
      Recommandé : JSON, Zlib, mbstring et iconv, ZipArchive
      *Pour une liste complète des modules nécessaires voir le [Dockerfile](https://github.com/FreshRSS/FreshRSS/blob/744a9e8cf00aef7dec0acfa5f90f0dcfa2ef8837/Docker/Dockerfile-Alpine#L7-L9)* | | - | Base de données | **MySQL 5.5.3+** | SQLite 3.7.4+ | + | PHP | **PHP 7+** | PHP 5.6+ | + | Modules PHP | Requis : libxml, cURL, JSON, PDO_MySQL, PCRE et ctype
      Requis (32 bits seulement) : GMP
      Recommandé : Zlib, mbstring et iconv, ZipArchive
      *Pour une liste complète des modules nécessaires voir le [Dockerfile](https://github.com/FreshRSS/FreshRSS/blob/master/Docker/Dockerfile-Alpine#L7-L9)* | | + | Base de données | **MySQL 5.5.3+** | SQLite 3.7.4+, PostgreSQL 9.5+ | | Navigateur | **Firefox** | Chrome, Opera, Safari, or IE 11+ | -## Note importante - -FreshRSS **PEUT** fonctionner sur la version de PHP 5.3.8+. En effet, nous utilisons des fonctions spécifiques pour la connexion par formulaire et notamment la [bibliothèque ''password_compat''](https://github.com/ircmaxell/password_compat#requirements). - # Choisir la bonne version de FreshRSS FreshRSS possède trois versions différentes (nous parlons de branches) qui sortent à des fréquences plus ou moins rapides. Aussi prenez le temps de comprendre à quoi correspond chacune de ces versions. diff --git a/docs/fr/users/06_Fever_API.md b/docs/fr/users/06_Fever_API.md index fb15e596b..c3623b341 100644 --- a/docs/fr/users/06_Fever_API.md +++ b/docs/fr/users/06_Fever_API.md @@ -8,15 +8,15 @@ et des généralités sur l’accès par API. Testé avec: - Android - [Readably](https://play.google.com/store/apps/details?id=com.isaiasmatewos.readably) + [Readably](https://play.google.com/store/apps/details?id=com.isaiasmatewos.readably) (Propriétaire) - iOS - - [Fiery Feeds](https://itunes.apple.com/app/fiery-feeds-rss-reader/id1158763303) - - [Unread](https://itunes.apple.com/app/unread-rss-reader/id1252376153) - - [Reeder-3](https://itunes.apple.com/app/reeder-3/id697846300) + - [Fiery Feeds](https://apps.apple.com/app/fiery-feeds-rss-reader/id1158763303) (Propriétaire) + - [Unread](https://apps.apple.com/app/unread-rss-reader/id1252376153) (Commercial) + - [Reeder](https://www.reederapp.com/) (Commercial) (Connectez-vous plutôt par son option Google Reader API) - MacOS - - [Readkit](https://itunes.apple.com/app/readkit/id588726889) + - [Readkit](https://apps.apple.com/app/readkit/id588726889) (Commercial) ## TODO diff --git a/docs/fr/users/06_Mobile_access.md b/docs/fr/users/06_Mobile_access.md index 7bce9eea3..f637ccf5b 100644 --- a/docs/fr/users/06_Mobile_access.md +++ b/docs/fr/users/06_Mobile_access.md @@ -29,7 +29,7 @@ Voir la [page sur l’API compatible Fever](06_Fever_API.md) pour une autre poss * Si vous obtenez *Service Unavailable!*, retourner à l’étape 6. * Avec __Apache__: * Si vous obtenez *FAIL getallheaders!*, alors la combinaison de votre version de PHP et de votre serveur Web ne permet pas l’accès à [`getallheaders`](http://php.net/getallheaders) - * Utilisez au moins PHP 5.4+, ou utilisez PHP en tant que module plutôt que CGI. Sinon, activer Apache `mod_setenvif` (souvent activé par défault), ou `mod_rewrite` avec la procédure suivante : + * Activer Apache `mod_setenvif` (souvent activé par défault), ou `mod_rewrite` avec la procédure suivante : * Autoriser [`FileInfo` dans `.htaccess`](http://httpd.apache.org/docs/trunk/mod/core.html#allowoverride) : revoir [l’installation du serveur](01_Installation.md). * Activer [`mod_rewrite`](http://httpd.apache.org/docs/trunk/mod/mod_rewrite.html) : * Sur Debian / Ubuntu : `sudo a2enmod rewrite` @@ -45,7 +45,7 @@ Voir la [page sur l’API compatible Fever](06_Fever_API.md) pour une autre poss 6. Vous pouvez maintenant tester sur une application mobile (News+, FeedMe, ou EasyRSS sur Android) * en utilisant comme adresse https://rss.example.net/api/greader.php ou http://example.net/FreshRSS/p/api/greader.php selon la configuration de votre site Web. - * ⚠️ attention aux majuscules et aux espaces en tapant l’adresse avec le clavier du mobile ⚠️ + * ⚠️ attention aux majuscules et aux espaces en tapant l’adresse avec le clavier du mobile ⚠️ * avec votre nom d’utilisateur et le mot de passe enregistré au point 2 (mot de passe API). @@ -53,7 +53,7 @@ Voir la [page sur l’API compatible Fever](06_Fever_API.md) pour une autre poss * Vous pouvez voir les logs API dans `./FreshRSS/data/users/_/log_api.txt` * Si vous avez une erreur 404 (fichier non trouvé) lors de l’étape de test, et que vous êtes sous Apache, - voir http://httpd.apache.org/docs/trunk/mod/core.html#allowencodedslashes pour utiliser News+ + voir http://httpd.apache.org/docs/trunk/mod/core.html#allowencodedslashes pour utiliser News+ (facultatif pour EasyRSS et FeedMe qui devraient fonctionner dès lors que vous obtenez un PASS au test *Check partial server configuration*). @@ -67,11 +67,14 @@ Tout client supportant une API de type Google Reader. Sélection : * [EasyRSS](https://github.com/Alkarex/EasyRSS) (Libre, F-Droid) * Linux * [FeedReader 2.0+](https://jangernert.github.io/FeedReader/) (Libre) +* iOS + * [Reeder-4](https://apps.apple.com/app/reeder-4/id1449412357) (Commercial) * MacOS * [Vienna RSS](http://www.vienna-rss.com/) (Libre) * Firefox * [FreshRSS-Notify](https://addons.mozilla.org/fr/firefox/addon/freshrss-notify-webextension/) (Libre) + # API compatible Google Reader Exemples de requêtes simples : diff --git a/docs/fr/users/07_Frequently_Asked_Questions.md b/docs/fr/users/07_Frequently_Asked_Questions.md index 2dc2cae97..87ff8631a 100644 --- a/docs/fr/users/07_Frequently_Asked_Questions.md +++ b/docs/fr/users/07_Frequently_Asked_Questions.md @@ -19,9 +19,9 @@ L'explication est la même pour les fichiers ```favicon.ico``` et ```.htaccess`` ## Pourquoi j'ai des erreurs quand j'essaye d'enregistrer un flux ? -Il peut y avoir différentes origines à ce problème. +Il peut y avoir différentes origines à ce problème. Le flux peut avoir une syntaxe invalide, il peut ne pas être reconnu par la bibliothèque SimplePie, l'hébergement peut avoir des problèmes, FreshRSS peut être boggué. -Il faut dans un premier temps déterminer la cause du problème. +Il faut dans un premier temps déterminer la cause du problème. Voici la liste des étapes à suivre pour la déterminer : 1. __Vérifier la validité du flux__ grâce à l'[outil en ligne du W3C](http://validator.w3.org/feed/ "Validateur en ligne de flux RSS et Atom"). Si ça ne fonctionne pas, nous ne pouvons rien faire. diff --git a/lib/.htaccess b/lib/.htaccess index 9e768397d..32eca30f7 100644 --- a/lib/.htaccess +++ b/lib/.htaccess @@ -1,3 +1,11 @@ -Order Allow,Deny -Deny from all -Satisfy all +# Apache 2.2 + + Order Allow,Deny + Deny from all + Satisfy all + + +# Apache 2.4 + + Require all denied + diff --git a/lib/JSON.php b/lib/JSON.php deleted file mode 100644 index 8dc8a6f01..000000000 --- a/lib/JSON.php +++ /dev/null @@ -1,933 +0,0 @@ - - * @author Matt Knapp - * @author Brett Stimmerman - * @copyright 2005 Michal Migurski - * @version CVS: $Id: JSON.php 305040 2010-11-02 23:19:03Z alan_k $ - * @license http://www.opensource.org/licenses/bsd-license.php - * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 - */ - -/** - * Marker constant for Services_JSON::decode(), used to flag stack state - */ -define('SERVICES_JSON_SLICE', 1); - -/** - * Marker constant for Services_JSON::decode(), used to flag stack state - */ -define('SERVICES_JSON_IN_STR', 2); - -/** - * Marker constant for Services_JSON::decode(), used to flag stack state - */ -define('SERVICES_JSON_IN_ARR', 3); - -/** - * Marker constant for Services_JSON::decode(), used to flag stack state - */ -define('SERVICES_JSON_IN_OBJ', 4); - -/** - * Marker constant for Services_JSON::decode(), used to flag stack state - */ -define('SERVICES_JSON_IN_CMT', 5); - -/** - * Behavior switch for Services_JSON::decode() - */ -define('SERVICES_JSON_LOOSE_TYPE', 16); - -/** - * Behavior switch for Services_JSON::decode() - */ -define('SERVICES_JSON_SUPPRESS_ERRORS', 32); - -/** - * Behavior switch for Services_JSON::decode() - */ -define('SERVICES_JSON_USE_TO_JSON', 64); - -/** - * Converts to and from JSON format. - * - * Brief example of use: - * - * - * // create a new instance of Services_JSON - * $json = new Services_JSON(); - * - * // convert a complexe value to JSON notation, and send it to the browser - * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); - * $output = $json->encode($value); - * - * print($output); - * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] - * - * // accept incoming POST data, assumed to be in JSON notation - * $input = file_get_contents('php://input', 1000000); - * $value = $json->decode($input); - * - */ -class Services_JSON -{ - /** - * constructs a new JSON instance - * - * @param int $use object behavior flags; combine with boolean-OR - * - * possible values: - * - SERVICES_JSON_LOOSE_TYPE: loose typing. - * "{...}" syntax creates associative arrays - * instead of objects in decode(). - * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression. - * Values which can't be encoded (e.g. resources) - * appear as NULL instead of throwing errors. - * By default, a deeply-nested resource will - * bubble up with an error, so all return values - * from encode() should be checked with isError() - * - SERVICES_JSON_USE_TO_JSON: call toJSON when serializing objects - * It serializes the return value from the toJSON call rather - * than the object it'self, toJSON can return associative arrays, - * strings or numbers, if you return an object, make sure it does - * not have a toJSON method, otherwise an error will occur. - */ - function Services_JSON($use = 0) - { - $this->use = $use; - $this->_mb_strlen = function_exists('mb_strlen'); - $this->_mb_convert_encoding = function_exists('mb_convert_encoding'); - $this->_mb_substr = function_exists('mb_substr'); - } - // private - cache the mbstring lookup results.. - var $_mb_strlen = false; - var $_mb_substr = false; - var $_mb_convert_encoding = false; - - /** - * convert a string from one UTF-16 char to one UTF-8 char - * - * Normally should be handled by mb_convert_encoding, but - * provides a slower PHP-only method for installations - * that lack the multibye string extension. - * - * @param string $utf16 UTF-16 character - * @return string UTF-8 character - * @access private - */ - function utf162utf8($utf16) - { - // oh please oh please oh please oh please oh please - if($this->_mb_convert_encoding) { - return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); - } - - $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); - - switch(true) { - case ((0x7F & $bytes) == $bytes): - // this case should never be reached, because we are in ASCII range - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return chr(0x7F & $bytes); - - case (0x07FF & $bytes) == $bytes: - // return a 2-byte UTF-8 character - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return chr(0xC0 | (($bytes >> 6) & 0x1F)) - . chr(0x80 | ($bytes & 0x3F)); - - case (0xFFFF & $bytes) == $bytes: - // return a 3-byte UTF-8 character - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return chr(0xE0 | (($bytes >> 12) & 0x0F)) - . chr(0x80 | (($bytes >> 6) & 0x3F)) - . chr(0x80 | ($bytes & 0x3F)); - } - - // ignoring UTF-32 for now, sorry - return ''; - } - - /** - * convert a string from one UTF-8 char to one UTF-16 char - * - * Normally should be handled by mb_convert_encoding, but - * provides a slower PHP-only method for installations - * that lack the multibye string extension. - * - * @param string $utf8 UTF-8 character - * @return string UTF-16 character - * @access private - */ - function utf82utf16($utf8) - { - // oh please oh please oh please oh please oh please - if($this->_mb_convert_encoding) { - return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); - } - - switch($this->strlen8($utf8)) { - case 1: - // this case should never be reached, because we are in ASCII range - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return $utf8; - - case 2: - // return a UTF-16 character from a 2-byte UTF-8 char - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return chr(0x07 & (ord($utf8{0}) >> 2)) - . chr((0xC0 & (ord($utf8{0}) << 6)) - | (0x3F & ord($utf8{1}))); - - case 3: - // return a UTF-16 character from a 3-byte UTF-8 char - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return chr((0xF0 & (ord($utf8{0}) << 4)) - | (0x0F & (ord($utf8{1}) >> 2))) - . chr((0xC0 & (ord($utf8{1}) << 6)) - | (0x7F & ord($utf8{2}))); - } - - // ignoring UTF-32 for now, sorry - return ''; - } - - /** - * encodes an arbitrary variable into JSON format (and sends JSON Header) - * - * @param mixed $var any number, boolean, string, array, or object to be encoded. - * see argument 1 to Services_JSON() above for array-parsing behavior. - * if var is a strng, note that encode() always expects it - * to be in ASCII or UTF-8 format! - * - * @return mixed JSON string representation of input var or an error if a problem occurs - * @access public - */ - function encode($var) - { - header('Content-type: application/json'); - return $this->encodeUnsafe($var); - } - /** - * encodes an arbitrary variable into JSON format without JSON Header - warning - may allow XSS!!!!) - * - * @param mixed $var any number, boolean, string, array, or object to be encoded. - * see argument 1 to Services_JSON() above for array-parsing behavior. - * if var is a strng, note that encode() always expects it - * to be in ASCII or UTF-8 format! - * - * @return mixed JSON string representation of input var or an error if a problem occurs - * @access public - */ - function encodeUnsafe($var) - { - // see bug #16908 - regarding numeric locale printing - $lc = setlocale(LC_NUMERIC, 0); - setlocale(LC_NUMERIC, 'C'); - $ret = $this->_encode($var); - setlocale(LC_NUMERIC, $lc); - return $ret; - - } - /** - * PRIVATE CODE that does the work of encodes an arbitrary variable into JSON format - * - * @param mixed $var any number, boolean, string, array, or object to be encoded. - * see argument 1 to Services_JSON() above for array-parsing behavior. - * if var is a strng, note that encode() always expects it - * to be in ASCII or UTF-8 format! - * - * @return mixed JSON string representation of input var or an error if a problem occurs - * @access public - */ - function _encode($var) - { - - switch (gettype($var)) { - case 'boolean': - return $var ? 'true' : 'false'; - - case 'NULL': - return 'null'; - - case 'integer': - return (int) $var; - - case 'double': - case 'float': - return (float) $var; - - case 'string': - // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT - $ascii = ''; - $strlen_var = $this->strlen8($var); - - /* - * Iterate over every character in the string, - * escaping with a slash or encoding to UTF-8 where necessary - */ - for ($c = 0; $c < $strlen_var; ++$c) { - - $ord_var_c = ord($var{$c}); - - switch (true) { - case $ord_var_c == 0x08: - $ascii .= '\b'; - break; - case $ord_var_c == 0x09: - $ascii .= '\t'; - break; - case $ord_var_c == 0x0A: - $ascii .= '\n'; - break; - case $ord_var_c == 0x0C: - $ascii .= '\f'; - break; - case $ord_var_c == 0x0D: - $ascii .= '\r'; - break; - - case $ord_var_c == 0x22: - case $ord_var_c == 0x2F: - case $ord_var_c == 0x5C: - // double quote, slash, slosh - $ascii .= '\\'.$var{$c}; - break; - - case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): - // characters U-00000000 - U-0000007F (same as ASCII) - $ascii .= $var{$c}; - break; - - case (($ord_var_c & 0xE0) == 0xC0): - // characters U-00000080 - U-000007FF, mask 110XXXXX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - if ($c+1 >= $strlen_var) { - $c += 1; - $ascii .= '?'; - break; - } - - $char = pack('C*', $ord_var_c, ord($var{$c + 1})); - $c += 1; - $utf16 = $this->utf82utf16($char); - $ascii .= sprintf('\u%04s', bin2hex($utf16)); - break; - - case (($ord_var_c & 0xF0) == 0xE0): - if ($c+2 >= $strlen_var) { - $c += 2; - $ascii .= '?'; - break; - } - // characters U-00000800 - U-0000FFFF, mask 1110XXXX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, - @ord($var{$c + 1}), - @ord($var{$c + 2})); - $c += 2; - $utf16 = $this->utf82utf16($char); - $ascii .= sprintf('\u%04s', bin2hex($utf16)); - break; - - case (($ord_var_c & 0xF8) == 0xF0): - if ($c+3 >= $strlen_var) { - $c += 3; - $ascii .= '?'; - break; - } - // characters U-00010000 - U-001FFFFF, mask 11110XXX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, - ord($var{$c + 1}), - ord($var{$c + 2}), - ord($var{$c + 3})); - $c += 3; - $utf16 = $this->utf82utf16($char); - $ascii .= sprintf('\u%04s', bin2hex($utf16)); - break; - - case (($ord_var_c & 0xFC) == 0xF8): - // characters U-00200000 - U-03FFFFFF, mask 111110XX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - if ($c+4 >= $strlen_var) { - $c += 4; - $ascii .= '?'; - break; - } - $char = pack('C*', $ord_var_c, - ord($var{$c + 1}), - ord($var{$c + 2}), - ord($var{$c + 3}), - ord($var{$c + 4})); - $c += 4; - $utf16 = $this->utf82utf16($char); - $ascii .= sprintf('\u%04s', bin2hex($utf16)); - break; - - case (($ord_var_c & 0xFE) == 0xFC): - if ($c+5 >= $strlen_var) { - $c += 5; - $ascii .= '?'; - break; - } - // characters U-04000000 - U-7FFFFFFF, mask 1111110X - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, - ord($var{$c + 1}), - ord($var{$c + 2}), - ord($var{$c + 3}), - ord($var{$c + 4}), - ord($var{$c + 5})); - $c += 5; - $utf16 = $this->utf82utf16($char); - $ascii .= sprintf('\u%04s', bin2hex($utf16)); - break; - } - } - return '"'.$ascii.'"'; - - case 'array': - /* - * As per JSON spec if any array key is not an integer - * we must treat the the whole array as an object. We - * also try to catch a sparsely populated associative - * array with numeric keys here because some JS engines - * will create an array with empty indexes up to - * max_index which can cause memory issues and because - * the keys, which may be relevant, will be remapped - * otherwise. - * - * As per the ECMA and JSON specification an object may - * have any string as a property. Unfortunately due to - * a hole in the ECMA specification if the key is a - * ECMA reserved word or starts with a digit the - * parameter is only accessible using ECMAScript's - * bracket notation. - */ - - // treat as a JSON object - if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { - $properties = array_map(array($this, 'name_value'), - array_keys($var), - array_values($var)); - - foreach($properties as $property) { - if(Services_JSON::isError($property)) { - return $property; - } - } - - return '{' . join(',', $properties) . '}'; - } - - // treat it like a regular array - $elements = array_map(array($this, '_encode'), $var); - - foreach($elements as $element) { - if(Services_JSON::isError($element)) { - return $element; - } - } - - return '[' . join(',', $elements) . ']'; - - case 'object': - - // support toJSON methods. - if (($this->use & SERVICES_JSON_USE_TO_JSON) && method_exists($var, 'toJSON')) { - // this may end up allowing unlimited recursion - // so we check the return value to make sure it's not got the same method. - $recode = $var->toJSON(); - - if (method_exists($recode, 'toJSON')) { - - return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) - ? 'null' - : new Services_JSON_Error(class_name($var). - " toJSON returned an object with a toJSON method."); - - } - - return $this->_encode( $recode ); - } - - $vars = get_object_vars($var); - - $properties = array_map(array($this, 'name_value'), - array_keys($vars), - array_values($vars)); - - foreach($properties as $property) { - if(Services_JSON::isError($property)) { - return $property; - } - } - - return '{' . join(',', $properties) . '}'; - - default: - return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) - ? 'null' - : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string"); - } - } - - /** - * array-walking function for use in generating JSON-formatted name-value pairs - * - * @param string $name name of key to use - * @param mixed $value reference to an array element to be encoded - * - * @return string JSON-formatted name-value pair, like '"name":value' - * @access private - */ - function name_value($name, $value) - { - $encoded_value = $this->_encode($value); - - if(Services_JSON::isError($encoded_value)) { - return $encoded_value; - } - - return $this->_encode(strval($name)) . ':' . $encoded_value; - } - - /** - * reduce a string by removing leading and trailing comments and whitespace - * - * @param $str string string value to strip of comments and whitespace - * - * @return string string value stripped of comments and whitespace - * @access private - */ - function reduce_string($str) - { - $str = preg_replace(array( - - // eliminate single line comments in '// ...' form - '#^\s*//(.+)$#m', - - // eliminate multi-line comments in '/* ... */' form, at start of string - '#^\s*/\*(.+)\*/#Us', - - // eliminate multi-line comments in '/* ... */' form, at end of string - '#/\*(.+)\*/\s*$#Us' - - ), '', $str); - - // eliminate extraneous space - return trim($str); - } - - /** - * decodes a JSON string into appropriate variable - * - * @param string $str JSON-formatted string - * - * @return mixed number, boolean, string, array, or object - * corresponding to given JSON input string. - * See argument 1 to Services_JSON() above for object-output behavior. - * Note that decode() always returns strings - * in ASCII or UTF-8 format! - * @access public - */ - function decode($str) - { - $str = $this->reduce_string($str); - - switch (strtolower($str)) { - case 'true': - return true; - - case 'false': - return false; - - case 'null': - return null; - - default: - $m = array(); - - if (is_numeric($str)) { - // Lookie-loo, it's a number - - // This would work on its own, but I'm trying to be - // good about returning integers where appropriate: - // return (float)$str; - - // Return float or int, as appropriate - return ((float)$str == (integer)$str) - ? (integer)$str - : (float)$str; - - } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { - // STRINGS RETURNED IN UTF-8 FORMAT - $delim = $this->substr8($str, 0, 1); - $chrs = $this->substr8($str, 1, -1); - $utf8 = ''; - $strlen_chrs = $this->strlen8($chrs); - - for ($c = 0; $c < $strlen_chrs; ++$c) { - - $substr_chrs_c_2 = $this->substr8($chrs, $c, 2); - $ord_chrs_c = ord($chrs{$c}); - - switch (true) { - case $substr_chrs_c_2 == '\b': - $utf8 .= chr(0x08); - ++$c; - break; - case $substr_chrs_c_2 == '\t': - $utf8 .= chr(0x09); - ++$c; - break; - case $substr_chrs_c_2 == '\n': - $utf8 .= chr(0x0A); - ++$c; - break; - case $substr_chrs_c_2 == '\f': - $utf8 .= chr(0x0C); - ++$c; - break; - case $substr_chrs_c_2 == '\r': - $utf8 .= chr(0x0D); - ++$c; - break; - - case $substr_chrs_c_2 == '\\"': - case $substr_chrs_c_2 == '\\\'': - case $substr_chrs_c_2 == '\\\\': - case $substr_chrs_c_2 == '\\/': - if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || - ($delim == "'" && $substr_chrs_c_2 != '\\"')) { - $utf8 .= $chrs{++$c}; - } - break; - - case preg_match('/\\\u[0-9A-F]{4}/i', $this->substr8($chrs, $c, 6)): - // single, escaped unicode character - $utf16 = chr(hexdec($this->substr8($chrs, ($c + 2), 2))) - . chr(hexdec($this->substr8($chrs, ($c + 4), 2))); - $utf8 .= $this->utf162utf8($utf16); - $c += 5; - break; - - case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): - $utf8 .= $chrs{$c}; - break; - - case ($ord_chrs_c & 0xE0) == 0xC0: - // characters U-00000080 - U-000007FF, mask 110XXXXX - //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= $this->substr8($chrs, $c, 2); - ++$c; - break; - - case ($ord_chrs_c & 0xF0) == 0xE0: - // characters U-00000800 - U-0000FFFF, mask 1110XXXX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= $this->substr8($chrs, $c, 3); - $c += 2; - break; - - case ($ord_chrs_c & 0xF8) == 0xF0: - // characters U-00010000 - U-001FFFFF, mask 11110XXX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= $this->substr8($chrs, $c, 4); - $c += 3; - break; - - case ($ord_chrs_c & 0xFC) == 0xF8: - // characters U-00200000 - U-03FFFFFF, mask 111110XX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= $this->substr8($chrs, $c, 5); - $c += 4; - break; - - case ($ord_chrs_c & 0xFE) == 0xFC: - // characters U-04000000 - U-7FFFFFFF, mask 1111110X - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= $this->substr8($chrs, $c, 6); - $c += 5; - break; - - } - - } - - return $utf8; - - } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { - // array, or object notation - - if ($str{0} == '[') { - $stk = array(SERVICES_JSON_IN_ARR); - $arr = array(); - } else { - if ($this->use & SERVICES_JSON_LOOSE_TYPE) { - $stk = array(SERVICES_JSON_IN_OBJ); - $obj = array(); - } else { - $stk = array(SERVICES_JSON_IN_OBJ); - $obj = new stdClass(); - } - } - - array_push($stk, array('what' => SERVICES_JSON_SLICE, - 'where' => 0, - 'delim' => false)); - - $chrs = $this->substr8($str, 1, -1); - $chrs = $this->reduce_string($chrs); - - if ($chrs == '') { - if (reset($stk) == SERVICES_JSON_IN_ARR) { - return $arr; - - } else { - return $obj; - - } - } - - //print("\nparsing {$chrs}\n"); - - $strlen_chrs = $this->strlen8($chrs); - - for ($c = 0; $c <= $strlen_chrs; ++$c) { - - $top = end($stk); - $substr_chrs_c_2 = $this->substr8($chrs, $c, 2); - - if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { - // found a comma that is not inside a string, array, etc., - // OR we've reached the end of the character list - $slice = $this->substr8($chrs, $top['where'], ($c - $top['where'])); - array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); - //print("Found split at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - if (reset($stk) == SERVICES_JSON_IN_ARR) { - // we are in an array, so just push an element onto the stack - array_push($arr, $this->decode($slice)); - - } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { - // we are in an object, so figure - // out the property name and set an - // element in an associative array, - // for now - $parts = array(); - - if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:/Uis', $slice, $parts)) { - // "name":value pair - $key = $this->decode($parts[1]); - $val = $this->decode(trim(substr($slice, strlen($parts[0])), ", \t\n\r\0\x0B")); - if ($this->use & SERVICES_JSON_LOOSE_TYPE) { - $obj[$key] = $val; - } else { - $obj->$key = $val; - } - } elseif (preg_match('/^\s*(\w+)\s*:/Uis', $slice, $parts)) { - // name:value pair, where name is unquoted - $key = $parts[1]; - $val = $this->decode(trim(substr($slice, strlen($parts[0])), ", \t\n\r\0\x0B")); - - if ($this->use & SERVICES_JSON_LOOSE_TYPE) { - $obj[$key] = $val; - } else { - $obj->$key = $val; - } - } - - } - - } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { - // found a quote, and we are not inside a string - array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); - //print("Found start of string at {$c}\n"); - - } elseif (($chrs{$c} == $top['delim']) && - ($top['what'] == SERVICES_JSON_IN_STR) && - (($this->strlen8($this->substr8($chrs, 0, $c)) - $this->strlen8(rtrim($this->substr8($chrs, 0, $c), '\\'))) % 2 != 1)) { - // found a quote, we're in a string, and it's not escaped - // we know that it's not escaped becase there is _not_ an - // odd number of backslashes at the end of the string so far - array_pop($stk); - //print("Found end of string at {$c}: ".$this->substr8($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); - - } elseif (($chrs{$c} == '[') && - in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { - // found a left-bracket, and we are in an array, object, or slice - array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); - //print("Found start of array at {$c}\n"); - - } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { - // found a right-bracket, and we're in an array - array_pop($stk); - //print("Found end of array at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - } elseif (($chrs{$c} == '{') && - in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { - // found a left-brace, and we are in an array, object, or slice - array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); - //print("Found start of object at {$c}\n"); - - } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { - // found a right-brace, and we're in an object - array_pop($stk); - //print("Found end of object at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - } elseif (($substr_chrs_c_2 == '/*') && - in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { - // found a comment start, and we are in an array, object, or slice - array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); - $c++; - //print("Found start of comment at {$c}\n"); - - } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { - // found a comment end, and we're in one now - array_pop($stk); - $c++; - - for ($i = $top['where']; $i <= $c; ++$i) - $chrs = substr_replace($chrs, ' ', $i, 1); - - //print("Found end of comment at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - } - - } - - if (reset($stk) == SERVICES_JSON_IN_ARR) { - return $arr; - - } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { - return $obj; - - } - - } - } - } - - /** - * @todo Ultimately, this should just call PEAR::isError() - */ - function isError($data, $code = null) - { - if (class_exists('pear')) { - return PEAR::isError($data, $code); - } elseif (is_object($data) && (get_class($data) == 'services_json_error' || - is_subclass_of($data, 'services_json_error'))) { - return true; - } - - return false; - } - - /** - * Calculates length of string in bytes - * @param string - * @return integer length - */ - function strlen8( $str ) - { - if ( $this->_mb_strlen ) { - return mb_strlen( $str, "8bit" ); - } - return strlen( $str ); - } - - /** - * Returns part of a string, interpreting $start and $length as number of bytes. - * @param string - * @param integer start - * @param integer length - * @return integer length - */ - function substr8( $string, $start, $length=false ) - { - if ( $length === false ) { - $length = $this->strlen8( $string ) - $start; - } - if ( $this->_mb_substr ) { - return mb_substr( $string, $start, $length, "8bit" ); - } - return substr( $string, $start, $length ); - } - -} - -if (class_exists('PEAR_Error')) { - - class Services_JSON_Error extends PEAR_Error - { - function Services_JSON_Error($message = 'unknown error', $code = null, - $mode = null, $options = null, $userinfo = null) - { - parent::PEAR_Error($message, $code, $mode, $options, $userinfo); - } - } - -} else { - - /** - * @todo Ultimately, this class shall be descended from PEAR_Error - */ - class Services_JSON_Error - { - function Services_JSON_Error($message = 'unknown error', $code = null, - $mode = null, $options = null, $userinfo = null) - { - - } - } - -} diff --git a/lib/Minz/ActionController.php b/lib/Minz/ActionController.php index 232a4ef9b..123b9054c 100644 --- a/lib/Minz/ActionController.php +++ b/lib/Minz/ActionController.php @@ -14,7 +14,9 @@ class Minz_ActionController { * Constructeur */ public function __construct () { - $this->view = new Minz_View (); + $this->view = new Minz_View(); + $view_path = Minz_Request::controllerName() . '/' . Minz_Request::actionName() . '.phtml'; + $this->view->_path($view_path); $this->view->attributeParams (); } diff --git a/lib/Minz/Configuration.php b/lib/Minz/Configuration.php index aae3accc6..93f6b494c 100644 --- a/lib/Minz/Configuration.php +++ b/lib/Minz/Configuration.php @@ -198,7 +198,7 @@ class Minz_Configuration { return false; } - // Clear PHP 5.5+ cache for include + // Clear PHP cache for include if (function_exists('opcache_invalidate')) { opcache_invalidate($this->config_filename); } diff --git a/lib/Minz/ExtensionManager.php b/lib/Minz/ExtensionManager.php index b086c4001..2240b7642 100644 --- a/lib/Minz/ExtensionManager.php +++ b/lib/Minz/ExtensionManager.php @@ -27,14 +27,30 @@ class Minz_ExtensionManager { 'list' => array(), 'signature' => 'OneToOne', ), - 'post_update' => array( // function(none) -> none + 'freshrss_init' => array( // function() -> none 'list' => array(), 'signature' => 'NoneToNone', ), + 'menu_admin_entry' => array( // function() -> string + 'list' => array(), + 'signature' => 'NoneToString', + ), + 'menu_configuration_entry' => array( // function() -> string + 'list' => array(), + 'signature' => 'NoneToString', + ), + 'menu_other_entry' => array( // function() -> string + 'list' => array(), + 'signature' => 'NoneToString', + ), 'nav_reading_modes' => array( // function($readingModes = array) -> array | null 'list' => array(), 'signature' => 'OneToOne', ), + 'post_update' => array( // function(none) -> none + 'list' => array(), + 'signature' => 'NoneToNone', + ), 'simplepie_before_init' => array( // function($simplePie, $feed) -> none 'list' => array(), 'signature' => 'PassArguments', @@ -306,6 +322,23 @@ class Minz_ExtensionManager { return $result; } + /** + * Call a hook which takes no argument and returns a string. + * + * The result is concatenated between each hook and the final string is + * returned. + * + * @param string $hook_name is the hook to call. + * @return a concatenated string, result of the call to all the hooks. + */ + private static function callNoneToString($hook_name) { + $result = ''; + foreach (self::$hook_list[$hook_name]['list'] as $function) { + $result = $result . call_user_func($function); + } + return $result; + } + /** * Call a hook which takes no argument and returns nothing. * diff --git a/lib/Minz/FrontController.php b/lib/Minz/FrontController.php index 066278b7c..e09b022fc 100644 --- a/lib/Minz/FrontController.php +++ b/lib/Minz/FrontController.php @@ -115,21 +115,26 @@ class Minz_FrontController { } private function setReporting() { - $conf = Minz_Configuration::get('system'); - switch($conf->environment) { - case 'production': - error_reporting(E_ALL); - ini_set('display_errors', 'Off'); - ini_set('log_errors', 'On'); - break; - case 'development': - error_reporting(E_ALL); - ini_set('display_errors', 'On'); - ini_set('log_errors', 'On'); - break; - case 'silent': - error_reporting(0); - break; + $envType = getenv('FRESHRSS_ENV'); + if ($envType == '') { + $conf = Minz_Configuration::get('system'); + $envType = $conf->environment; + } + switch ($envType) { + case 'development': + error_reporting(E_ALL); + ini_set('display_errors', 'On'); + ini_set('log_errors', 'On'); + break; + case 'silent': + error_reporting(0); + break; + case 'production': + default: + error_reporting(E_ALL); + ini_set('display_errors', 'Off'); + ini_set('log_errors', 'On'); + break; } } } diff --git a/lib/Minz/Log.php b/lib/Minz/Log.php index a8dbf8350..3e6a25f27 100644 --- a/lib/Minz/Log.php +++ b/lib/Minz/Log.php @@ -8,26 +8,14 @@ * La classe Log permet de logger des erreurs */ class Minz_Log { - /** - * Les différents niveau de log - * ERROR erreurs bloquantes de l'application - * WARNING erreurs pouvant géner le bon fonctionnement, mais non bloquantes - * NOTICE erreurs mineures ou messages d'informations - * DEBUG Informations affichées pour le déboggage - */ - const ERROR = 2; - const WARNING = 4; - const NOTICE = 8; - const DEBUG = 16; - /** * Enregistre un message dans un fichier de log spécifique * Message non loggué si * - environment = SILENT - * - level = WARNING et environment = PRODUCTION - * - level = NOTICE et environment = PRODUCTION + * - level = LOG_WARNING et environment = PRODUCTION + * - level = LOG_NOTICE et environment = PRODUCTION * @param $information message d'erreur / information à enregistrer - * @param $level niveau d'erreur + * @param $level niveau d'erreur https://php.net/function.syslog * @param $file_name fichier de log * @throws Minz_PermissionDeniedException */ @@ -41,7 +29,7 @@ class Minz_Log { if (! ($env === 'silent' || ($env === 'production' - && ($level >= Minz_Log::NOTICE)))) { + && ($level >= LOG_NOTICE)))) { if ($file_name === null) { $username = Minz_Session::param('currentUser', ''); if ($username == '') { @@ -51,16 +39,16 @@ class Minz_Log { } switch ($level) { - case Minz_Log::ERROR : + case LOG_ERR : $level_label = 'error'; break; - case Minz_Log::WARNING : + case LOG_WARNING : $level_label = 'warning'; break; - case Minz_Log::NOTICE : + case LOG_NOTICE : $level_label = 'notice'; break; - case Minz_Log::DEBUG : + case LOG_DEBUG : $level_label = 'debug'; break; default : @@ -71,6 +59,10 @@ class Minz_Log { . ' [' . $level_label . ']' . ' --- ' . $information . "\n"; + if (defined('COPY_LOG_TO_SYSLOG') && COPY_LOG_TO_SYSLOG) { + syslog($level, '[' . $username . '] ' . $log); + } + self::ensureMaxLogSize($file_name); if (file_put_contents($file_name, $log, FILE_APPEND | LOCK_EX) === false) { @@ -120,8 +112,8 @@ class Minz_Log { $msg_get = str_replace("\n", '', '$_GET content : ' . print_r($_GET, true)); $msg_post = str_replace("\n", '', '$_POST content : ' . print_r($_POST, true)); - self::record($msg_get, Minz_Log::DEBUG, $file_name); - self::record($msg_post, Minz_Log::DEBUG, $file_name); + self::record($msg_get, LOG_DEBUG, $file_name); + self::record($msg_post, LOG_DEBUG, $file_name); } /** @@ -129,15 +121,15 @@ class Minz_Log { * Parameters are the same of those of the record() method. */ public static function debug($msg, $file_name = null) { - self::record($msg, Minz_Log::DEBUG, $file_name); + self::record($msg, LOG_DEBUG, $file_name); } public static function notice($msg, $file_name = null) { - self::record($msg, Minz_Log::NOTICE, $file_name); + self::record($msg, LOG_NOTICE, $file_name); } public static function warning($msg, $file_name = null) { - self::record($msg, Minz_Log::WARNING, $file_name); + self::record($msg, LOG_WARNING, $file_name); } public static function error($msg, $file_name = null) { - self::record($msg, Minz_Log::ERROR, $file_name); + self::record($msg, LOG_ERR, $file_name); } } diff --git a/lib/Minz/Mailer.php b/lib/Minz/Mailer.php new file mode 100644 index 000000000..04392982b --- /dev/null +++ b/lib/Minz/Mailer.php @@ -0,0 +1,115 @@ +view->foo = 'bar'). + * + * The view file is not determined automatically, so you have to select one + * with, for instance: + * + * ``` + * $this->view->_path('user_mailer/email_need_validation.txt') + * ``` + * + * Minz_Mailer uses the PHPMailer library under the hood. The latter requires + * PHP >= 5.5 to work. If you instantiate a Minz_Mailer with PHP < 5.5, a + * warning will be logged. + * + * The email is sent by calling the `mail` method. + */ +class Minz_Mailer { + /** + * The view attached to the mailer. + * You should set its file with `$this->view->_path($path)` + * + * @var Minz_View + */ + protected $view; + + /** + * Constructor. + * + * If PHP version is < 5.5, a warning is logged. + */ + public function __construct () { + if (version_compare(PHP_VERSION, '5.5') < 0) { + Minz_Log::warning('Minz_Mailer cannot be used with a version of PHP < 5.5.'); + } + + $this->view = new Minz_View(); + $this->view->_layout(false); + $this->view->attributeParams(); + + $conf = Minz_Configuration::get('system'); + $this->mailer = $conf->mailer; + $this->smtp_config = $conf->smtp; + + // According to https://github.com/PHPMailer/PHPMailer/wiki/SMTP-Debugging#debug-levels + // we should not use debug level above 2 unless if we have big trouble + // to connect. + if ($conf->environment === 'development') { + $this->debug_level = 2; + } else { + $this->debug_level = 0; + } + } + + /** + * Send an email. + * + * @param string $to The recipient of the email + * @param string $subject The subject of the email + * + * @return bool true on success, false if a SMTP error happens + */ + public function mail($to, $subject) { + ob_start(); + $this->view->render(); + $body = ob_get_contents(); + ob_end_clean(); + + PHPMailer::$validator = 'html5'; + + $mail = new PHPMailer(true); + try { + // Server settings + $mail->SMTPDebug = $this->debug_level; + $mail->Debugoutput = 'error_log'; + + if ($this->mailer === 'smtp') { + $mail->isSMTP(); + $mail->Hostname = $this->smtp_config['hostname']; + $mail->Host = $this->smtp_config['host']; + $mail->SMTPAuth = $this->smtp_config['auth']; + $mail->Username = $this->smtp_config['username']; + $mail->Password = $this->smtp_config['password']; + $mail->SMTPSecure = $this->smtp_config['secure']; + $mail->Port = $this->smtp_config['port']; + } else { + $mail->isMail(); + } + + // Recipients + $mail->setFrom($this->smtp_config['from']); + $mail->addAddress($to); + + // Content + $mail->isHTML(false); + $mail->CharSet = 'utf-8'; + $mail->Subject = $subject; + $mail->Body = $body; + + $mail->send(); + return true; + } catch (Exception $e) { + Minz_Log::error('Minz_Mailer cannot send a message: ' . $mail->ErrorInfo); + return false; + } + } +} diff --git a/lib/Minz/ModelArray.php b/lib/Minz/ModelArray.php index 1ac2b313d..4938f4b1d 100644 --- a/lib/Minz/ModelArray.php +++ b/lib/Minz/ModelArray.php @@ -48,7 +48,7 @@ class Minz_ModelArray { throw new Minz_PermissionDeniedException($this->filename); } if (function_exists('opcache_invalidate')) { - opcache_invalidate($this->filename); //Clear PHP 5.5+ cache for include + opcache_invalidate($this->filename); //Clear PHP cache for include } return true; } diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index 733982c14..69785c253 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -6,43 +6,35 @@ /** * La classe Model_sql représente le modèle interragissant avec les bases de données - * Seul la connexion MySQL est prise en charge pour le moment */ class Minz_ModelPdo { /** * Partage la connexion à la base de données entre toutes les instances. */ - public static $useSharedBd = true; - private static $sharedBd = null; + public static $usesSharedPdo = true; + private static $sharedPdo = null; private static $sharedPrefix; private static $sharedCurrentUser; - protected static $sharedDbType; - - /** - * $bd variable représentant la base de données - */ - protected $bd; + protected $pdo; protected $current_user; - protected $prefix; - - public function dbType() { - return self::$sharedDbType; - } /** * Créé la connexion à la base de données à l'aide des variables * HOST, BASE, USER et PASS définies dans le fichier de configuration */ - public function __construct($currentUser = null) { + public function __construct($currentUser = null, $currentPdo = null) { if ($currentUser === null) { $currentUser = Minz_Session::param('currentUser'); } - if (self::$useSharedBd && self::$sharedBd != null && - ($currentUser == null || $currentUser === self::$sharedCurrentUser)) { - $this->bd = self::$sharedBd; - $this->prefix = self::$sharedPrefix; + if ($currentPdo != null) { + $this->pdo = $currentPdo; + return; + } + if (self::$usesSharedPdo && self::$sharedPdo != null && + ($currentUser == '' || $currentUser === self::$sharedCurrentUser)) { + $this->pdo = self::$sharedPdo; $this->current_user = self::$sharedCurrentUser; return; } @@ -52,34 +44,40 @@ class Minz_ModelPdo { $conf = Minz_Configuration::get('system'); $db = $conf->db; - $driver_options = isset($conf->db['pdo_options']) && is_array($conf->db['pdo_options']) ? $conf->db['pdo_options'] : array(); + $driver_options = isset($db['pdo_options']) && is_array($db['pdo_options']) ? $db['pdo_options'] : []; $dbServer = parse_url('db://' . $db['host']); + $dsn = ''; + $dsnParams = empty($db['connection_uri_params']) ? '' : (';' . $db['connection_uri_params']); try { switch ($db['type']) { case 'mysql': - $string = 'mysql:host=' . (empty($dbServer['host']) ? $db['host'] : $dbServer['host']) . ';dbname=' . $db['base'] . ';charset=utf8mb4'; + $dsn = 'mysql:host=' . (empty($dbServer['host']) ? $db['host'] : $dbServer['host']) . ';charset=utf8mb4'; + if (!empty($db['base'])) { + $dsn .= ';dbname=' . $db['base']; + } if (!empty($dbServer['port'])) { - $string .= ';port=' . $dbServer['port']; + $dsn .= ';port=' . $dbServer['port']; } $driver_options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES utf8mb4'; - $this->prefix = $db['prefix'] . $currentUser . '_'; - $this->bd = new MinzPDOMySql($string, $db['user'], $db['password'], $driver_options); + $this->pdo = new MinzPDOMySql($dsn . $dsnParams, $db['user'], $db['password'], $driver_options); + $this->pdo->setPrefix($db['prefix'] . $currentUser . '_'); break; case 'sqlite': - $string = 'sqlite:' . join_path(DATA_PATH, 'users', $currentUser, 'db.sqlite'); - $this->prefix = ''; - $this->bd = new MinzPDOSQLite($string, $db['user'], $db['password'], $driver_options); - $this->bd->exec('PRAGMA foreign_keys = ON;'); + $dsn = 'sqlite:' . join_path(DATA_PATH, 'users', $currentUser, 'db.sqlite'); + $this->pdo = new MinzPDOSQLite($dsn . $dsnParams, $db['user'], $db['password'], $driver_options); + $this->pdo->setPrefix(''); break; case 'pgsql': - $string = 'pgsql:host=' . (empty($dbServer['host']) ? $db['host'] : $dbServer['host']) . ';dbname=' . $db['base']; + $dsn = 'pgsql:host=' . (empty($dbServer['host']) ? $db['host'] : $dbServer['host']); + if (!empty($db['base'])) { + $dsn .= ';dbname=' . $db['base']; + } if (!empty($dbServer['port'])) { - $string .= ';port=' . $dbServer['port']; + $dsn .= ';port=' . $dbServer['port']; } - $this->prefix = $db['prefix'] . $currentUser . '_'; - $this->bd = new MinzPDOPGSQL($string, $db['user'], $db['password'], $driver_options); - $this->bd->exec("SET NAMES 'UTF8';"); + $this->pdo = new MinzPDOPGSQL($dsn . $dsnParams, $db['user'], $db['password'], $driver_options); + $this->pdo->setPrefix($db['prefix'] . $currentUser . '_'); break; default: throw new Minz_PDOConnectionException( @@ -88,86 +86,122 @@ class Minz_ModelPdo { ); break; } - self::$sharedBd = $this->bd; - self::$sharedDbType = $db['type']; - self::$sharedPrefix = $this->prefix; + self::$sharedPdo = $this->pdo; } catch (Exception $e) { throw new Minz_PDOConnectionException( - $string, + $dsn . $dsnParams, $db['user'], Minz_Exception::ERROR ); } } public function beginTransaction() { - $this->bd->beginTransaction(); + $this->pdo->beginTransaction(); } public function inTransaction() { - return $this->bd->inTransaction(); //requires PHP >= 5.3.3 + return $this->pdo->inTransaction(); } public function commit() { - $this->bd->commit(); + $this->pdo->commit(); } public function rollBack() { - $this->bd->rollBack(); + $this->pdo->rollBack(); } public static function clean() { - self::$sharedBd = null; - self::$sharedPrefix = ''; + self::$sharedPdo = null; + self::$sharedCurrentUser = ''; } +} - public function disableBuffering() { - if ((self::$sharedDbType === 'mysql') && defined('PDO::MYSQL_ATTR_USE_BUFFERED_QUERY')) { - $this->bd->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); - } +abstract class MinzPDO extends PDO { + public function __construct($dsn, $username = null, $passwd = null, $options = null) { + parent::__construct($dsn, $username, $passwd, $options); + $this->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); + } + + abstract public function dbType(); + + private $prefix = ''; + public function prefix() { return $this->prefix; } + public function setPrefix($prefix) { $this->prefix = $prefix; } + + private function autoPrefix($sql) { + return str_replace('`_', '`' . $this->prefix, $sql); } -} -class MinzPDO extends PDO { - private static function check($statement) { + protected function preSql($statement) { if (preg_match('/^(?:UPDATE|INSERT|DELETE)/i', $statement)) { invalidateHttpCache(); } + return $this->autoPrefix($statement); } - protected function compatibility($statement) { - return $statement; + public function lastInsertId($name = null) { + if ($name != null) { + $name = $this->preSql($name); + } + return parent::lastInsertId($name); } public function prepare($statement, $driver_options = array()) { - MinzPDO::check($statement); - $statement = $this->compatibility($statement); + $statement = $this->preSql($statement); return parent::prepare($statement, $driver_options); } public function exec($statement) { - MinzPDO::check($statement); - $statement = $this->compatibility($statement); + $statement = $this->preSql($statement); return parent::exec($statement); } public function query($statement) { - MinzPDO::check($statement); - $statement = $this->compatibility($statement); + $statement = $this->preSql($statement); return parent::query($statement); } } class MinzPDOMySql extends MinzPDO { + public function __construct($dsn, $username = null, $passwd = null, $options = null) { + parent::__construct($dsn, $username, $passwd, $options); + $this->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); + } + + public function dbType() { + return 'mysql'; + } + public function lastInsertId($name = null) { return parent::lastInsertId(); //We discard the name, only used by PostgreSQL } } class MinzPDOSQLite extends MinzPDO { + public function __construct($dsn, $username = null, $passwd = null, $options = null) { + parent::__construct($dsn, $username, $passwd, $options); + $this->exec('PRAGMA foreign_keys = ON;'); + } + + public function dbType() { + return 'sqlite'; + } + public function lastInsertId($name = null) { return parent::lastInsertId(); //We discard the name, only used by PostgreSQL } } class MinzPDOPGSQL extends MinzPDO { - protected function compatibility($statement) { + public function __construct($dsn, $username = null, $passwd = null, $options = null) { + parent::__construct($dsn, $username, $passwd, $options); + $this->exec("SET NAMES 'UTF8';"); + } + + public function dbType() { + return 'pgsql'; + } + + protected function preSql($statement) { + $statement = parent::preSql($statement); return str_replace(array('`', ' LIKE '), array('"', ' ILIKE '), $statement); } } diff --git a/lib/Minz/Request.php b/lib/Minz/Request.php index 912c354ac..9235f873a 100644 --- a/lib/Minz/Request.php +++ b/lib/Minz/Request.php @@ -52,6 +52,12 @@ class Minz_Request { } return null; } + public static function paramBoolean($key) { + if (null === $value = self::paramTernary($key)) { + return false; + } + return $value; + } public static function defaultControllerName() { return self::$default_controller_name; } @@ -98,6 +104,13 @@ class Minz_Request { self::initJSON(); } + public static function is($controller_name, $action_name) { + return ( + self::$controller_name === $controller_name && + self::$action_name === $action_name + ); + } + /** * Return true if the request is over HTTPS, false otherwise (HTTP) */ diff --git a/lib/Minz/View.php b/lib/Minz/View.php index d6bf6ea2c..5b6676690 100644 --- a/lib/Minz/View.php +++ b/lib/Minz/View.php @@ -9,11 +9,11 @@ */ class Minz_View { const VIEWS_PATH_NAME = '/views'; - const LAYOUT_PATH_NAME = '/layout'; - const LAYOUT_FILENAME = '/layout.phtml'; + const LAYOUT_PATH_NAME = '/layout/'; + const LAYOUT_DEFAULT = 'layout'; private $view_filename = ''; - private $use_layout = true; + private $layout_filename = ''; private static $base_pathnames = array(APP_PATH); private static $title = ''; @@ -26,21 +26,27 @@ class Minz_View { * Constructeur * Détermine si on utilise un layout ou non */ - public function __construct () { - $this->change_view(Minz_Request::controllerName(), - Minz_Request::actionName()); - + public function __construct() { + $this->_layout(self::LAYOUT_DEFAULT); $conf = Minz_Configuration::get('system'); self::$title = $conf->title; } /** - * Change le fichier de vue en fonction d'un controller / action + * [deprecated] Change the view file based on controller and action. */ public function change_view($controller_name, $action_name) { - $this->view_filename = self::VIEWS_PATH_NAME . '/' - . $controller_name . '/' - . $action_name . '.phtml'; + Minz_Log::warning('Minz_View::change_view is deprecated, it will be removed in a future version. Please use Minz_View::_path instead.'); + $this->_path($controller_name. '/' . $action_name . '.phtml'); + } + + /** + * Change the view file based on a pathname relative to VIEWS_PATH_NAME. + * + * @param string $path the new path + */ + public function _path($path) { + $this->view_filename = self::VIEWS_PATH_NAME . '/' . $path; } /** @@ -58,7 +64,7 @@ class Minz_View { * Construit la vue */ public function build () { - if ($this->use_layout) { + if ($this->layout_filename !== '') { $this->buildLayout (); } else { $this->render (); @@ -92,7 +98,9 @@ class Minz_View { */ public function buildLayout () { header('Content-Type: text/html; charset=UTF-8'); - $this->includeFile(self::LAYOUT_PATH_NAME . self::LAYOUT_FILENAME); + if (!$this->includeFile($this->layout_filename)) { + Minz_Log::notice('File not found: `' . $this->layout_filename . '`'); + } } /** @@ -137,11 +145,29 @@ class Minz_View { } /** - * Permet de choisir si on souhaite utiliser le layout - * @param $use true si on souhaite utiliser le layout, false sinon + * Choose the current view layout. + * @param $layout the layout name to use, false to use no layouts. + */ + public function _layout($layout) { + if ($layout) { + $this->layout_filename = self::LAYOUT_PATH_NAME . $layout . '.phtml'; + } else { + $this->layout_filename = ''; + } + } + + /** + * [deprecated] Choose if we want to use the layout or not. + * Please use the `_layout` function instead. + * @param $use true if we want to use the layout, false else */ public function _useLayout ($use) { - $this->use_layout = $use; + Minz_Log::warning('Minz_View::_useLayout is deprecated, it will be removed in a future version. Please use Minz_View::_layout instead.'); + if ($use) { + $this->_layout(self::LAYOUT_DEFAULT); + } else { + $this->_layout(false); + } } /** diff --git a/lib/PHPMailer/PHPMailer/Exception.php b/lib/PHPMailer/PHPMailer/Exception.php new file mode 100644 index 000000000..9a05dec3c --- /dev/null +++ b/lib/PHPMailer/PHPMailer/Exception.php @@ -0,0 +1,39 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2017 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * PHPMailer exception handler. + * + * @author Marcus Bointon + */ +class Exception extends \Exception +{ + /** + * Prettify error message output. + * + * @return string + */ + public function errorMessage() + { + return '' . htmlspecialchars($this->getMessage()) . "
      \n"; + } +} diff --git a/lib/PHPMailer/PHPMailer/PHPMailer.php b/lib/PHPMailer/PHPMailer/PHPMailer.php new file mode 100644 index 000000000..52104924d --- /dev/null +++ b/lib/PHPMailer/PHPMailer/PHPMailer.php @@ -0,0 +1,4502 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2017 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * PHPMailer - PHP email creation and transport class. + * + * @author Marcus Bointon (Synchro/coolbru) + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + */ +class PHPMailer +{ + const CHARSET_ISO88591 = 'iso-8859-1'; + const CHARSET_UTF8 = 'utf-8'; + + const CONTENT_TYPE_PLAINTEXT = 'text/plain'; + const CONTENT_TYPE_TEXT_CALENDAR = 'text/calendar'; + const CONTENT_TYPE_TEXT_HTML = 'text/html'; + const CONTENT_TYPE_MULTIPART_ALTERNATIVE = 'multipart/alternative'; + const CONTENT_TYPE_MULTIPART_MIXED = 'multipart/mixed'; + const CONTENT_TYPE_MULTIPART_RELATED = 'multipart/related'; + + const ENCODING_7BIT = '7bit'; + const ENCODING_8BIT = '8bit'; + const ENCODING_BASE64 = 'base64'; + const ENCODING_BINARY = 'binary'; + const ENCODING_QUOTED_PRINTABLE = 'quoted-printable'; + + /** + * Email priority. + * Options: null (default), 1 = High, 3 = Normal, 5 = low. + * When null, the header is not set at all. + * + * @var int + */ + public $Priority; + + /** + * The character set of the message. + * + * @var string + */ + public $CharSet = self::CHARSET_ISO88591; + + /** + * The MIME Content-type of the message. + * + * @var string + */ + public $ContentType = self::CONTENT_TYPE_PLAINTEXT; + + /** + * The message encoding. + * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable". + * + * @var string + */ + public $Encoding = self::ENCODING_8BIT; + + /** + * Holds the most recent mailer error message. + * + * @var string + */ + public $ErrorInfo = ''; + + /** + * The From email address for the message. + * + * @var string + */ + public $From = 'root@localhost'; + + /** + * The From name of the message. + * + * @var string + */ + public $FromName = 'Root User'; + + /** + * The envelope sender of the message. + * This will usually be turned into a Return-Path header by the receiver, + * and is the address that bounces will be sent to. + * If not empty, will be passed via `-f` to sendmail or as the 'MAIL FROM' value over SMTP. + * + * @var string + */ + public $Sender = ''; + + /** + * The Subject of the message. + * + * @var string + */ + public $Subject = ''; + + /** + * An HTML or plain text message body. + * If HTML then call isHTML(true). + * + * @var string + */ + public $Body = ''; + + /** + * The plain-text message body. + * This body can be read by mail clients that do not have HTML email + * capability such as mutt & Eudora. + * Clients that can read HTML will view the normal Body. + * + * @var string + */ + public $AltBody = ''; + + /** + * An iCal message part body. + * Only supported in simple alt or alt_inline message types + * To generate iCal event structures, use classes like EasyPeasyICS or iCalcreator. + * + * @see http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/ + * @see http://kigkonsult.se/iCalcreator/ + * + * @var string + */ + public $Ical = ''; + + /** + * The complete compiled MIME message body. + * + * @var string + */ + protected $MIMEBody = ''; + + /** + * The complete compiled MIME message headers. + * + * @var string + */ + protected $MIMEHeader = ''; + + /** + * Extra headers that createHeader() doesn't fold in. + * + * @var string + */ + protected $mailHeader = ''; + + /** + * Word-wrap the message body to this number of chars. + * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance. + * + * @see static::STD_LINE_LENGTH + * + * @var int + */ + public $WordWrap = 0; + + /** + * Which method to use to send mail. + * Options: "mail", "sendmail", or "smtp". + * + * @var string + */ + public $Mailer = 'mail'; + + /** + * The path to the sendmail program. + * + * @var string + */ + public $Sendmail = '/usr/sbin/sendmail'; + + /** + * Whether mail() uses a fully sendmail-compatible MTA. + * One which supports sendmail's "-oi -f" options. + * + * @var bool + */ + public $UseSendmailOptions = true; + + /** + * The email address that a reading confirmation should be sent to, also known as read receipt. + * + * @var string + */ + public $ConfirmReadingTo = ''; + + /** + * The hostname to use in the Message-ID header and as default HELO string. + * If empty, PHPMailer attempts to find one with, in order, + * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value + * 'localhost.localdomain'. + * + * @var string + */ + public $Hostname = ''; + + /** + * An ID to be used in the Message-ID header. + * If empty, a unique id will be generated. + * You can set your own, but it must be in the format "", + * as defined in RFC5322 section 3.6.4 or it will be ignored. + * + * @see https://tools.ietf.org/html/rfc5322#section-3.6.4 + * + * @var string + */ + public $MessageID = ''; + + /** + * The message Date to be used in the Date header. + * If empty, the current date will be added. + * + * @var string + */ + public $MessageDate = ''; + + /** + * SMTP hosts. + * Either a single hostname or multiple semicolon-delimited hostnames. + * You can also specify a different port + * for each host by using this format: [hostname:port] + * (e.g. "smtp1.example.com:25;smtp2.example.com"). + * You can also specify encryption type, for example: + * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465"). + * Hosts will be tried in order. + * + * @var string + */ + public $Host = 'localhost'; + + /** + * The default SMTP server port. + * + * @var int + */ + public $Port = 25; + + /** + * The SMTP HELO of the message. + * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find + * one with the same method described above for $Hostname. + * + * @see PHPMailer::$Hostname + * + * @var string + */ + public $Helo = ''; + + /** + * What kind of encryption to use on the SMTP connection. + * Options: '', 'ssl' or 'tls'. + * + * @var string + */ + public $SMTPSecure = ''; + + /** + * Whether to enable TLS encryption automatically if a server supports it, + * even if `SMTPSecure` is not set to 'tls'. + * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid. + * + * @var bool + */ + public $SMTPAutoTLS = true; + + /** + * Whether to use SMTP authentication. + * Uses the Username and Password properties. + * + * @see PHPMailer::$Username + * @see PHPMailer::$Password + * + * @var bool + */ + public $SMTPAuth = false; + + /** + * Options array passed to stream_context_create when connecting via SMTP. + * + * @var array + */ + public $SMTPOptions = []; + + /** + * SMTP username. + * + * @var string + */ + public $Username = ''; + + /** + * SMTP password. + * + * @var string + */ + public $Password = ''; + + /** + * SMTP auth type. + * Options are CRAM-MD5, LOGIN, PLAIN, XOAUTH2, attempted in that order if not specified. + * + * @var string + */ + public $AuthType = ''; + + /** + * An instance of the PHPMailer OAuth class. + * + * @var OAuth + */ + protected $oauth; + + /** + * The SMTP server timeout in seconds. + * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2. + * + * @var int + */ + public $Timeout = 300; + + /** + * SMTP class debug output mode. + * Debug output level. + * Options: + * * `0` No output + * * `1` Commands + * * `2` Data and commands + * * `3` As 2 plus connection status + * * `4` Low-level data output. + * + * @see SMTP::$do_debug + * + * @var int + */ + public $SMTPDebug = 0; + + /** + * How to handle debug output. + * Options: + * * `echo` Output plain-text as-is, appropriate for CLI + * * `html` Output escaped, line breaks converted to `
      `, appropriate for browser output + * * `error_log` Output to error log as configured in php.ini + * By default PHPMailer will use `echo` if run from a `cli` or `cli-server` SAPI, `html` otherwise. + * Alternatively, you can provide a callable expecting two params: a message string and the debug level: + * + * ```php + * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; + * ``` + * + * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug` + * level output is used: + * + * ```php + * $mail->Debugoutput = new myPsr3Logger; + * ``` + * + * @see SMTP::$Debugoutput + * + * @var string|callable|\Psr\Log\LoggerInterface + */ + public $Debugoutput = 'echo'; + + /** + * Whether to keep SMTP connection open after each message. + * If this is set to true then to close the connection + * requires an explicit call to smtpClose(). + * + * @var bool + */ + public $SMTPKeepAlive = false; + + /** + * Whether to split multiple to addresses into multiple messages + * or send them all in one message. + * Only supported in `mail` and `sendmail` transports, not in SMTP. + * + * @var bool + */ + public $SingleTo = false; + + /** + * Storage for addresses when SingleTo is enabled. + * + * @var array + */ + protected $SingleToArray = []; + + /** + * Whether to generate VERP addresses on send. + * Only applicable when sending via SMTP. + * + * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path + * @see http://www.postfix.org/VERP_README.html Postfix VERP info + * + * @var bool + */ + public $do_verp = false; + + /** + * Whether to allow sending messages with an empty body. + * + * @var bool + */ + public $AllowEmpty = false; + + /** + * DKIM selector. + * + * @var string + */ + public $DKIM_selector = ''; + + /** + * DKIM Identity. + * Usually the email address used as the source of the email. + * + * @var string + */ + public $DKIM_identity = ''; + + /** + * DKIM passphrase. + * Used if your key is encrypted. + * + * @var string + */ + public $DKIM_passphrase = ''; + + /** + * DKIM signing domain name. + * + * @example 'example.com' + * + * @var string + */ + public $DKIM_domain = ''; + + /** + * DKIM Copy header field values for diagnostic use. + * + * @var bool + */ + public $DKIM_copyHeaderFields = true; + + /** + * DKIM Extra signing headers. + * + * @example ['List-Unsubscribe', 'List-Help'] + * + * @var array + */ + public $DKIM_extraHeaders = []; + + /** + * DKIM private key file path. + * + * @var string + */ + public $DKIM_private = ''; + + /** + * DKIM private key string. + * + * If set, takes precedence over `$DKIM_private`. + * + * @var string + */ + public $DKIM_private_string = ''; + + /** + * Callback Action function name. + * + * The function that handles the result of the send email action. + * It is called out by send() for each email sent. + * + * Value can be any php callable: http://www.php.net/is_callable + * + * Parameters: + * bool $result result of the send action + * array $to email addresses of the recipients + * array $cc cc email addresses + * array $bcc bcc email addresses + * string $subject the subject + * string $body the email body + * string $from email address of sender + * string $extra extra information of possible use + * "smtp_transaction_id' => last smtp transaction id + * + * @var string + */ + public $action_function = ''; + + /** + * What to put in the X-Mailer header. + * Options: An empty string for PHPMailer default, whitespace for none, or a string to use. + * + * @var string + */ + public $XMailer = ''; + + /** + * Which validator to use by default when validating email addresses. + * May be a callable to inject your own validator, but there are several built-in validators. + * The default validator uses PHP's FILTER_VALIDATE_EMAIL filter_var option. + * + * @see PHPMailer::validateAddress() + * + * @var string|callable + */ + public static $validator = 'php'; + + /** + * An instance of the SMTP sender class. + * + * @var SMTP + */ + protected $smtp; + + /** + * The array of 'to' names and addresses. + * + * @var array + */ + protected $to = []; + + /** + * The array of 'cc' names and addresses. + * + * @var array + */ + protected $cc = []; + + /** + * The array of 'bcc' names and addresses. + * + * @var array + */ + protected $bcc = []; + + /** + * The array of reply-to names and addresses. + * + * @var array + */ + protected $ReplyTo = []; + + /** + * An array of all kinds of addresses. + * Includes all of $to, $cc, $bcc. + * + * @see PHPMailer::$to + * @see PHPMailer::$cc + * @see PHPMailer::$bcc + * + * @var array + */ + protected $all_recipients = []; + + /** + * An array of names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $all_recipients + * and one of $to, $cc, or $bcc. + * This array is used only for addresses with IDN. + * + * @see PHPMailer::$to + * @see PHPMailer::$cc + * @see PHPMailer::$bcc + * @see PHPMailer::$all_recipients + * + * @var array + */ + protected $RecipientsQueue = []; + + /** + * An array of reply-to names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $ReplyTo. + * This array is used only for addresses with IDN. + * + * @see PHPMailer::$ReplyTo + * + * @var array + */ + protected $ReplyToQueue = []; + + /** + * The array of attachments. + * + * @var array + */ + protected $attachment = []; + + /** + * The array of custom headers. + * + * @var array + */ + protected $CustomHeader = []; + + /** + * The most recent Message-ID (including angular brackets). + * + * @var string + */ + protected $lastMessageID = ''; + + /** + * The message's MIME type. + * + * @var string + */ + protected $message_type = ''; + + /** + * The array of MIME boundary strings. + * + * @var array + */ + protected $boundary = []; + + /** + * The array of available languages. + * + * @var array + */ + protected $language = []; + + /** + * The number of errors encountered. + * + * @var int + */ + protected $error_count = 0; + + /** + * The S/MIME certificate file path. + * + * @var string + */ + protected $sign_cert_file = ''; + + /** + * The S/MIME key file path. + * + * @var string + */ + protected $sign_key_file = ''; + + /** + * The optional S/MIME extra certificates ("CA Chain") file path. + * + * @var string + */ + protected $sign_extracerts_file = ''; + + /** + * The S/MIME password for the key. + * Used only if the key is encrypted. + * + * @var string + */ + protected $sign_key_pass = ''; + + /** + * Whether to throw exceptions for errors. + * + * @var bool + */ + protected $exceptions = false; + + /** + * Unique ID used for message ID and boundaries. + * + * @var string + */ + protected $uniqueid = ''; + + /** + * The PHPMailer Version number. + * + * @var string + */ + const VERSION = '6.0.7'; + + /** + * Error severity: message only, continue processing. + * + * @var int + */ + const STOP_MESSAGE = 0; + + /** + * Error severity: message, likely ok to continue processing. + * + * @var int + */ + const STOP_CONTINUE = 1; + + /** + * Error severity: message, plus full stop, critical error reached. + * + * @var int + */ + const STOP_CRITICAL = 2; + + /** + * SMTP RFC standard line ending. + * + * @var string + */ + protected static $LE = "\r\n"; + + /** + * The maximum line length allowed by RFC 2822 section 2.1.1. + * + * @var int + */ + const MAX_LINE_LENGTH = 998; + + /** + * The lower maximum line length allowed by RFC 2822 section 2.1.1. + * This length does NOT include the line break + * 76 means that lines will be 77 or 78 chars depending on whether + * the line break format is LF or CRLF; both are valid. + * + * @var int + */ + const STD_LINE_LENGTH = 76; + + /** + * Constructor. + * + * @param bool $exceptions Should we throw external exceptions? + */ + public function __construct($exceptions = null) + { + if (null !== $exceptions) { + $this->exceptions = (bool) $exceptions; + } + //Pick an appropriate debug output format automatically + $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html'); + } + + /** + * Destructor. + */ + public function __destruct() + { + //Close any open SMTP connection nicely + $this->smtpClose(); + } + + /** + * Call mail() in a safe_mode-aware fashion. + * Also, unless sendmail_path points to sendmail (or something that + * claims to be sendmail), don't pass params (not a perfect fix, + * but it will do). + * + * @param string $to To + * @param string $subject Subject + * @param string $body Message Body + * @param string $header Additional Header(s) + * @param string|null $params Params + * + * @return bool + */ + private function mailPassthru($to, $subject, $body, $header, $params) + { + //Check overloading of mail function to avoid double-encoding + if (ini_get('mbstring.func_overload') & 1) { + $subject = $this->secureHeader($subject); + } else { + $subject = $this->encodeHeader($this->secureHeader($subject)); + } + //Calling mail() with null params breaks + if (!$this->UseSendmailOptions or null === $params) { + $result = @mail($to, $subject, $body, $header); + } else { + $result = @mail($to, $subject, $body, $header, $params); + } + + return $result; + } + + /** + * Output debugging info via user-defined method. + * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug). + * + * @see PHPMailer::$Debugoutput + * @see PHPMailer::$SMTPDebug + * + * @param string $str + */ + protected function edebug($str) + { + if ($this->SMTPDebug <= 0) { + return; + } + //Is this a PSR-3 logger? + if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) { + $this->Debugoutput->debug($str); + + return; + } + //Avoid clash with built-in function names + if (!in_array($this->Debugoutput, ['error_log', 'html', 'echo']) and is_callable($this->Debugoutput)) { + call_user_func($this->Debugoutput, $str, $this->SMTPDebug); + + return; + } + switch ($this->Debugoutput) { + case 'error_log': + //Don't output, just log + error_log($str); + break; + case 'html': + //Cleans up output a bit for a better looking, HTML-safe output + echo htmlentities( + preg_replace('/[\r\n]+/', '', $str), + ENT_QUOTES, + 'UTF-8' + ), "
      \n"; + break; + case 'echo': + default: + //Normalize line breaks + $str = preg_replace('/\r\n|\r/ms', "\n", $str); + echo gmdate('Y-m-d H:i:s'), + "\t", + //Trim trailing space + trim( + //Indent for readability, except for trailing break + str_replace( + "\n", + "\n \t ", + trim($str) + ) + ), + "\n"; + } + } + + /** + * Sets message type to HTML or plain. + * + * @param bool $isHtml True for HTML mode + */ + public function isHTML($isHtml = true) + { + if ($isHtml) { + $this->ContentType = static::CONTENT_TYPE_TEXT_HTML; + } else { + $this->ContentType = static::CONTENT_TYPE_PLAINTEXT; + } + } + + /** + * Send messages using SMTP. + */ + public function isSMTP() + { + $this->Mailer = 'smtp'; + } + + /** + * Send messages using PHP's mail() function. + */ + public function isMail() + { + $this->Mailer = 'mail'; + } + + /** + * Send messages using $Sendmail. + */ + public function isSendmail() + { + $ini_sendmail_path = ini_get('sendmail_path'); + + if (false === stripos($ini_sendmail_path, 'sendmail')) { + $this->Sendmail = '/usr/sbin/sendmail'; + } else { + $this->Sendmail = $ini_sendmail_path; + } + $this->Mailer = 'sendmail'; + } + + /** + * Send messages using qmail. + */ + public function isQmail() + { + $ini_sendmail_path = ini_get('sendmail_path'); + + if (false === stripos($ini_sendmail_path, 'qmail')) { + $this->Sendmail = '/var/qmail/bin/qmail-inject'; + } else { + $this->Sendmail = $ini_sendmail_path; + } + $this->Mailer = 'qmail'; + } + + /** + * Add a "To" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addAddress($address, $name = '') + { + return $this->addOrEnqueueAnAddress('to', $address, $name); + } + + /** + * Add a "CC" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addCC($address, $name = '') + { + return $this->addOrEnqueueAnAddress('cc', $address, $name); + } + + /** + * Add a "BCC" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addBCC($address, $name = '') + { + return $this->addOrEnqueueAnAddress('bcc', $address, $name); + } + + /** + * Add a "Reply-To" address. + * + * @param string $address The email address to reply to + * @param string $name + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addReplyTo($address, $name = '') + { + return $this->addOrEnqueueAnAddress('Reply-To', $address, $name); + } + + /** + * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer + * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still + * be modified after calling this function), addition of such addresses is delayed until send(). + * Addresses that have been added already return false, but do not throw exceptions. + * + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address to send, resp. to reply to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + protected function addOrEnqueueAnAddress($kind, $address, $name) + { + $address = trim($address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + $pos = strrpos($address, '@'); + if (false === $pos) { + // At-sign is missing. + $error_message = sprintf('%s (%s): %s', + $this->lang('invalid_address'), + $kind, + $address); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + $params = [$kind, $address, $name]; + // Enqueue addresses with IDN until we know the PHPMailer::$CharSet. + if ($this->has8bitChars(substr($address, ++$pos)) and static::idnSupported()) { + if ('Reply-To' != $kind) { + if (!array_key_exists($address, $this->RecipientsQueue)) { + $this->RecipientsQueue[$address] = $params; + + return true; + } + } else { + if (!array_key_exists($address, $this->ReplyToQueue)) { + $this->ReplyToQueue[$address] = $params; + + return true; + } + } + + return false; + } + + // Immediately add standard addresses without IDN. + return call_user_func_array([$this, 'addAnAddress'], $params); + } + + /** + * Add an address to one of the recipient arrays or to the ReplyTo array. + * Addresses that have been added already return false, but do not throw exceptions. + * + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address to send, resp. to reply to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + protected function addAnAddress($kind, $address, $name = '') + { + if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) { + $error_message = sprintf('%s: %s', + $this->lang('Invalid recipient kind'), + $kind); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if (!static::validateAddress($address)) { + $error_message = sprintf('%s (%s): %s', + $this->lang('invalid_address'), + $kind, + $address); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if ('Reply-To' != $kind) { + if (!array_key_exists(strtolower($address), $this->all_recipients)) { + $this->{$kind}[] = [$address, $name]; + $this->all_recipients[strtolower($address)] = true; + + return true; + } + } else { + if (!array_key_exists(strtolower($address), $this->ReplyTo)) { + $this->ReplyTo[strtolower($address)] = [$address, $name]; + + return true; + } + } + + return false; + } + + /** + * Parse and validate a string containing one or more RFC822-style comma-separated email addresses + * of the form "display name
      " into an array of name/address pairs. + * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available. + * Note that quotes in the name part are removed. + * + * @see http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation + * + * @param string $addrstr The address list string + * @param bool $useimap Whether to use the IMAP extension to parse the list + * + * @return array + */ + public static function parseAddresses($addrstr, $useimap = true) + { + $addresses = []; + if ($useimap and function_exists('imap_rfc822_parse_adrlist')) { + //Use this built-in parser if it's available + $list = imap_rfc822_parse_adrlist($addrstr, ''); + foreach ($list as $address) { + if ('.SYNTAX-ERROR.' != $address->host) { + if (static::validateAddress($address->mailbox . '@' . $address->host)) { + $addresses[] = [ + 'name' => (property_exists($address, 'personal') ? $address->personal : ''), + 'address' => $address->mailbox . '@' . $address->host, + ]; + } + } + } + } else { + //Use this simpler parser + $list = explode(',', $addrstr); + foreach ($list as $address) { + $address = trim($address); + //Is there a separate name part? + if (strpos($address, '<') === false) { + //No separate name, just use the whole thing + if (static::validateAddress($address)) { + $addresses[] = [ + 'name' => '', + 'address' => $address, + ]; + } + } else { + list($name, $email) = explode('<', $address); + $email = trim(str_replace('>', '', $email)); + if (static::validateAddress($email)) { + $addresses[] = [ + 'name' => trim(str_replace(['"', "'"], '', $name)), + 'address' => $email, + ]; + } + } + } + } + + return $addresses; + } + + /** + * Set the From and FromName properties. + * + * @param string $address + * @param string $name + * @param bool $auto Whether to also set the Sender address, defaults to true + * + * @throws Exception + * + * @return bool + */ + public function setFrom($address, $name = '', $auto = true) + { + $address = trim($address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + // Don't validate now addresses with IDN. Will be done in send(). + $pos = strrpos($address, '@'); + if (false === $pos or + (!$this->has8bitChars(substr($address, ++$pos)) or !static::idnSupported()) and + !static::validateAddress($address)) { + $error_message = sprintf('%s (From): %s', + $this->lang('invalid_address'), + $address); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + $this->From = $address; + $this->FromName = $name; + if ($auto) { + if (empty($this->Sender)) { + $this->Sender = $address; + } + } + + return true; + } + + /** + * Return the Message-ID header of the last email. + * Technically this is the value from the last time the headers were created, + * but it's also the message ID of the last sent message except in + * pathological cases. + * + * @return string + */ + public function getLastMessageID() + { + return $this->lastMessageID; + } + + /** + * Check that a string looks like an email address. + * Validation patterns supported: + * * `auto` Pick best pattern automatically; + * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0; + * * `pcre` Use old PCRE implementation; + * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; + * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements. + * * `noregex` Don't use a regex: super fast, really dumb. + * Alternatively you may pass in a callable to inject your own validator, for example: + * + * ```php + * PHPMailer::validateAddress('user@example.com', function($address) { + * return (strpos($address, '@') !== false); + * }); + * ``` + * + * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator. + * + * @param string $address The email address to check + * @param string|callable $patternselect Which pattern to use + * + * @return bool + */ + public static function validateAddress($address, $patternselect = null) + { + if (null === $patternselect) { + $patternselect = static::$validator; + } + if (is_callable($patternselect)) { + return call_user_func($patternselect, $address); + } + //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321 + if (strpos($address, "\n") !== false or strpos($address, "\r") !== false) { + return false; + } + switch ($patternselect) { + case 'pcre': //Kept for BC + case 'pcre8': + /* + * A more complex and more permissive version of the RFC5322 regex on which FILTER_VALIDATE_EMAIL + * is based. + * In addition to the addresses allowed by filter_var, also permits: + * * dotless domains: `a@b` + * * comments: `1234 @ local(blah) .machine .example` + * * quoted elements: `'"test blah"@example.org'` + * * numeric TLDs: `a@b.123` + * * unbracketed IPv4 literals: `a@192.168.0.1` + * * IPv6 literals: 'first.last@[IPv6:a1::]' + * Not all of these will necessarily work for sending! + * + * @see http://squiloople.com/2009/12/20/email-address-validation/ + * @copyright 2009-2010 Michael Rushton + * Feel free to use and redistribute this code. But please keep this copyright notice. + */ + return (bool) preg_match( + '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' . + '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' . + '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' . + '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' . + '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' . + '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' . + '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' . + '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' . + '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', + $address + ); + case 'html5': + /* + * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements. + * + * @see http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email) + */ + return (bool) preg_match( + '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' . + '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD', + $address + ); + case 'php': + default: + return (bool) filter_var($address, FILTER_VALIDATE_EMAIL); + } + } + + /** + * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the + * `intl` and `mbstring` PHP extensions. + * + * @return bool `true` if required functions for IDN support are present + */ + public static function idnSupported() + { + return function_exists('idn_to_ascii') and function_exists('mb_convert_encoding'); + } + + /** + * Converts IDN in given email address to its ASCII form, also known as punycode, if possible. + * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet. + * This function silently returns unmodified address if: + * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form) + * - Conversion to punycode is impossible (e.g. required PHP functions are not available) + * or fails for any reason (e.g. domain contains characters not allowed in an IDN). + * + * @see PHPMailer::$CharSet + * + * @param string $address The email address to convert + * + * @return string The encoded address in ASCII form + */ + public function punyencodeAddress($address) + { + // Verify we have required functions, CharSet, and at-sign. + $pos = strrpos($address, '@'); + if (static::idnSupported() and + !empty($this->CharSet) and + false !== $pos + ) { + $domain = substr($address, ++$pos); + // Verify CharSet string is a valid one, and domain properly encoded in this CharSet. + if ($this->has8bitChars($domain) and @mb_check_encoding($domain, $this->CharSet)) { + $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet); + //Ignore IDE complaints about this line - method signature changed in PHP 5.4 + $errorcode = 0; + $punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARIANT_UTS46); + if (false !== $punycode) { + return substr($address, 0, $pos) . $punycode; + } + } + } + + return $address; + } + + /** + * Create a message and send it. + * Uses the sending method specified by $Mailer. + * + * @throws Exception + * + * @return bool false on error - See the ErrorInfo property for details of the error + */ + public function send() + { + try { + if (!$this->preSend()) { + return false; + } + + return $this->postSend(); + } catch (Exception $exc) { + $this->mailHeader = ''; + $this->setError($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + } + + /** + * Prepare a message for sending. + * + * @throws Exception + * + * @return bool + */ + public function preSend() + { + if ('smtp' == $this->Mailer or + ('mail' == $this->Mailer and stripos(PHP_OS, 'WIN') === 0) + ) { + //SMTP mandates RFC-compliant line endings + //and it's also used with mail() on Windows + static::setLE("\r\n"); + } else { + //Maintain backward compatibility with legacy Linux command line mailers + static::setLE(PHP_EOL); + } + //Check for buggy PHP versions that add a header with an incorrect line break + if (ini_get('mail.add_x_header') == 1 + and 'mail' == $this->Mailer + and stripos(PHP_OS, 'WIN') === 0 + and ((version_compare(PHP_VERSION, '7.0.0', '>=') + and version_compare(PHP_VERSION, '7.0.17', '<')) + or (version_compare(PHP_VERSION, '7.1.0', '>=') + and version_compare(PHP_VERSION, '7.1.3', '<'))) + ) { + trigger_error( + 'Your version of PHP is affected by a bug that may result in corrupted messages.' . + ' To fix it, switch to sending using SMTP, disable the mail.add_x_header option in' . + ' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.', + E_USER_WARNING + ); + } + + try { + $this->error_count = 0; // Reset errors + $this->mailHeader = ''; + + // Dequeue recipient and Reply-To addresses with IDN + foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) { + $params[1] = $this->punyencodeAddress($params[1]); + call_user_func_array([$this, 'addAnAddress'], $params); + } + if (count($this->to) + count($this->cc) + count($this->bcc) < 1) { + throw new Exception($this->lang('provide_address'), self::STOP_CRITICAL); + } + + // Validate From, Sender, and ConfirmReadingTo addresses + foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) { + $this->$address_kind = trim($this->$address_kind); + if (empty($this->$address_kind)) { + continue; + } + $this->$address_kind = $this->punyencodeAddress($this->$address_kind); + if (!static::validateAddress($this->$address_kind)) { + $error_message = sprintf('%s (%s): %s', + $this->lang('invalid_address'), + $address_kind, + $this->$address_kind); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + } + + // Set whether the message is multipart/alternative + if ($this->alternativeExists()) { + $this->ContentType = static::CONTENT_TYPE_MULTIPART_ALTERNATIVE; + } + + $this->setMessageType(); + // Refuse to send an empty message unless we are specifically allowing it + if (!$this->AllowEmpty and empty($this->Body)) { + throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL); + } + + //Trim subject consistently + $this->Subject = trim($this->Subject); + // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding) + $this->MIMEHeader = ''; + $this->MIMEBody = $this->createBody(); + // createBody may have added some headers, so retain them + $tempheaders = $this->MIMEHeader; + $this->MIMEHeader = $this->createHeader(); + $this->MIMEHeader .= $tempheaders; + + // To capture the complete message when using mail(), create + // an extra header list which createHeader() doesn't fold in + if ('mail' == $this->Mailer) { + if (count($this->to) > 0) { + $this->mailHeader .= $this->addrAppend('To', $this->to); + } else { + $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;'); + } + $this->mailHeader .= $this->headerLine( + 'Subject', + $this->encodeHeader($this->secureHeader($this->Subject)) + ); + } + + // Sign with DKIM if enabled + if (!empty($this->DKIM_domain) + and !empty($this->DKIM_selector) + and (!empty($this->DKIM_private_string) + or (!empty($this->DKIM_private) + and static::isPermittedPath($this->DKIM_private) + and file_exists($this->DKIM_private) + ) + ) + ) { + $header_dkim = $this->DKIM_Add( + $this->MIMEHeader . $this->mailHeader, + $this->encodeHeader($this->secureHeader($this->Subject)), + $this->MIMEBody + ); + $this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . static::$LE . + static::normalizeBreaks($header_dkim) . static::$LE; + } + + return true; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + } + + /** + * Actually send a message via the selected mechanism. + * + * @throws Exception + * + * @return bool + */ + public function postSend() + { + try { + // Choose the mailer and send through it + switch ($this->Mailer) { + case 'sendmail': + case 'qmail': + return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody); + case 'smtp': + return $this->smtpSend($this->MIMEHeader, $this->MIMEBody); + case 'mail': + return $this->mailSend($this->MIMEHeader, $this->MIMEBody); + default: + $sendMethod = $this->Mailer . 'Send'; + if (method_exists($this, $sendMethod)) { + return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody); + } + + return $this->mailSend($this->MIMEHeader, $this->MIMEBody); + } + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + } + + return false; + } + + /** + * Send mail using the $Sendmail program. + * + * @see PHPMailer::$Sendmail + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function sendmailSend($header, $body) + { + // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. + if (!empty($this->Sender) and self::isShellSafe($this->Sender)) { + if ('qmail' == $this->Mailer) { + $sendmailFmt = '%s -f%s'; + } else { + $sendmailFmt = '%s -oi -f%s -t'; + } + } else { + if ('qmail' == $this->Mailer) { + $sendmailFmt = '%s'; + } else { + $sendmailFmt = '%s -oi -t'; + } + } + + $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender); + + if ($this->SingleTo) { + foreach ($this->SingleToArray as $toAddr) { + $mail = @popen($sendmail, 'w'); + if (!$mail) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fwrite($mail, 'To: ' . $toAddr . "\n"); + fwrite($mail, $header); + fwrite($mail, $body); + $result = pclose($mail); + $this->doCallback( + ($result == 0), + [$toAddr], + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + if (0 !== $result) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + } else { + $mail = @popen($sendmail, 'w'); + if (!$mail) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fwrite($mail, $header); + fwrite($mail, $body); + $result = pclose($mail); + $this->doCallback( + ($result == 0), + $this->to, + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + if (0 !== $result) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + + return true; + } + + /** + * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters. + * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows. + * + * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report + * + * @param string $string The string to be validated + * + * @return bool + */ + protected static function isShellSafe($string) + { + // Future-proof + if (escapeshellcmd($string) !== $string + or !in_array(escapeshellarg($string), ["'$string'", "\"$string\""]) + ) { + return false; + } + + $length = strlen($string); + + for ($i = 0; $i < $length; ++$i) { + $c = $string[$i]; + + // All other characters have a special meaning in at least one common shell, including = and +. + // Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here. + // Note that this does permit non-Latin alphanumeric characters based on the current locale. + if (!ctype_alnum($c) && strpos('@_-.', $c) === false) { + return false; + } + } + + return true; + } + + /** + * Check whether a file path is of a permitted type. + * Used to reject URLs and phar files from functions that access local file paths, + * such as addAttachment. + * + * @param string $path A relative or absolute path to a file + * + * @return bool + */ + protected static function isPermittedPath($path) + { + return !preg_match('#^[a-z]+://#i', $path); + } + + /** + * Send mail using the PHP mail() function. + * + * @see http://www.php.net/manual/en/book.mail.php + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function mailSend($header, $body) + { + $toArr = []; + foreach ($this->to as $toaddr) { + $toArr[] = $this->addrFormat($toaddr); + } + $to = implode(', ', $toArr); + + $params = null; + //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver + if (!empty($this->Sender) and static::validateAddress($this->Sender)) { + //A space after `-f` is optional, but there is a long history of its presence + //causing problems, so we don't use one + //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html + //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html + //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html + //Example problem: https://www.drupal.org/node/1057954 + // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. + if (self::isShellSafe($this->Sender)) { + $params = sprintf('-f%s', $this->Sender); + } + } + if (!empty($this->Sender) and static::validateAddress($this->Sender)) { + $old_from = ini_get('sendmail_from'); + ini_set('sendmail_from', $this->Sender); + } + $result = false; + if ($this->SingleTo and count($toArr) > 1) { + foreach ($toArr as $toAddr) { + $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params); + $this->doCallback($result, [$toAddr], $this->cc, $this->bcc, $this->Subject, $body, $this->From, []); + } + } else { + $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params); + $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From, []); + } + if (isset($old_from)) { + ini_set('sendmail_from', $old_from); + } + if (!$result) { + throw new Exception($this->lang('instantiate'), self::STOP_CRITICAL); + } + + return true; + } + + /** + * Get an instance to use for SMTP operations. + * Override this function to load your own SMTP implementation, + * or set one with setSMTPInstance. + * + * @return SMTP + */ + public function getSMTPInstance() + { + if (!is_object($this->smtp)) { + $this->smtp = new SMTP(); + } + + return $this->smtp; + } + + /** + * Provide an instance to use for SMTP operations. + * + * @param SMTP $smtp + * + * @return SMTP + */ + public function setSMTPInstance(SMTP $smtp) + { + $this->smtp = $smtp; + + return $this->smtp; + } + + /** + * Send mail via SMTP. + * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. + * + * @see PHPMailer::setSMTPInstance() to use a different class. + * + * @uses \PHPMailer\PHPMailer\SMTP + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function smtpSend($header, $body) + { + $bad_rcpt = []; + if (!$this->smtpConnect($this->SMTPOptions)) { + throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL); + } + //Sender already validated in preSend() + if ('' == $this->Sender) { + $smtp_from = $this->From; + } else { + $smtp_from = $this->Sender; + } + if (!$this->smtp->mail($smtp_from)) { + $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError())); + throw new Exception($this->ErrorInfo, self::STOP_CRITICAL); + } + + $callbacks = []; + // Attempt to send to all recipients + foreach ([$this->to, $this->cc, $this->bcc] as $togroup) { + foreach ($togroup as $to) { + if (!$this->smtp->recipient($to[0])) { + $error = $this->smtp->getError(); + $bad_rcpt[] = ['to' => $to[0], 'error' => $error['detail']]; + $isSent = false; + } else { + $isSent = true; + } + + $callbacks[] = ['issent'=>$isSent, 'to'=>$to[0]]; + } + } + + // Only send the DATA command if we have viable recipients + if ((count($this->all_recipients) > count($bad_rcpt)) and !$this->smtp->data($header . $body)) { + throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL); + } + + $smtp_transaction_id = $this->smtp->getLastTransactionID(); + + if ($this->SMTPKeepAlive) { + $this->smtp->reset(); + } else { + $this->smtp->quit(); + $this->smtp->close(); + } + + foreach ($callbacks as $cb) { + $this->doCallback( + $cb['issent'], + [$cb['to']], + [], + [], + $this->Subject, + $body, + $this->From, + ['smtp_transaction_id' => $smtp_transaction_id] + ); + } + + //Create error message for any bad addresses + if (count($bad_rcpt) > 0) { + $errstr = ''; + foreach ($bad_rcpt as $bad) { + $errstr .= $bad['to'] . ': ' . $bad['error']; + } + throw new Exception( + $this->lang('recipients_failed') . $errstr, + self::STOP_CONTINUE + ); + } + + return true; + } + + /** + * Initiate a connection to an SMTP server. + * Returns false if the operation failed. + * + * @param array $options An array of options compatible with stream_context_create() + * + * @throws Exception + * + * @uses \PHPMailer\PHPMailer\SMTP + * + * @return bool + */ + public function smtpConnect($options = null) + { + if (null === $this->smtp) { + $this->smtp = $this->getSMTPInstance(); + } + + //If no options are provided, use whatever is set in the instance + if (null === $options) { + $options = $this->SMTPOptions; + } + + // Already connected? + if ($this->smtp->connected()) { + return true; + } + + $this->smtp->setTimeout($this->Timeout); + $this->smtp->setDebugLevel($this->SMTPDebug); + $this->smtp->setDebugOutput($this->Debugoutput); + $this->smtp->setVerp($this->do_verp); + $hosts = explode(';', $this->Host); + $lastexception = null; + + foreach ($hosts as $hostentry) { + $hostinfo = []; + if (!preg_match( + '/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*|\[[a-fA-F0-9:]+\]):?([0-9]*)$/', + trim($hostentry), + $hostinfo + )) { + static::edebug($this->lang('connect_host') . ' ' . $hostentry); + // Not a valid host entry + continue; + } + // $hostinfo[2]: optional ssl or tls prefix + // $hostinfo[3]: the hostname + // $hostinfo[4]: optional port number + // The host string prefix can temporarily override the current setting for SMTPSecure + // If it's not specified, the default value is used + + //Check the host name is a valid name or IP address before trying to use it + if (!static::isValidHost($hostinfo[3])) { + static::edebug($this->lang('connect_host') . ' ' . $hostentry); + continue; + } + $prefix = ''; + $secure = $this->SMTPSecure; + $tls = ('tls' == $this->SMTPSecure); + if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) { + $prefix = 'ssl://'; + $tls = false; // Can't have SSL and TLS at the same time + $secure = 'ssl'; + } elseif ('tls' == $hostinfo[2]) { + $tls = true; + // tls doesn't use a prefix + $secure = 'tls'; + } + //Do we need the OpenSSL extension? + $sslext = defined('OPENSSL_ALGO_SHA256'); + if ('tls' === $secure or 'ssl' === $secure) { + //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled + if (!$sslext) { + throw new Exception($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL); + } + } + $host = $hostinfo[3]; + $port = $this->Port; + $tport = (int) $hostinfo[4]; + if ($tport > 0 and $tport < 65536) { + $port = $tport; + } + if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) { + try { + if ($this->Helo) { + $hello = $this->Helo; + } else { + $hello = $this->serverHostname(); + } + $this->smtp->hello($hello); + //Automatically enable TLS encryption if: + // * it's not disabled + // * we have openssl extension + // * we are not already using SSL + // * the server offers STARTTLS + if ($this->SMTPAutoTLS and $sslext and 'ssl' != $secure and $this->smtp->getServerExt('STARTTLS')) { + $tls = true; + } + if ($tls) { + if (!$this->smtp->startTLS()) { + throw new Exception($this->lang('connect_host')); + } + // We must resend EHLO after TLS negotiation + $this->smtp->hello($hello); + } + if ($this->SMTPAuth) { + if (!$this->smtp->authenticate( + $this->Username, + $this->Password, + $this->AuthType, + $this->oauth + ) + ) { + throw new Exception($this->lang('authenticate')); + } + } + + return true; + } catch (Exception $exc) { + $lastexception = $exc; + $this->edebug($exc->getMessage()); + // We must have connected, but then failed TLS or Auth, so close connection nicely + $this->smtp->quit(); + } + } + } + // If we get here, all connection attempts have failed, so close connection hard + $this->smtp->close(); + // As we've caught all exceptions, just report whatever the last one was + if ($this->exceptions and null !== $lastexception) { + throw $lastexception; + } + + return false; + } + + /** + * Close the active SMTP session if one exists. + */ + public function smtpClose() + { + if (null !== $this->smtp) { + if ($this->smtp->connected()) { + $this->smtp->quit(); + $this->smtp->close(); + } + } + } + + /** + * Set the language for error messages. + * Returns false if it cannot load the language file. + * The default language is English. + * + * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr") + * @param string $lang_path Path to the language file directory, with trailing separator (slash) + * + * @return bool + */ + public function setLanguage($langcode = 'en', $lang_path = '') + { + // Backwards compatibility for renamed language codes + $renamed_langcodes = [ + 'br' => 'pt_br', + 'cz' => 'cs', + 'dk' => 'da', + 'no' => 'nb', + 'se' => 'sv', + 'rs' => 'sr', + 'tg' => 'tl', + ]; + + if (isset($renamed_langcodes[$langcode])) { + $langcode = $renamed_langcodes[$langcode]; + } + + // Define full set of translatable strings in English + $PHPMAILER_LANG = [ + 'authenticate' => 'SMTP Error: Could not authenticate.', + 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', + 'data_not_accepted' => 'SMTP Error: data not accepted.', + 'empty_message' => 'Message body empty', + 'encoding' => 'Unknown encoding: ', + 'execute' => 'Could not execute: ', + 'file_access' => 'Could not access file: ', + 'file_open' => 'File Error: Could not open file: ', + 'from_failed' => 'The following From address failed: ', + 'instantiate' => 'Could not instantiate mail function.', + 'invalid_address' => 'Invalid address: ', + 'mailer_not_supported' => ' mailer is not supported.', + 'provide_address' => 'You must provide at least one recipient email address.', + 'recipients_failed' => 'SMTP Error: The following recipients failed: ', + 'signing' => 'Signing Error: ', + 'smtp_connect_failed' => 'SMTP connect() failed.', + 'smtp_error' => 'SMTP server error: ', + 'variable_set' => 'Cannot set or reset variable: ', + 'extension_missing' => 'Extension missing: ', + ]; + if (empty($lang_path)) { + // Calculate an absolute path so it can work if CWD is not here + $lang_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR; + } + //Validate $langcode + if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) { + $langcode = 'en'; + } + $foundlang = true; + $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php'; + // There is no English translation file + if ('en' != $langcode) { + // Make sure language file path is readable + if (!static::isPermittedPath($lang_file) || !file_exists($lang_file)) { + $foundlang = false; + } else { + // Overwrite language-specific strings. + // This way we'll never have missing translation keys. + $foundlang = include $lang_file; + } + } + $this->language = $PHPMAILER_LANG; + + return (bool) $foundlang; // Returns false if language not found + } + + /** + * Get the array of strings for the current language. + * + * @return array + */ + public function getTranslations() + { + return $this->language; + } + + /** + * Create recipient headers. + * + * @param string $type + * @param array $addr An array of recipients, + * where each recipient is a 2-element indexed array with element 0 containing an address + * and element 1 containing a name, like: + * [['joe@example.com', 'Joe User'], ['zoe@example.com', 'Zoe User']] + * + * @return string + */ + public function addrAppend($type, $addr) + { + $addresses = []; + foreach ($addr as $address) { + $addresses[] = $this->addrFormat($address); + } + + return $type . ': ' . implode(', ', $addresses) . static::$LE; + } + + /** + * Format an address for use in a message header. + * + * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name like + * ['joe@example.com', 'Joe User'] + * + * @return string + */ + public function addrFormat($addr) + { + if (empty($addr[1])) { // No name provided + return $this->secureHeader($addr[0]); + } + + return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . ' <' . $this->secureHeader( + $addr[0] + ) . '>'; + } + + /** + * Word-wrap message. + * For use with mailers that do not automatically perform wrapping + * and for quoted-printable encoded messages. + * Original written by philippe. + * + * @param string $message The message to wrap + * @param int $length The line length to wrap to + * @param bool $qp_mode Whether to run in Quoted-Printable mode + * + * @return string + */ + public function wrapText($message, $length, $qp_mode = false) + { + if ($qp_mode) { + $soft_break = sprintf(' =%s', static::$LE); + } else { + $soft_break = static::$LE; + } + // If utf-8 encoding is used, we will need to make sure we don't + // split multibyte characters when we wrap + $is_utf8 = static::CHARSET_UTF8 === strtolower($this->CharSet); + $lelen = strlen(static::$LE); + $crlflen = strlen(static::$LE); + + $message = static::normalizeBreaks($message); + //Remove a trailing line break + if (substr($message, -$lelen) == static::$LE) { + $message = substr($message, 0, -$lelen); + } + + //Split message into lines + $lines = explode(static::$LE, $message); + //Message will be rebuilt in here + $message = ''; + foreach ($lines as $line) { + $words = explode(' ', $line); + $buf = ''; + $firstword = true; + foreach ($words as $word) { + if ($qp_mode and (strlen($word) > $length)) { + $space_left = $length - strlen($buf) - $crlflen; + if (!$firstword) { + if ($space_left > 20) { + $len = $space_left; + if ($is_utf8) { + $len = $this->utf8CharBoundary($word, $len); + } elseif ('=' == substr($word, $len - 1, 1)) { + --$len; + } elseif ('=' == substr($word, $len - 2, 1)) { + $len -= 2; + } + $part = substr($word, 0, $len); + $word = substr($word, $len); + $buf .= ' ' . $part; + $message .= $buf . sprintf('=%s', static::$LE); + } else { + $message .= $buf . $soft_break; + } + $buf = ''; + } + while (strlen($word) > 0) { + if ($length <= 0) { + break; + } + $len = $length; + if ($is_utf8) { + $len = $this->utf8CharBoundary($word, $len); + } elseif ('=' == substr($word, $len - 1, 1)) { + --$len; + } elseif ('=' == substr($word, $len - 2, 1)) { + $len -= 2; + } + $part = substr($word, 0, $len); + $word = substr($word, $len); + + if (strlen($word) > 0) { + $message .= $part . sprintf('=%s', static::$LE); + } else { + $buf = $part; + } + } + } else { + $buf_o = $buf; + if (!$firstword) { + $buf .= ' '; + } + $buf .= $word; + + if (strlen($buf) > $length and '' != $buf_o) { + $message .= $buf_o . $soft_break; + $buf = $word; + } + } + $firstword = false; + } + $message .= $buf . static::$LE; + } + + return $message; + } + + /** + * Find the last character boundary prior to $maxLength in a utf-8 + * quoted-printable encoded string. + * Original written by Colin Brown. + * + * @param string $encodedText utf-8 QP text + * @param int $maxLength Find the last character boundary prior to this length + * + * @return int + */ + public function utf8CharBoundary($encodedText, $maxLength) + { + $foundSplitPos = false; + $lookBack = 3; + while (!$foundSplitPos) { + $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack); + $encodedCharPos = strpos($lastChunk, '='); + if (false !== $encodedCharPos) { + // Found start of encoded character byte within $lookBack block. + // Check the encoded byte value (the 2 chars after the '=') + $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2); + $dec = hexdec($hex); + if ($dec < 128) { + // Single byte character. + // If the encoded char was found at pos 0, it will fit + // otherwise reduce maxLength to start of the encoded char + if ($encodedCharPos > 0) { + $maxLength -= $lookBack - $encodedCharPos; + } + $foundSplitPos = true; + } elseif ($dec >= 192) { + // First byte of a multi byte character + // Reduce maxLength to split at start of character + $maxLength -= $lookBack - $encodedCharPos; + $foundSplitPos = true; + } elseif ($dec < 192) { + // Middle byte of a multi byte character, look further back + $lookBack += 3; + } + } else { + // No encoded character found + $foundSplitPos = true; + } + } + + return $maxLength; + } + + /** + * Apply word wrapping to the message body. + * Wraps the message body to the number of chars set in the WordWrap property. + * You should only do this to plain-text bodies as wrapping HTML tags may break them. + * This is called automatically by createBody(), so you don't need to call it yourself. + */ + public function setWordWrap() + { + if ($this->WordWrap < 1) { + return; + } + + switch ($this->message_type) { + case 'alt': + case 'alt_inline': + case 'alt_attach': + case 'alt_inline_attach': + $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap); + break; + default: + $this->Body = $this->wrapText($this->Body, $this->WordWrap); + break; + } + } + + /** + * Assemble message headers. + * + * @return string The assembled headers + */ + public function createHeader() + { + $result = ''; + + $result .= $this->headerLine('Date', '' == $this->MessageDate ? self::rfcDate() : $this->MessageDate); + + // To be created automatically by mail() + if ($this->SingleTo) { + if ('mail' != $this->Mailer) { + foreach ($this->to as $toaddr) { + $this->SingleToArray[] = $this->addrFormat($toaddr); + } + } + } else { + if (count($this->to) > 0) { + if ('mail' != $this->Mailer) { + $result .= $this->addrAppend('To', $this->to); + } + } elseif (count($this->cc) == 0) { + $result .= $this->headerLine('To', 'undisclosed-recipients:;'); + } + } + + $result .= $this->addrAppend('From', [[trim($this->From), $this->FromName]]); + + // sendmail and mail() extract Cc from the header before sending + if (count($this->cc) > 0) { + $result .= $this->addrAppend('Cc', $this->cc); + } + + // sendmail and mail() extract Bcc from the header before sending + if (( + 'sendmail' == $this->Mailer or 'qmail' == $this->Mailer or 'mail' == $this->Mailer + ) + and count($this->bcc) > 0 + ) { + $result .= $this->addrAppend('Bcc', $this->bcc); + } + + if (count($this->ReplyTo) > 0) { + $result .= $this->addrAppend('Reply-To', $this->ReplyTo); + } + + // mail() sets the subject itself + if ('mail' != $this->Mailer) { + $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject))); + } + + // Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4 + // https://tools.ietf.org/html/rfc5322#section-3.6.4 + if ('' != $this->MessageID and preg_match('/^<.*@.*>$/', $this->MessageID)) { + $this->lastMessageID = $this->MessageID; + } else { + $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname()); + } + $result .= $this->headerLine('Message-ID', $this->lastMessageID); + if (null !== $this->Priority) { + $result .= $this->headerLine('X-Priority', $this->Priority); + } + if ('' == $this->XMailer) { + $result .= $this->headerLine( + 'X-Mailer', + 'PHPMailer ' . self::VERSION . ' (https://github.com/PHPMailer/PHPMailer)' + ); + } else { + $myXmailer = trim($this->XMailer); + if ($myXmailer) { + $result .= $this->headerLine('X-Mailer', $myXmailer); + } + } + + if ('' != $this->ConfirmReadingTo) { + $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>'); + } + + // Add custom headers + foreach ($this->CustomHeader as $header) { + $result .= $this->headerLine( + trim($header[0]), + $this->encodeHeader(trim($header[1])) + ); + } + if (!$this->sign_key_file) { + $result .= $this->headerLine('MIME-Version', '1.0'); + $result .= $this->getMailMIME(); + } + + return $result; + } + + /** + * Get the message MIME type headers. + * + * @return string + */ + public function getMailMIME() + { + $result = ''; + $ismultipart = true; + switch ($this->message_type) { + case 'inline': + $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';'); + $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"'); + break; + case 'attach': + case 'inline_attach': + case 'alt_attach': + case 'alt_inline_attach': + $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_MIXED . ';'); + $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"'); + break; + case 'alt': + case 'alt_inline': + $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';'); + $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"'); + break; + default: + // Catches case 'plain': and case '': + $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet); + $ismultipart = false; + break; + } + // RFC1341 part 5 says 7bit is assumed if not specified + if (static::ENCODING_7BIT != $this->Encoding) { + // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE + if ($ismultipart) { + if (static::ENCODING_8BIT == $this->Encoding) { + $result .= $this->headerLine('Content-Transfer-Encoding', static::ENCODING_8BIT); + } + // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible + } else { + $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding); + } + } + + if ('mail' != $this->Mailer) { + $result .= static::$LE; + } + + return $result; + } + + /** + * Returns the whole MIME message. + * Includes complete headers and body. + * Only valid post preSend(). + * + * @see PHPMailer::preSend() + * + * @return string + */ + public function getSentMIMEMessage() + { + return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . static::$LE . static::$LE . $this->MIMEBody; + } + + /** + * Create a unique ID to use for boundaries. + * + * @return string + */ + protected function generateId() + { + $len = 32; //32 bytes = 256 bits + if (function_exists('random_bytes')) { + $bytes = random_bytes($len); + } elseif (function_exists('openssl_random_pseudo_bytes')) { + $bytes = openssl_random_pseudo_bytes($len); + } else { + //Use a hash to force the length to the same as the other methods + $bytes = hash('sha256', uniqid((string) mt_rand(), true), true); + } + + //We don't care about messing up base64 format here, just want a random string + return str_replace(['=', '+', '/'], '', base64_encode(hash('sha256', $bytes, true))); + } + + /** + * Assemble the message body. + * Returns an empty string on failure. + * + * @throws Exception + * + * @return string The assembled message body + */ + public function createBody() + { + $body = ''; + //Create unique IDs and preset boundaries + $this->uniqueid = $this->generateId(); + $this->boundary[1] = 'b1_' . $this->uniqueid; + $this->boundary[2] = 'b2_' . $this->uniqueid; + $this->boundary[3] = 'b3_' . $this->uniqueid; + + if ($this->sign_key_file) { + $body .= $this->getMailMIME() . static::$LE; + } + + $this->setWordWrap(); + + $bodyEncoding = $this->Encoding; + $bodyCharSet = $this->CharSet; + //Can we do a 7-bit downgrade? + if (static::ENCODING_8BIT == $bodyEncoding and !$this->has8bitChars($this->Body)) { + $bodyEncoding = static::ENCODING_7BIT; + //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit + $bodyCharSet = 'us-ascii'; + } + //If lines are too long, and we're not already using an encoding that will shorten them, + //change to quoted-printable transfer encoding for the body part only + if (static::ENCODING_BASE64 != $this->Encoding and static::hasLineLongerThanMax($this->Body)) { + $bodyEncoding = static::ENCODING_QUOTED_PRINTABLE; + } + + $altBodyEncoding = $this->Encoding; + $altBodyCharSet = $this->CharSet; + //Can we do a 7-bit downgrade? + if (static::ENCODING_8BIT == $altBodyEncoding and !$this->has8bitChars($this->AltBody)) { + $altBodyEncoding = static::ENCODING_7BIT; + //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit + $altBodyCharSet = 'us-ascii'; + } + //If lines are too long, and we're not already using an encoding that will shorten them, + //change to quoted-printable transfer encoding for the alt body part only + if (static::ENCODING_BASE64 != $altBodyEncoding and static::hasLineLongerThanMax($this->AltBody)) { + $altBodyEncoding = static::ENCODING_QUOTED_PRINTABLE; + } + //Use this as a preamble in all multipart message types + $mimepre = 'This is a multi-part message in MIME format.' . static::$LE; + switch ($this->message_type) { + case 'inline': + $body .= $mimepre; + $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + $body .= $this->attachAll('inline', $this->boundary[1]); + break; + case 'attach': + $body .= $mimepre; + $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + $body .= $this->attachAll('attachment', $this->boundary[1]); + break; + case 'inline_attach': + $body .= $mimepre; + $body .= $this->textLine('--' . $this->boundary[1]); + $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';'); + $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); + $body .= static::$LE; + $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + $body .= $this->attachAll('inline', $this->boundary[2]); + $body .= static::$LE; + $body .= $this->attachAll('attachment', $this->boundary[1]); + break; + case 'alt': + $body .= $mimepre; + $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, static::CONTENT_TYPE_PLAINTEXT, $altBodyEncoding); + $body .= $this->encodeString($this->AltBody, $altBodyEncoding); + $body .= static::$LE; + $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, static::CONTENT_TYPE_TEXT_HTML, $bodyEncoding); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + if (!empty($this->Ical)) { + $body .= $this->getBoundary($this->boundary[1], '', static::CONTENT_TYPE_TEXT_CALENDAR . '; method=REQUEST', ''); + $body .= $this->encodeString($this->Ical, $this->Encoding); + $body .= static::$LE; + } + $body .= $this->endBoundary($this->boundary[1]); + break; + case 'alt_inline': + $body .= $mimepre; + $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, static::CONTENT_TYPE_PLAINTEXT, $altBodyEncoding); + $body .= $this->encodeString($this->AltBody, $altBodyEncoding); + $body .= static::$LE; + $body .= $this->textLine('--' . $this->boundary[1]); + $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';'); + $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); + $body .= static::$LE; + $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, static::CONTENT_TYPE_TEXT_HTML, $bodyEncoding); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + $body .= $this->attachAll('inline', $this->boundary[2]); + $body .= static::$LE; + $body .= $this->endBoundary($this->boundary[1]); + break; + case 'alt_attach': + $body .= $mimepre; + $body .= $this->textLine('--' . $this->boundary[1]); + $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';'); + $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); + $body .= static::$LE; + $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, static::CONTENT_TYPE_PLAINTEXT, $altBodyEncoding); + $body .= $this->encodeString($this->AltBody, $altBodyEncoding); + $body .= static::$LE; + $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, static::CONTENT_TYPE_TEXT_HTML, $bodyEncoding); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + if (!empty($this->Ical)) { + $body .= $this->getBoundary($this->boundary[2], '', static::CONTENT_TYPE_TEXT_CALENDAR . '; method=REQUEST', ''); + $body .= $this->encodeString($this->Ical, $this->Encoding); + } + $body .= $this->endBoundary($this->boundary[2]); + $body .= static::$LE; + $body .= $this->attachAll('attachment', $this->boundary[1]); + break; + case 'alt_inline_attach': + $body .= $mimepre; + $body .= $this->textLine('--' . $this->boundary[1]); + $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';'); + $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); + $body .= static::$LE; + $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, static::CONTENT_TYPE_PLAINTEXT, $altBodyEncoding); + $body .= $this->encodeString($this->AltBody, $altBodyEncoding); + $body .= static::$LE; + $body .= $this->textLine('--' . $this->boundary[2]); + $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';'); + $body .= $this->textLine("\tboundary=\"" . $this->boundary[3] . '"'); + $body .= static::$LE; + $body .= $this->getBoundary($this->boundary[3], $bodyCharSet, static::CONTENT_TYPE_TEXT_HTML, $bodyEncoding); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + $body .= $this->attachAll('inline', $this->boundary[3]); + $body .= static::$LE; + $body .= $this->endBoundary($this->boundary[2]); + $body .= static::$LE; + $body .= $this->attachAll('attachment', $this->boundary[1]); + break; + default: + // Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types + //Reset the `Encoding` property in case we changed it for line length reasons + $this->Encoding = $bodyEncoding; + $body .= $this->encodeString($this->Body, $this->Encoding); + break; + } + + if ($this->isError()) { + $body = ''; + if ($this->exceptions) { + throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL); + } + } elseif ($this->sign_key_file) { + try { + if (!defined('PKCS7_TEXT')) { + throw new Exception($this->lang('extension_missing') . 'openssl'); + } + // @TODO would be nice to use php://temp streams here + $file = tempnam(sys_get_temp_dir(), 'mail'); + if (false === file_put_contents($file, $body)) { + throw new Exception($this->lang('signing') . ' Could not write temp file'); + } + $signed = tempnam(sys_get_temp_dir(), 'signed'); + //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197 + if (empty($this->sign_extracerts_file)) { + $sign = @openssl_pkcs7_sign( + $file, + $signed, + 'file://' . realpath($this->sign_cert_file), + ['file://' . realpath($this->sign_key_file), $this->sign_key_pass], + [] + ); + } else { + $sign = @openssl_pkcs7_sign( + $file, + $signed, + 'file://' . realpath($this->sign_cert_file), + ['file://' . realpath($this->sign_key_file), $this->sign_key_pass], + [], + PKCS7_DETACHED, + $this->sign_extracerts_file + ); + } + @unlink($file); + if ($sign) { + $body = file_get_contents($signed); + @unlink($signed); + //The message returned by openssl contains both headers and body, so need to split them up + $parts = explode("\n\n", $body, 2); + $this->MIMEHeader .= $parts[0] . static::$LE . static::$LE; + $body = $parts[1]; + } else { + @unlink($signed); + throw new Exception($this->lang('signing') . openssl_error_string()); + } + } catch (Exception $exc) { + $body = ''; + if ($this->exceptions) { + throw $exc; + } + } + } + + return $body; + } + + /** + * Return the start of a message boundary. + * + * @param string $boundary + * @param string $charSet + * @param string $contentType + * @param string $encoding + * + * @return string + */ + protected function getBoundary($boundary, $charSet, $contentType, $encoding) + { + $result = ''; + if ('' == $charSet) { + $charSet = $this->CharSet; + } + if ('' == $contentType) { + $contentType = $this->ContentType; + } + if ('' == $encoding) { + $encoding = $this->Encoding; + } + $result .= $this->textLine('--' . $boundary); + $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet); + $result .= static::$LE; + // RFC1341 part 5 says 7bit is assumed if not specified + if (static::ENCODING_7BIT != $encoding) { + $result .= $this->headerLine('Content-Transfer-Encoding', $encoding); + } + $result .= static::$LE; + + return $result; + } + + /** + * Return the end of a message boundary. + * + * @param string $boundary + * + * @return string + */ + protected function endBoundary($boundary) + { + return static::$LE . '--' . $boundary . '--' . static::$LE; + } + + /** + * Set the message type. + * PHPMailer only supports some preset message types, not arbitrary MIME structures. + */ + protected function setMessageType() + { + $type = []; + if ($this->alternativeExists()) { + $type[] = 'alt'; + } + if ($this->inlineImageExists()) { + $type[] = 'inline'; + } + if ($this->attachmentExists()) { + $type[] = 'attach'; + } + $this->message_type = implode('_', $type); + if ('' == $this->message_type) { + //The 'plain' message_type refers to the message having a single body element, not that it is plain-text + $this->message_type = 'plain'; + } + } + + /** + * Format a header line. + * + * @param string $name + * @param string|int $value + * + * @return string + */ + public function headerLine($name, $value) + { + return $name . ': ' . $value . static::$LE; + } + + /** + * Return a formatted mail line. + * + * @param string $value + * + * @return string + */ + public function textLine($value) + { + return $value . static::$LE; + } + + /** + * Add an attachment from a path on the filesystem. + * Never use a user-supplied path to a file! + * Returns false if the file could not be found or read. + * Explicitly *does not* support passing URLs; PHPMailer is not an HTTP client. + * If you need to do that, fetch the resource yourself and pass it in via a local file or string. + * + * @param string $path Path to the attachment + * @param string $name Overrides the attachment name + * @param string $encoding File encoding (see $Encoding) + * @param string $type File extension (MIME) type + * @param string $disposition Disposition to use + * + * @throws Exception + * + * @return bool + */ + public function addAttachment($path, $name = '', $encoding = self::ENCODING_BASE64, $type = '', $disposition = 'attachment') + { + try { + if (!static::isPermittedPath($path) || !@is_file($path)) { + throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE); + } + + // If a MIME type is not specified, try to work it out from the file name + if ('' == $type) { + $type = static::filenameToType($path); + } + + $filename = basename($path); + if ('' == $name) { + $name = $filename; + } + + $this->attachment[] = [ + 0 => $path, + 1 => $filename, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => false, // isStringAttachment + 6 => $disposition, + 7 => $name, + ]; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + + return true; + } + + /** + * Return the array of attachments. + * + * @return array + */ + public function getAttachments() + { + return $this->attachment; + } + + /** + * Attach all file, string, and binary attachments to the message. + * Returns an empty string on failure. + * + * @param string $disposition_type + * @param string $boundary + * + * @return string + */ + protected function attachAll($disposition_type, $boundary) + { + // Return text of body + $mime = []; + $cidUniq = []; + $incl = []; + + // Add all attachments + foreach ($this->attachment as $attachment) { + // Check if it is a valid disposition_filter + if ($attachment[6] == $disposition_type) { + // Check for string attachment + $string = ''; + $path = ''; + $bString = $attachment[5]; + if ($bString) { + $string = $attachment[0]; + } else { + $path = $attachment[0]; + } + + $inclhash = hash('sha256', serialize($attachment)); + if (in_array($inclhash, $incl)) { + continue; + } + $incl[] = $inclhash; + $name = $attachment[2]; + $encoding = $attachment[3]; + $type = $attachment[4]; + $disposition = $attachment[6]; + $cid = $attachment[7]; + if ('inline' == $disposition and array_key_exists($cid, $cidUniq)) { + continue; + } + $cidUniq[$cid] = true; + + $mime[] = sprintf('--%s%s', $boundary, static::$LE); + //Only include a filename property if we have one + if (!empty($name)) { + $mime[] = sprintf( + 'Content-Type: %s; name="%s"%s', + $type, + $this->encodeHeader($this->secureHeader($name)), + static::$LE + ); + } else { + $mime[] = sprintf( + 'Content-Type: %s%s', + $type, + static::$LE + ); + } + // RFC1341 part 5 says 7bit is assumed if not specified + if (static::ENCODING_7BIT != $encoding) { + $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, static::$LE); + } + + if (!empty($cid)) { + $mime[] = sprintf('Content-ID: <%s>%s', $cid, static::$LE); + } + + // If a filename contains any of these chars, it should be quoted, + // but not otherwise: RFC2183 & RFC2045 5.1 + // Fixes a warning in IETF's msglint MIME checker + // Allow for bypassing the Content-Disposition header totally + if (!(empty($disposition))) { + $encoded_name = $this->encodeHeader($this->secureHeader($name)); + if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name)) { + $mime[] = sprintf( + 'Content-Disposition: %s; filename="%s"%s', + $disposition, + $encoded_name, + static::$LE . static::$LE + ); + } else { + if (!empty($encoded_name)) { + $mime[] = sprintf( + 'Content-Disposition: %s; filename=%s%s', + $disposition, + $encoded_name, + static::$LE . static::$LE + ); + } else { + $mime[] = sprintf( + 'Content-Disposition: %s%s', + $disposition, + static::$LE . static::$LE + ); + } + } + } else { + $mime[] = static::$LE; + } + + // Encode as string attachment + if ($bString) { + $mime[] = $this->encodeString($string, $encoding); + } else { + $mime[] = $this->encodeFile($path, $encoding); + } + if ($this->isError()) { + return ''; + } + $mime[] = static::$LE; + } + } + + $mime[] = sprintf('--%s--%s', $boundary, static::$LE); + + return implode('', $mime); + } + + /** + * Encode a file attachment in requested format. + * Returns an empty string on failure. + * + * @param string $path The full path to the file + * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' + * + * @throws Exception + * + * @return string + */ + protected function encodeFile($path, $encoding = self::ENCODING_BASE64) + { + try { + if (!static::isPermittedPath($path) || !file_exists($path)) { + throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE); + } + $file_buffer = file_get_contents($path); + if (false === $file_buffer) { + throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE); + } + $file_buffer = $this->encodeString($file_buffer, $encoding); + + return $file_buffer; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + + return ''; + } + } + + /** + * Encode a string in requested format. + * Returns an empty string on failure. + * + * @param string $str The text to encode + * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' + * + * @return string + */ + public function encodeString($str, $encoding = self::ENCODING_BASE64) + { + $encoded = ''; + switch (strtolower($encoding)) { + case static::ENCODING_BASE64: + $encoded = chunk_split( + base64_encode($str), + static::STD_LINE_LENGTH, + static::$LE + ); + break; + case static::ENCODING_7BIT: + case static::ENCODING_8BIT: + $encoded = static::normalizeBreaks($str); + // Make sure it ends with a line break + if (substr($encoded, -(strlen(static::$LE))) != static::$LE) { + $encoded .= static::$LE; + } + break; + case static::ENCODING_BINARY: + $encoded = $str; + break; + case static::ENCODING_QUOTED_PRINTABLE: + $encoded = $this->encodeQP($str); + break; + default: + $this->setError($this->lang('encoding') . $encoding); + break; + } + + return $encoded; + } + + /** + * Encode a header value (not including its label) optimally. + * Picks shortest of Q, B, or none. Result includes folding if needed. + * See RFC822 definitions for phrase, comment and text positions. + * + * @param string $str The header value to encode + * @param string $position What context the string will be used in + * + * @return string + */ + public function encodeHeader($str, $position = 'text') + { + $matchcount = 0; + switch (strtolower($position)) { + case 'phrase': + if (!preg_match('/[\200-\377]/', $str)) { + // Can't use addslashes as we don't know the value of magic_quotes_sybase + $encoded = addcslashes($str, "\0..\37\177\\\""); + if (($str == $encoded) and !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) { + return $encoded; + } + + return "\"$encoded\""; + } + $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches); + break; + /* @noinspection PhpMissingBreakStatementInspection */ + case 'comment': + $matchcount = preg_match_all('/[()"]/', $str, $matches); + //fallthrough + case 'text': + default: + $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches); + break; + } + + //RFCs specify a maximum line length of 78 chars, however mail() will sometimes + //corrupt messages with headers longer than 65 chars. See #818 + $lengthsub = 'mail' == $this->Mailer ? 13 : 0; + $maxlen = static::STD_LINE_LENGTH - $lengthsub; + // Try to select the encoding which should produce the shortest output + if ($matchcount > strlen($str) / 3) { + // More than a third of the content will need encoding, so B encoding will be most efficient + $encoding = 'B'; + //This calculation is: + // max line length + // - shorten to avoid mail() corruption + // - Q/B encoding char overhead ("` =??[QB]??=`") + // - charset name length + $maxlen = static::STD_LINE_LENGTH - $lengthsub - 8 - strlen($this->CharSet); + if ($this->hasMultiBytes($str)) { + // Use a custom function which correctly encodes and wraps long + // multibyte strings without breaking lines within a character + $encoded = $this->base64EncodeWrapMB($str, "\n"); + } else { + $encoded = base64_encode($str); + $maxlen -= $maxlen % 4; + $encoded = trim(chunk_split($encoded, $maxlen, "\n")); + } + $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded); + } elseif ($matchcount > 0) { + //1 or more chars need encoding, use Q-encode + $encoding = 'Q'; + //Recalc max line length for Q encoding - see comments on B encode + $maxlen = static::STD_LINE_LENGTH - $lengthsub - 8 - strlen($this->CharSet); + $encoded = $this->encodeQ($str, $position); + $encoded = $this->wrapText($encoded, $maxlen, true); + $encoded = str_replace('=' . static::$LE, "\n", trim($encoded)); + $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded); + } elseif (strlen($str) > $maxlen) { + //No chars need encoding, but line is too long, so fold it + $encoded = trim($this->wrapText($str, $maxlen, false)); + if ($str == $encoded) { + //Wrapping nicely didn't work, wrap hard instead + $encoded = trim(chunk_split($str, static::STD_LINE_LENGTH, static::$LE)); + } + $encoded = str_replace(static::$LE, "\n", trim($encoded)); + $encoded = preg_replace('/^(.*)$/m', ' \\1', $encoded); + } else { + //No reformatting needed + return $str; + } + + return trim(static::normalizeBreaks($encoded)); + } + + /** + * Check if a string contains multi-byte characters. + * + * @param string $str multi-byte text to wrap encode + * + * @return bool + */ + public function hasMultiBytes($str) + { + if (function_exists('mb_strlen')) { + return strlen($str) > mb_strlen($str, $this->CharSet); + } + + // Assume no multibytes (we can't handle without mbstring functions anyway) + return false; + } + + /** + * Does a string contain any 8-bit chars (in any charset)? + * + * @param string $text + * + * @return bool + */ + public function has8bitChars($text) + { + return (bool) preg_match('/[\x80-\xFF]/', $text); + } + + /** + * Encode and wrap long multibyte strings for mail headers + * without breaking lines within a character. + * Adapted from a function by paravoid. + * + * @see http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283 + * + * @param string $str multi-byte text to wrap encode + * @param string $linebreak string to use as linefeed/end-of-line + * + * @return string + */ + public function base64EncodeWrapMB($str, $linebreak = null) + { + $start = '=?' . $this->CharSet . '?B?'; + $end = '?='; + $encoded = ''; + if (null === $linebreak) { + $linebreak = static::$LE; + } + + $mb_length = mb_strlen($str, $this->CharSet); + // Each line must have length <= 75, including $start and $end + $length = 75 - strlen($start) - strlen($end); + // Average multi-byte ratio + $ratio = $mb_length / strlen($str); + // Base64 has a 4:3 ratio + $avgLength = floor($length * $ratio * .75); + + for ($i = 0; $i < $mb_length; $i += $offset) { + $lookBack = 0; + do { + $offset = $avgLength - $lookBack; + $chunk = mb_substr($str, $i, $offset, $this->CharSet); + $chunk = base64_encode($chunk); + ++$lookBack; + } while (strlen($chunk) > $length); + $encoded .= $chunk . $linebreak; + } + + // Chomp the last linefeed + return substr($encoded, 0, -strlen($linebreak)); + } + + /** + * Encode a string in quoted-printable format. + * According to RFC2045 section 6.7. + * + * @param string $string The text to encode + * + * @return string + */ + public function encodeQP($string) + { + return static::normalizeBreaks(quoted_printable_encode($string)); + } + + /** + * Encode a string using Q encoding. + * + * @see http://tools.ietf.org/html/rfc2047#section-4.2 + * + * @param string $str the text to encode + * @param string $position Where the text is going to be used, see the RFC for what that means + * + * @return string + */ + public function encodeQ($str, $position = 'text') + { + // There should not be any EOL in the string + $pattern = ''; + $encoded = str_replace(["\r", "\n"], '', $str); + switch (strtolower($position)) { + case 'phrase': + // RFC 2047 section 5.3 + $pattern = '^A-Za-z0-9!*+\/ -'; + break; + /* + * RFC 2047 section 5.2. + * Build $pattern without including delimiters and [] + */ + /* @noinspection PhpMissingBreakStatementInspection */ + case 'comment': + $pattern = '\(\)"'; + /* Intentional fall through */ + case 'text': + default: + // RFC 2047 section 5.1 + // Replace every high ascii, control, =, ? and _ characters + /** @noinspection SuspiciousAssignmentsInspection */ + $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern; + break; + } + $matches = []; + if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) { + // If the string contains an '=', make sure it's the first thing we replace + // so as to avoid double-encoding + $eqkey = array_search('=', $matches[0]); + if (false !== $eqkey) { + unset($matches[0][$eqkey]); + array_unshift($matches[0], '='); + } + foreach (array_unique($matches[0]) as $char) { + $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded); + } + } + // Replace spaces with _ (more readable than =20) + // RFC 2047 section 4.2(2) + return str_replace(' ', '_', $encoded); + } + + /** + * Add a string or binary attachment (non-filesystem). + * This method can be used to attach ascii or binary data, + * such as a BLOB record from a database. + * + * @param string $string String attachment data + * @param string $filename Name of the attachment + * @param string $encoding File encoding (see $Encoding) + * @param string $type File extension (MIME) type + * @param string $disposition Disposition to use + */ + public function addStringAttachment( + $string, + $filename, + $encoding = self::ENCODING_BASE64, + $type = '', + $disposition = 'attachment' + ) { + // If a MIME type is not specified, try to work it out from the file name + if ('' == $type) { + $type = static::filenameToType($filename); + } + // Append to $attachment array + $this->attachment[] = [ + 0 => $string, + 1 => $filename, + 2 => basename($filename), + 3 => $encoding, + 4 => $type, + 5 => true, // isStringAttachment + 6 => $disposition, + 7 => 0, + ]; + } + + /** + * Add an embedded (inline) attachment from a file. + * This can include images, sounds, and just about any other document type. + * These differ from 'regular' attachments in that they are intended to be + * displayed inline with the message, not just attached for download. + * This is used in HTML messages that embed the images + * the HTML refers to using the $cid value. + * Never use a user-supplied path to a file! + * + * @param string $path Path to the attachment + * @param string $cid Content ID of the attachment; Use this to reference + * the content when using an embedded image in HTML + * @param string $name Overrides the attachment name + * @param string $encoding File encoding (see $Encoding) + * @param string $type File MIME type + * @param string $disposition Disposition to use + * + * @return bool True on successfully adding an attachment + */ + public function addEmbeddedImage($path, $cid, $name = '', $encoding = self::ENCODING_BASE64, $type = '', $disposition = 'inline') + { + if (!static::isPermittedPath($path) || !@is_file($path)) { + $this->setError($this->lang('file_access') . $path); + + return false; + } + + // If a MIME type is not specified, try to work it out from the file name + if ('' == $type) { + $type = static::filenameToType($path); + } + + $filename = basename($path); + if ('' == $name) { + $name = $filename; + } + + // Append to $attachment array + $this->attachment[] = [ + 0 => $path, + 1 => $filename, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => false, // isStringAttachment + 6 => $disposition, + 7 => $cid, + ]; + + return true; + } + + /** + * Add an embedded stringified attachment. + * This can include images, sounds, and just about any other document type. + * If your filename doesn't contain an extension, be sure to set the $type to an appropriate MIME type. + * + * @param string $string The attachment binary data + * @param string $cid Content ID of the attachment; Use this to reference + * the content when using an embedded image in HTML + * @param string $name A filename for the attachment. If this contains an extension, + * PHPMailer will attempt to set a MIME type for the attachment. + * For example 'file.jpg' would get an 'image/jpeg' MIME type. + * @param string $encoding File encoding (see $Encoding), defaults to 'base64' + * @param string $type MIME type - will be used in preference to any automatically derived type + * @param string $disposition Disposition to use + * + * @return bool True on successfully adding an attachment + */ + public function addStringEmbeddedImage( + $string, + $cid, + $name = '', + $encoding = self::ENCODING_BASE64, + $type = '', + $disposition = 'inline' + ) { + // If a MIME type is not specified, try to work it out from the name + if ('' == $type and !empty($name)) { + $type = static::filenameToType($name); + } + + // Append to $attachment array + $this->attachment[] = [ + 0 => $string, + 1 => $name, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => true, // isStringAttachment + 6 => $disposition, + 7 => $cid, + ]; + + return true; + } + + /** + * Check if an embedded attachment is present with this cid. + * + * @param string $cid + * + * @return bool + */ + protected function cidExists($cid) + { + foreach ($this->attachment as $attachment) { + if ('inline' == $attachment[6] and $cid == $attachment[7]) { + return true; + } + } + + return false; + } + + /** + * Check if an inline attachment is present. + * + * @return bool + */ + public function inlineImageExists() + { + foreach ($this->attachment as $attachment) { + if ('inline' == $attachment[6]) { + return true; + } + } + + return false; + } + + /** + * Check if an attachment (non-inline) is present. + * + * @return bool + */ + public function attachmentExists() + { + foreach ($this->attachment as $attachment) { + if ('attachment' == $attachment[6]) { + return true; + } + } + + return false; + } + + /** + * Check if this message has an alternative body set. + * + * @return bool + */ + public function alternativeExists() + { + return !empty($this->AltBody); + } + + /** + * Clear queued addresses of given kind. + * + * @param string $kind 'to', 'cc', or 'bcc' + */ + public function clearQueuedAddresses($kind) + { + $this->RecipientsQueue = array_filter( + $this->RecipientsQueue, + function ($params) use ($kind) { + return $params[0] != $kind; + } + ); + } + + /** + * Clear all To recipients. + */ + public function clearAddresses() + { + foreach ($this->to as $to) { + unset($this->all_recipients[strtolower($to[0])]); + } + $this->to = []; + $this->clearQueuedAddresses('to'); + } + + /** + * Clear all CC recipients. + */ + public function clearCCs() + { + foreach ($this->cc as $cc) { + unset($this->all_recipients[strtolower($cc[0])]); + } + $this->cc = []; + $this->clearQueuedAddresses('cc'); + } + + /** + * Clear all BCC recipients. + */ + public function clearBCCs() + { + foreach ($this->bcc as $bcc) { + unset($this->all_recipients[strtolower($bcc[0])]); + } + $this->bcc = []; + $this->clearQueuedAddresses('bcc'); + } + + /** + * Clear all ReplyTo recipients. + */ + public function clearReplyTos() + { + $this->ReplyTo = []; + $this->ReplyToQueue = []; + } + + /** + * Clear all recipient types. + */ + public function clearAllRecipients() + { + $this->to = []; + $this->cc = []; + $this->bcc = []; + $this->all_recipients = []; + $this->RecipientsQueue = []; + } + + /** + * Clear all filesystem, string, and binary attachments. + */ + public function clearAttachments() + { + $this->attachment = []; + } + + /** + * Clear all custom headers. + */ + public function clearCustomHeaders() + { + $this->CustomHeader = []; + } + + /** + * Add an error message to the error container. + * + * @param string $msg + */ + protected function setError($msg) + { + ++$this->error_count; + if ('smtp' == $this->Mailer and null !== $this->smtp) { + $lasterror = $this->smtp->getError(); + if (!empty($lasterror['error'])) { + $msg .= $this->lang('smtp_error') . $lasterror['error']; + if (!empty($lasterror['detail'])) { + $msg .= ' Detail: ' . $lasterror['detail']; + } + if (!empty($lasterror['smtp_code'])) { + $msg .= ' SMTP code: ' . $lasterror['smtp_code']; + } + if (!empty($lasterror['smtp_code_ex'])) { + $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex']; + } + } + } + $this->ErrorInfo = $msg; + } + + /** + * Return an RFC 822 formatted date. + * + * @return string + */ + public static function rfcDate() + { + // Set the time zone to whatever the default is to avoid 500 errors + // Will default to UTC if it's not set properly in php.ini + date_default_timezone_set(@date_default_timezone_get()); + + return date('D, j M Y H:i:s O'); + } + + /** + * Get the server hostname. + * Returns 'localhost.localdomain' if unknown. + * + * @return string + */ + protected function serverHostname() + { + $result = ''; + if (!empty($this->Hostname)) { + $result = $this->Hostname; + } elseif (isset($_SERVER) and array_key_exists('SERVER_NAME', $_SERVER)) { + $result = $_SERVER['SERVER_NAME']; + } elseif (function_exists('gethostname') and gethostname() !== false) { + $result = gethostname(); + } elseif (php_uname('n') !== false) { + $result = php_uname('n'); + } + if (!static::isValidHost($result)) { + return 'localhost.localdomain'; + } + + return $result; + } + + /** + * Validate whether a string contains a valid value to use as a hostname or IP address. + * IPv6 addresses must include [], e.g. `[::1]`, not just `::1`. + * + * @param string $host The host name or IP address to check + * + * @return bool + */ + public static function isValidHost($host) + { + //Simple syntax limits + if (empty($host) + or !is_string($host) + or strlen($host) > 256 + ) { + return false; + } + //Looks like a bracketed IPv6 address + if (trim($host, '[]') != $host) { + return (bool) filter_var(trim($host, '[]'), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6); + } + //If removing all the dots results in a numeric string, it must be an IPv4 address. + //Need to check this first because otherwise things like `999.0.0.0` are considered valid host names + if (is_numeric(str_replace('.', '', $host))) { + //Is it a valid IPv4 address? + return (bool) filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); + } + if (filter_var('http://' . $host, FILTER_VALIDATE_URL)) { + //Is it a syntactically valid hostname? + return true; + } + + return false; + } + + /** + * Get an error message in the current language. + * + * @param string $key + * + * @return string + */ + protected function lang($key) + { + if (count($this->language) < 1) { + $this->setLanguage('en'); // set the default language + } + + if (array_key_exists($key, $this->language)) { + if ('smtp_connect_failed' == $key) { + //Include a link to troubleshooting docs on SMTP connection failure + //this is by far the biggest cause of support questions + //but it's usually not PHPMailer's fault. + return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting'; + } + + return $this->language[$key]; + } + + //Return the key as a fallback + return $key; + } + + /** + * Check if an error occurred. + * + * @return bool True if an error did occur + */ + public function isError() + { + return $this->error_count > 0; + } + + /** + * Add a custom header. + * $name value can be overloaded to contain + * both header name and value (name:value). + * + * @param string $name Custom header name + * @param string|null $value Header value + */ + public function addCustomHeader($name, $value = null) + { + if (null === $value) { + // Value passed in as name:value + $this->CustomHeader[] = explode(':', $name, 2); + } else { + $this->CustomHeader[] = [$name, $value]; + } + } + + /** + * Returns all custom headers. + * + * @return array + */ + public function getCustomHeaders() + { + return $this->CustomHeader; + } + + /** + * Create a message body from an HTML string. + * Automatically inlines images and creates a plain-text version by converting the HTML, + * overwriting any existing values in Body and AltBody. + * Do not source $message content from user input! + * $basedir is prepended when handling relative URLs, e.g. and must not be empty + * will look for an image file in $basedir/images/a.png and convert it to inline. + * If you don't provide a $basedir, relative paths will be left untouched (and thus probably break in email) + * Converts data-uri images into embedded attachments. + * If you don't want to apply these transformations to your HTML, just set Body and AltBody directly. + * + * @param string $message HTML message string + * @param string $basedir Absolute path to a base directory to prepend to relative paths to images + * @param bool|callable $advanced Whether to use the internal HTML to text converter + * or your own custom converter @see PHPMailer::html2text() + * + * @return string $message The transformed message Body + */ + public function msgHTML($message, $basedir = '', $advanced = false) + { + preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images); + if (array_key_exists(2, $images)) { + if (strlen($basedir) > 1 && '/' != substr($basedir, -1)) { + // Ensure $basedir has a trailing / + $basedir .= '/'; + } + foreach ($images[2] as $imgindex => $url) { + // Convert data URIs into embedded images + //e.g. "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" + if (preg_match('#^data:(image/(?:jpe?g|gif|png));?(base64)?,(.+)#', $url, $match)) { + if (count($match) == 4 and static::ENCODING_BASE64 == $match[2]) { + $data = base64_decode($match[3]); + } elseif ('' == $match[2]) { + $data = rawurldecode($match[3]); + } else { + //Not recognised so leave it alone + continue; + } + //Hash the decoded data, not the URL so that the same data-URI image used in multiple places + //will only be embedded once, even if it used a different encoding + $cid = hash('sha256', $data) . '@phpmailer.0'; // RFC2392 S 2 + + if (!$this->cidExists($cid)) { + $this->addStringEmbeddedImage($data, $cid, 'embed' . $imgindex, static::ENCODING_BASE64, $match[1]); + } + $message = str_replace( + $images[0][$imgindex], + $images[1][$imgindex] . '="cid:' . $cid . '"', + $message + ); + continue; + } + if (// Only process relative URLs if a basedir is provided (i.e. no absolute local paths) + !empty($basedir) + // Ignore URLs containing parent dir traversal (..) + and (strpos($url, '..') === false) + // Do not change urls that are already inline images + and 0 !== strpos($url, 'cid:') + // Do not change absolute URLs, including anonymous protocol + and !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url) + ) { + $filename = basename($url); + $directory = dirname($url); + if ('.' == $directory) { + $directory = ''; + } + $cid = hash('sha256', $url) . '@phpmailer.0'; // RFC2392 S 2 + if (strlen($basedir) > 1 and '/' != substr($basedir, -1)) { + $basedir .= '/'; + } + if (strlen($directory) > 1 and '/' != substr($directory, -1)) { + $directory .= '/'; + } + if ($this->addEmbeddedImage( + $basedir . $directory . $filename, + $cid, + $filename, + static::ENCODING_BASE64, + static::_mime_types((string) static::mb_pathinfo($filename, PATHINFO_EXTENSION)) + ) + ) { + $message = preg_replace( + '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui', + $images[1][$imgindex] . '="cid:' . $cid . '"', + $message + ); + } + } + } + } + $this->isHTML(true); + // Convert all message body line breaks to LE, makes quoted-printable encoding work much better + $this->Body = static::normalizeBreaks($message); + $this->AltBody = static::normalizeBreaks($this->html2text($message, $advanced)); + if (!$this->alternativeExists()) { + $this->AltBody = 'This is an HTML-only message. To view it, activate HTML in your email application.' + . static::$LE; + } + + return $this->Body; + } + + /** + * Convert an HTML string into plain text. + * This is used by msgHTML(). + * Note - older versions of this function used a bundled advanced converter + * which was removed for license reasons in #232. + * Example usage: + * + * ```php + * // Use default conversion + * $plain = $mail->html2text($html); + * // Use your own custom converter + * $plain = $mail->html2text($html, function($html) { + * $converter = new MyHtml2text($html); + * return $converter->get_text(); + * }); + * ``` + * + * @param string $html The HTML text to convert + * @param bool|callable $advanced Any boolean value to use the internal converter, + * or provide your own callable for custom conversion + * + * @return string + */ + public function html2text($html, $advanced = false) + { + if (is_callable($advanced)) { + return call_user_func($advanced, $html); + } + + return html_entity_decode( + trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))), + ENT_QUOTES, + $this->CharSet + ); + } + + /** + * Get the MIME type for a file extension. + * + * @param string $ext File extension + * + * @return string MIME type of file + */ + public static function _mime_types($ext = '') + { + $mimes = [ + 'xl' => 'application/excel', + 'js' => 'application/javascript', + 'hqx' => 'application/mac-binhex40', + 'cpt' => 'application/mac-compactpro', + 'bin' => 'application/macbinary', + 'doc' => 'application/msword', + 'word' => 'application/msword', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'class' => 'application/octet-stream', + 'dll' => 'application/octet-stream', + 'dms' => 'application/octet-stream', + 'exe' => 'application/octet-stream', + 'lha' => 'application/octet-stream', + 'lzh' => 'application/octet-stream', + 'psd' => 'application/octet-stream', + 'sea' => 'application/octet-stream', + 'so' => 'application/octet-stream', + 'oda' => 'application/oda', + 'pdf' => 'application/pdf', + 'ai' => 'application/postscript', + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'mif' => 'application/vnd.mif', + 'xls' => 'application/vnd.ms-excel', + 'ppt' => 'application/vnd.ms-powerpoint', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'dcr' => 'application/x-director', + 'dir' => 'application/x-director', + 'dxr' => 'application/x-director', + 'dvi' => 'application/x-dvi', + 'gtar' => 'application/x-gtar', + 'php3' => 'application/x-httpd-php', + 'php4' => 'application/x-httpd-php', + 'php' => 'application/x-httpd-php', + 'phtml' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'swf' => 'application/x-shockwave-flash', + 'sit' => 'application/x-stuffit', + 'tar' => 'application/x-tar', + 'tgz' => 'application/x-tar', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'zip' => 'application/zip', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'm4a' => 'audio/mp4', + 'mpga' => 'audio/mpeg', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'ram' => 'audio/x-pn-realaudio', + 'rm' => 'audio/x-pn-realaudio', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'ra' => 'audio/x-realaudio', + 'wav' => 'audio/x-wav', + 'mka' => 'audio/x-matroska', + 'bmp' => 'image/bmp', + 'gif' => 'image/gif', + 'jpeg' => 'image/jpeg', + 'jpe' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'png' => 'image/png', + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', + 'webp' => 'image/webp', + 'heif' => 'image/heif', + 'heifs' => 'image/heif-sequence', + 'heic' => 'image/heic', + 'heics' => 'image/heic-sequence', + 'eml' => 'message/rfc822', + 'css' => 'text/css', + 'html' => 'text/html', + 'htm' => 'text/html', + 'shtml' => 'text/html', + 'log' => 'text/plain', + 'text' => 'text/plain', + 'txt' => 'text/plain', + 'rtx' => 'text/richtext', + 'rtf' => 'text/rtf', + 'vcf' => 'text/vcard', + 'vcard' => 'text/vcard', + 'ics' => 'text/calendar', + 'xml' => 'text/xml', + 'xsl' => 'text/xml', + 'wmv' => 'video/x-ms-wmv', + 'mpeg' => 'video/mpeg', + 'mpe' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mp4' => 'video/mp4', + 'm4v' => 'video/mp4', + 'mov' => 'video/quicktime', + 'qt' => 'video/quicktime', + 'rv' => 'video/vnd.rn-realvideo', + 'avi' => 'video/x-msvideo', + 'movie' => 'video/x-sgi-movie', + 'webm' => 'video/webm', + 'mkv' => 'video/x-matroska', + ]; + $ext = strtolower($ext); + if (array_key_exists($ext, $mimes)) { + return $mimes[$ext]; + } + + return 'application/octet-stream'; + } + + /** + * Map a file name to a MIME type. + * Defaults to 'application/octet-stream', i.e.. arbitrary binary data. + * + * @param string $filename A file name or full path, does not need to exist as a file + * + * @return string + */ + public static function filenameToType($filename) + { + // In case the path is a URL, strip any query string before getting extension + $qpos = strpos($filename, '?'); + if (false !== $qpos) { + $filename = substr($filename, 0, $qpos); + } + $ext = static::mb_pathinfo($filename, PATHINFO_EXTENSION); + + return static::_mime_types($ext); + } + + /** + * Multi-byte-safe pathinfo replacement. + * Drop-in replacement for pathinfo(), but multibyte- and cross-platform-safe. + * + * @see http://www.php.net/manual/en/function.pathinfo.php#107461 + * + * @param string $path A filename or path, does not need to exist as a file + * @param int|string $options Either a PATHINFO_* constant, + * or a string name to return only the specified piece + * + * @return string|array + */ + public static function mb_pathinfo($path, $options = null) + { + $ret = ['dirname' => '', 'basename' => '', 'extension' => '', 'filename' => '']; + $pathinfo = []; + if (preg_match('#^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$#im', $path, $pathinfo)) { + if (array_key_exists(1, $pathinfo)) { + $ret['dirname'] = $pathinfo[1]; + } + if (array_key_exists(2, $pathinfo)) { + $ret['basename'] = $pathinfo[2]; + } + if (array_key_exists(5, $pathinfo)) { + $ret['extension'] = $pathinfo[5]; + } + if (array_key_exists(3, $pathinfo)) { + $ret['filename'] = $pathinfo[3]; + } + } + switch ($options) { + case PATHINFO_DIRNAME: + case 'dirname': + return $ret['dirname']; + case PATHINFO_BASENAME: + case 'basename': + return $ret['basename']; + case PATHINFO_EXTENSION: + case 'extension': + return $ret['extension']; + case PATHINFO_FILENAME: + case 'filename': + return $ret['filename']; + default: + return $ret; + } + } + + /** + * Set or reset instance properties. + * You should avoid this function - it's more verbose, less efficient, more error-prone and + * harder to debug than setting properties directly. + * Usage Example: + * `$mail->set('SMTPSecure', 'tls');` + * is the same as: + * `$mail->SMTPSecure = 'tls';`. + * + * @param string $name The property name to set + * @param mixed $value The value to set the property to + * + * @return bool + */ + public function set($name, $value = '') + { + if (property_exists($this, $name)) { + $this->$name = $value; + + return true; + } + $this->setError($this->lang('variable_set') . $name); + + return false; + } + + /** + * Strip newlines to prevent header injection. + * + * @param string $str + * + * @return string + */ + public function secureHeader($str) + { + return trim(str_replace(["\r", "\n"], '', $str)); + } + + /** + * Normalize line breaks in a string. + * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format. + * Defaults to CRLF (for message bodies) and preserves consecutive breaks. + * + * @param string $text + * @param string $breaktype What kind of line break to use; defaults to static::$LE + * + * @return string + */ + public static function normalizeBreaks($text, $breaktype = null) + { + if (null === $breaktype) { + $breaktype = static::$LE; + } + // Normalise to \n + $text = str_replace(["\r\n", "\r"], "\n", $text); + // Now convert LE as needed + if ("\n" !== $breaktype) { + $text = str_replace("\n", $breaktype, $text); + } + + return $text; + } + + /** + * Return the current line break format string. + * + * @return string + */ + public static function getLE() + { + return static::$LE; + } + + /** + * Set the line break format string, e.g. "\r\n". + * + * @param string $le + */ + protected static function setLE($le) + { + static::$LE = $le; + } + + /** + * Set the public and private key files and password for S/MIME signing. + * + * @param string $cert_filename + * @param string $key_filename + * @param string $key_pass Password for private key + * @param string $extracerts_filename Optional path to chain certificate + */ + public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '') + { + $this->sign_cert_file = $cert_filename; + $this->sign_key_file = $key_filename; + $this->sign_key_pass = $key_pass; + $this->sign_extracerts_file = $extracerts_filename; + } + + /** + * Quoted-Printable-encode a DKIM header. + * + * @param string $txt + * + * @return string + */ + public function DKIM_QP($txt) + { + $line = ''; + $len = strlen($txt); + for ($i = 0; $i < $len; ++$i) { + $ord = ord($txt[$i]); + if (((0x21 <= $ord) and ($ord <= 0x3A)) or $ord == 0x3C or ((0x3E <= $ord) and ($ord <= 0x7E))) { + $line .= $txt[$i]; + } else { + $line .= '=' . sprintf('%02X', $ord); + } + } + + return $line; + } + + /** + * Generate a DKIM signature. + * + * @param string $signHeader + * + * @throws Exception + * + * @return string The DKIM signature value + */ + public function DKIM_Sign($signHeader) + { + if (!defined('PKCS7_TEXT')) { + if ($this->exceptions) { + throw new Exception($this->lang('extension_missing') . 'openssl'); + } + + return ''; + } + $privKeyStr = !empty($this->DKIM_private_string) ? + $this->DKIM_private_string : + file_get_contents($this->DKIM_private); + if ('' != $this->DKIM_passphrase) { + $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase); + } else { + $privKey = openssl_pkey_get_private($privKeyStr); + } + if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) { + openssl_pkey_free($privKey); + + return base64_encode($signature); + } + openssl_pkey_free($privKey); + + return ''; + } + + /** + * Generate a DKIM canonicalization header. + * Uses the 'relaxed' algorithm from RFC6376 section 3.4.2. + * Canonicalized headers should *always* use CRLF, regardless of mailer setting. + * + * @see https://tools.ietf.org/html/rfc6376#section-3.4.2 + * + * @param string $signHeader Header + * + * @return string + */ + public function DKIM_HeaderC($signHeader) + { + //Unfold all header continuation lines + //Also collapses folded whitespace. + //Note PCRE \s is too broad a definition of whitespace; RFC5322 defines it as `[ \t]` + //@see https://tools.ietf.org/html/rfc5322#section-2.2 + //That means this may break if you do something daft like put vertical tabs in your headers. + $signHeader = preg_replace('/\r\n[ \t]+/', ' ', $signHeader); + $lines = explode("\r\n", $signHeader); + foreach ($lines as $key => $line) { + //If the header is missing a :, skip it as it's invalid + //This is likely to happen because the explode() above will also split + //on the trailing LE, leaving an empty line + if (strpos($line, ':') === false) { + continue; + } + list($heading, $value) = explode(':', $line, 2); + //Lower-case header name + $heading = strtolower($heading); + //Collapse white space within the value + $value = preg_replace('/[ \t]{2,}/', ' ', $value); + //RFC6376 is slightly unclear here - it says to delete space at the *end* of each value + //But then says to delete space before and after the colon. + //Net result is the same as trimming both ends of the value. + //by elimination, the same applies to the field name + $lines[$key] = trim($heading, " \t") . ':' . trim($value, " \t"); + } + + return implode("\r\n", $lines); + } + + /** + * Generate a DKIM canonicalization body. + * Uses the 'simple' algorithm from RFC6376 section 3.4.3. + * Canonicalized bodies should *always* use CRLF, regardless of mailer setting. + * + * @see https://tools.ietf.org/html/rfc6376#section-3.4.3 + * + * @param string $body Message Body + * + * @return string + */ + public function DKIM_BodyC($body) + { + if (empty($body)) { + return "\r\n"; + } + // Normalize line endings to CRLF + $body = static::normalizeBreaks($body, "\r\n"); + + //Reduce multiple trailing line breaks to a single one + return rtrim($body, "\r\n") . "\r\n"; + } + + /** + * Create the DKIM header and body in a new message header. + * + * @param string $headers_line Header lines + * @param string $subject Subject + * @param string $body Body + * + * @return string + */ + public function DKIM_Add($headers_line, $subject, $body) + { + $DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms + $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body + $DKIMquery = 'dns/txt'; // Query method + $DKIMtime = time(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone) + $subject_header = "Subject: $subject"; + $headers = explode(static::$LE, $headers_line); + $from_header = ''; + $to_header = ''; + $date_header = ''; + $current = ''; + $copiedHeaderFields = ''; + $foundExtraHeaders = []; + $extraHeaderKeys = ''; + $extraHeaderValues = ''; + $extraCopyHeaderFields = ''; + foreach ($headers as $header) { + if (strpos($header, 'From:') === 0) { + $from_header = $header; + $current = 'from_header'; + } elseif (strpos($header, 'To:') === 0) { + $to_header = $header; + $current = 'to_header'; + } elseif (strpos($header, 'Date:') === 0) { + $date_header = $header; + $current = 'date_header'; + } elseif (!empty($this->DKIM_extraHeaders)) { + foreach ($this->DKIM_extraHeaders as $extraHeader) { + if (strpos($header, $extraHeader . ':') === 0) { + $headerValue = $header; + foreach ($this->CustomHeader as $customHeader) { + if ($customHeader[0] === $extraHeader) { + $headerValue = trim($customHeader[0]) . + ': ' . + $this->encodeHeader(trim($customHeader[1])); + break; + } + } + $foundExtraHeaders[$extraHeader] = $headerValue; + $current = ''; + break; + } + } + } else { + if (!empty($$current) and strpos($header, ' =?') === 0) { + $$current .= $header; + } else { + $current = ''; + } + } + } + foreach ($foundExtraHeaders as $key => $value) { + $extraHeaderKeys .= ':' . $key; + $extraHeaderValues .= $value . "\r\n"; + if ($this->DKIM_copyHeaderFields) { + $extraCopyHeaderFields .= "\t|" . str_replace('|', '=7C', $this->DKIM_QP($value)) . ";\r\n"; + } + } + if ($this->DKIM_copyHeaderFields) { + $from = str_replace('|', '=7C', $this->DKIM_QP($from_header)); + $to = str_replace('|', '=7C', $this->DKIM_QP($to_header)); + $date = str_replace('|', '=7C', $this->DKIM_QP($date_header)); + $subject = str_replace('|', '=7C', $this->DKIM_QP($subject_header)); + $copiedHeaderFields = "\tz=$from\r\n" . + "\t|$to\r\n" . + "\t|$date\r\n" . + "\t|$subject;\r\n" . + $extraCopyHeaderFields; + } + $body = $this->DKIM_BodyC($body); + $DKIMlen = strlen($body); // Length of body + $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body + if ('' == $this->DKIM_identity) { + $ident = ''; + } else { + $ident = ' i=' . $this->DKIM_identity . ';'; + } + $dkimhdrs = 'DKIM-Signature: v=1; a=' . + $DKIMsignatureType . '; q=' . + $DKIMquery . '; l=' . + $DKIMlen . '; s=' . + $this->DKIM_selector . + ";\r\n" . + "\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" . + "\th=From:To:Date:Subject" . $extraHeaderKeys . ";\r\n" . + "\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" . + $copiedHeaderFields . + "\tbh=" . $DKIMb64 . ";\r\n" . + "\tb="; + $toSign = $this->DKIM_HeaderC( + $from_header . "\r\n" . + $to_header . "\r\n" . + $date_header . "\r\n" . + $subject_header . "\r\n" . + $extraHeaderValues . + $dkimhdrs + ); + $signed = $this->DKIM_Sign($toSign); + + return static::normalizeBreaks($dkimhdrs . $signed) . static::$LE; + } + + /** + * Detect if a string contains a line longer than the maximum line length + * allowed by RFC 2822 section 2.1.1. + * + * @param string $str + * + * @return bool + */ + public static function hasLineLongerThanMax($str) + { + return (bool) preg_match('/^(.{' . (self::MAX_LINE_LENGTH + strlen(static::$LE)) . ',})/m', $str); + } + + /** + * Allows for public read access to 'to' property. + * Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * + * @return array + */ + public function getToAddresses() + { + return $this->to; + } + + /** + * Allows for public read access to 'cc' property. + * Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * + * @return array + */ + public function getCcAddresses() + { + return $this->cc; + } + + /** + * Allows for public read access to 'bcc' property. + * Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * + * @return array + */ + public function getBccAddresses() + { + return $this->bcc; + } + + /** + * Allows for public read access to 'ReplyTo' property. + * Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * + * @return array + */ + public function getReplyToAddresses() + { + return $this->ReplyTo; + } + + /** + * Allows for public read access to 'all_recipients' property. + * Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * + * @return array + */ + public function getAllRecipientAddresses() + { + return $this->all_recipients; + } + + /** + * Perform a callback. + * + * @param bool $isSent + * @param array $to + * @param array $cc + * @param array $bcc + * @param string $subject + * @param string $body + * @param string $from + * @param array $extra + */ + protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from, $extra) + { + if (!empty($this->action_function) and is_callable($this->action_function)) { + call_user_func($this->action_function, $isSent, $to, $cc, $bcc, $subject, $body, $from, $extra); + } + } + + /** + * Get the OAuth instance. + * + * @return OAuth + */ + public function getOAuth() + { + return $this->oauth; + } + + /** + * Set an OAuth instance. + * + * @param OAuth $oauth + */ + public function setOAuth(OAuth $oauth) + { + $this->oauth = $oauth; + } +} diff --git a/lib/PHPMailer/PHPMailer/SMTP.php b/lib/PHPMailer/PHPMailer/SMTP.php new file mode 100644 index 000000000..da85442bf --- /dev/null +++ b/lib/PHPMailer/PHPMailer/SMTP.php @@ -0,0 +1,1326 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2017 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * PHPMailer RFC821 SMTP email transport class. + * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server. + * + * @author Chris Ryan + * @author Marcus Bointon + */ +class SMTP +{ + /** + * The PHPMailer SMTP version number. + * + * @var string + */ + const VERSION = '6.0.7'; + + /** + * SMTP line break constant. + * + * @var string + */ + const LE = "\r\n"; + + /** + * The SMTP port to use if one is not specified. + * + * @var int + */ + const DEFAULT_PORT = 25; + + /** + * The maximum line length allowed by RFC 2822 section 2.1.1. + * + * @var int + */ + const MAX_LINE_LENGTH = 998; + + /** + * Debug level for no output. + */ + const DEBUG_OFF = 0; + + /** + * Debug level to show client -> server messages. + */ + const DEBUG_CLIENT = 1; + + /** + * Debug level to show client -> server and server -> client messages. + */ + const DEBUG_SERVER = 2; + + /** + * Debug level to show connection status, client -> server and server -> client messages. + */ + const DEBUG_CONNECTION = 3; + + /** + * Debug level to show all messages. + */ + const DEBUG_LOWLEVEL = 4; + + /** + * Debug output level. + * Options: + * * self::DEBUG_OFF (`0`) No debug output, default + * * self::DEBUG_CLIENT (`1`) Client commands + * * self::DEBUG_SERVER (`2`) Client commands and server responses + * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status + * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages. + * + * @var int + */ + public $do_debug = self::DEBUG_OFF; + + /** + * How to handle debug output. + * Options: + * * `echo` Output plain-text as-is, appropriate for CLI + * * `html` Output escaped, line breaks converted to `
      `, appropriate for browser output + * * `error_log` Output to error log as configured in php.ini + * Alternatively, you can provide a callable expecting two params: a message string and the debug level: + * + * ```php + * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; + * ``` + * + * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug` + * level output is used: + * + * ```php + * $mail->Debugoutput = new myPsr3Logger; + * ``` + * + * @var string|callable|\Psr\Log\LoggerInterface + */ + public $Debugoutput = 'echo'; + + /** + * Whether to use VERP. + * + * @see http://en.wikipedia.org/wiki/Variable_envelope_return_path + * @see http://www.postfix.org/VERP_README.html Info on VERP + * + * @var bool + */ + public $do_verp = false; + + /** + * The timeout value for connection, in seconds. + * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2. + * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure. + * + * @see http://tools.ietf.org/html/rfc2821#section-4.5.3.2 + * + * @var int + */ + public $Timeout = 300; + + /** + * How long to wait for commands to complete, in seconds. + * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2. + * + * @var int + */ + public $Timelimit = 300; + + /** + * Patterns to extract an SMTP transaction id from reply to a DATA command. + * The first capture group in each regex will be used as the ID. + * MS ESMTP returns the message ID, which may not be correct for internal tracking. + * + * @var string[] + */ + protected $smtp_transaction_id_patterns = [ + 'exim' => '/[\d]{3} OK id=(.*)/', + 'sendmail' => '/[\d]{3} 2.0.0 (.*) Message/', + 'postfix' => '/[\d]{3} 2.0.0 Ok: queued as (.*)/', + 'Microsoft_ESMTP' => '/[0-9]{3} 2.[\d].0 (.*)@(?:.*) Queued mail for delivery/', + 'Amazon_SES' => '/[\d]{3} Ok (.*)/', + 'SendGrid' => '/[\d]{3} Ok: queued as (.*)/', + 'CampaignMonitor' => '/[\d]{3} 2.0.0 OK:([a-zA-Z\d]{48})/', + ]; + + /** + * The last transaction ID issued in response to a DATA command, + * if one was detected. + * + * @var string|bool|null + */ + protected $last_smtp_transaction_id; + + /** + * The socket for the server connection. + * + * @var ?resource + */ + protected $smtp_conn; + + /** + * Error information, if any, for the last SMTP command. + * + * @var array + */ + protected $error = [ + 'error' => '', + 'detail' => '', + 'smtp_code' => '', + 'smtp_code_ex' => '', + ]; + + /** + * The reply the server sent to us for HELO. + * If null, no HELO string has yet been received. + * + * @var string|null + */ + protected $helo_rply = null; + + /** + * The set of SMTP extensions sent in reply to EHLO command. + * Indexes of the array are extension names. + * Value at index 'HELO' or 'EHLO' (according to command that was sent) + * represents the server name. In case of HELO it is the only element of the array. + * Other values can be boolean TRUE or an array containing extension options. + * If null, no HELO/EHLO string has yet been received. + * + * @var array|null + */ + protected $server_caps = null; + + /** + * The most recent reply received from the server. + * + * @var string + */ + protected $last_reply = ''; + + /** + * Output debugging info via a user-selected method. + * + * @param string $str Debug string to output + * @param int $level The debug level of this message; see DEBUG_* constants + * + * @see SMTP::$Debugoutput + * @see SMTP::$do_debug + */ + protected function edebug($str, $level = 0) + { + if ($level > $this->do_debug) { + return; + } + //Is this a PSR-3 logger? + if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) { + $this->Debugoutput->debug($str); + + return; + } + //Avoid clash with built-in function names + if (!in_array($this->Debugoutput, ['error_log', 'html', 'echo']) and is_callable($this->Debugoutput)) { + call_user_func($this->Debugoutput, $str, $level); + + return; + } + switch ($this->Debugoutput) { + case 'error_log': + //Don't output, just log + error_log($str); + break; + case 'html': + //Cleans up output a bit for a better looking, HTML-safe output + echo gmdate('Y-m-d H:i:s'), ' ', htmlentities( + preg_replace('/[\r\n]+/', '', $str), + ENT_QUOTES, + 'UTF-8' + ), "
      \n"; + break; + case 'echo': + default: + //Normalize line breaks + $str = preg_replace('/\r\n|\r/ms', "\n", $str); + echo gmdate('Y-m-d H:i:s'), + "\t", + //Trim trailing space + trim( + //Indent for readability, except for trailing break + str_replace( + "\n", + "\n \t ", + trim($str) + ) + ), + "\n"; + } + } + + /** + * Connect to an SMTP server. + * + * @param string $host SMTP server IP or host name + * @param int $port The port number to connect to + * @param int $timeout How long to wait for the connection to open + * @param array $options An array of options for stream_context_create() + * + * @return bool + */ + public function connect($host, $port = null, $timeout = 30, $options = []) + { + static $streamok; + //This is enabled by default since 5.0.0 but some providers disable it + //Check this once and cache the result + if (null === $streamok) { + $streamok = function_exists('stream_socket_client'); + } + // Clear errors to avoid confusion + $this->setError(''); + // Make sure we are __not__ connected + if ($this->connected()) { + // Already connected, generate error + $this->setError('Already connected to a server'); + + return false; + } + if (empty($port)) { + $port = self::DEFAULT_PORT; + } + // Connect to the SMTP server + $this->edebug( + "Connection: opening to $host:$port, timeout=$timeout, options=" . + (count($options) > 0 ? var_export($options, true) : 'array()'), + self::DEBUG_CONNECTION + ); + $errno = 0; + $errstr = ''; + if ($streamok) { + $socket_context = stream_context_create($options); + set_error_handler([$this, 'errorHandler']); + $this->smtp_conn = stream_socket_client( + $host . ':' . $port, + $errno, + $errstr, + $timeout, + STREAM_CLIENT_CONNECT, + $socket_context + ); + restore_error_handler(); + } else { + //Fall back to fsockopen which should work in more places, but is missing some features + $this->edebug( + 'Connection: stream_socket_client not available, falling back to fsockopen', + self::DEBUG_CONNECTION + ); + set_error_handler([$this, 'errorHandler']); + $this->smtp_conn = fsockopen( + $host, + $port, + $errno, + $errstr, + $timeout + ); + restore_error_handler(); + } + // Verify we connected properly + if (!is_resource($this->smtp_conn)) { + $this->setError( + 'Failed to connect to server', + '', + (string) $errno, + (string) $errstr + ); + $this->edebug( + 'SMTP ERROR: ' . $this->error['error'] + . ": $errstr ($errno)", + self::DEBUG_CLIENT + ); + + return false; + } + $this->edebug('Connection: opened', self::DEBUG_CONNECTION); + // SMTP server can take longer to respond, give longer timeout for first read + // Windows does not have support for this timeout function + if (substr(PHP_OS, 0, 3) != 'WIN') { + $max = ini_get('max_execution_time'); + // Don't bother if unlimited + if (0 != $max and $timeout > $max) { + @set_time_limit($timeout); + } + stream_set_timeout($this->smtp_conn, $timeout, 0); + } + // Get any announcement + $announce = $this->get_lines(); + $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER); + + return true; + } + + /** + * Initiate a TLS (encrypted) session. + * + * @return bool + */ + public function startTLS() + { + if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) { + return false; + } + + //Allow the best TLS version(s) we can + $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT; + + //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT + //so add them back in manually if we can + if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) { + $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; + $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT; + } + + // Begin encrypted connection + set_error_handler([$this, 'errorHandler']); + $crypto_ok = stream_socket_enable_crypto( + $this->smtp_conn, + true, + $crypto_method + ); + restore_error_handler(); + + return (bool) $crypto_ok; + } + + /** + * Perform SMTP authentication. + * Must be run after hello(). + * + * @see hello() + * + * @param string $username The user name + * @param string $password The password + * @param string $authtype The auth type (CRAM-MD5, PLAIN, LOGIN, XOAUTH2) + * @param OAuth $OAuth An optional OAuth instance for XOAUTH2 authentication + * + * @return bool True if successfully authenticated + */ + public function authenticate( + $username, + $password, + $authtype = null, + $OAuth = null + ) { + if (!$this->server_caps) { + $this->setError('Authentication is not allowed before HELO/EHLO'); + + return false; + } + + if (array_key_exists('EHLO', $this->server_caps)) { + // SMTP extensions are available; try to find a proper authentication method + if (!array_key_exists('AUTH', $this->server_caps)) { + $this->setError('Authentication is not allowed at this stage'); + // 'at this stage' means that auth may be allowed after the stage changes + // e.g. after STARTTLS + + return false; + } + + $this->edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNSPECIFIED'), self::DEBUG_LOWLEVEL); + $this->edebug( + 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']), + self::DEBUG_LOWLEVEL + ); + + //If we have requested a specific auth type, check the server supports it before trying others + if (null !== $authtype and !in_array($authtype, $this->server_caps['AUTH'])) { + $this->edebug('Requested auth method not available: ' . $authtype, self::DEBUG_LOWLEVEL); + $authtype = null; + } + + if (empty($authtype)) { + //If no auth mechanism is specified, attempt to use these, in this order + //Try CRAM-MD5 first as it's more secure than the others + foreach (['CRAM-MD5', 'LOGIN', 'PLAIN', 'XOAUTH2'] as $method) { + if (in_array($method, $this->server_caps['AUTH'])) { + $authtype = $method; + break; + } + } + if (empty($authtype)) { + $this->setError('No supported authentication methods found'); + + return false; + } + self::edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL); + } + + if (!in_array($authtype, $this->server_caps['AUTH'])) { + $this->setError("The requested authentication method \"$authtype\" is not supported by the server"); + + return false; + } + } elseif (empty($authtype)) { + $authtype = 'LOGIN'; + } + switch ($authtype) { + case 'PLAIN': + // Start authentication + if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) { + return false; + } + // Send encoded username and password + if (!$this->sendCommand( + 'User & Password', + base64_encode("\0" . $username . "\0" . $password), + 235 + ) + ) { + return false; + } + break; + case 'LOGIN': + // Start authentication + if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) { + return false; + } + if (!$this->sendCommand('Username', base64_encode($username), 334)) { + return false; + } + if (!$this->sendCommand('Password', base64_encode($password), 235)) { + return false; + } + break; + case 'CRAM-MD5': + // Start authentication + if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) { + return false; + } + // Get the challenge + $challenge = base64_decode(substr($this->last_reply, 4)); + + // Build the response + $response = $username . ' ' . $this->hmac($challenge, $password); + + // send encoded credentials + return $this->sendCommand('Username', base64_encode($response), 235); + case 'XOAUTH2': + //The OAuth instance must be set up prior to requesting auth. + if (null === $OAuth) { + return false; + } + $oauth = $OAuth->getOauth64(); + + // Start authentication + if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) { + return false; + } + break; + default: + $this->setError("Authentication method \"$authtype\" is not supported"); + + return false; + } + + return true; + } + + /** + * Calculate an MD5 HMAC hash. + * Works like hash_hmac('md5', $data, $key) + * in case that function is not available. + * + * @param string $data The data to hash + * @param string $key The key to hash with + * + * @return string + */ + protected function hmac($data, $key) + { + if (function_exists('hash_hmac')) { + return hash_hmac('md5', $data, $key); + } + + // The following borrowed from + // http://php.net/manual/en/function.mhash.php#27225 + + // RFC 2104 HMAC implementation for php. + // Creates an md5 HMAC. + // Eliminates the need to install mhash to compute a HMAC + // by Lance Rushing + + $bytelen = 64; // byte length for md5 + if (strlen($key) > $bytelen) { + $key = pack('H*', md5($key)); + } + $key = str_pad($key, $bytelen, chr(0x00)); + $ipad = str_pad('', $bytelen, chr(0x36)); + $opad = str_pad('', $bytelen, chr(0x5c)); + $k_ipad = $key ^ $ipad; + $k_opad = $key ^ $opad; + + return md5($k_opad . pack('H*', md5($k_ipad . $data))); + } + + /** + * Check connection state. + * + * @return bool True if connected + */ + public function connected() + { + if (is_resource($this->smtp_conn)) { + $sock_status = stream_get_meta_data($this->smtp_conn); + if ($sock_status['eof']) { + // The socket is valid but we are not connected + $this->edebug( + 'SMTP NOTICE: EOF caught while checking if connected', + self::DEBUG_CLIENT + ); + $this->close(); + + return false; + } + + return true; // everything looks good + } + + return false; + } + + /** + * Close the socket and clean up the state of the class. + * Don't use this function without first trying to use QUIT. + * + * @see quit() + */ + public function close() + { + $this->setError(''); + $this->server_caps = null; + $this->helo_rply = null; + if (is_resource($this->smtp_conn)) { + // close the connection and cleanup + fclose($this->smtp_conn); + $this->smtp_conn = null; //Makes for cleaner serialization + $this->edebug('Connection: closed', self::DEBUG_CONNECTION); + } + } + + /** + * Send an SMTP DATA command. + * Issues a data command and sends the msg_data to the server, + * finializing the mail transaction. $msg_data is the message + * that is to be send with the headers. Each header needs to be + * on a single line followed by a with the message headers + * and the message body being separated by an additional . + * Implements RFC 821: DATA . + * + * @param string $msg_data Message data to send + * + * @return bool + */ + public function data($msg_data) + { + //This will use the standard timelimit + if (!$this->sendCommand('DATA', 'DATA', 354)) { + return false; + } + + /* The server is ready to accept data! + * According to rfc821 we should not send more than 1000 characters on a single line (including the LE) + * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into + * smaller lines to fit within the limit. + * We will also look for lines that start with a '.' and prepend an additional '.'. + * NOTE: this does not count towards line-length limit. + */ + + // Normalize line breaks before exploding + $lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $msg_data)); + + /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field + * of the first line (':' separated) does not contain a space then it _should_ be a header and we will + * process all lines before a blank line as headers. + */ + + $field = substr($lines[0], 0, strpos($lines[0], ':')); + $in_headers = false; + if (!empty($field) and strpos($field, ' ') === false) { + $in_headers = true; + } + + foreach ($lines as $line) { + $lines_out = []; + if ($in_headers and $line == '') { + $in_headers = false; + } + //Break this line up into several smaller lines if it's too long + //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len), + while (isset($line[self::MAX_LINE_LENGTH])) { + //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on + //so as to avoid breaking in the middle of a word + $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' '); + //Deliberately matches both false and 0 + if (!$pos) { + //No nice break found, add a hard break + $pos = self::MAX_LINE_LENGTH - 1; + $lines_out[] = substr($line, 0, $pos); + $line = substr($line, $pos); + } else { + //Break at the found point + $lines_out[] = substr($line, 0, $pos); + //Move along by the amount we dealt with + $line = substr($line, $pos + 1); + } + //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1 + if ($in_headers) { + $line = "\t" . $line; + } + } + $lines_out[] = $line; + + //Send the lines to the server + foreach ($lines_out as $line_out) { + //RFC2821 section 4.5.2 + if (!empty($line_out) and $line_out[0] == '.') { + $line_out = '.' . $line_out; + } + $this->client_send($line_out . static::LE, 'DATA'); + } + } + + //Message data has been sent, complete the command + //Increase timelimit for end of DATA command + $savetimelimit = $this->Timelimit; + $this->Timelimit = $this->Timelimit * 2; + $result = $this->sendCommand('DATA END', '.', 250); + $this->recordLastTransactionID(); + //Restore timelimit + $this->Timelimit = $savetimelimit; + + return $result; + } + + /** + * Send an SMTP HELO or EHLO command. + * Used to identify the sending server to the receiving server. + * This makes sure that client and server are in a known state. + * Implements RFC 821: HELO + * and RFC 2821 EHLO. + * + * @param string $host The host name or IP to connect to + * + * @return bool + */ + public function hello($host = '') + { + //Try extended hello first (RFC 2821) + return $this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host); + } + + /** + * Send an SMTP HELO or EHLO command. + * Low-level implementation used by hello(). + * + * @param string $hello The HELO string + * @param string $host The hostname to say we are + * + * @return bool + * + * @see hello() + */ + protected function sendHello($hello, $host) + { + $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250); + $this->helo_rply = $this->last_reply; + if ($noerror) { + $this->parseHelloFields($hello); + } else { + $this->server_caps = null; + } + + return $noerror; + } + + /** + * Parse a reply to HELO/EHLO command to discover server extensions. + * In case of HELO, the only parameter that can be discovered is a server name. + * + * @param string $type `HELO` or `EHLO` + */ + protected function parseHelloFields($type) + { + $this->server_caps = []; + $lines = explode("\n", $this->helo_rply); + + foreach ($lines as $n => $s) { + //First 4 chars contain response code followed by - or space + $s = trim(substr($s, 4)); + if (empty($s)) { + continue; + } + $fields = explode(' ', $s); + if (!empty($fields)) { + if (!$n) { + $name = $type; + $fields = $fields[0]; + } else { + $name = array_shift($fields); + switch ($name) { + case 'SIZE': + $fields = ($fields ? $fields[0] : 0); + break; + case 'AUTH': + if (!is_array($fields)) { + $fields = []; + } + break; + default: + $fields = true; + } + } + $this->server_caps[$name] = $fields; + } + } + } + + /** + * Send an SMTP MAIL command. + * Starts a mail transaction from the email address specified in + * $from. Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more recipient + * commands may be called followed by a data command. + * Implements RFC 821: MAIL FROM: . + * + * @param string $from Source address of this message + * + * @return bool + */ + public function mail($from) + { + $useVerp = ($this->do_verp ? ' XVERP' : ''); + + return $this->sendCommand( + 'MAIL FROM', + 'MAIL FROM:<' . $from . '>' . $useVerp, + 250 + ); + } + + /** + * Send an SMTP QUIT command. + * Closes the socket if there is no error or the $close_on_error argument is true. + * Implements from RFC 821: QUIT . + * + * @param bool $close_on_error Should the connection close if an error occurs? + * + * @return bool + */ + public function quit($close_on_error = true) + { + $noerror = $this->sendCommand('QUIT', 'QUIT', 221); + $err = $this->error; //Save any error + if ($noerror or $close_on_error) { + $this->close(); + $this->error = $err; //Restore any error from the quit command + } + + return $noerror; + } + + /** + * Send an SMTP RCPT command. + * Sets the TO argument to $toaddr. + * Returns true if the recipient was accepted false if it was rejected. + * Implements from RFC 821: RCPT TO: . + * + * @param string $address The address the message is being sent to + * + * @return bool + */ + public function recipient($address) + { + return $this->sendCommand( + 'RCPT TO', + 'RCPT TO:<' . $address . '>', + [250, 251] + ); + } + + /** + * Send an SMTP RSET command. + * Abort any transaction that is currently in progress. + * Implements RFC 821: RSET . + * + * @return bool True on success + */ + public function reset() + { + return $this->sendCommand('RSET', 'RSET', 250); + } + + /** + * Send a command to an SMTP server and check its return code. + * + * @param string $command The command name - not sent to the server + * @param string $commandstring The actual command to send + * @param int|array $expect One or more expected integer success codes + * + * @return bool True on success + */ + protected function sendCommand($command, $commandstring, $expect) + { + if (!$this->connected()) { + $this->setError("Called $command without being connected"); + + return false; + } + //Reject line breaks in all commands + if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) { + $this->setError("Command '$command' contained line breaks"); + + return false; + } + $this->client_send($commandstring . static::LE, $command); + + $this->last_reply = $this->get_lines(); + // Fetch SMTP code and possible error code explanation + $matches = []; + if (preg_match('/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/', $this->last_reply, $matches)) { + $code = $matches[1]; + $code_ex = (count($matches) > 2 ? $matches[2] : null); + // Cut off error code from each response line + $detail = preg_replace( + "/{$code}[ -]" . + ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . '/m', + '', + $this->last_reply + ); + } else { + // Fall back to simple parsing if regex fails + $code = substr($this->last_reply, 0, 3); + $code_ex = null; + $detail = substr($this->last_reply, 4); + } + + $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER); + + if (!in_array($code, (array) $expect)) { + $this->setError( + "$command command failed", + $detail, + $code, + $code_ex + ); + $this->edebug( + 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply, + self::DEBUG_CLIENT + ); + + return false; + } + + $this->setError(''); + + return true; + } + + /** + * Send an SMTP SAML command. + * Starts a mail transaction from the email address specified in $from. + * Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more recipient + * commands may be called followed by a data command. This command + * will send the message to the users terminal if they are logged + * in and send them an email. + * Implements RFC 821: SAML FROM: . + * + * @param string $from The address the message is from + * + * @return bool + */ + public function sendAndMail($from) + { + return $this->sendCommand('SAML', "SAML FROM:$from", 250); + } + + /** + * Send an SMTP VRFY command. + * + * @param string $name The name to verify + * + * @return bool + */ + public function verify($name) + { + return $this->sendCommand('VRFY', "VRFY $name", [250, 251]); + } + + /** + * Send an SMTP NOOP command. + * Used to keep keep-alives alive, doesn't actually do anything. + * + * @return bool + */ + public function noop() + { + return $this->sendCommand('NOOP', 'NOOP', 250); + } + + /** + * Send an SMTP TURN command. + * This is an optional command for SMTP that this class does not support. + * This method is here to make the RFC821 Definition complete for this class + * and _may_ be implemented in future. + * Implements from RFC 821: TURN . + * + * @return bool + */ + public function turn() + { + $this->setError('The SMTP TURN command is not implemented'); + $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT); + + return false; + } + + /** + * Send raw data to the server. + * + * @param string $data The data to send + * @param string $command Optionally, the command this is part of, used only for controlling debug output + * + * @return int|bool The number of bytes sent to the server or false on error + */ + public function client_send($data, $command = '') + { + //If SMTP transcripts are left enabled, or debug output is posted online + //it can leak credentials, so hide credentials in all but lowest level + if (self::DEBUG_LOWLEVEL > $this->do_debug and + in_array($command, ['User & Password', 'Username', 'Password'], true)) { + $this->edebug('CLIENT -> SERVER:
      '),this.mouseTrack=c,e.insert(b||this.el,c));if(!g||g<0)g=0;a&&a.toFixed&&(a=a.toFixed(g)),f&&f.toFixed&&(f=f.toFixed(g)),T=n.mouse.trackFormatter({x:a,y:f,series:n.series,index:n.index,nearest:n,fraction:n.fraction});if(t.isNull(T)||t.isUndefined(T)){e.hide(c);return}c.innerHTML=T,e.show(c);if(!o)return;x=e.size(c),b&&(S=e.position(this.el),w=S.top,E=S.left);if(!n.mouse.relative)i+="top:",o.charAt(0)=="n"?i+=w+u+m:o.charAt(0)=="s"&&(i+=w-u+m+this.plotHeight-x.height),i+="px;bottom:auto;left:",o.charAt(1)=="e"?i+=E-u+p+this.plotWidth-x.width:o.charAt(1)=="w"&&(i+=E+u+p),i+="px;right:auto;";else if(s.pie&&s.pie.show){var N={x:this.plotWidth/2,y:this.plotHeight/2},C=Math.min(this.canvasWidth,this.canvasHeight)*s.pie.sizeRatio/2,k=n.sAngle=5||Math.abs(e.second.y-e.first.y)>=5}})}(),function(){var e=Flotr.DOM;Flotr.addPlugin("labels",{callbacks:{"flotr:afterdraw":function(){this.labels.draw()}},draw:function(){function b(e,t,r){var i=r?t.minorTicks:t.ticks,s=t.orientation===1,u=t.n===1,l,h;l={color:t.options.color||d.grid.color,angle:Flotr.toRad(t.options.labelsAngle),textBaseline:"middle"};for(c=0;c(s?e.plotWidth:e.plotHeight))continue;Flotr.drawText(v,n.label,l(e,s,o,a),h(e,s,o,a),u),!s&&!o&&(v.save(),v.strokeStyle=u.color,v.beginPath(),v.moveTo(e.plotOffset.left+e.plotWidth-8,e.plotOffset.top+t.d2p(n.v)),v.lineTo(e.plotOffset.left+e.plotWidth,e.plotOffset.top+t.d2p(n.v)),v.stroke(),v.restore())}}function E(e,t){var r=t.orientation===1,i=t.n===1,o="",u,a,f,l=e.plotOffset;!r&&!i&&(v.save(),v.strokeStyle=t.options.color||d.grid.color,v.beginPath());if(t.options.showLabels&&(i?!0:t.used))for(c=0;c(r?e.canvasWidth:e.canvasHeight))continue;f=l.top+(r?(i?1:-1)*(e.plotHeight+d.grid.labelMargin):t.d2p(n.v)-t.maxLabel.height/2),u=r?l.left+t.d2p(n.v)-s/2:0,o="",c===0?o=" first":c===t.ticks.length-1&&(o=" last"),o+=r?" flotr-grid-label-x":" flotr-grid-label-y",h+=['
      '+n.label+"
      "].join(" "),!r&&!i&&(v.moveTo(l.left+e.plotWidth-8,l.top+t.d2p(n.v)),v.lineTo(l.left+e.plotWidth,l.top+t.d2p(n.v)))}}var t,n,r,i,s,o,u,a,f,l,c,h="",p=0,d=this.options,v=this.ctx,m=this.axes,g={size:d.fontSize};for(c=0;c-1;--p){if(!n[p].label||n[p].hide)continue;d=s.labelFormatter(n[p].label),E=Math.max(E,this._text.measureText(d,S).width)}var x=Math.round(m+y*3+E),T=Math.round(f*(y+g)+y);!h&&h!==0&&(h=.1);if(!i.HtmlText&&this.textEnabled&&!s.container){l.charAt(0)=="s"&&(w=r.top+this.plotHeight-(c+T)),l.charAt(0)=="c"&&(w=r.top+this.plotHeight/2-(c+T/2)),l.charAt(1)=="e"&&(b=r.left+this.plotWidth-(c+x)),v=this.processColor(s.backgroundColor,{opacity:h}),a.fillStyle=v,a.fillRect(b,w,x,T),a.strokeStyle=s.labelBoxBorderColor,a.strokeRect(Flotr.toPixel(b),Flotr.toPixel(w),x,T);var N=b+y,C=w+y;for(p=0;p
    ','
    ','
    ','
    ',"
    ","
    ","
    ',d,"
    '+o.join("")+"
    ";if(s.container)O=e.node(O),this.legend.markup=O,e.insert(s.container,O);else{var M={position:"absolute",zIndex:"2",border:"1px solid "+s.labelBoxBorderColor};l.charAt(0)=="n"?(M.top=c+r.top+"px",M.bottom="auto"):l.charAt(0)=="c"?(M.top=c+(this.plotHeight-T)/2+"px",M.bottom="auto"):l.charAt(0)=="s"&&(M.bottom=c+r.bottom+"px",M.top="auto"),l.charAt(1)=="e"?(M.right=c+r.right+"px",M.left="auto"):l.charAt(1)=="w"&&(M.left=c+r.left+"px",M.right="auto");var P=e.create("div"),H;P.className="flotr-legend",e.setStyles(P,M),e.insert(P,O),e.insert(this.el,P);if(!h)return;var B=s.backgroundColor||i.grid.backgroundColor||"#ffffff";t.extend(M,e.size(P),{backgroundColor:B,zIndex:"",border:""}),M.width+="px",M.height+="px",P=e.create("div"),P.className="flotr-legend-bg",e.setStyles(P,M),e.opacity(P,h),e.insert(P," "),e.insert(this.el,P)}}}}}})}(),function(){function e(e){if(this.options.spreadsheet.tickFormatter)return this.options.spreadsheet.tickFormatter(e);var t=n.find(this.axes.x.ticks,function(t){return t.v==e});return t?t.label:e}var t=Flotr.DOM,n=Flotr._;Flotr.addPlugin("spreadsheet",{options:{show:!1,tabGraphLabel:"Graph",tabDataLabel:"Data",toolbarDownload:"Download CSV",toolbarSelectAll:"Select all",csvFileSeparator:",",decimalSeparator:".",tickFormatter:null,initialTab:"graph"},callbacks:{"flotr:afterconstruct":function(){if(!this.options.spreadsheet.show)return;var e=this.spreadsheet,n=t.node('
    '),r=t.node('
    '+this.options.spreadsheet.tabGraphLabel+"
    "),i=t.node('
    '+this.options.spreadsheet.tabDataLabel+"
    "),s;e.tabsContainer=n,e.tabs={graph:r,data:i},t.insert(n,r),t.insert(n,i),t.insert(this.el,n),s=t.size(i).height+2,this.plotOffset.bottom+=s,t.setStyles(n,{top:this.canvasHeight-s+"px"}),this.observe(r,"click",function(){e.showTab("graph")}).observe(i,"click",function(){e.showTab("data")}),this.options.spreadsheet.initialTab!=="graph"&&e.showTab(this.options.spreadsheet.initialTab)}},loadDataGrid:function(){if(this.seriesData)return this.seriesData;var e=this.series,t={};return n.each(e,function(e,r){n.each(e.data,function(e){var n=e[0],s=e[1],o=t[n];if(o)o[r+1]=s;else{var u=[];u[0]=n,u[r+1]=s,t[n]=u}})}),this.seriesData=n.sortBy(t,function(e,t){return parseInt(t,10)}),this.seriesData},constructDataGrid:function(){if(this.spreadsheet.datagrid)return this.spreadsheet.datagrid;var r=this.series,i=this.spreadsheet.loadDataGrid(),s=[""],o,u,a,f=[''];f.push(""),n.each(r,function(e,t){f.push('"),s.push("")}),f.push(""),n.each(i,function(t){f.push(""),n.times(r.length+1,function(r){var i="td",s=t[r],o=n.isUndefined(s)?"":Math.round(s*1e5)/1e5;if(r===0){i="th";var u=e.call(this,o);u&&(o=u)}f.push("<"+i+(i=="th"?' scope="row"':"")+">"+o+"")},this),f.push("")},this),s.push(""),a=t.node(f.join("")),o=t.node('"),u=t.node('"),this.observe(o,"click",n.bind(this.spreadsheet.downloadCSV,this)).observe(u,"click",n.bind(this.spreadsheet.selectAllData,this));var l=t.node('
    ');t.insert(l,o),t.insert(l,u);var c=this.canvasHeight-t.size(this.spreadsheet.tabsContainer).height-2,h=t.node('
    ');return t.insert(h,l),t.insert(h,a),t.insert(this.el,h),this.spreadsheet.datagrid=a,this.spreadsheet.container=h,a},showTab:function(e){if(this.spreadsheet.activeTab===e)return;switch(e){case"graph":t.hide(this.spreadsheet.container),t.removeClass(this.spreadsheet.tabs.data,"selected"),t.addClass(this.spreadsheet.tabs.graph,"selected");break;case"data":this.spreadsheet.datagrid||this.spreadsheet.constructDataGrid(),t.show(this.spreadsheet.container),t.addClass(this.spreadsheet.tabs.data,"selected"),t.removeClass(this.spreadsheet.tabs.graph,"selected");break;default:throw"Illegal tab name: "+e}this.spreadsheet.activeTab=e},selectAllData:function(){if(this.spreadsheet.tabs){var e,t,n,r,i=this.spreadsheet.constructDataGrid();return this.spreadsheet.showTab("data"),setTimeout(function(){(n=i.ownerDocument)&&(r=n.defaultView)&&r.getSelection&&n.createRange&&(e=window.getSelection())&&e.removeAllRanges?(t=n.createRange(),t.selectNode(i),e.removeAllRanges(),e.addRange(t)):document.body&&document.body.createTextRange&&(t=document.body.createTextRange())&&(t.moveToElementText(i),t.select())},0),!0}return!1},downloadCSV:function(){var t="",r=this.series,i=this.options,s=this.spreadsheet.loadDataGrid(),o=encodeURIComponent(i.spreadsheet.csvFileSeparator);if(i.spreadsheet.decimalSeparator===i.spreadsheet.csvFileSeparator)throw"The decimal separator is the same as the column separator ("+i.spreadsheet.decimalSeparator+")";n.each(r,function(e,n){t+=o+'"'+(e.label||String.fromCharCode(65+n)).replace(/\"/g,'\\"')+'"'}),t+="%0D%0A",t+=n.reduce(s,function(t,n){var r=e.call(this,n[0])||"";r='"'+(r+"").replace(/\"/g,'\\"')+'"';var s=n.slice(1).join(o);return i.spreadsheet.decimalSeparator!=="."&&(s=s.replace(/\./g,i.spreadsheet.decimalSeparator)),t+r+o+s+"%0D%0A"},"",this),Flotr.isIE&&Flotr.isIE<9?(t=t.replace(new RegExp(o,"g"),decodeURIComponent(o)).replace(/%0A/g,"\n").replace(/%0D/g,"\r"),window.open().document.write(t)):window.open("data:text/csv,"+t)}})}(),function(){var e=Flotr.DOM;Flotr.addPlugin("titles",{callbacks:{"flotr:afterdraw":function(){this.titles.drawTitles()}},drawTitles:function(){var t,n=this.options,r=n.grid.labelMargin,i=this.ctx,s=this.axes;if(!n.HtmlText&&this.textEnabled){var o={size:n.fontSize,color:n.grid.color,textAlign:"center"};n.subtitle&&Flotr.drawText(i,n.subtitle,this.plotOffset.left+this.plotWidth/2,this.titleHeight+this.subtitleHeight-2,o),o.weight=1.5,o.size*=1.5,n.title&&Flotr.drawText(i,n.title,this.plotOffset.left+this.plotWidth/2,this.titleHeight-2,o),o.weight=1.8,o.size*=.8,s.x.options.title&&s.x.used&&(o.textAlign=s.x.options.titleAlign||"center",o.textBaseline="top",o.angle=Flotr.toRad(s.x.options.titleAngle),o=Flotr.getBestTextAlign(o.angle,o),Flotr.drawText(i,s.x.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top+s.x.maxLabel.height+this.plotHeight+2*r,o)),s.x2.options.title&&s.x2.used&&(o.textAlign=s.x2.options.titleAlign||"center",o.textBaseline="bottom",o.angle=Flotr.toRad(s.x2.options.titleAngle),o=Flotr.getBestTextAlign(o.angle,o),Flotr.drawText(i,s.x2.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top-s.x2.maxLabel.height-2*r,o)),s.y.options.title&&s.y.used&&(o.textAlign=s.y.options.titleAlign||"right",o.textBaseline="middle",o.angle=Flotr.toRad(s.y.options.titleAngle),o=Flotr.getBestTextAlign(o.angle,o),Flotr.drawText(i,s.y.options.title,this.plotOffset.left-s.y.maxLabel.width-2*r,this.plotOffset.top+this.plotHeight/2,o)),s.y2.options.title&&s.y2.used&&(o.textAlign=s.y2.options.titleAlign||"left",o.textBaseline="middle",o.angle=Flotr.toRad(s.y2.options.titleAngle),o=Flotr.getBestTextAlign(o.angle,o),Flotr.drawText(i,s.y2.options.title,this.plotOffset.left+this.plotWidth+s.y2.maxLabel.width+2*r,this.plotOffset.top+this.plotHeight/2,o))}else{t=[],n.title&&t.push('
    ',n.title,"
    "),n.subtitle&&t.push('
    ',n.subtitle,"
    "),t.push(""),t.push('
    '),s.x.options.title&&s.x.used&&t.push('
    ',s.x.options.title,"
    "),s.x2.options.title&&s.x2.used&&t.push('
    ',s.x2.options.title,"
    "),s.y.options.title&&s.y.used&&t.push('
    ',s.y.options.title,"
    "),s.y2.options.title&&s.y2.used&&t.push('
    ',s.y2.options.title,"
    "),t=t.join("");var u=e.create("div");e.setStyles({color:n.grid.color}),u.className="flotr-titles",e.insert(this.el,u),e.insert(u,t)}}})}(); +// @license-end diff --git a/p/scripts/global_view.js b/p/scripts/global_view.js index b1581614a..bc4a48b91 100644 --- a/p/scripts/global_view.js +++ b/p/scripts/global_view.js @@ -1,3 +1,4 @@ +// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0 "use strict"; /* globals context, init_load_more, init_posts, init_stream */ /* jshint esversion:6, strict:global */ @@ -110,3 +111,4 @@ if (document.readyState && document.readyState !== 'loading') { init_all_global_view(); }, false); } +// @license-end diff --git a/p/scripts/install.js b/p/scripts/install.js index 967d27627..ccf3b6bb0 100644 --- a/p/scripts/install.js +++ b/p/scripts/install.js @@ -1,3 +1,4 @@ +// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0 "use strict"; /* jshint globalstrict: true */ @@ -71,3 +72,4 @@ var confirms = document.getElementsByClassName('confirm'); for (var i = 0 ; i < confirms.length ; i++) { confirms[i].addEventListener('click', ask_confirmation); } +// @license-end diff --git a/p/scripts/main.js b/p/scripts/main.js index 4fd91235e..361bed02a 100644 --- a/p/scripts/main.js +++ b/p/scripts/main.js @@ -1,3 +1,4 @@ +// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0 "use strict"; /* jshint esversion:6, strict:global */ @@ -720,8 +721,14 @@ function init_shortcuts() { return true; } - const s = context.shortcuts, - k = (ev.key.trim() || ev.code).toUpperCase(); + const s = context.shortcuts; + let k = (ev.key.trim() || ev.code || 'Space').toUpperCase(); + + //IE11 + if (k === 'SPACEBAR') k = 'SPACE'; + else if (k === 'DEL') k = 'DELETE'; + else if (k === 'ESC') k = 'ESCAPE'; + if (location.hash.match(/^#dropdown-/)) { const n = parseInt(k); if (n) { @@ -803,7 +810,11 @@ function init_shortcuts() { if (context.auto_mark_site) { mark_read(document.querySelector('.flux.current'), true, false); } - window.open(document.querySelector('.flux.current a.go_website').href); + const newWindow = window.open(); + if (newWindow) { + newWindow.opener = null; + newWindow.location = document.querySelector('.flux.current a.go_website').href; + } return false; } if (k === s.skip_next_entry) { next_entry(true); return false; } @@ -1387,9 +1398,10 @@ function faviconNbUnread(n) { } //http://remysharp.com/2010/08/24/dynamic-favicons/ const canvas = document.createElement('canvas'), - link = document.getElementById('favicon').cloneNode(true); + link = document.getElementById('favicon').cloneNode(true), + ratio = window.devicePixelRatio; if (canvas.getContext && link) { - canvas.height = canvas.width = 16; + canvas.height = canvas.width = 16 * ratio; const img = document.createElement('img'); img.onload = function () { const ctx = canvas.getContext('2d'); @@ -1403,9 +1415,9 @@ function faviconNbUnread(n) { } else { text = 'E' + Math.floor(Math.log10(n)); } - ctx.font = 'bold 9px "Arial", sans-serif'; + ctx.font = 'bold ' + 9 * ratio + 'px "Arial", sans-serif'; ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; - ctx.fillRect(0, 7, ctx.measureText(text).width, 9); + ctx.fillRect(0, 7 * ratio, ctx.measureText(text).width, 9 * ratio); ctx.fillStyle = '#F00'; ctx.fillText(text, 0, canvas.height - 1); } @@ -1454,6 +1466,7 @@ function init_afterDOM() { init_posts(); init_nav_entries(); init_notifs_html5(); + setTimeout(faviconNbUnread, 1000); setInterval(refreshUnreads, 120000); } @@ -1474,3 +1487,4 @@ if (document.readyState && document.readyState !== 'loading') { init_afterDOM(); }, false); } +// @license-end diff --git a/p/scripts/repartition.js b/p/scripts/repartition.js index e71fa71c4..8837c687b 100644 --- a/p/scripts/repartition.js +++ b/p/scripts/repartition.js @@ -1,3 +1,4 @@ +// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0 "use strict"; /* globals Flotr, numberFormat */ /* jshint esversion:6, strict:global */ @@ -70,3 +71,4 @@ function initStats() { } initStats(); +// @license-end diff --git a/p/scripts/stats.js b/p/scripts/stats.js index b47188d77..54ea0e4de 100644 --- a/p/scripts/stats.js +++ b/p/scripts/stats.js @@ -1,3 +1,4 @@ +// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0 "use strict"; /* globals Flotr, numberFormat */ /* jshint esversion:6, strict:global */ @@ -57,3 +58,4 @@ function initStats() { }); } initStats(); +// @license-end diff --git a/p/themes/.gitignore b/p/themes/.gitignore new file mode 100644 index 000000000..18cb4b685 --- /dev/null +++ b/p/themes/.gitignore @@ -0,0 +1 @@ +xTheme-* diff --git a/p/themes/Ansum/_components.scss b/p/themes/Ansum/_components.scss index be27fcd92..d7b6d4ed6 100644 --- a/p/themes/Ansum/_components.scss +++ b/p/themes/Ansum/_components.scss @@ -5,119 +5,119 @@ /*=== Horizontal-list */ .horizontal-list { - margin: 0; - padding: 0.1rem 0; + margin: 0; + padding: 0.1rem 0; - .item{ - vertical-align: middle; + .item { + vertical-align: middle; - &:first-child{ - padding-left: 0.5rem; - } + &:first-child { + padding-left: 0.5rem; + } - } + } } /*=== Dropdown */ .dropdown-menu { - background: $grey-lighter; - margin: 0; - font-size: 1rem; - text-align: left; - padding: 0.5rem 0 1rem 0; - border: none; - border-radius: 3px; - - -webkit-box-shadow: 0px 6px 8px 0px rgba(0,0,0,0.35); - -moz-box-shadow: 0px 6px 8px 0px rgba(0,0,0,0.35); - box-shadow: 0px 6px 8px 0px rgba(0,0,0,0.35); - - &::after { - content: ""; - position: absolute; - top: -4px; - right: 13px; - width: 10px; - height: 10px; - z-index: -10; - transform: rotate(45deg); - -moz-transform: rotate(45deg); - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); - background: white; - // border-top: 1px solid #95a5a6; - // border-left: 1px solid #95a5a6; - } - - .dropdown-header { - // padding: 0 5px 5px; - margin: 1.75rem 0 0.5rem 2rem; - font-weight: bold; + margin: 0; + padding: 0.5rem 0 1rem 0; + background: $grey-lighter; + font-size: 1rem; + border: none; + border-radius: 3px; + box-shadow: 0px 6px 8px 0px rgba(0,0,0,0.35); text-align: left; - color: $grey-dark; - text-transform: uppercase; - letter-spacing: 1px; - - - } - - .item{ - @include transition(all, 0.075s, ease-in-out); - a, span, .as-link{ - padding: 0 2rem; - line-height: 2.5em; - font-size: 1rem; - color: $main-font-color; - } - &:hover{ - background: $main-first; - color: $white; - a, button{ - text-decoration: none; - color: $white; - } + &::after { + background: white; + width: 10px; + height: 10px; + content: ""; + position: absolute; + top: -4px; + right: 13px; + z-index: -10; + transform: rotate(45deg); + // border-top: 1px solid #95a5a6; + // border-left: 1px solid #95a5a6; } - &[aria-checked="true"]{ - a::before{ + + .dropdown-header { + // padding: 0 5px 5px; + margin: 1.75rem 0 0.5rem 2rem; font-weight: bold; - margin: 0 0 0 -14px; - } + text-align: left; + color: $grey-dark; + text-transform: uppercase; + letter-spacing: 1px; + + } - } - .input{ - select, input{ - margin: 0 auto 5px; - padding: 2px 5px; - border-radius: 3px; + + .item { + + @include transition(all, 0.075s, ease-in-out); + + a, span, .as-link { + padding: 0 2rem; + color: $main-font-color; + font-size: 1rem; + line-height: 2.5em; + } + + &:hover { + background: $main-first; + color: $white; + + a, button { + text-decoration: none; + color: $white; + } + } + + &[aria-checked="true"] { + a::before { + margin: 0 0 0 -14px; + font-weight: bold; + } + } } - } - .separator { - margin: 0.75rem 0; - border-bottom: 1px solid $grey-light; - // display: none; - } -} -.tree .tree-folder .tree-folder-items .dropdown-menu, -.tree .tree-folder .tree-folder-items .dropdown-menu{ - // tout ça sert à restaurer l'apparence du dropdown dans un contexte de sidebar sombre - - .item{ - padding: 0; - - a, - button{ - color: $main-font-color; - - &:hover{ - color: $white; - } + .input { + select, input { + margin: 0 auto 5px; + padding: 2px 5px; + border-radius: 3px; + } + } + + .separator { + margin: 0.75rem 0; + border-bottom: 1px solid $grey-light; + // display: none; } - &:hover{ - background: $main-first; + +} + +.tree .tree-folder .tree-folder-items .dropdown-menu { + // tout ça sert à restaurer l'apparence du dropdown dans un contexte de sidebar sombre + .item { + padding: 0; + + a, + button { + color: $main-font-color; + + &:hover { + color: $white; + } + } + + &:hover { + background: $main-first; + } } - } } @@ -125,256 +125,273 @@ /*=== Alerts */ .alert { - margin: 1rem 0; - // width: 100%; - padding: 1rem; - font-size: 1rem; - background: $grey-lighter; - border: 1px solid $grey-medium; - border-radius: 3px; - color: $grey-dark; - text-shadow: 0 0 1px $grey-light; + margin: 1rem 0; + // width: 100%; + padding: 1rem; + background: $grey-lighter; + color: $grey-dark; + font-size: 1rem; + border: 1px solid $grey-medium; + border-radius: 3px; + text-shadow: 0 0 1px $grey-light; } + .alert-head { - font-size: 1.15em; + font-size: 1.15em; } + .alert > a { - text-decoration: underline; - color: inherit; + text-decoration: underline; + color: inherit; } + .alert-warn { - background: $warning-light; - border: 1px solid unquote($warning-text+'33'); // on ajoute l'opacité à la fin - color: $warning-text; + background: $warning-light; + color: $warning-text; + border: 1px solid unquote($warning-text+'33'); // on ajoute l'opacité à la fin } + .alert-success { - background: $success-light; - border: 1px solid unquote($success-text+'33'); - color: $success-text; + background: $success-light; + color: $success-text; + border: 1px solid unquote($success-text+'33'); } + .alert-error { - background: $alert-light; - border: 1px solid unquote($alert-text+'33'); - color: $alert-text; + background: $alert-light; + color: $alert-text; + border: 1px solid unquote($alert-text+'33'); } /*=== Pagination */ .pagination { - text-align: center; - font-size: 0.8em; - background: $grey-light; - color: $main-font-color; - - .item{ - &.pager-current { - font-weight: bold; - font-size: 1.5em; - background: $sid-bg; - color: $grey-light; - } - a { - display: block; - font-style: italic; - line-height: 3em; - text-decoration: none; - color: $main-font-color; - - &:hover{ - background: $main-font-color; - color: $grey-light; - } + background: $grey-light; + color: $main-font-color; + font-size: 0.8em; + text-align: center; + + .item { + &.pager-current { + background: $sid-bg; + color: $grey-light; + font-size: 1.5em; + font-weight: bold; + } + + a { + display: block; + color: $main-font-color; + font-style: italic; + line-height: 3em; + text-decoration: none; + + &:hover { + background: $main-font-color; + color: $grey-light; + } + } } - } - .loading, - a:hover.loading { - font-size: 0; - background: url("loader.gif") center center no-repeat #34495e; - } + .loading, + a:hover.loading { + background: url("loader.gif") center center no-repeat #34495e; + font-size: 0; + } } + .content .pagination { - margin: 0; - padding: 0; + margin: 0; + padding: 0; } /*=== Boxes */ .box { - // border: 1px solid #ddd; - border: none; - border-radius: 3px; - background: $white; - - -webkit-box-shadow: 0px 2px 2px 0px rgba(0,0,0,0.25); - -moz-box-shadow: 0px 2px 2px 0px rgba(0,0,0,0.25); - box-shadow: 0px 2px 2px 0px rgba(0,0,0,0.25); - - .box-title { - margin: 0; - padding: 0.5rem 0.75rem; - background: $grey-light; - color: $main-font-color; - // border-bottom: 1px solid #ddd; - border-radius: 2px 2px 0 0; + background: $white; + // border: 1px solid #ddd; + border: none; + border-radius: 3px; + box-shadow: 0px 2px 2px 0px rgba(0,0,0,0.25); + + .box-title { + margin: 0; + padding: 0.5rem 0.75rem; + background: $grey-light; + color: $main-font-color; + // border-bottom: 1px solid #ddd; + border-radius: 2px 2px 0 0; + + img { + margin-right: 0.75rem; + } - img{ - margin-right: 0.75rem; - } + &:hover { + .configure { + background: url("icons/cog.svg") no-repeat 4px 4px; + display: block; + float: left; + width: 1.75rem; + height: 1.75rem; + border-radius: 2px; + visibility: visible; + margin-right: 0.5rem; + + .icon { + display: none; + border-radius: 3px; + vertical-align: middle; + } + + &:hover { + background: url("icons/cog-white.svg") no-repeat 4px 4px $main-first; + } + } + } - &:hover{ .configure { - visibility: visible; - background: url("icons/cog.svg") no-repeat 4px 4px; - width: 1.75rem; - height: 1.75rem; - display: block; - border-radius: 2px; - float: left; - margin-right: 0.5rem; - .icon { - vertical-align: middle; - border-radius: 3px; - display: none; + visibility: hidden; + } + + form { + input { + width: 85%; } - &:hover { - background: url("icons/cog-white.svg") no-repeat 4px 4px $main-first; + + .dropdown { + float: right; + + a.dropdown-toggle { + padding: 0; + // float: right; + border-radius: 0; + background-image: url(icons/more.svg); + background-repeat: no-repeat; + background-position: right 8px; + + img { + display: none; + } + } } } } - .configure { - visibility: hidden; - } - form{ - input{ - width: 85%; - } - .dropdown{ - float: right; - a.dropdown-toggle{ - padding: 0; - background-image: url(icons/more.svg); - background-repeat: no-repeat; - background-position: right 8px; - // float: right; - border-radius: 0; - img{ - display: none; - } - } - } - } - } - .box-content { - // max-height: 260px; + .box-content { + // max-height: 260px; + .item { + padding: 0.5rem 0.75rem; + color: $main-font-color; + font-size: 1rem; + border-bottom: 1px solid $grey-light; + line-height: 1.7em; + + img { + margin-right: 0.75rem; + } - .item { - padding: 0.5rem 0.75rem; - font-size: 1rem; - color: $main-font-color; - line-height: 1.7em; - border-bottom: 1px solid $grey-light; - - img{ - margin-right: 0.75rem; - } - - .configure { - visibility: hidden; - width: 1.75rem; - height: 1.75rem; - display: block; - border-radius: 2px; - float: left; - margin-right: 0.5rem; - background: url("icons/cog.svg") no-repeat 4px 4px; - - .icon { - vertical-align: middle; - border-radius: 3px; - display: none; + .configure { + background: url("icons/cog.svg") no-repeat 4px 4px; + display: block; + float: left; + width: 1.75rem; + height: 1.75rem; + border-radius: 2px; + visibility: hidden; + margin-right: 0.5rem; + + .icon { + display: none; + border-radius: 3px; + vertical-align: middle; + } + + &:hover { + // background: $main-first; + background: url("icons/cog-white.svg") no-repeat 4px 4px $main-first; + } + } + + &:hover .configure { + visibility: visible; + } } - &:hover{ - // background: $main-first; - background: url("icons/cog-white.svg") no-repeat 4px 4px $main-first; + + .item:last-child { + border-bottom: none; } - } - &:hover .configure { - visibility: visible; - } - } - .item:last-child{ - border-bottom: none; } - } } /*=== "Load more" part */ #bigMarkAsRead { - text-align: center; - text-decoration: none; - background: $main-first-light; - color: $main-first; + text-align: center; + text-decoration: none; + background: $main-first-light; + color: $main-first; + + @include transition(all, 0.15s, ease-in-out); - @include transition(all, 0.15s, ease-in-out); + &:hover { + background: $main-first; + color: #fff; - &:hover { - background: $main-first; - color: #fff; + .bigTick { + background: url(icons/tick-white.svg) center no-repeat; + } + } - .bigTick{ - background: url(icons/tick-white.svg) center no-repeat; + .bigTick { + margin: 0.5rem 0; + background: url(icons/tick-color.svg) center no-repeat; + display: inline-block; + width: 64px; + height: 64px; + text-indent: -9999px; + white-space: nowrap; } - } - .bigTick{ - margin: 0.5rem 0; - display: inline-block; - text-indent: -9999px; - background: url(icons/tick-color.svg) center no-repeat; - height: 64px; - width: 64px; - white-space: nowrap; - } } // page de login -.formLogin{ - background: $sid-bg; - - .header{ - .configure{ - padding-right: 1rem; - img{ - margin-right: 0.5rem; - } - - a.signin{ +.formLogin { + background: $sid-bg; + + .header { + .configure { + padding-right: 1rem; + + img { + margin-right: 0.5rem; + } + + a.signin { + color: $white; + } + } + } + + h1 { color: $white; - } } - } - - h1{ - color: $white; - } - form#crypto-form{ - div{ - margin-bottom: 1rem; - - label{ - font-size: 1rem; - color: $grey-medium; - - - } - input{ - background: $main-first-darker; - - &:focus{ - background: $grey-lighter; - color: $main-font-color; + + form#crypto-form { + div { + margin-bottom: 1rem; + + label { + color: $grey-medium; + font-size: 1rem; + + + } + + input { + background: $main-first-darker; + + &:focus { + background: $grey-lighter; + color: $main-font-color; + } + } } - } - } - } + } } diff --git a/p/themes/Ansum/_configuration.scss b/p/themes/Ansum/_configuration.scss index c3c6fd417..78e471f19 100644 --- a/p/themes/Ansum/_configuration.scss +++ b/p/themes/Ansum/_configuration.scss @@ -1,90 +1,89 @@ /*=== Configuration pages */ .post { - padding: 1rem 2rem; - font-size: 1rem; - - form { - margin: 1rem 0; - - // Gestion des extensions - .horizontal-list{ - margin-bottom: 0.5rem; - - .item{ - .stick{ - // width: 65%; - // margin-right: 1rem; - // display:flex; - - } - .btn{ - // width: 8rem; - // flex-grow: 1; + padding: 1rem 2rem; + font-size: 1rem; + + form { + margin: 1rem 0; + + // Gestion des extensions + .horizontal-list { + margin-bottom: 0.5rem; + + .item { + .stick { + // width: 65%; + // margin-right: 1rem; + // display:flex; + + } + + .btn { + // width: 8rem; + // flex-grow: 1; + } + } + } - } - } - } - &.content { - max-width: 550px; - } - - h1, h2{ // pages titles - font-size: 3rem; - margin-top: 1.75rem; - font-weight: 300; - line-height: 1.2em; - // font-family: "spectral"; - color: $main-font-color; - } - - a[href="./"]{ // C'est le bouton "Retour à vos flux" - display: inline-block; - // min-height: 38px; - min-width: 15px; - line-height: 25px; - margin: 0; - padding: 0.75rem 1.5rem; - font-size: 1rem; - vertical-align: middle; - cursor: pointer; - overflow: hidden; - background: $grey-lighter; - border: 1px solid $grey-medium-light; - border-radius: 5px; - // border: none; - color: $grey-dark; - - &:hover{ - text-decoration: none; - background: $main-first; - color: white; - border: 1px solid $main-first; + + &.content { + max-width: 550px; + } + + h1, h2 { // pages titles + // font-family: "spectral"; + color: $main-font-color; + font-size: 3rem; + margin-top: 1.75rem; + font-weight: 300; + line-height: 1.2em; } - } - + + a[href="./"] { // C'est le bouton "Retour à vos flux" + margin: 0; + padding: 0.75rem 1.5rem; + background: $grey-lighter; + display: inline-block; + // border: none; + color: $grey-dark; + font-size: 1rem; + border: 1px solid $grey-medium-light; + border-radius: 5px; + // min-height: 38px; + min-width: 15px; + line-height: 25px; + vertical-align: middle; + cursor: pointer; + overflow: hidden; + + &:hover { + background: $main-first; + color: white; + border: 1px solid $main-first; + text-decoration: none; + } + } + } -#slider{ - border-left: none; - - -webkit-box-shadow: 0px 6px 8px 0px rgba(0,0,0,0.35); - -moz-box-shadow: 0px 6px 8px 0px rgba(0,0,0,0.35); - box-shadow: 0px 6px 8px 0px rgba(0,0,0,0.35); +#slider { + border-left: none; + box-shadow: 0px 6px 8px 0px rgba(0,0,0,0.35); } -.slide-container{ - .properties{ - background: rgba(0, 0, 0, 0.75); - border: 0; - padding: 1rem; - color: white; +.slide-container { + .properties { + padding: 1rem; + background: rgba(0, 0, 0, 0.75); + color: white; + border: 0; - .page-number{ - right: 1rem; - top: 1rem; + .page-number { + right: 1rem; + top: 1rem; + } } - } } diff --git a/p/themes/Ansum/_divers.scss b/p/themes/Ansum/_divers.scss index 7d122f1b4..c6fb1f283 100644 --- a/p/themes/Ansum/_divers.scss +++ b/p/themes/Ansum/_divers.scss @@ -4,9 +4,11 @@ .aside.aside_feed .nav-form select { width: 140px; } + .aside.aside_feed .nav-form .dropdown .dropdown-menu { right: -20px; } + .aside.aside_feed .nav-form .dropdown .dropdown-menu::after { right: 33px; } diff --git a/p/themes/Ansum/_fonts.scss b/p/themes/Ansum/_fonts.scss index 5891be834..da1cf4ac7 100644 --- a/p/themes/Ansum/_fonts.scss +++ b/p/themes/Ansum/_fonts.scss @@ -1,56 +1,63 @@ @font-face { - font-family: "lato"; - font-style: normal; - font-stretch: normal; - font-weight: 400; - src: local("Lato"), url("../fonts/LatoLatin-Regular.woff") format("woff"); -} -@font-face { - font-family: "lato"; - font-style: italic; - font-stretch: normal; - font-weight: 400; - src: local("Lato"), url("../fonts/LatoLatin-Italic.woff") format("woff"); -} -@font-face { - font-family: "lato"; - font-style: normal; - font-stretch: normal; - font-weight: 700; - src: local("Lato"), url("../fonts/LatoLatin-Bold.woff") format("woff"); -} -@font-face { - font-family: "lato"; - font-style: italic; - font-stretch: normal; - font-weight: 700; - src: local("Lato"), url("../fonts/LatoLatin-BoldItalic.woff") format("woff"); -} -@font-face { - font-family: "spectral"; - font-style: normal; - font-stretch: normal; - font-weight: 400; - src: local("Spectral"), url("../fonts/Spectral-Regular.woff") format("woff"); -} -@font-face { - font-family: "spectral"; - font-style: italic; - font-stretch: normal; - font-weight: 400; - src: local("Spectral"), url("../fonts/Spectral-Italic.woff") format("woff"); -} -@font-face { - font-family: "spectral"; - font-style: normal; - font-stretch: normal; - font-weight: 700; - src: local("Spectral"), url("../fonts/Spectral-Bold.woff") format("woff"); -} -@font-face { - font-family: "spectral"; - font-style: italic; - font-stretch: normal; - font-weight: 700; - src: local("Spectral"), url("../fonts/Spectral-BoldItalic.woff") format("woff"); + font-family: "lato"; + font-style: normal; + font-stretch: normal; + font-weight: 400; + src: local("Lato"), url("../fonts/LatoLatin-Regular.woff") format("woff"); +} + +@font-face { + font-family: "lato"; + font-style: italic; + font-stretch: normal; + font-weight: 400; + src: local("Lato"), url("../fonts/LatoLatin-Italic.woff") format("woff"); +} + +@font-face { + font-family: "lato"; + font-style: normal; + font-stretch: normal; + font-weight: 700; + src: local("Lato"), url("../fonts/LatoLatin-Bold.woff") format("woff"); +} + +@font-face { + font-family: "lato"; + font-style: italic; + font-stretch: normal; + font-weight: 700; + src: local("Lato"), url("../fonts/LatoLatin-BoldItalic.woff") format("woff"); +} + +@font-face { + font-family: "spectral"; + font-style: normal; + font-stretch: normal; + font-weight: 400; + src: local("Spectral"), url("../fonts/Spectral-Regular.woff") format("woff"); +} + +@font-face { + font-family: "spectral"; + font-style: italic; + font-stretch: normal; + font-weight: 400; + src: local("Spectral"), url("../fonts/Spectral-Italic.woff") format("woff"); +} + +@font-face { + font-family: "spectral"; + font-style: normal; + font-stretch: normal; + font-weight: 700; + src: local("Spectral"), url("../fonts/Spectral-Bold.woff") format("woff"); +} + +@font-face { + font-family: "spectral"; + font-style: italic; + font-stretch: normal; + font-weight: 700; + src: local("Spectral"), url("../fonts/Spectral-BoldItalic.woff") format("woff"); } diff --git a/p/themes/Ansum/_forms.scss b/p/themes/Ansum/_forms.scss index 8a388aa8a..ff1691894 100644 --- a/p/themes/Ansum/_forms.scss +++ b/p/themes/Ansum/_forms.scss @@ -1,152 +1,160 @@ /* btns */ - .btn { - display: inline-block; - min-height: 38px; - min-width: 15px; - line-height: 25px; - margin: 0; - padding: 0.5rem 1.5rem; - font-size: 1rem; - vertical-align: middle; - cursor: pointer; - overflow: hidden; - background: $grey-lighter; - border-radius: 5px; - border: none; - color: $grey-dark; - - @include transition(all, 0.15s, ease-in-out); - - &.btn-important { - background: $main-first; - color: $white; - - // @include transition(all, 0.15s, ease-in-out); - - &:hover, - &:active { - background: $main-first-alt; + margin: 0; + padding: 0.5rem 1.5rem; + background: $grey-lighter; + display: inline-block; + color: $grey-dark; + font-size: 1rem; + border: none; + border-radius: 5px; + min-height: 38px; + min-width: 15px; + line-height: 25px; + vertical-align: middle; + cursor: pointer; + overflow: hidden; + + @include transition(all, 0.15s, ease-in-out); + + &.btn-important { + background: $main-first; + color: $white; + + // @include transition(all, 0.15s, ease-in-out); + &:hover, + &:active { + background: $main-first-alt; + } } - } - &.btn-attention { - background: $alert-bg; - color: #fff; + &.btn-attention { + background: $alert-bg; + color: #fff; - &:hover, - &:active { - background: $alert-text; + &:hover, + &:active { + background: $alert-text; + } } - } - &:hover { - text-decoration: none; - } + &:hover { + text-decoration: none; + } } a.btn { - min-height: 25px; - line-height: 25px; + min-height: 25px; + line-height: 25px; } /*=== Forms */ legend { - display: inline-block; - width: auto; - margin: 2rem 0 1rem 0; - padding: 0; - font-size: 1rem; - clear: both; - text-transform: uppercase; - letter-spacing: 1px; - font-weight: 700; + margin: 2rem 0 1rem 0; + padding: 0; + display: inline-block; + width: auto; + font-size: 1rem; + clear: both; + text-transform: uppercase; + letter-spacing: 1px; + font-weight: 700; } + label { - min-height: 25px; - padding: 5px 0; - cursor: pointer; - color: $grey-dark; + min-height: 25px; + padding: 5px 0; + cursor: pointer; + color: $grey-dark; } + textarea { - width: 360px; - height: 100px; + width: 360px; + height: 100px; } + input, select, textarea, button { - font-family: "lato", "Helvetica", "Arial", sans-serif; - min-height: 25px; - padding: 5px 10px; - line-height: 25px; - vertical-align: middle; - background: $white; - border: 1px solid $grey-light; - font-size: 1rem; - color: $grey-dark; - border-radius: 2px; + padding: 5px 10px; + background: $white; + color: $grey-dark; + font-family: "lato", "Helvetica", "Arial", sans-serif; + font-size: 1rem; + border: 1px solid $grey-light; + border-radius: 2px; + min-height: 25px; + line-height: 25px; + vertical-align: middle; } + option { - padding: 0 .5em; + padding: 0 .5em; } + input:focus, select:focus, textarea:focus { - color: $main-font-color; - border-color: $main-first; + color: $main-font-color; + border-color: $main-first; } + input:invalid, select:invalid { - color: $alert-bg; - border-color: $alert-bg; - box-shadow: none; + color: $alert-bg; + border-color: $alert-bg; + box-shadow: none; } + input:disabled, select:disabled { - background: $grey-light; + background: $grey-light; } + input.extend { - transition: width 200ms linear; - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; + transition: width 200ms linear; } .form-group { - padding: 5px; - border-radius: 3px; + padding: 5px; + border-radius: 3px; - &::after { - content: ""; - display: block; - clear: both; - } - &:hover { - // background: #fff; - // border: 1px solid #eee; - // border-radius: 3px; - // border: 1px solid #eee; - } - .group-name { - padding: 10px 0; - text-align: right; - } - .group-controls { - min-height: 25px; - padding: 5px 0; - } - .group-controls .control { - line-height: 2.0em; - } - table { - margin: 10px 0 0 220px; - } - - &.form-actions { - margin: 15px 0 25px; - padding: 5px 0; - // background: #333; - } - &.form-actions .btn { - margin: 0 0.5rem 0 0; - } + &::after { + content: ""; + display: block; + clear: both; + } + + &:hover { + // background: #fff; + // border: 1px solid #eee; + // border-radius: 3px; + // border: 1px solid #eee; + } + + .group-name { + padding: 10px 0; + text-align: right; + } + + .group-controls { + min-height: 25px; + padding: 5px 0; + } + + .group-controls .control { + line-height: 2.0em; + } + + table { + margin: 10px 0 0 220px; + } + + &.form-actions { + margin: 15px 0 25px; + padding: 5px 0; + // background: #333; + } + + &.form-actions .btn { + margin: 0 0.5rem 0 0; + } } diff --git a/p/themes/Ansum/_global-view.scss b/p/themes/Ansum/_global-view.scss index 30979bd6c..7d24a9109 100644 --- a/p/themes/Ansum/_global-view.scss +++ b/p/themes/Ansum/_global-view.scss @@ -1,80 +1,83 @@ /*=== GLOBAL VIEW */ /*================*/ +#stream { + .box.category { + &:not([data-unread="0"]) .box-title { + // background: #3498db; + } -#stream{ - .box.category{ - - &:not([data-unread="0"]) .box-title { - // background: #3498db; - } - &:not([data-unread="0"]) .box-title:active { - // background: #2980b9; - } - &:not([data-unread="0"]) .box-title .title { - font-weight: bold; - // color: #fff; - } - - .box-title{ - background: none; - padding: 1.5rem; - - a.title{ - font-weight: normal; - text-decoration: none; - text-align: left; - font-size: 1rem; - text-transform: uppercase; - letter-spacing: 1px; - color: $grey-dark; + &:not([data-unread="0"]) .box-title:active { + // background: #2980b9; + } - &:not([data-unread="0"])::after { - position: absolute; - top: 1.75rem; - right: 0; - line-height: 1.5rem; - background: $grey-light; - border-radius: 12px; - padding: 0 0.75rem; - margin: -0.5rem 1rem 0 0; - text-align: center; + &:not([data-unread="0"]) .box-title .title { + font-weight: bold; + // color: #fff; } - &:hover{ - color: $main-first; + + .box-title { + padding: 1.5rem; + background: none; + + a.title { + color: $grey-dark; + font-size: 1rem; + font-weight: normal; + text-decoration: none; + text-align: left; + text-transform: uppercase; + letter-spacing: 1px; + + &:not([data-unread="0"])::after { + margin: -0.5rem 1rem 0 0; + padding: 0 0.75rem; + background: $grey-light; + border-radius: 12px; + position: absolute; + top: 1.75rem; + right: 0; + line-height: 1.5rem; + text-align: center; + } + + &:hover { + color: $main-first; + } + } } - } - } - .box-content{ - padding-bottom: 0.5rem; - .item.feed { - // padding: 2px 10px; - font-size: 1rem; - padding: 0.5rem 1.5rem; - - a{ - color: $main-font-color; - font-weight: 400; + .box-content { + padding-bottom: 0.5rem; + + .item.feed { + padding: 0.5rem 1.5rem; + // padding: 2px 10px; + font-size: 1rem; - &:hover{ - color: $main-first; - text-decoration: none; - } + a { + color: $main-font-color; + font-weight: 400; + + &:hover { + color: $main-first; + text-decoration: none; + } + } + } } - } - } - } + } } // le panel qui apparait en overlay pour afficher les flux -#overlay{ - background: rgba(0, 0, 0, 0.65); +#overlay { + background: rgba(0, 0, 0, 0.65); } -#panel{ - top: 3rem; - right: 3rem; - bottom: 3rem; - left: 3rem; - border-radius: 3px; + +#panel { + top: 3rem; + right: 3rem; + bottom: 3rem; + left: 3rem; + border-radius: 3px; } diff --git a/p/themes/Ansum/_layout.scss b/p/themes/Ansum/_layout.scss index de684504a..a75601d06 100644 --- a/p/themes/Ansum/_layout.scss +++ b/p/themes/Ansum/_layout.scss @@ -2,118 +2,117 @@ /*===============*/ /*=== Header */ .header { - background: $sid-bg; - padding: 0.5rem 1.35rem; - display: block; - table-layout: none; - width: auto; - - .item{ - vertical-align: middle; - // text-align: center; - // display: flex; - // justify-content: space-between; - // flex-direction: row; - - &.title{ - - font-weight: 400; - width: 280px; - - h1{ - a{ - text-decoration: none; - color: $sid-font-color; - font-size: 1rem; - text-transform: uppercase; - letter-spacing: 1px; - - img{ - margin-right: 0.5rem; - - } + padding: 0.5rem 1.35rem; + background: $sid-bg; + display: block; + width: auto; + table-layout: none; + + .item { + vertical-align: middle; + // text-align: center; + // display: flex; + // justify-content: space-between; + // flex-direction: row; + &.title { + width: 280px; + + font-weight: 400; + + h1 { + a { + text-decoration: none; + color: $sid-font-color; + font-size: 1rem; + text-transform: uppercase; + letter-spacing: 1px; + + img { + margin-right: 0.5rem; + + } + } + } } - } - } - &.search{ - // text-align: center; - // width: 50%; - input{ - width: 230px; - border-radius: 2px 0 0 2px; - background-color: $sid-bg-alt; - color: $sid-font-color; - border: none; + &.search { + // text-align: center; + // width: 50%; + input { + width: 230px; + color: $sid-font-color; + border: none; + border-radius: 2px 0 0 2px; + background-color: $sid-bg-alt; - @include transition(all, 0.15s, ease-in-out); + @include transition(all, 0.15s, ease-in-out); - &:hover{ - background-color: $sid-bg-dark; - } - - &:focus{ - width: 350px; + &:hover { + background-color: $sid-bg-dark; + } - background-color: $white; - color: $grey-dark; - } - } - .btn{ - img{display: none;} - border-radius: 0 2px 2px 0; + &:focus { + width: 350px; + color: $grey-dark; - background-color: $main-first; - background-position: center; - background-repeat: no-repeat; - background-image: url(icons/magnifier.svg); - - border-left-width: 0; + background-color: $white; + } + } - width: 3rem; - min-height: 35px; + .btn { + img {display: none;} - &:hover{ - background-color: $main-first-alt; - } - } - } - &.configure{ - width: 2rem; - position: absolute; - right: 1rem; - top: 1.25rem; - text-align: center; - // float: right; - - .btn{ - img{display: none;} - // border-radius: 0 2px 2px 0; - - background-color: transparent; - background-position: center; - background-repeat: no-repeat; - background-image: url(icons/cog.svg); + width: 3rem; + border-radius: 0 2px 2px 0; + + background-color: $main-first; + background-position: center; + background-repeat: no-repeat; + background-image: url(icons/magnifier.svg); - padding: 0 0.5rem; - - // border-left-width: 0; + border-left-width: 0; + min-height: 35px; - // width: 3rem; + &:hover { + background-color: $main-first-alt; + } + } + } - &:hover{ - // background-color: $main-first-alt; + &.configure { + width: 2rem; + position: absolute; + right: 1rem; + top: 1.25rem; + text-align: center; + // float: right; + .btn { + img {display: none;} + + padding: 0 0.5rem; + // border-radius: 0 2px 2px 0; + + background-color: transparent; + background-position: center; + background-repeat: no-repeat; + background-image: url(icons/cog.svg); + + // border-left-width: 0; + + // width: 3rem; + &:hover { + // background-color: $main-first-alt; + } + } } - } } - } - + } /*=== Body */ #global { - height: calc(100% - 85px); + height: calc(100% - 85px); } @@ -121,342 +120,352 @@ /*=== Prompt (centered) */ .prompt { - text-align: center; + text-align: center; } + .prompt label { - text-align: left; + text-align: left; } + .prompt form { - margin: 10px auto 20px auto; - width: 200px; + margin: 10px auto 20px auto; + width: 200px; } + .prompt input { - margin: 5px auto; - width: 100%; + margin: 5px auto; + width: 100%; } + .prompt p { - margin: 20px 0; + margin: 20px 0; } /*=== New article notification */ #new-article { - text-align: center; - font-size: 1rem; - background: $main-first; + background: $main-first; + font-size: 1rem; + text-align: center; } + #new-article:hover { - background: $main-first-alt; + background: $main-first-alt; } + #new-article > a { - line-height: 3em; - font-weight: bold; - color: $white; + line-height: 3em; + font-weight: bold; + color: $white; } + #new-article > a:hover { - text-decoration: none; + text-decoration: none; } /*=== Day indication */ .day { - padding: 1rem 0 0 1.25rem; - font-weight: 700; - line-height: 3em; - letter-spacing: 1px; - text-transform: uppercase; - font-size: 0.875rem; - color: $light-font-color; - // border-left: 2px solid #ecf0f1; - - .name{ - padding: 0 1rem 0 1rem; + padding: 1rem 0 0 1.25rem; + color: $light-font-color; font-size: 0.875rem; - // font-weight: 700; - color: $main-font-color; - position: relative; - left: 0; - - // letter-spacing: 1px; + font-weight: 700; + line-height: 3em; + letter-spacing: 1px; text-transform: uppercase; - } + // border-left: 2px solid #ecf0f1; + .name { + padding: 0 1rem 0 1rem; + // font-weight: 700; + color: $main-font-color; + font-size: 0.875rem; + position: relative; + left: 0; + + // letter-spacing: 1px; + text-transform: uppercase; + } } /*=== Index menu */ .nav_menu { - text-align: center; - padding: 5px 0; - - .btn{ - border-left-width: 0; - padding: 0.5rem 1rem; - background-color: $grey-lighter; - background-position: center; - background-repeat: no-repeat; - - &:hover{ - background-color: $grey-light; - } - } - - .stick{ - background: $grey-lighter; - - .btn{ - border-left-width: 0; - padding: 0.5rem 1rem; - background-color: $grey-lighter; - background-position: center; - background-repeat: no-repeat; - @include transition(all, 0.15s, ease-in-out); - - &:hover{ - background-color: $grey-medium-light; - } - - &.active{ - background-color: $main-first; - } - - img.icon{display: none;} // on efface pour afficher nos icones, mouhahaha !! - - - // actions - &#toggle-read{ - background-image: url(icons/read.svg); - } - &#toggle-read.active{ - background-image: url(icons/read-white.svg); - } - - &#toggle-unread{ - background-image: url(icons/unread.svg); - } - &#toggle-unread.active{ - background-image: url(icons/unread-white.svg); - } - - &#toggle-starred{ - background-image: url(icons/starred.svg); - } - &#toggle-starred.active{ - background-image: url(icons/starred-white.svg); - } - - &#toggle-non-starred{ - background-image: url(icons/non-starred.svg); - } - &#toggle-non-starred.active{ - background-image: url(icons/non-starred-white.svg); - } - - // read all - &.read_all{ + text-align: center; + padding: 5px 0; + + .btn { + border-left-width: 0; + padding: 0.5rem 1rem; background-color: $grey-lighter; - // min-height: 0; - color:$main-font-color; - padding: 5px 16px; - @include transition(all, 0.15s, ease-in-out); + background-position: center; + background-repeat: no-repeat; - &:hover{ - background-color: $grey-medium-light; + &:hover { + background-color: $grey-light; } - } - - // views - &.view-normal{ - background-image: url(icons/view-list.svg); - } - &.view-normal.active{ - background-image: url(icons/view-list-white.svg); - } - - &.view-global{ - background-image: url(icons/view-global.svg); - } - &.view-global.active{ - background-image: url(icons/view-global-white.svg); - } - - &.view-reader{ - background-image: url(icons/view-reader.svg); - } - &.view-reader.active{ - background-image: url(icons/view-reader-white.svg); - } - - &.view-rss{ - background-image: url(icons/rss.svg); - } - - } - .dropdown { - a.dropdown-toggle{ - border-left-width: 0; - background-image: url(icons/more.svg); - } - } - - &#nav_menu_action{ - } - &#nav_menu_read_all{ - - } - &#nav_menu_views{ - + + .stick { + background: $grey-lighter; + + .btn { + border-left-width: 0; + padding: 0.5rem 1rem; + background-color: $grey-lighter; + background-position: center; + background-repeat: no-repeat; + + @include transition(all, 0.15s, ease-in-out); + + &:hover { + background-color: $grey-medium-light; + } + + &.active { + background-color: $main-first; + } + + img.icon {display: none;} // on efface pour afficher nos icones, mouhahaha !! + + + // actions + &#toggle-read { + background-image: url(icons/read.svg); + } + + &#toggle-read.active { + background-image: url(icons/read-white.svg); + } + + &#toggle-unread { + background-image: url(icons/unread.svg); + } + + &#toggle-unread.active { + background-image: url(icons/unread-white.svg); + } + + &#toggle-starred { + background-image: url(icons/starred.svg); + } + + &#toggle-starred.active { + background-image: url(icons/starred-white.svg); + } + + &#toggle-non-starred { + background-image: url(icons/non-starred.svg); + } + + &#toggle-non-starred.active { + background-image: url(icons/non-starred-white.svg); + } + + // read all + &.read_all { + padding: 5px 16px; + // min-height: 0; + color: $main-font-color; + background-color: $grey-lighter; + + @include transition(all, 0.15s, ease-in-out); + + &:hover { + background-color: $grey-medium-light; + } + } + + // views + &.view-normal { + background-image: url(icons/view-list.svg); + } + + &.view-normal.active { + background-image: url(icons/view-list-white.svg); + } + + &.view-global { + background-image: url(icons/view-global.svg); + } + + &.view-global.active { + background-image: url(icons/view-global-white.svg); + } + + &.view-reader { + background-image: url(icons/view-reader.svg); + } + + &.view-reader.active { + background-image: url(icons/view-reader-white.svg); + } + + &.view-rss { + background-image: url(icons/rss.svg); + } + + } + + .dropdown { + a.dropdown-toggle { + border-left-width: 0; + background-image: url(icons/more.svg); + } + } } - } } #dropdown-query ~ .dropdown-menu .dropdown-header .icon { - vertical-align: middle; - background-color: $grey-medium-dark; - border-radius: 3px; + vertical-align: middle; + background-color: $grey-medium-dark; + border-radius: 3px; } /*=== Content of feed articles */ .content, .content.thin { - padding: 20px 10px; + padding: 20px 10px; - font-size: 1.125rem; - line-height: 1.8rem; + font-size: 1.125rem; + line-height: 1.8rem; - h1.title, h1{ + h1.title, h1 { + a { + color: $main-font-color; + font-family: "spectral", serif; + font-size: 2rem; - a{ - color: $main-font-color; - font-family: "spectral"; - font-size: 2rem; + &:hover { + color: $main-first; + text-decoration: none; + } + } + } - &:hover{ - color: $main-first; - text-decoration: none; - } + .author { + color: $light-font-color; + font-size: 1.125rem; + } + + p, ul { + font-size: 1.125rem; + line-height: 1.8rem; + } + + .content hr { + margin: 30px 10px; + background: $grey-medium-light; + height: 1px; + border: 0; + box-shadow: 0 2px 5px #ccc; + } + + pre { + margin: 10px auto; + padding: 10px 20px; + overflow: auto; + background: $main-first-darker; + color: $white; + font-size: 0.9rem; + border-radius: 3px; + + code { + background: transparent; + color: $white; + border: none; + } } - } - .author{ - font-size: 1.125rem; - color: $light-font-color; - } - p, ul{ - font-size: 1.125rem; - line-height: 1.8rem; - } - hr{ - } - .content hr { - margin: 30px 10px; - height: 1px; - background: $grey-medium-light; - border: 0; - box-shadow: 0 2px 5px #ccc; - } - - pre { - margin: 10px auto; - padding: 10px 20px; - overflow: auto; - background: $main-first-darker; - color: $white; - font-size: 0.9rem; - border-radius: 3px; code { - background: transparent; - color: $white; - border: none; + padding: 2px 5px; + background: $grey-lighter; + color: $grey-light; + border: 1px solid $grey-light; + border-radius: 3px; } - } - code { - padding: 2px 5px; - color: $grey-light; - background: $grey-lighter; - border: 1px solid $grey-light; - border-radius: 3px; - } - blockquote { - display: block; - margin: 0; - padding: 5px 20px; - border-top: 1px solid $grey-medium-light; - border-bottom: 1px solid $grey-medium-light; - background: $grey-lighter; - color: $main-font-color; - - p { - margin: 0; + blockquote { + margin: 0; + padding: 5px 20px; + background: $grey-lighter; + display: block; + color: $main-font-color; + border-top: 1px solid $grey-medium-light; + border-bottom: 1px solid $grey-medium-light; + + p { + margin: 0; + } } - } } /*=== Notification and actualize notification */ .notification { - position: fixed; - top: auto; - bottom: 0; - left: 0; - right: 0; - width: 100%; - height: 3rem; - - padding: 1rem 0; - text-align: center; - // font-weight: bold; - font-size: 1em; - line-height: 3em; - z-index: 10; - vertical-align: middle; - background: $grey-medium-light; - color: $grey-dark; - // border-radius: 3px; - border: none; - - .msg{ - font-size: 1rem; - display: inline-block; - } - - &.good { - background: $success-bg; - color: $white; - } - &.bad { - background: $alert-bg; - color: $white; - } - a.close { - padding: 0 15px; + + padding: 1rem 0; + background: $grey-medium-light; + width: 100%; + height: 3rem; + color: $grey-dark; + // font-weight: bold; + font-size: 1em; + // border-radius: 3px; + border: none; + position: fixed; + top: auto; + bottom: 0; + left: 0; + right: 0; + text-align: center; line-height: 3em; - border-radius: 0 3px 3px 0; - } - - &.good a.close:hover { - background: $success-text; - } - &.bad a.close:hover { - background: $alert-text; - } - - &#actualizeProgress { - line-height: 2em; - - br{ - display: none; + z-index: 10; + vertical-align: middle; + + .msg { + display: inline-block; + font-size: 1rem; + } + + &.good { + background: $success-bg; + color: $white; + } + + &.bad { + background: $alert-bg; + color: $white; + } + + a.close { + padding: 0 15px; + border-radius: 0 3px 3px 0; + line-height: 3em; + } + + &.good a.close:hover { + background: $success-text; + } + + &.bad a.close:hover { + background: $alert-text; + } + + &#actualizeProgress { + line-height: 2em; + + br { + display: none; + } } - } } /*=== Navigation menu (for articles) */ #nav_entries { - margin: 0; - text-align: center; - line-height: 3em; - table-layout: fixed; - background: $sid-bg; + margin: 0; + text-align: center; + line-height: 3em; + table-layout: fixed; + background: $sid-bg; } diff --git a/p/themes/Ansum/_list-view.scss b/p/themes/Ansum/_list-view.scss index 78f100203..8b8398c6f 100644 --- a/p/themes/Ansum/_list-view.scss +++ b/p/themes/Ansum/_list-view.scss @@ -1,92 +1,96 @@ /*=== Feed articles */ .flux { - // border-left: 2px solid #ecf0f1; - background: $white; + // border-left: 2px solid #ecf0f1; + background: $white; - @include transition(all, 0.15s, ease-in-out); - - &:hover{ - background: $grey-lighter; + @include transition(all, 0.15s, ease-in-out); - &:not(.current):hover .item.title { - background: $grey-lighter; + &:hover { + background: $grey-lighter; - - } - } - &.current{ - border-left-color: $main-first; - background: $white; - } - &.not_read{ - background: $unread-bg; //-------------------- - // border-left-color: #FF5300; + &:not(.current):hover .item.title { + background: $grey-lighter; - &:hover{ - background: $unread-bg-light; //-------------------- - } - - &:not(.current):hover .item.title { - background: $unread-bg-light; - + } } - .item.title{ - a{ - color: $unread-font-color; //-------------------- - } - + + &.current { + background: $white; + border-left-color: $main-first; } - .item.website{ - a{ - color: $unread-font-color; //-------------------- - } + + &.not_read { + background: $unread-bg; //-------------------- + // border-left-color: #FF5300; + &:hover { + background: $unread-bg-light; //-------------------- + } + + &:not(.current):hover .item.title { + background: $unread-bg-light; + + + } + + .item.title { + a { + color: $unread-font-color; //-------------------- + } + + } + + .item.website { + a { + color: $unread-font-color; //-------------------- + } + } + + .item.date { + color: unquote($unread-font-color+"99"); //-------------------- + } } - .item.date{ - color: unquote($unread-font-color+"99"); //-------------------- + + &.favorite { + background: $fav-light; + border-left-color: $fav-bg; + + @include transition(all, 0.15s, ease-in-out); + + &:not(.current):hover .item.title { + background: $fav-light; + } } -} - - &.favorite { - background: $fav-light; - border-left-color: $fav-bg; - @include transition(all, 0.15s, ease-in-out); + .website { + a { + color: $main-font-color; + opacity: 0.75; + } - &:not(.current):hover .item.title { - background: $fav-light; + .favicon { + padding: 5px; + } } - } - .website{ - a{ - color: $main-font-color; - opacity: 0.75; + .date { + color: $main-font-color; + font-size: 0.85rem; + opacity: 0.75; } - - .favicon { - padding: 5px; + + .bottom { + font-size: 1rem; + text-align: center; } - } - .date { - font-size: 0.85rem; - color: $main-font-color; - opacity: 0.75; - } - - .bottom { - font-size: 1rem; - text-align: center; - } } .flux_header { - font-size: 1rem; - cursor: pointer; - border-top: 1px solid $grey-light; - - .title { font-size: 1rem; - } -} + cursor: pointer; + border-top: 1px solid $grey-light; + .title { + font-size: 1rem; + } +} diff --git a/p/themes/Ansum/_logs.scss b/p/themes/Ansum/_logs.scss index ae1e24c6c..82eead3c9 100644 --- a/p/themes/Ansum/_logs.scss +++ b/p/themes/Ansum/_logs.scss @@ -4,13 +4,14 @@ overflow: hidden; border: 1px solid $grey-medium-dark; } + .log { margin: 10px 0; padding: 5px 2%; - overflow: auto; - font-size: 0.8rem; background: $grey-lighter; color: $grey-dark; + font-size: 0.8rem; + overflow: auto; } .log > .date { @@ -18,16 +19,20 @@ padding: 5px 10px; border-radius: 20px; } + .log.error > .date { background: $alert-bg; color: #fff; } + .log.warning > .date { background: $warning-bg; } + .log.notice > .date { background: $grey-light; } + .log.debug > .date { background: $main-first-darker; color: $white; diff --git a/p/themes/Ansum/_mixins.scss b/p/themes/Ansum/_mixins.scss index 4c82c438a..84ef1d492 100644 --- a/p/themes/Ansum/_mixins.scss +++ b/p/themes/Ansum/_mixins.scss @@ -1,57 +1,69 @@ +/* stylelint-disable property-no-vendor-prefix */ + /* FUNCTIONS */ //animation + @mixin transition($target, $duration, $ease) { - -webkit-transition: $target $duration $ease; - -moz-transition: $target $duration $ease; - -o-transition: $target $duration $ease; - transition: $target $duration $ease; + -webkit-transition: $target $duration $ease; + -moz-transition: $target $duration $ease; + -o-transition: $target $duration $ease; + transition: $target $duration $ease; } //animation + @mixin animation-delay($delay) { - -webkit-animation-delay: $delay; - /* Safari 4.0 - 8.0 */ - animation-delay: $delay; + -webkit-animation-delay: $delay; + /* Safari 4.0 - 8.0 */ + animation-delay: $delay; } //animation + @mixin animation($animate...) { - $max: length($animate); - $animations: ''; - @for $i from 1 through $max { - $animations: #{$animations + nth($animate, $i)}; - @if $i < $max { - $animations: #{$animations + ", "}; - } - } - -webkit-animation: $animations; - -moz-animation: $animations; - -o-animation: $animations; - animation: $animations; + $max: length($animate); + $animations: ''; + + @for $i from 1 through $max { + $animations: #{$animations + nth($animate, $i)}; + + @if $i < $max { + $animations: #{$animations + ", "}; + } + } + -webkit-animation: $animations; + -moz-animation: $animations; + -o-animation: $animations; + animation: $animations; } //keyframes + @mixin keyframes($animationName) { - @-webkit-keyframes #{$animationName} { - @content; - } - @-moz-keyframes #{$animationName} { - @content; - } - @-o-keyframes #{$animationName} { - @content; - } - @keyframes #{$animationName} { - @content; - } + + @-webkit-keyframes #{$animationName} { + @content; + } + + @-moz-keyframes #{$animationName} { + @content; + } + + @-o-keyframes #{$animationName} { + @content; + } + + @keyframes #{$animationName} { + @content; + } } -@mixin border-radius($radius: 4px){ - -moz-border-radius: $radius; - -webkit-border-radius: $radius; - -ms-border-radius: $radius; - -o-border-radius: $radius; - -khtml-border-radius: $radius; - border-radius: $radius; +@mixin border-radius($radius: 4px) { + -moz-border-radius: $radius; + -webkit-border-radius: $radius; + -ms-border-radius: $radius; + -o-border-radius: $radius; + -khtml-border-radius: $radius; + border-radius: $radius; } diff --git a/p/themes/Ansum/_mobile.scss b/p/themes/Ansum/_mobile.scss index 10b432241..8922ea370 100644 --- a/p/themes/Ansum/_mobile.scss +++ b/p/themes/Ansum/_mobile.scss @@ -1,170 +1,186 @@ /*=== MOBILE */ /*===========*/ -@media(max-width: 840px) { - html, body{ - // font-size: 1rem; - } - ul.nav{ - .item{ - width: 100%; - - img{ - display: none; - } - a{ - display: inline-block; - padding: 1rem 1rem 1rem 2.5rem; - color: $sid-font-color; - width: 100%; - - background: url("../../themes/icons/logout.svg") no-repeat $sid-bg-dark 3% center; + +@media (max-width: 840px) { + html, body { + // font-size: 1rem; + } + + ul.nav { + .item { + width: 100%; + + img { + display: none; + } + + a { + padding: 1rem 1rem 1rem 2.5rem; + + background: url("../../themes/icons/logout.svg") no-repeat $sid-bg-dark 3% center; + display: inline-block; + width: 100%; + color: $sid-font-color; + + @include transition(all, 0.2s, ease-in-out); + + &:hover, + &:active { + background: url("../../themes/icons/logout.svg") no-repeat $alert-bg 3% center; + text-decoration: none; + color: $white; + } + } + + } + + } + + .aside { @include transition(all, 0.2s, ease-in-out); - &:hover, - &:active{ - background: url("../../themes/icons/logout.svg") no-repeat $alert-bg 3% center; - text-decoration: none; - color: $white; + &.aside_feed { + padding: 0; } - } + .tree .tree-folder .tree-folder-items .item a { + padding: 0.5rem 1rem; + } } - - } - .aside { - @include transition(all, 0.2s, ease-in-out); - - &.aside_feed { - padding: 0; + + .aside .toggle_aside, + #panel .close { + background: $main-first-alt; + display: block; + width: 100%; + height: 50px; + line-height: 50px; + text-align: center; } - .tree .tree-folder .tree-folder-items .item a{ - padding: 0.5rem 1rem; + .header { + padding: 0.5rem; + + .item { + &.title { + display: none; + } + + &.search { + input { + width: 90%; + height: 3.5rem; + + &:focus { + width: 100%; + + } + } + + .btn { + min-height: 49px; + padding: 0.5rem 2rem; + } + } + + &.configure { + width: 2.75rem; + top: 3.125rem; + + .dropdown { + .btn { + padding: 1.125rem; + } + } + } + } } - } - .aside .toggle_aside, - #panel .close { - display: block; - width: 100%; - height: 50px; - line-height: 50px; - text-align: center; - background: $main-first-alt; - } - - .header{ - padding: 0.5rem; - .item{ - &.title{ - display: none; - } - - &.search{ - input{ - width: 90%; - height: 3.5rem; - - &:focus{ - width: 100%; - } + .nav_menu { + .btn { + margin: 0; + padding: 0.85rem 1.25rem; } - .btn{ - min-height: 49px; - padding: 0.5rem 2rem; + + .stick { + margin: 0.5rem 0.5rem; + + .btn { + margin: 0; + padding: 0.85rem 1.25rem; + + &.read_all { + padding: 0.85rem 1.25rem; + } + } } - } - &.configure{ - width: 2.75rem; - top: 3.125rem; - .dropdown{ - .btn{ - padding: 1.125rem; - } + + .search { + display: none; + max-width: 97%; + + .input { + + max-width: 97%; + width: 90px; + + &:focus { + width: 400px; + } + } } - } - } - } - - .nav_menu{ - .btn { - margin: 0; - padding: 0.85rem 1.25rem; } - .stick { - margin: 0.5rem 0.5rem; - .btn{ - margin: 0; - padding: 0.85rem 1.25rem; - - &.read_all{ - padding: 0.85rem 1.25rem; + #stream { + .flux { + .flux_header { + padding: 0.5rem 0; + } } - } } - .search { - display: none; - max-width: 97%; - .input{ - max-width: 97%; - width: 90px; - &::focus{ - width: 400px; + .day { + text-align: center; + padding: 1rem 0; + + .name { + padding: 0; + // font-size: 1.1rem; + display: block; + width: 100%; + line-height: 1.5rem; + margin-bottom: 1rem; } - } + } - } - #stream{ - .flux{ - .flux_header{ - padding: 0.5rem 0; - } + + .pagination { + margin: 0 0 3.5em; } - } - - - - .day{ - text-align: center; - padding: 1rem 0; - .name { - // font-size: 1.1rem; - display: block; - padding: 0; - width: 100%; - line-height: 1.5rem; - margin-bottom: 1rem; + + #nav_entries { + line-height: 4.5rem; } - } + .notification { + border-radius: 0; - .pagination { - margin: 0 0 3.5em; - } + a.close { + background: transparent; + display: block; + left: 0; + } - #nav_entries{ - line-height: 4.5rem; - } + a.close:hover { + opacity: 0.5; + } - .notification { - border-radius: 0; + a.close .icon { + display: none; - a.close { - display: block; - left: 0; - background: transparent; - } - a.close:hover { - opacity: 0.5; - } - a.close .icon { - display: none; - + } } - } } diff --git a/p/themes/Ansum/_reader-view.scss b/p/themes/Ansum/_reader-view.scss index 4368908d6..e75e43d00 100644 --- a/p/themes/Ansum/_reader-view.scss +++ b/p/themes/Ansum/_reader-view.scss @@ -6,8 +6,9 @@ color: $main-font-color; border: none; } + #stream.reader .flux .author { margin: 0 0 10px; - font-size: 90%; color: $grey-medium-dark; + font-size: 90%; } diff --git a/p/themes/Ansum/_sidebar.scss b/p/themes/Ansum/_sidebar.scss index 87d5bd1a9..86f3e346d 100644 --- a/p/themes/Ansum/_sidebar.scss +++ b/p/themes/Ansum/_sidebar.scss @@ -1,142 +1,141 @@ /*=== Tree */ .tree { - margin: 10px 0; - - &#sidebar{ - scrollbar-color: rgba(255,255, 0, 0.1) rgba(0, 0, 0, 0.05); - scrollbar-color: unquote($sid-font-color+"33") unquote($sid-font-color+"22"); - - - } - - - .tree-folder{ - border-bottom: 1px solid $sid-sep; - - -moz-box-shadow: inset -1px -11px 8px #00000033; - -webkit-box-shadow: inset -1px -11px 8px #00000033; - box-shadow: inset -1px -11px 8px #00000033; - - .tree-folder-title { - position: relative; - background: $sid-bg; - font-size: 0.85rem; - letter-spacing: 1px; - padding: 12px 16px; - font-weight: 700; - text-transform: uppercase; - - .title { - background: inherit; - color: $sid-font-color; - &:hover{ - text-decoration: none; - } - } - } - &.active { - .tree-folder-title { - background: $sid-bg; - font-weight: bold; - } + margin: 10px 0; + + &#sidebar { + scrollbar-color: rgba(255,255, 0, 0.1) rgba(0, 0, 0, 0.05); + scrollbar-color: unquote($sid-font-color+"33") unquote($sid-font-color+"22"); } - .tree-folder-items { - background: $sid-bg-alt; - .item{ - padding: 0 1rem; - line-height: 2.5rem; - font-size: 1rem; - font-weight: 400; - @include transition(all, 0.15s, ease-in-out); + .tree-folder { + border-bottom: 1px solid $sid-sep; + box-shadow: inset -1px -11px 8px #0003; + + .tree-folder-title { + padding: 12px 16px; + background: $sid-bg; + position: relative; + font-size: 0.85rem; + letter-spacing: 1px; + font-weight: 700; + text-transform: uppercase; + + .title { + background: inherit; + color: $sid-font-color; + + &:hover { + text-decoration: none; + } + } + } - &.active{ - background: $sid-active; + &.active { + .tree-folder-title { + background: $sid-bg; + font-weight: bold; + } + } - .dropdown li a{ - color: $main-font-color; + .tree-folder-items { + background: $sid-bg-alt; - &:hover{ - color: $sid-font-color; - } - } + .item { + padding: 0 1rem; + line-height: 2.5rem; + font-size: 1rem; + font-weight: 400; - a{ - color: $sid-active-font; - } - } + @include transition(all, 0.15s, ease-in-out); - &:hover{ - background: $sid-bg-dark; - } + &.active { + background: $sid-active; + + .dropdown li a { + color: $main-font-color; + + &:hover { + color: $sid-font-color; + } + } + + a { + color: $sid-active-font; + } + } + + &:hover { + background: $sid-bg-dark; + } - a{ - text-decoration: none; - color: $sid-font-color; + a { + text-decoration: none; + color: $sid-font-color; + } + } + + .feed .item-title:not([data-unread="0"])::before { + margin: 11px 6px 0 4px; + padding: 3px 4px; + background: $sid-pills; + display: block; + float: left; + font-size: 0.75rem; + border-radius: 12px; + content: attr(data-unread); + text-align: center; + line-height: 0.75rem; + } } - } - - .feed .item-title:not([data-unread="0"])::before { - content: attr(data-unread); - background: $sid-pills; - font-size: 0.75rem; - display: block; - float: left; - padding: 3px 4px; - text-align:center; - border-radius: 12px; - margin: 11px 6px 0 4px; - line-height: 0.75rem; - } - .feed .item-title:not([data-unread="0"]) { - - } } - } } /*=== Buttons */ .stick { - vertical-align: middle; - font-size: 0; - - input, .btn { - border-radius: 0; - } - .btn:first-child, - input:first-child { - border-radius: 5px 0 0 5px; - } - .btn:last-child, input:last-child, .btn + .dropdown > .btn { - border-radius: 0 5px 5px 0; - } - .btn + .btn, - .btn + input, - .btn + .dropdown > .btn, - input + .btn, - input + input, - input + .dropdown > .btn, - .dropdown + .btn, - .dropdown + input, - .dropdown + .dropdown > .btn { - border-left: 1px solid $grey-medium-light; - } + vertical-align: middle; + font-size: 0; + + input, .btn { + border-radius: 0; + } + + .btn:first-child, + input:first-child { + border-radius: 5px 0 0 5px; + } + + .btn:last-child, input:last-child, .btn + .dropdown > .btn { + border-radius: 0 5px 5px 0; + } + + .btn + .btn, + .btn + input, + .btn + .dropdown > .btn, + input + .btn, + input + input, + input + .dropdown > .btn, + .dropdown + .btn, + .dropdown + input, + .dropdown + .dropdown > .btn { + border-left: 1px solid $grey-medium-light; + } } .aside { - background: $sid-bg; - - - &.aside_feed { - padding: 10px 0; - text-align: center; background: $sid-bg; - border-right: 1px solid $sid-sep; - } - &.aside_feed .tree { - margin: 10px 0 50px; - } + + + &.aside_feed { + padding: 10px 0; + text-align: center; + background: $sid-bg; + border-right: 1px solid $sid-sep; + } + + &.aside_feed .tree { + margin: 10px 0 50px; + } } @@ -146,154 +145,171 @@ /*=== Navigation */ - -.nav-list{ - .nav-header, - .item{ - height: 2.5em; - line-height: 2.5em; - font-size: 1rem; - } - .item{ - background: $sid-bg; - @include transition(all, 0.15s, ease-in-out); - a{ - padding: 0 1rem; - color: $sid-font-color; - } - .error{ - a{ - color: $alert-bg; - } +.nav-list { + .nav-header, + .item { + height: 2.5em; + line-height: 2.5em; + font-size: 1rem; } - &:hover{ - background: $sid-bg-dark; - color: $sid-font-color; - - .error{ - a{ - color: $sid-font-color; - background: $main-first; + + .item { + background: $sid-bg; + + @include transition(all, 0.15s, ease-in-out); + + a { + padding: 0 1rem; + color: $sid-font-color; } - } - .empty{ - a{ - color: $sid-font-color; - background: $warning-bg; + + .error { + a { + color: $alert-bg; + } } - } - - a{ - color: $sid-font-color; - text-decoration: none; - } - } - &.active{ - background: $main-first; - color: $white; - - .error{ - a{ - color: $white; - background: $main-first; + + &:hover { + background: $sid-bg-dark; + color: $sid-font-color; + + .error { + a { + background: $main-first; + color: $sid-font-color; + } + } + + .empty { + a { + background: $warning-bg; + color: $sid-font-color; + } + } + + a { + color: $sid-font-color; + text-decoration: none; + } } - } - .empty{ - a{ - color: $white; - background: $warning-bg; + &.active { + background: $main-first; + color: $white; + + .error { + a { + background: $main-first; + color: $white; + } + } + + .empty { + a { + background: $warning-bg; + color: $white; + } + } + + a { + color: $white; + text-decoration: none; + } } - } - - a{ - color: $white; - text-decoration: none; - } + } - - } - &.empty{ - a{ - color: $warning-bg; + + &.empty { + a { + color: $warning-bg; + } } - } - .disable{ - text-align: center; - background: $grey-lighter; - color: $grey-medium-dark; - } - .nav-header { - padding: 0 10px; - font-weight: bold; - color: $grey-dark; - text-transform: uppercase; - letter-spacing: 1px; - margin-top: 1rem; - } - - .nav-form { - padding: 3px; - text-align: center; - } - - .nav-head { - margin: 0; - text-align: right; - // background: #34495e; - color: $white; - a { - color: $white; - } - .item { - padding: 5px 10px; - font-size: 0.9rem; - line-height: 1.5rem; - } - } + .disable { + text-align: center; + background: $grey-lighter; + color: $grey-medium-dark; + } + + .nav-header { + padding: 0 10px; + font-weight: bold; + color: $grey-dark; + text-transform: uppercase; + letter-spacing: 1px; + margin-top: 1rem; + } + + .nav-form { + padding: 3px; + text-align: center; + } + + .nav-head { + margin: 0; + text-align: right; + // background: #34495e; + color: $white; + + a { + color: $white; + } + + .item { + padding: 5px 10px; + font-size: 0.9rem; + line-height: 1.5rem; + } + } } /*=== Aside main page (categories) */ .aside_feed .tree-folder-title > .title:not([data-unread="0"])::after { - position: absolute; - right: 0; - line-height: 1.5rem; - background: $sid-pills; - border-radius: 12px; - padding: 0 0.75rem; - margin: -0.5rem 1rem 0 0; - text-align: center; + margin: -0.5rem 1rem 0 0; + padding: 0 0.75rem; + background: $sid-pills; + border-radius: 12px; + position: absolute; + right: 0; + line-height: 1.5rem; + text-align: center; } .feed.item.empty.active { - background: $grey-dark; + background: $grey-dark; } + .feed.item.error.active { - background: $grey-dark; + background: $grey-dark; } + .feed.item.empty, .feed.item.empty > a { - color: $grey-dark; + color: $grey-dark; } + .feed.item.error, .feed.item.error > a { - color: $grey-dark; + color: $grey-dark; } + .feed.item.empty.active, .feed.item.error.active, .feed.item.empty.active > a, .feed.item.error.active > a { - color: $white; + color: $white; } + .aside_feed .tree-folder-items .dropdown-menu::after { - left: 2px; + left: 2px; } + .aside_feed .tree-folder-items .item .dropdown-target:target ~ .dropdown-toggle > .icon, .aside_feed .tree-folder-items .item:hover .dropdown-toggle > .icon, .aside_feed .tree-folder-items .item.active .dropdown-toggle > .icon { - border-radius: 3px; + border-radius: 3px; } -.aside_feed .stick #btn-importExport{ - border-left-color: $sid-bg; + +.aside_feed .stick #btn-importExport { + border-left-color: $sid-bg; } diff --git a/p/themes/Ansum/_stats.scss b/p/themes/Ansum/_stats.scss index f287efa08..592419166 100644 --- a/p/themes/Ansum/_stats.scss +++ b/p/themes/Ansum/_stats.scss @@ -9,6 +9,7 @@ .stat tr { border: none; } + .stat > table td, .stat > table th { border-bottom: 1px solid $grey-medium-light; @@ -17,11 +18,13 @@ .stat > .horizontal-list { margin: 0 0 5px; } + .stat > .horizontal-list .item { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } + .stat > .horizontal-list .item:first-child { width: 270px; } diff --git a/p/themes/Ansum/_tables.scss b/p/themes/Ansum/_tables.scss index 608e19aca..2d0311608 100644 --- a/p/themes/Ansum/_tables.scss +++ b/p/themes/Ansum/_tables.scss @@ -7,9 +7,11 @@ tr, th, td { padding: 0.5em; border: 1px solid $grey-medium-light; } + th { background: $grey-lighter; } + form td, form th { font-weight: normal; diff --git a/p/themes/Ansum/_variables.scss b/p/themes/Ansum/_variables.scss index 9fc4238ab..60472019d 100644 --- a/p/themes/Ansum/_variables.scss +++ b/p/themes/Ansum/_variables.scss @@ -15,11 +15,11 @@ $white: #fff; // le blanc (des fois qu'on aurait envie de le teinter un peu) // on essaiera de teinter ces gris suivant la couleur principale $grey-darker: #524236; $grey-dark: #766556; -$grey-medium-dark: #bbaa99; +$grey-medium-dark: #ba9; $grey-medium: #d9ccbf; -$grey-medium-light: #e4d8cc; -$grey-light: #f5f0ec; -$grey-lighter: #fcfaf8; +$grey-medium-light: #e4d8cc; +$grey-light: #f5f0ec; +$grey-lighter: #fcfaf8; $unread-font-color: #161a38; $unread-bg: #f2f6f8; @@ -39,8 +39,8 @@ $success-light: #cffdef; $success-text: #0c7556; // les favoris -$fav-bg: #FFC300; -$fav-light: #FFF6DA; +$fav-bg: #ffc300; +$fav-light: #fff6da; // la couleur de sidebar, utile si on a envie d'un thème qui aurait une sidebar foncé, e.g. $sid-font-color: #363330; // la couleur de fond de la barre de gauche et du header @@ -49,5 +49,5 @@ $sid-bg-alt: #f7f2ea; // le background de l'intérieur des groupes $sid-bg-dark: #efe3d3; // les hovers $sid-sep: #f0e7da; // les séparateurs $sid-active: $main-first; // la couleur active -$sid-active-font: #FFFFFF; // la couleur active +$sid-active-font: #fff; // la couleur active $sid-pills: rgba(35,35,0, 0.15); // les gélules diff --git a/p/themes/Ansum/ansum.css b/p/themes/Ansum/ansum.css index 026ce875b..525e7e079 100644 --- a/p/themes/Ansum/ansum.css +++ b/p/themes/Ansum/ansum.css @@ -46,23 +46,24 @@ font-stretch: normal; font-weight: 700; src: local("Spectral"), url("../fonts/Spectral-BoldItalic.woff") format("woff"); } +/* stylelint-disable property-no-vendor-prefix */ /* FUNCTIONS */ /* btns */ .btn { + margin: 0; + padding: 0.5rem 1.5rem; + background: #fcfaf8; display: inline-block; + color: #766556; + font-size: 1rem; + border: none; + border-radius: 5px; min-height: 38px; min-width: 15px; line-height: 25px; - margin: 0; - padding: 0.5rem 1.5rem; - font-size: 1rem; vertical-align: middle; cursor: pointer; overflow: hidden; - background: #fcfaf8; - border-radius: 5px; - border: none; - color: #766556; -webkit-transition: all 0.15s ease-in-out; -moz-transition: all 0.15s ease-in-out; -o-transition: all 0.15s ease-in-out; @@ -86,10 +87,10 @@ a.btn { /*=== Forms */ legend { - display: inline-block; - width: auto; margin: 2rem 0 1rem 0; padding: 0; + display: inline-block; + width: auto; font-size: 1rem; clear: both; text-transform: uppercase; @@ -107,16 +108,16 @@ textarea { height: 100px; } input, select, textarea, button { - font-family: "lato", "Helvetica", "Arial", sans-serif; - min-height: 25px; padding: 5px 10px; - line-height: 25px; - vertical-align: middle; background: #fff; - border: 1px solid #f5f0ec; - font-size: 1rem; color: #766556; - border-radius: 2px; } + font-family: "lato", "Helvetica", "Arial", sans-serif; + font-size: 1rem; + border: 1px solid #f5f0ec; + border-radius: 2px; + min-height: 25px; + line-height: 25px; + vertical-align: middle; } option { padding: 0 .5em; } @@ -134,11 +135,7 @@ input:disabled, select:disabled { background: #f5f0ec; } input.extend { - transition: width 200ms linear; - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; } + transition: width 200ms linear; } .form-group { padding: 5px; @@ -193,29 +190,24 @@ form th { /*=== Dropdown */ .dropdown-menu { - background: #fcfaf8; margin: 0; - font-size: 1rem; - text-align: left; padding: 0.5rem 0 1rem 0; + background: #fcfaf8; + font-size: 1rem; border: none; border-radius: 3px; - -webkit-box-shadow: 0px 6px 8px 0px rgba(0, 0, 0, 0.35); - -moz-box-shadow: 0px 6px 8px 0px rgba(0, 0, 0, 0.35); - box-shadow: 0px 6px 8px 0px rgba(0, 0, 0, 0.35); } + box-shadow: 0px 6px 8px 0px rgba(0, 0, 0, 0.35); + text-align: left; } .dropdown-menu::after { + background: white; + width: 10px; + height: 10px; content: ""; position: absolute; top: -4px; right: 13px; - width: 10px; - height: 10px; z-index: -10; - transform: rotate(45deg); - -moz-transform: rotate(45deg); - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); - background: white; } + transform: rotate(45deg); } .dropdown-menu .dropdown-header { margin: 1.75rem 0 0.5rem 2rem; font-weight: bold; @@ -230,9 +222,9 @@ form th { transition: all 0.075s ease-in-out; } .dropdown-menu .item a, .dropdown-menu .item span, .dropdown-menu .item .as-link { padding: 0 2rem; - line-height: 2.5em; + color: #363330; font-size: 1rem; - color: #363330; } + line-height: 2.5em; } .dropdown-menu .item:hover { background: #ca7227; color: #fff; } @@ -240,8 +232,8 @@ form th { text-decoration: none; color: #fff; } .dropdown-menu .item[aria-checked="true"] a::before { - font-weight: bold; - margin: 0 0 0 -14px; } + margin: 0 0 0 -14px; + font-weight: bold; } .dropdown-menu .input select, .dropdown-menu .input input { margin: 0 auto 5px; padding: 2px 5px; @@ -250,20 +242,14 @@ form th { margin: 0.75rem 0; border-bottom: 1px solid #f5f0ec; } -.tree .tree-folder .tree-folder-items .dropdown-menu .item, .tree .tree-folder .tree-folder-items .dropdown-menu .item { padding: 0; } .tree .tree-folder .tree-folder-items .dropdown-menu .item a, - .tree .tree-folder .tree-folder-items .dropdown-menu .item button, - .tree .tree-folder .tree-folder-items .dropdown-menu .item a, .tree .tree-folder .tree-folder-items .dropdown-menu .item button { color: #363330; } .tree .tree-folder .tree-folder-items .dropdown-menu .item a:hover, - .tree .tree-folder .tree-folder-items .dropdown-menu .item button:hover, - .tree .tree-folder .tree-folder-items .dropdown-menu .item a:hover, .tree .tree-folder .tree-folder-items .dropdown-menu .item button:hover { color: #fff; } - .tree .tree-folder .tree-folder-items .dropdown-menu .item:hover, .tree .tree-folder .tree-folder-items .dropdown-menu .item:hover { background: #ca7227; } @@ -271,11 +257,11 @@ form th { .alert { margin: 1rem 0; padding: 1rem; - font-size: 1rem; background: #fcfaf8; + color: #766556; + font-size: 1rem; border: 1px solid #d9ccbf; border-radius: 3px; - color: #766556; text-shadow: 0 0 1px #f5f0ec; } .alert-head { @@ -287,43 +273,43 @@ form th { .alert-warn { background: #fdfde0; - border: 1px solid #73762f33; - color: #73762f; } + color: #73762f; + border: 1px solid #73762f33; } .alert-success { background: #cffdef; - border: 1px solid #0c755633; - color: #0c7556; } + color: #0c7556; + border: 1px solid #0c755633; } .alert-error { background: #fde0d8; - border: 1px solid #73341f33; - color: #73341f; } + color: #73341f; + border: 1px solid #73341f33; } /*=== Pagination */ .pagination { - text-align: center; - font-size: 0.8em; background: #f5f0ec; - color: #363330; } + color: #363330; + font-size: 0.8em; + text-align: center; } .pagination .item.pager-current { - font-weight: bold; - font-size: 1.5em; background: #fbf9f6; - color: #f5f0ec; } + color: #f5f0ec; + font-size: 1.5em; + font-weight: bold; } .pagination .item a { display: block; + color: #363330; font-style: italic; line-height: 3em; - text-decoration: none; - color: #363330; } + text-decoration: none; } .pagination .item a:hover { background: #363330; color: #f5f0ec; } .pagination .loading, .pagination a:hover.loading { - font-size: 0; - background: url("loader.gif") center center no-repeat #34495e; } + background: url("loader.gif") center center no-repeat #34495e; + font-size: 0; } .content .pagination { margin: 0; @@ -331,11 +317,9 @@ form th { /*=== Boxes */ .box { + background: #fff; border: none; border-radius: 3px; - background: #fff; - -webkit-box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.25); - -moz-box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.25); box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.25); } .box .box-title { margin: 0; @@ -346,18 +330,18 @@ form th { .box .box-title img { margin-right: 0.75rem; } .box .box-title:hover .configure { - visibility: visible; background: url("icons/cog.svg") no-repeat 4px 4px; + display: block; + float: left; width: 1.75rem; height: 1.75rem; - display: block; border-radius: 2px; - float: left; + visibility: visible; margin-right: 0.5rem; } .box .box-title:hover .configure .icon { - vertical-align: middle; + display: none; border-radius: 3px; - display: none; } + vertical-align: middle; } .box .box-title:hover .configure:hover { background: url("icons/cog-white.svg") no-repeat 4px 4px #ca7227; } .box .box-title .configure { @@ -368,33 +352,33 @@ form th { float: right; } .box .box-title form .dropdown a.dropdown-toggle { padding: 0; + border-radius: 0; background-image: url(icons/more.svg); background-repeat: no-repeat; - background-position: right 8px; - border-radius: 0; } + background-position: right 8px; } .box .box-title form .dropdown a.dropdown-toggle img { display: none; } .box .box-content .item { padding: 0.5rem 0.75rem; - font-size: 1rem; color: #363330; - line-height: 1.7em; - border-bottom: 1px solid #f5f0ec; } + font-size: 1rem; + border-bottom: 1px solid #f5f0ec; + line-height: 1.7em; } .box .box-content .item img { margin-right: 0.75rem; } .box .box-content .item .configure { - visibility: hidden; + background: url("icons/cog.svg") no-repeat 4px 4px; + display: block; + float: left; width: 1.75rem; height: 1.75rem; - display: block; border-radius: 2px; - float: left; - margin-right: 0.5rem; - background: url("icons/cog.svg") no-repeat 4px 4px; } + visibility: hidden; + margin-right: 0.5rem; } .box .box-content .item .configure .icon { - vertical-align: middle; + display: none; border-radius: 3px; - display: none; } + vertical-align: middle; } .box .box-content .item .configure:hover { background: url("icons/cog-white.svg") no-repeat 4px 4px #ca7227; } .box .box-content .item:hover .configure { @@ -419,11 +403,11 @@ form th { background: url(icons/tick-white.svg) center no-repeat; } #bigMarkAsRead .bigTick { margin: 0.5rem 0; - display: inline-block; - text-indent: -9999px; background: url(icons/tick-color.svg) center no-repeat; - height: 64px; + display: inline-block; width: 64px; + height: 64px; + text-indent: -9999px; white-space: nowrap; } .formLogin { @@ -439,8 +423,8 @@ form th { .formLogin form#crypto-form div { margin-bottom: 1rem; } .formLogin form#crypto-form div label { - font-size: 1rem; - color: #d9ccbf; } + color: #d9ccbf; + font-size: 1rem; } .formLogin form#crypto-form div input { background: #221f1d; } .formLogin form#crypto-form div input:focus { @@ -467,15 +451,13 @@ form th { scrollbar-color: #36333033 #36333022; } .tree .tree-folder { border-bottom: 1px solid #f0e7da; - -moz-box-shadow: inset -1px -11px 8px #00000033; - -webkit-box-shadow: inset -1px -11px 8px #00000033; - box-shadow: inset -1px -11px 8px #00000033; } + box-shadow: inset -1px -11px 8px #0003; } .tree .tree-folder .tree-folder-title { - position: relative; + padding: 12px 16px; background: #fbf9f6; + position: relative; font-size: 0.85rem; letter-spacing: 1px; - padding: 12px 16px; font-weight: 700; text-transform: uppercase; } .tree .tree-folder .tree-folder-title .title { @@ -504,22 +486,22 @@ form th { .tree .tree-folder .tree-folder-items .item.active .dropdown li a:hover { color: #363330; } .tree .tree-folder .tree-folder-items .item.active a { - color: #FFFFFF; } + color: #fff; } .tree .tree-folder .tree-folder-items .item:hover { background: #efe3d3; } .tree .tree-folder .tree-folder-items .item a { text-decoration: none; color: #363330; } .tree .tree-folder .tree-folder-items .feed .item-title:not([data-unread="0"])::before { - content: attr(data-unread); + margin: 11px 6px 0 4px; + padding: 3px 4px; background: rgba(35, 35, 0, 0.15); - font-size: 0.75rem; display: block; float: left; - padding: 3px 4px; - text-align: center; + font-size: 0.75rem; border-radius: 12px; - margin: 11px 6px 0 4px; + content: attr(data-unread); + text-align: center; line-height: 0.75rem; } /*=== Buttons */ @@ -576,11 +558,11 @@ form th { background: #efe3d3; color: #363330; } .nav-list .item:hover .error a { - color: #363330; - background: #ca7227; } + background: #ca7227; + color: #363330; } .nav-list .item:hover .empty a { - color: #363330; - background: #f4f762; } + background: #f4f762; + color: #363330; } .nav-list .item:hover a { color: #363330; text-decoration: none; } @@ -588,11 +570,11 @@ form th { background: #ca7227; color: #fff; } .nav-list .item.active .error a { - color: #fff; - background: #ca7227; } + background: #ca7227; + color: #fff; } .nav-list .item.active .empty a { - color: #fff; - background: #f4f762; } + background: #f4f762; + color: #fff; } .nav-list .item.active a { color: #fff; text-decoration: none; } @@ -601,7 +583,7 @@ form th { .nav-list .disable { text-align: center; background: #fcfaf8; - color: #bbaa99; } + color: #ba9; } .nav-list .nav-header { padding: 0 10px; font-weight: bold; @@ -625,13 +607,13 @@ form th { /*=== Aside main page (categories) */ .aside_feed .tree-folder-title > .title:not([data-unread="0"])::after { + margin: -0.5rem 1rem 0 0; + padding: 0 0.75rem; + background: rgba(35, 35, 0, 0.15); + border-radius: 12px; position: absolute; right: 0; line-height: 1.5rem; - background: rgba(35, 35, 0, 0.15); - border-radius: 12px; - padding: 0 0.75rem; - margin: -0.5rem 1rem 0 0; text-align: center; } .feed.item.empty.active { @@ -669,16 +651,16 @@ form th { /*===============*/ /*=== Header */ .header { - background: #fbf9f6; padding: 0.5rem 1.35rem; + background: #fbf9f6; display: block; - table-layout: none; - width: auto; } + width: auto; + table-layout: none; } .header .item { vertical-align: middle; } .header .item.title { - font-weight: 400; - width: 280px; } + width: 280px; + font-weight: 400; } .header .item.title h1 a { text-decoration: none; color: #363330; @@ -689,10 +671,10 @@ form th { margin-right: 0.5rem; } .header .item.search input { width: 230px; - border-radius: 2px 0 0 2px; - background-color: #f7f2ea; color: #363330; border: none; + border-radius: 2px 0 0 2px; + background-color: #f7f2ea; -webkit-transition: all 0.15s ease-in-out; -moz-transition: all 0.15s ease-in-out; -o-transition: all 0.15s ease-in-out; @@ -701,16 +683,16 @@ form th { background-color: #efe3d3; } .header .item.search input:focus { width: 350px; - background-color: #fff; - color: #766556; } + color: #766556; + background-color: #fff; } .header .item.search .btn { + width: 3rem; border-radius: 0 2px 2px 0; background-color: #ca7227; background-position: center; background-repeat: no-repeat; background-image: url(icons/magnifier.svg); border-left-width: 0; - width: 3rem; min-height: 35px; } .header .item.search .btn img { display: none; } @@ -723,11 +705,11 @@ form th { top: 1.25rem; text-align: center; } .header .item.configure .btn { + padding: 0 0.5rem; background-color: transparent; background-position: center; background-repeat: no-repeat; - background-image: url(icons/cog.svg); - padding: 0 0.5rem; } + background-image: url(icons/cog.svg); } .header .item.configure .btn img { display: none; } @@ -755,9 +737,9 @@ form th { /*=== New article notification */ #new-article { - text-align: center; + background: #ca7227; font-size: 1rem; - background: #ca7227; } + text-align: center; } #new-article:hover { background: #b7641d; } @@ -773,16 +755,16 @@ form th { /*=== Day indication */ .day { padding: 1rem 0 0 1.25rem; + color: #6d655f; + font-size: 0.875rem; font-weight: 700; line-height: 3em; letter-spacing: 1px; - text-transform: uppercase; - font-size: 0.875rem; - color: #6d655f; } + text-transform: uppercase; } .day .name { padding: 0 1rem 0 1rem; - font-size: 0.875rem; color: #363330; + font-size: 0.875rem; position: relative; left: 0; text-transform: uppercase; } @@ -834,9 +816,9 @@ form th { .nav_menu .stick .btn#toggle-non-starred.active { background-image: url(icons/non-starred-white.svg); } .nav_menu .stick .btn.read_all { - background-color: #fcfaf8; - color: #363330; padding: 5px 16px; + color: #363330; + background-color: #fcfaf8; -webkit-transition: all 0.15s ease-in-out; -moz-transition: all 0.15s ease-in-out; -o-transition: all 0.15s ease-in-out; @@ -863,7 +845,7 @@ form th { #dropdown-query ~ .dropdown-menu .dropdown-header .icon { vertical-align: middle; - background-color: #bbaa99; + background-color: #ba9; border-radius: 3px; } /*=== Content of feed articles */ @@ -873,21 +855,21 @@ form th { line-height: 1.8rem; } .content h1.title a, .content h1 a, .content.thin h1.title a, .content.thin h1 a { color: #363330; - font-family: "spectral"; + font-family: "spectral", serif; font-size: 2rem; } .content h1.title a:hover, .content h1 a:hover, .content.thin h1.title a:hover, .content.thin h1 a:hover { color: #ca7227; text-decoration: none; } .content .author, .content.thin .author { - font-size: 1.125rem; - color: #6d655f; } + color: #6d655f; + font-size: 1.125rem; } .content p, .content ul, .content.thin p, .content.thin ul { font-size: 1.125rem; line-height: 1.8rem; } .content .content hr, .content.thin .content hr { margin: 30px 10px; - height: 1px; background: #e4d8cc; + height: 1px; border: 0; box-shadow: 0 2px 5px #ccc; } .content pre, .content.thin pre { @@ -904,42 +886,42 @@ form th { border: none; } .content code, .content.thin code { padding: 2px 5px; - color: #f5f0ec; background: #fcfaf8; + color: #f5f0ec; border: 1px solid #f5f0ec; border-radius: 3px; } .content blockquote, .content.thin blockquote { - display: block; margin: 0; padding: 5px 20px; - border-top: 1px solid #e4d8cc; - border-bottom: 1px solid #e4d8cc; background: #fcfaf8; - color: #363330; } + display: block; + color: #363330; + border-top: 1px solid #e4d8cc; + border-bottom: 1px solid #e4d8cc; } .content blockquote p, .content.thin blockquote p { margin: 0; } /*=== Notification and actualize notification */ .notification { + padding: 1rem 0; + background: #e4d8cc; + width: 100%; + height: 3rem; + color: #766556; + font-size: 1em; + border: none; position: fixed; top: auto; bottom: 0; left: 0; right: 0; - width: 100%; - height: 3rem; - padding: 1rem 0; text-align: center; - font-size: 1em; line-height: 3em; z-index: 10; - vertical-align: middle; - background: #e4d8cc; - color: #766556; - border: none; } + vertical-align: middle; } .notification .msg { - font-size: 1rem; - display: inline-block; } + display: inline-block; + font-size: 1rem; } .notification.good { background: #10f5b2; color: #fff; } @@ -948,8 +930,8 @@ form th { color: #fff; } .notification a.close { padding: 0 15px; - line-height: 3em; - border-radius: 0 3px 3px 0; } + border-radius: 0 3px 3px 0; + line-height: 3em; } .notification.good a.close:hover { background: #0c7556; } .notification.bad a.close:hover { @@ -979,8 +961,8 @@ form th { .flux:hover:not(.current):hover .item.title { background: #fcfaf8; } .flux.current { - border-left-color: #ca7227; - background: #fff; } + background: #fff; + border-left-color: #ca7227; } .flux.not_read { background: #f2f6f8; } .flux.not_read:hover { @@ -994,22 +976,22 @@ form th { .flux.not_read .item.date { color: #161a3899; } .flux.favorite { - background: #FFF6DA; - border-left-color: #FFC300; + background: #fff6da; + border-left-color: #ffc300; -webkit-transition: all 0.15s ease-in-out; -moz-transition: all 0.15s ease-in-out; -o-transition: all 0.15s ease-in-out; transition: all 0.15s ease-in-out; } .flux.favorite:not(.current):hover .item.title { - background: #FFF6DA; } + background: #fff6da; } .flux .website a { color: #363330; opacity: 0.75; } .flux .website .favicon { padding: 5px; } .flux .date { - font-size: 0.85rem; color: #363330; + font-size: 0.85rem; opacity: 0.75; } .flux .bottom { font-size: 1rem; @@ -1027,33 +1009,33 @@ form th { #stream .box.category:not([data-unread="0"]) .box-title .title { font-weight: bold; } #stream .box.category .box-title { - background: none; - padding: 1.5rem; } + padding: 1.5rem; + background: none; } #stream .box.category .box-title a.title { + color: #766556; + font-size: 1rem; font-weight: normal; text-decoration: none; text-align: left; - font-size: 1rem; text-transform: uppercase; - letter-spacing: 1px; - color: #766556; } + letter-spacing: 1px; } #stream .box.category .box-title a.title:not([data-unread="0"])::after { + margin: -0.5rem 1rem 0 0; + padding: 0 0.75rem; + background: #f5f0ec; + border-radius: 12px; position: absolute; top: 1.75rem; right: 0; line-height: 1.5rem; - background: #f5f0ec; - border-radius: 12px; - padding: 0 0.75rem; - margin: -0.5rem 1rem 0 0; text-align: center; } #stream .box.category .box-title a.title:hover { color: #ca7227; } #stream .box.category .box-content { padding-bottom: 0.5rem; } #stream .box.category .box-content .item.feed { - font-size: 1rem; - padding: 0.5rem 1.5rem; } + padding: 0.5rem 1.5rem; + font-size: 1rem; } #stream .box.category .box-content .item.feed a { color: #363330; font-weight: 400; } @@ -1081,8 +1063,8 @@ form th { #stream.reader .flux .author { margin: 0 0 10px; - font-size: 90%; - color: #bbaa99; } + color: #ba9; + font-size: 90%; } /*=== Configuration pages */ .post { @@ -1095,42 +1077,40 @@ form th { .post.content { max-width: 550px; } .post h1, .post h2 { + color: #363330; font-size: 3rem; margin-top: 1.75rem; font-weight: 300; - line-height: 1.2em; - color: #363330; } + line-height: 1.2em; } .post a[href="./"] { - display: inline-block; - min-width: 15px; - line-height: 25px; margin: 0; padding: 0.75rem 1.5rem; - font-size: 1rem; - vertical-align: middle; - cursor: pointer; - overflow: hidden; background: #fcfaf8; + display: inline-block; + color: #766556; + font-size: 1rem; border: 1px solid #e4d8cc; border-radius: 5px; - color: #766556; } + min-width: 15px; + line-height: 25px; + vertical-align: middle; + cursor: pointer; + overflow: hidden; } .post a[href="./"]:hover { - text-decoration: none; background: #ca7227; color: white; - border: 1px solid #ca7227; } + border: 1px solid #ca7227; + text-decoration: none; } #slider { border-left: none; - -webkit-box-shadow: 0px 6px 8px 0px rgba(0, 0, 0, 0.35); - -moz-box-shadow: 0px 6px 8px 0px rgba(0, 0, 0, 0.35); box-shadow: 0px 6px 8px 0px rgba(0, 0, 0, 0.35); } .slide-container .properties { - background: rgba(0, 0, 0, 0.75); - border: 0; padding: 1rem; - color: white; } + background: rgba(0, 0, 0, 0.75); + color: white; + border: 0; } .slide-container .properties .page-number { right: 1rem; top: 1rem; } @@ -1139,15 +1119,15 @@ form th { /*=========*/ .loglist { overflow: hidden; - border: 1px solid #bbaa99; } + border: 1px solid #ba9; } .log { margin: 10px 0; padding: 5px 2%; - overflow: auto; - font-size: 0.8rem; background: #fcfaf8; - color: #766556; } + color: #766556; + font-size: 0.8rem; + overflow: auto; } .log > .date { margin: 0 10px 0 0; @@ -1201,11 +1181,11 @@ form th { ul.nav .item img { display: none; } ul.nav .item a { - display: inline-block; padding: 1rem 1rem 1rem 2.5rem; - color: #363330; - width: 100%; background: url("../../themes/icons/logout.svg") no-repeat #efe3d3 3% center; + display: inline-block; + width: 100%; + color: #363330; -webkit-transition: all 0.2s ease-in-out; -moz-transition: all 0.2s ease-in-out; -o-transition: all 0.2s ease-in-out; @@ -1227,12 +1207,12 @@ form th { .aside .toggle_aside, #panel .close { + background: #b7641d; display: block; width: 100%; height: 50px; line-height: 50px; - text-align: center; - background: #b7641d; } + text-align: center; } .header { padding: 0.5rem; } @@ -1268,7 +1248,7 @@ form th { .nav_menu .search .input { max-width: 97%; width: 90px; } - .nav_menu .search .input::focus { + .nav_menu .search .input:focus { width: 400px; } #stream .flux .flux_header { @@ -1278,8 +1258,8 @@ form th { text-align: center; padding: 1rem 0; } .day .name { - display: block; padding: 0; + display: block; width: 100%; line-height: 1.5rem; margin-bottom: 1rem; } @@ -1293,22 +1273,20 @@ form th { .notification { border-radius: 0; } .notification a.close { + background: transparent; display: block; - left: 0; - background: transparent; } + left: 0; } .notification a.close:hover { opacity: 0.5; } .notification a.close .icon { display: none; } } -html, body { - font-family: "lato", "Helvetica", "Arial", sans-serif; - font-size: 0.875rem; } - /*=== GENERAL */ /*============*/ html, body { + background: #f5f0ec; height: 100%; - background: #f5f0ec; } + font-family: "lato", "Helvetica", "Arial", sans-serif; + font-size: 0.875rem; } /*=== Links */ a, button.as-link { diff --git a/p/themes/Ansum/ansum.css.map b/p/themes/Ansum/ansum.css.map deleted file mode 100644 index 40e796955..000000000 --- a/p/themes/Ansum/ansum.css.map +++ /dev/null @@ -1,7 +0,0 @@ -{ -"version": 3, -"mappings": "AAAA,UAMC;EALG,WAAW,EAAE,MAAM;EACnB,UAAU,EAAE,MAAM;EAClB,YAAY,EAAE,MAAM;EACpB,WAAW,EAAE,GAAG;EAChB,GAAG,EAAE,oEAAoE;AAE7E,UAMC;EALG,WAAW,EAAE,MAAM;EACnB,UAAU,EAAE,MAAM;EAClB,YAAY,EAAE,MAAM;EACpB,WAAW,EAAE,GAAG;EAChB,GAAG,EAAE,mEAAmE;AAE5E,UAMC;EALG,WAAW,EAAE,MAAM;EACnB,UAAU,EAAE,MAAM;EAClB,YAAY,EAAE,MAAM;EACpB,WAAW,EAAE,GAAG;EAChB,GAAG,EAAE,iEAAiE;AAE1E,UAMC;EALG,WAAW,EAAE,MAAM;EACnB,UAAU,EAAE,MAAM;EAClB,YAAY,EAAE,MAAM;EACpB,WAAW,EAAE,GAAG;EAChB,GAAG,EAAE,uEAAuE;AAEhF,UAMC;EALG,WAAW,EAAE,UAAU;EACvB,UAAU,EAAE,MAAM;EAClB,YAAY,EAAE,MAAM;EACpB,WAAW,EAAE,GAAG;EAChB,GAAG,EAAE,uEAAuE;AAEhF,UAMC;EALG,WAAW,EAAE,UAAU;EACvB,UAAU,EAAE,MAAM;EAClB,YAAY,EAAE,MAAM;EACpB,WAAW,EAAE,GAAG;EAChB,GAAG,EAAE,sEAAsE;AAE/E,UAMC;EALG,WAAW,EAAE,UAAU;EACvB,UAAU,EAAE,MAAM;EAClB,YAAY,EAAE,MAAM;EACpB,WAAW,EAAE,GAAG;EAChB,GAAG,EAAE,oEAAoE;AAE7E,UAMC;EALG,WAAW,EAAE,UAAU;EACvB,UAAU,EAAE,MAAM;EAClB,YAAY,EAAE,MAAM;EACpB,WAAW,EAAE,GAAG;EAChB,GAAG,EAAE,0EAA0E;ACtDnF,eAAe;ACAf,UAAU;AAEV,IAAK;EACD,OAAO,EAAE,YAAY;EACrB,UAAU,EAAE,IAAI;EAChB,SAAS,EAAE,IAAI;EACf,WAAW,EAAE,IAAI;EACjB,MAAM,EAAE,CAAC;EACT,OAAO,EAAE,aAAa;EACtB,SAAS,EAAE,IAAI;EACf,cAAc,EAAE,MAAM;EACtB,MAAM,EAAE,OAAO;EACf,QAAQ,EAAE,MAAM;EAChB,UAAU,ECQC,OAAO;EDPlB,aAAa,EAAE,GAAG;EAClB,MAAM,EAAE,IAAI;EACZ,KAAK,ECAG,OAAO;EFZf,kBAAkB,EAAE,qBAAuB;EAC3C,eAAe,EAAE,qBAAuB;EACxC,aAAa,EAAE,qBAAuB;EACtC,UAAU,EAAE,qBAAuB;ECanC,kBAAgB;IACnB,UAAU,EC6BE,OAAW;ID5BvB,KAAK,ECXE,IAAI;IDeX,mDACS;MACL,UAAU,EC1BE,OAAO;ED+BpB,kBAAgB;IACnB,UAAU,ECNA,OAAO;IDOjB,KAAK,EAAE,IAAI;IAEX,mDACS;MACL,UAAU,ECTF,OAAO;EDahB,UAAQ;IACX,eAAe,EAAE,IAAI;;AAItB,KAAM;EACF,UAAU,EAAE,IAAI;EAChB,WAAW,EAAE,IAAI;;AAGrB,cAAc;AACd,MAAO;EACH,OAAO,EAAE,YAAY;EACrB,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,aAAa;EACrB,OAAO,EAAE,CAAC;EACV,SAAS,EAAE,IAAI;EACf,KAAK,EAAE,IAAI;EACX,cAAc,EAAE,SAAS;EACzB,cAAc,EAAE,GAAG;EACnB,WAAW,EAAE,GAAG;;AAEpB,KAAM;EACF,UAAU,EAAE,IAAI;EAChB,OAAO,EAAE,KAAK;EACd,MAAM,EAAE,OAAO;EACf,KAAK,ECrDG,OAAO;;ADuDnB,QAAS;EACL,KAAK,EAAE,KAAK;EACZ,MAAM,EAAE,KAAK;;AAEjB,+BAAgC;EAC5B,WAAW,EAAE,wCAAwC;EACrD,UAAU,EAAE,IAAI;EAChB,OAAO,EAAE,QAAQ;EACjB,WAAW,EAAE,IAAI;EACjB,cAAc,EAAE,MAAM;EACtB,UAAU,ECtEN,IAAI;EDuER,MAAM,EAAE,iBAAqB;EAC7B,SAAS,EAAE,IAAI;EACf,KAAK,ECpEG,OAAO;EDqEf,aAAa,EAAE,GAAG;;AAEtB,MAAO;EACH,OAAO,EAAE,MAAM;;AAEnB,yCAA0C;EACtC,KAAK,EClFS,OAAO;EDmFrB,YAAY,EC1CH,OAAW;;AD4CxB,6BAA8B;EAC1B,KAAK,ECnEE,OAAO;EDoEd,YAAY,ECpEL,OAAO;EDqEd,UAAU,EAAE,IAAI;;AAEpB,+BAAgC;EAC5B,UAAU,EChFD,OAAO;;ADkFpB,YAAa;EACT,UAAU,EAAE,kBAAkB;EAC9B,eAAe,EAAE,kBAAkB;EACnC,kBAAkB,EAAE,kBAAkB;EACtC,aAAa,EAAE,kBAAkB;EACjC,cAAc,EAAE,kBAAkB;;AAItC,WAAY;EACR,OAAO,EAAE,GAAG;EACZ,aAAa,EAAE,GAAG;EAElB,kBAAS;IACZ,OAAO,EAAE,EAAE;IACX,OAAO,EAAE,KAAK;IACd,KAAK,EAAE,IAAI;EAQR,uBAAY;IACf,OAAO,EAAE,MAAM;IACf,UAAU,EAAE,KAAK;EAEd,2BAAgB;IACnB,UAAU,EAAE,IAAI;IAChB,OAAO,EAAE,KAAK;EAEX,oCAAyB;IAC5B,WAAW,EAAE,KAAK;EAEf,iBAAM;IACT,MAAM,EAAE,cAAc;EAGnB,wBAAe;IAClB,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,KAAK;EAGX,6BAAoB;IACvB,MAAM,EAAE,YAAY;;AEnJrB,eAAe;AACf,KAAM;EACL,eAAe,EAAE,QAAQ;;AAG1B,UAAW;EACV,OAAO,EAAE,KAAK;EACd,MAAM,EAAE,iBAA4B;;AAErC,EAAG;EACF,UAAU,EDWI,OAAO;;ACTtB;OACQ;EACP,WAAW,EAAE,MAAM;EACnB,UAAU,EAAE,MAAM;;ACfnB,mBAAmB;AACnB,mBAAmB;AACnB,cAAc;AAGd,wBAAwB;AACxB,gBAAiB;EACb,MAAM,EAAE,CAAC;EACT,OAAO,EAAE,QAAQ;EAEjB,sBAAK;IACR,cAAc,EAAE,MAAM;IAEtB,kCAAa;MACT,YAAY,EAAE,MAAM;;AAMzB,iBAAiB;AACjB,cAAe;EACX,UAAU,EFDC,OAAO;EEElB,MAAM,EAAE,CAAC;EACT,SAAS,EAAE,IAAI;EACf,UAAU,EAAE,IAAI;EAChB,OAAO,EAAE,eAAe;EACxB,MAAM,EAAE,IAAI;EACZ,aAAa,EAAE,GAAG;EAElB,kBAAkB,EAAE,mCAAgC;EACpD,eAAe,EAAE,mCAAgC;EACjD,UAAU,EAAE,mCAAgC;EAE5C,qBAAS;IACZ,OAAO,EAAE,EAAE;IACX,QAAQ,EAAE,QAAQ;IAClB,GAAG,EAAE,IAAI;IACT,KAAK,EAAE,IAAI;IACX,KAAK,EAAE,IAAI;IACX,MAAM,EAAE,IAAI;IACZ,OAAO,EAAE,GAAG;IACZ,SAAS,EAAE,aAAa;IACxB,cAAc,EAAE,aAAa;IAC7B,iBAAiB,EAAE,aAAa;IAChC,aAAa,EAAE,aAAa;IAC5B,UAAU,EAAE,KAAK;EAKd,+BAAiB;IAEpB,MAAM,EAAE,qBAAqB;IAC7B,WAAW,EAAE,IAAI;IACjB,UAAU,EAAE,IAAI;IAChB,KAAK,EFxCM,OAAO;IEyClB,cAAc,EAAE,SAAS;IACzB,cAAc,EAAE,GAAG;EAKhB,oBAAK;IJ3DL,kBAAkB,EAAE,sBAAuB;IAC3C,eAAe,EAAE,sBAAuB;IACxC,aAAa,EAAE,sBAAuB;IACtC,UAAU,EAAE,sBAAuB;II0DtC,gFAAiB;MACb,OAAO,EAAE,MAAM;MACf,WAAW,EAAE,KAAK;MAClB,SAAS,EAAE,IAAI;MACf,KAAK,EF5DQ,OAAO;IE8DxB,0BAAO;MACH,UAAU,EFtBF,OAAW;MEuBnB,KAAK,EF9DF,IAAI;MEgEP,+DAAS;QACZ,eAAe,EAAE,IAAI;QACrB,KAAK,EFlEC,IAAI;IEsEP,mDAAS;MACZ,WAAW,EAAE,IAAI;MACjB,MAAM,EAAE,WAAW;EAKpB,yDAAa;IACT,MAAM,EAAE,UAAU;IAClB,OAAO,EAAE,OAAO;IAChB,aAAa,EAAE,GAAG;EAGnB,yBAAW;IACd,MAAM,EAAE,SAAS;IACjB,aAAa,EAAE,iBAAqB;;AASjC;0DAAK;EACR,OAAO,EAAE,CAAC;EAEV;;;mEACM;IACF,KAAK,EFrGQ,OAAO;IEuGpB;;;2EAAO;MACV,KAAK,EFtGC,IAAI;EEyGX;kEAAO;IACH,UAAU,EFnEF,OAAW;;AE2ExB,eAAe;AACf,MAAO;EACH,MAAM,EAAE,MAAM;EAEd,OAAO,EAAE,IAAI;EACb,SAAS,EAAE,IAAI;EACf,UAAU,EF9GC,OAAO;EE+GlB,MAAM,EAAE,iBAAsB;EAC9B,aAAa,EAAE,GAAG;EAClB,KAAK,EFtHG,OAAO;EEuHf,WAAW,EAAE,eAAmB;;AAEpC,WAAY;EACR,SAAS,EAAE,MAAM;;AAErB,UAAW;EACP,eAAe,EAAE,SAAS;EAC1B,KAAK,EAAE,OAAO;;AAElB,WAAY;EACR,UAAU,EFhHE,OAAO;EEiHnB,MAAM,EAAE,mBAAqC;EAC7C,KAAK,EFjHM,OAAO;;AEmHtB,cAAe;EACX,UAAU,EFjHE,OAAO;EEkHnB,MAAM,EAAE,mBAAqC;EAC7C,KAAK,EFlHM,OAAO;;AEoHtB,YAAa;EACT,UAAU,EF9HA,OAAO;EE+HjB,MAAM,EAAE,mBAAmC;EAC3C,KAAK,EF/HI,OAAO;;AEkIpB,mBAAmB;AACnB,WAAY;EACR,UAAU,EAAE,MAAM;EAClB,SAAS,EAAE,KAAK;EAChB,UAAU,EFhJD,OAAO;EEiJhB,KAAK,EF5JS,OAAO;EE+JxB,+BAAgB;IACZ,WAAW,EAAE,IAAI;IACjB,SAAS,EAAE,KAAK;IAChB,UAAU,EF7HN,OAAO;IE8HX,KAAK,EFxJG,OAAO;EE0JnB,mBAAE;IACE,OAAO,EAAE,KAAK;IACd,UAAU,EAAE,MAAM;IAClB,WAAW,EAAE,GAAG;IAChB,eAAe,EAAE,IAAI;IACrB,KAAK,EF1KQ,OAAO;IE4KpB,yBAAO;MACV,UAAU,EF7KM,OAAO;ME8KvB,KAAK,EFnKM,OAAO;EEwKhB;6BACgB;IACnB,SAAS,EAAE,CAAC;IACZ,UAAU,EAAE,iDAAiD;;AAG9D,oBAAqB;EACjB,MAAM,EAAE,CAAC;EACT,OAAO,EAAE,CAAC;;AAId,cAAc;AACd,IAAK;EAED,MAAM,EAAE,IAAI;EACZ,aAAa,EAAE,GAAG;EAClB,UAAU,EFlMN,IAAI;EEoMR,kBAAkB,EAAE,mCAAgC;EACpD,eAAe,EAAE,mCAAgC;EACjD,UAAU,EAAE,mCAAgC;EAE5C,eAAW;IACd,MAAM,EAAE,CAAC;IACT,OAAO,EAAE,cAAc;IACvB,UAAU,EFlME,OAAO;IEmMnB,KAAK,EF9MY,OAAO;IEgNxB,aAAa,EAAE,WAAW;IAE1B,mBAAG;MACF,YAAY,EAAE,OAAO;IAIrB,gCAAW;MACV,UAAU,EAAE,OAAO;MACnB,UAAU,EAAE,sCAAsC;MAClD,KAAK,EAAE,OAAO;MACd,MAAM,EAAE,OAAO;MACf,OAAO,EAAE,KAAK;MACd,aAAa,EAAE,GAAG;MAClB,KAAK,EAAE,IAAI;MACX,YAAY,EAAE,MAAM;MACpB,sCAAM;QACL,cAAc,EAAE,MAAM;QACtB,aAAa,EAAE,GAAG;QAClB,OAAO,EAAE,IAAI;MAEd,sCAAQ;QACP,UAAU,EAAE,oDAAwD;IAIvE,0BAAW;MACV,UAAU,EAAE,MAAM;IAIf,0BAAK;MACR,KAAK,EAAE,GAAG;IAEP,8BAAS;MACZ,KAAK,EAAE,KAAK;MACZ,gDAAiB;QACb,OAAO,EAAE,CAAC;QACV,gBAAgB,EAAE,mBAAmB;QACrC,iBAAiB,EAAE,SAAS;QAC5B,mBAAmB,EAAE,SAAS;QAE9B,aAAa,EAAE,CAAC;QAChB,oDAAG;UACN,OAAO,EAAE,IAAI;EASf,uBAAM;IACF,OAAO,EAAE,cAAc;IACvB,SAAS,EAAE,IAAI;IACf,KAAK,EFxQQ,OAAO;IEyQpB,WAAW,EAAE,KAAK;IAClB,aAAa,EAAE,iBAAqB;IAEpC,2BAAG;MACN,YAAY,EAAE,OAAO;IAGlB,kCAAW;MACd,UAAU,EAAE,MAAM;MAClB,KAAK,EAAE,OAAO;MACd,MAAM,EAAE,OAAO;MACf,OAAO,EAAE,KAAK;MACd,aAAa,EAAE,GAAG;MAClB,KAAK,EAAE,IAAI;MACX,YAAY,EAAE,MAAM;MACpB,UAAU,EAAE,sCAAsC;MAElD,wCAAM;QACF,cAAc,EAAE,MAAM;QACtB,aAAa,EAAE,GAAG;QAClB,OAAO,EAAE,IAAI;MAEjB,wCAAO;QAEH,UAAU,EAAE,oDAAwD;IAGrE,wCAAmB;MACtB,UAAU,EAAE,OAAO;EAGpB,kCAAgB;IACZ,aAAa,EAAE,IAAI;;AAKxB,yBAAyB;AACzB,cAAe;EACX,UAAU,EAAE,MAAM;EAClB,eAAe,EAAE,IAAI;EACrB,UAAU,EFxTK,OAAO;EEyTtB,KAAK,EF1QI,OAAW;EF9CpB,kBAAkB,EAAE,qBAAuB;EAC3C,eAAe,EAAE,qBAAuB;EACxC,aAAa,EAAE,qBAAuB;EACtC,UAAU,EAAE,qBAAuB;EIyTnC,oBAAQ;IACX,UAAU,EF/QE,OAAW;IEgRvB,KAAK,EAAE,IAAI;IAEX,6BAAQ;MACJ,UAAU,EAAE,0CAA0C;EAGvD,uBAAQ;IACX,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,YAAY;IACrB,WAAW,EAAE,OAAO;IACpB,UAAU,EAAE,0CAA0C;IACtD,MAAM,EAAE,IAAI;IACZ,KAAK,EAAE,IAAI;IACX,WAAW,EAAE,MAAM;;AAKpB,UAAU;EACN,UAAU,EFvSL,OAAO;EE0Sf,6BAAU;IACN,aAAa,EAAE,IAAI;IACnB,iCAAG;MACN,YAAY,EAAE,MAAM;IAGjB,sCAAQ;MACX,KAAK,EFpVC,IAAI;EEyVR,aAAE;IACL,KAAK,EF1VE,IAAI;EE6VX,+BAAG;IACC,aAAa,EAAE,IAAI;IAEnB,qCAAK;MACR,SAAS,EAAE,IAAI;MACf,KAAK,EF3VO,OAAO;IE+VhB,qCAAK;MACR,UAAU,EF5WQ,OAAO;ME8WzB,2CAAO;QACH,UAAU,EFhWD,OAAO;QEiWhB,KAAK,EF7WO,OAAO;;AGTzB,eAAe;AACf,eAAe;AACf;kCACmC;EAClC,KAAK,EAAE,KAAK;;AAEb,oDAAqD;EACpD,KAAK,EAAE,KAAK;;AAEb,2DAA4D;EAC3D,KAAK,EAAE,IAAI;;ACVZ,aAAa;AACb,KAAM;EACF,MAAM,EAAE,MAAM;EAEd,aAAS;IACZ,eAAe,EAAE,0CAAyC;IAC1D,eAAe,EAAE,mBAA2D;EAMzE,kBAAY;IACf,aAAa,EAAE,iBAAkB;IAEjC,eAAe,EAAK,8BAA8B;IAClD,kBAAkB,EAAE,8BAA8B;IAClD,UAAU,EAAU,8BAA8B;IAElD,qCAAmB;MACf,QAAQ,EAAE,QAAQ;MAClB,UAAU,EJyBN,OAAO;MIxBX,SAAS,EAAE,OAAO;MAClB,cAAc,EAAE,GAAG;MACnB,OAAO,EAAE,SAAS;MAClB,WAAW,EAAE,GAAG;MAChB,cAAc,EAAE,SAAS;MAEzB,4CAAO;QACV,UAAU,EAAE,OAAO;QACnB,KAAK,EJeU,OAAO;QIdtB,kDAAO;UACH,eAAe,EAAE,IAAI;IAKtB,4CAAmB;MACtB,UAAU,EJQH,OAAO;MIPd,WAAW,EAAE,IAAI;IAGlB,qCAAmB;MACf,UAAU,EJIF,OAAO;MIFf,2CAAK;QACR,OAAO,EAAE,MAAM;QACf,WAAW,EAAE,MAAM;QACnB,SAAS,EAAE,IAAI;QACf,WAAW,EAAE,GAAG;QN7Cd,kBAAkB,EAAE,qBAAuB;QAC3C,eAAe,EAAE,qBAAuB;QACxC,aAAa,EAAE,qBAAuB;QACtC,UAAU,EAAE,qBAAuB;QM6CrC,kDAAQ;UACJ,UAAU,EJHH,OAAW;UIKlB,iEAAc;YACjB,KAAK,EJ/CU,OAAO;YIiDtB,uEAAO;cACH,KAAK,EJdK,OAAO;UIkBlB,oDAAC;YACJ,KAAK,EJbU,OAAO;QIiBvB,iDAAO;UACH,UAAU,EJrBF,OAAO;QIwBnB,6CAAC;UACG,eAAe,EAAE,IAAI;UACrB,KAAK,EJ7BM,OAAO;MIiCnB,sFAAiD;QACpD,OAAO,EAAE,iBAAiB;QAC1B,UAAU,EJ5BA,qBAAmB;QI6B7B,SAAS,EAAE,OAAO;QAClB,OAAO,EAAE,KAAK;QACd,KAAK,EAAE,IAAI;QACX,OAAO,EAAE,OAAO;QAChB,UAAU,EAAC,MAAM;QACjB,aAAa,EAAE,IAAI;QACnB,MAAM,EAAE,cAAc;QACtB,WAAW,EAAE,OAAO;;AAStB,gBAAgB;AAChB,MAAO;EACH,cAAc,EAAE,MAAM;EACtB,SAAS,EAAE,CAAC;EAEZ,yBAAY;IACf,aAAa,EAAE,CAAC;EAEb;0BACkB;IACrB,aAAa,EAAE,WAAW;EAEvB,+EAA2D;IAC9D,aAAa,EAAE,WAAW;EAEvB;;;;;;;;qCAQ6B;IAChC,WAAW,EAAE,iBAA4B;;AAK1C,MAAO;EACH,UAAU,EJjFL,OAAO;EIoFZ,iBAAa;IAChB,OAAO,EAAE,MAAM;IACf,UAAU,EAAE,MAAM;IAClB,UAAU,EJvFF,OAAO;IIwFf,YAAY,EAAE,iBAAkB;EAE7B,uBAAmB;IACtB,MAAM,EAAE,WAAW;;AAMpB,wCAAwC;AAIxC,mBAAmB;AAGf;eACK;EACR,MAAM,EAAE,KAAK;EACb,WAAW,EAAE,KAAK;EAClB,SAAS,EAAE,IAAI;AAEZ,eAAK;EACR,UAAU,EJ/GF,OAAO;EF1CZ,kBAAkB,EAAE,qBAAuB;EAC3C,eAAe,EAAE,qBAAuB;EACxC,aAAa,EAAE,qBAAuB;EACtC,UAAU,EAAE,qBAAuB;EMwJtC,iBAAC;IACG,OAAO,EAAE,MAAM;IACf,KAAK,EJpHO,OAAO;EIuHnB,wBAAC;IACJ,KAAK,EJzII,OAAO;EI4IjB,qBAAO;IACH,UAAU,EJzHD,OAAO;II0HhB,KAAK,EJ7HO,OAAO;IIgItB,8BAAC;MACG,KAAK,EJjIM,OAAO;MIkIlB,UAAU,EJ7HH,OAAW;IIiItB,8BAAC;MACG,KAAK,EJvIM,OAAO;MIwIlB,UAAU,EJrJH,OAAO;IIyJf,uBAAC;MACJ,KAAK,EJ7IU,OAAO;MI8ItB,eAAe,EAAE,IAAI;EAGtB,sBAAQ;IACJ,UAAU,EJ7IF,OAAW;II8InB,KAAK,EJrLF,IAAI;IIwLV,+BAAC;MACG,KAAK,EJzLH,IAAI;MI0LN,UAAU,EJnJH,OAAW;IIwJtB,+BAAC;MACG,KAAK,EJhMH,IAAI;MIiMN,UAAU,EJ5KH,OAAO;IIgLf,wBAAC;MACJ,KAAK,EJtMC,IAAI;MIuMV,eAAe,EAAE,IAAI;AAMtB,iBAAC;EACG,KAAK,EJzLG,OAAO;AI4LhB,kBAAQ;EACX,UAAU,EAAE,MAAM;EAClB,UAAU,EJzMI,OAAO;EI0MrB,KAAK,EJ9Ma,OAAO;AIiNtB,qBAAY;EACf,OAAO,EAAE,MAAM;EACf,WAAW,EAAE,IAAI;EACjB,KAAK,EJrNM,OAAO;EIsNlB,cAAc,EAAE,SAAS;EACzB,cAAc,EAAE,GAAG;EACnB,UAAU,EAAE,IAAI;AAGZ,mBAAU;EACb,OAAO,EAAE,GAAG;EACZ,UAAU,EAAE,MAAM;AAGf,mBAAU;EACb,MAAM,EAAE,CAAC;EACT,UAAU,EAAE,KAAK;EAEjB,KAAK,EJzOC,IAAI;EI0OV,qBAAE;IACE,KAAK,EJ3OH,IAAI;EI6OV,yBAAM;IACF,OAAO,EAAE,QAAQ;IACjB,SAAS,EAAE,MAAM;IACjB,WAAW,EAAE,MAAM;;AAKzB,qCAAqC;AACrC,qEAAsE;EAClE,QAAQ,EAAE,QAAQ;EAClB,KAAK,EAAE,CAAC;EACR,WAAW,EAAE,MAAM;EACnB,UAAU,EJjNF,qBAAmB;EIkN3B,aAAa,EAAE,IAAI;EACnB,OAAO,EAAE,SAAS;EAClB,MAAM,EAAE,gBAAgB;EACxB,UAAU,EAAE,MAAM;;AAGtB,uBAAwB;EACpB,UAAU,EJ7PF,OAAO;;AI+PnB,uBAAwB;EACpB,UAAU,EJhQF,OAAO;;AIkQnB;oBACqB;EACjB,KAAK,EJpQG,OAAO;;AIsQnB;oBACqB;EACjB,KAAK,EJxQG,OAAO;;AI0QnB;;;2BAG4B;EACxB,KAAK,EJnRD,IAAI;;AIqRZ,oDAAqD;EACjD,IAAI,EAAE,GAAG;;AAEb;;oEAEqE;EACjE,aAAa,EAAE,GAAG;;AAEtB,oCAAoC;EAChC,iBAAiB,EJ3PZ,OAAO;;AK9ChB,kBAAkB;AAClB,mBAAmB;AACnB,eAAe;AACf,OAAQ;EACJ,UAAU,EL0CL,OAAO;EKzCZ,OAAO,EAAE,cAAc;EACvB,OAAO,EAAE,KAAK;EACd,YAAY,EAAE,IAAI;EAClB,KAAK,EAAE,IAAI;EAEX,aAAK;IACR,cAAc,EAAE,MAAM;IAMtB,mBAAO;MAEH,WAAW,EAAE,GAAG;MAChB,KAAK,EAAE,KAAK;MAGf,wBAAC;QACG,eAAe,EAAE,IAAI;QACrB,KAAK,ELoBM,OAAO;QKnBlB,SAAS,EAAE,IAAI;QACf,cAAc,EAAE,SAAS;QACzB,cAAc,EAAE,GAAG;QAEnB,4BAAG;UACN,YAAY,EAAE,MAAM;IAUlB,0BAAK;MACR,KAAK,EAAE,KAAK;MACZ,aAAa,EAAE,WAAW;MAC1B,gBAAgB,ELGL,OAAO;MKFlB,KAAK,ELAU,OAAO;MKCtB,MAAM,EAAE,IAAI;MP1CV,kBAAkB,EAAE,qBAAuB;MAC3C,eAAe,EAAE,qBAAuB;MACxC,aAAa,EAAE,qBAAuB;MACtC,UAAU,EAAE,qBAAuB;MO2CrC,gCAAO;QACH,gBAAgB,ELHR,OAAO;MKMnB,gCAAO;QACH,KAAK,EAAE,KAAK;QAEZ,gBAAgB,EL9Cd,IAAI;QK+CN,KAAK,EL1CC,OAAO;IK6Cd,yBAAI;MAEP,aAAa,EAAE,WAAW;MAE1B,gBAAgB,ELfL,OAAW;MKgBtB,mBAAmB,EAAE,MAAM;MAC3B,iBAAiB,EAAE,SAAS;MAC5B,gBAAgB,EAAE,wBAAwB;MAE1C,iBAAiB,EAAE,CAAC;MAEpB,KAAK,EAAE,IAAI;MACX,UAAU,EAAE,IAAI;MAXhB,6BAAG;QAAC,OAAO,EAAE,IAAI;MAajB,+BAAO;QACH,gBAAgB,EL1EL,OAAO;IK8EvB,uBAAW;MACP,KAAK,EAAE,IAAI;MACX,QAAQ,EAAE,QAAQ;MAClB,KAAK,EAAE,IAAI;MACX,GAAG,EAAE,OAAO;MACZ,UAAU,EAAE,MAAM;MAGlB,4BAAI;QAIP,gBAAgB,EAAE,WAAW;QAC7B,mBAAmB,EAAE,MAAM;QAC3B,iBAAiB,EAAE,SAAS;QAC5B,gBAAgB,EAAE,kBAAkB;QAEpC,OAAO,EAAE,QAAQ;QARjB,gCAAG;UAAC,OAAO,EAAE,IAAI;;AAwBnB,aAAa;AACb,OAAQ;EACJ,MAAM,EAAE,iBAAiB;;AAM7B,0BAA0B;AAC1B,OAAQ;EACJ,UAAU,EAAE,MAAM;;AAEtB,aAAc;EACV,UAAU,EAAE,IAAI;;AAEpB,YAAa;EACT,MAAM,EAAE,mBAAmB;EAC3B,KAAK,EAAE,KAAK;;AAEhB,aAAc;EACV,MAAM,EAAE,QAAQ;EAChB,KAAK,EAAE,IAAI;;AAEf,SAAU;EACN,MAAM,EAAE,MAAM;;AAGlB,iCAAiC;AACjC,YAAa;EACT,UAAU,EAAE,MAAM;EAClB,SAAS,EAAE,IAAI;EACf,UAAU,EL9FD,OAAW;;AKgGxB,kBAAmB;EACf,UAAU,ELjJG,OAAO;;AKmJxB,gBAAiB;EACb,WAAW,EAAE,GAAG;EAChB,WAAW,EAAE,IAAI;EACjB,KAAK,EL7ID,IAAI;;AK+IZ,sBAAuB;EACnB,eAAe,EAAE,IAAI;;AAGzB,uBAAuB;AACvB,IAAK;EACD,OAAO,EAAE,gBAAgB;EACzB,WAAW,EAAE,GAAG;EAChB,WAAW,EAAE,GAAG;EAChB,cAAc,EAAE,GAAG;EACnB,cAAc,EAAE,SAAS;EACzB,SAAS,EAAE,QAAQ;EACnB,KAAK,EL5JU,OAAO;EK+JtB,UAAK;IACR,OAAO,EAAE,aAAa;IACtB,SAAS,EAAE,QAAQ;IAEnB,KAAK,ELpKY,OAAO;IKqKxB,QAAQ,EAAE,QAAQ;IAClB,IAAI,EAAE,CAAC;IAGP,cAAc,EAAE,SAAS;;AAI1B,mBAAmB;AACnB,SAAU;EACN,UAAU,EAAE,MAAM;EAClB,OAAO,EAAE,KAAK;EAEd,cAAI;IACP,iBAAiB,EAAE,CAAC;IACpB,OAAO,EAAE,WAAW;IACpB,gBAAgB,ELzKF,OAAO;IK0KrB,mBAAmB,EAAE,MAAM;IAC3B,iBAAiB,EAAE,SAAS;IAE5B,oBAAO;MACH,gBAAgB,EL/KR,OAAO;EKmLhB,gBAAM;IACT,UAAU,ELnLI,OAAO;IKqLrB,qBAAI;MACA,iBAAiB,EAAE,CAAC;MACpB,OAAO,EAAE,WAAW;MACpB,gBAAgB,ELxLN,OAAO;MKyLjB,mBAAmB,EAAE,MAAM;MAC3B,iBAAiB,EAAE,SAAS;MP3M7B,kBAAkB,EAAE,qBAAuB;MAC3C,eAAe,EAAE,qBAAuB;MACxC,aAAa,EAAE,qBAAuB;MACtC,UAAU,EAAE,qBAAuB;MO2MlC,2BAAO;QACV,gBAAgB,ELhME,OAAO;MKmMtB,4BAAQ;QACX,gBAAgB,ELrKL,OAAW;MKwKnB,8BAAQ;QAAC,OAAO,EAAE,IAAI;MAItB,iCAAa;QAChB,gBAAgB,EAAE,mBAAmB;MAElC,wCAAoB;QACvB,gBAAgB,EAAE,yBAAyB;MAGxC,mCAAe;QAClB,gBAAgB,EAAE,qBAAqB;MAEpC,0CAAsB;QACzB,gBAAgB,EAAE,2BAA2B;MAG1C,oCAAgB;QACnB,gBAAgB,EAAE,sBAAsB;MAErC,2CAAuB;QAC1B,gBAAgB,EAAE,4BAA4B;MAG3C,wCAAoB;QACvB,gBAAgB,EAAE,0BAA0B;MAEzC,+CAA2B;QAC9B,gBAAgB,EAAE,gCAAgC;MAI/C,8BAAU;QACb,gBAAgB,ELvOH,OAAO;QKyOpB,KAAK,ELrPW,OAAO;QKsPvB,OAAO,EAAE,QAAQ;QP3Pf,kBAAkB,EAAE,qBAAuB;QAC3C,eAAe,EAAE,qBAAuB;QACxC,aAAa,EAAE,qBAAuB;QACtC,UAAU,EAAE,qBAAuB;QO2PrC,oCAAO;UACH,gBAAgB,ELhPF,OAAO;MKqPtB,iCAAa;QAChB,gBAAgB,EAAE,wBAAwB;MAEvC,wCAAoB;QACvB,gBAAgB,EAAE,8BAA8B;MAG7C,iCAAa;QAChB,gBAAgB,EAAE,0BAA0B;MAEzC,wCAAoB;QACvB,gBAAgB,EAAE,gCAAgC;MAG/C,iCAAa;QAChB,gBAAgB,EAAE,0BAA0B;MAEzC,wCAAoB;QACvB,gBAAgB,EAAE,gCAAgC;MAG/C,8BAAU;QACb,gBAAgB,EAAE,kBAAkB;IAKjC,4CAAiB;MACpB,iBAAiB,EAAE,CAAC;MACpB,gBAAgB,EAAE,mBAAmB;;AAiBvC,uDAAwD;EACpD,cAAc,EAAE,MAAM;EACtB,gBAAgB,ELvSD,OAAO;EKwStB,aAAa,EAAE,GAAG;;AAItB,iCAAiC;AACjC,uBAAwB;EACpB,OAAO,EAAE,SAAS;EAElB,SAAS,EAAE,QAAQ;EACnB,WAAW,EAAE,MAAM;EAItB,gFAAC;IACG,KAAK,EL9TQ,OAAO;IK+TpB,WAAW,EAAE,UAAU;IACvB,SAAS,EAAE,IAAI;IAEf,wGAAO;MACV,KAAK,EL1RM,OAAW;MK2RtB,eAAe,EAAE,IAAI;EAInB,uCAAO;IACV,SAAS,EAAE,QAAQ;IACnB,KAAK,ELzUa,OAAO;EK2UtB,0DAAK;IACR,SAAS,EAAE,QAAQ;IACnB,WAAW,EAAE,MAAM;EAIhB,+CAAY;IACf,MAAM,EAAE,SAAS;IACjB,MAAM,EAAE,GAAG;IACX,UAAU,EL3US,OAAO;IK4U1B,MAAM,EAAE,CAAC;IACT,UAAU,EAAE,cAAc;EAGvB,+BAAI;IACP,MAAM,EAAE,SAAS;IACjB,OAAO,EAAE,SAAS;IAClB,QAAQ,EAAE,IAAI;IACd,UAAU,ELjWS,OAAO;IKkW1B,KAAK,EL7VE,IAAI;IK8VX,SAAS,EAAE,MAAM;IACjB,aAAa,EAAE,GAAG;IAElB,yCAAK;MACD,UAAU,EAAE,WAAW;MACvB,KAAK,ELnWF,IAAI;MKoWP,MAAM,EAAE,IAAI;EAGb,iCAAK;IACR,OAAO,EAAE,OAAO;IAChB,KAAK,ELhWO,OAAO;IKiWnB,UAAU,ELhWI,OAAO;IKiWrB,MAAM,EAAE,iBAAqB;IAC7B,aAAa,EAAE,GAAG;EAIf,6CAAW;IACd,OAAO,EAAE,KAAK;IACd,MAAM,EAAE,CAAC;IACT,OAAO,EAAE,QAAQ;IACjB,UAAU,EAAE,iBAA4B;IACxC,aAAa,EAAE,iBAA4B;IAC3C,UAAU,EL5WI,OAAO;IK6WrB,KAAK,ELzXY,OAAO;IK2XxB,iDAAE;MACE,MAAM,EAAE,CAAC;;AAOd,gDAAgD;AAChD,aAAc;EACV,QAAQ,EAAE,KAAK;EACf,GAAG,EAAE,IAAI;EACT,MAAM,EAAE,CAAC;EACT,IAAI,EAAE,CAAC;EACP,KAAK,EAAE,CAAC;EACR,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,IAAI;EAEZ,OAAO,EAAE,MAAM;EACf,UAAU,EAAE,MAAM;EAElB,SAAS,EAAE,GAAG;EACd,WAAW,EAAE,GAAG;EAChB,OAAO,EAAE,EAAE;EACX,cAAc,EAAE,MAAM;EACtB,UAAU,EL1YM,OAAO;EK2YvB,KAAK,EL9YG,OAAO;EKgZf,MAAM,EAAE,IAAI;EAEZ,kBAAI;IACP,SAAS,EAAE,IAAI;IACf,OAAO,EAAE,YAAY;EAGlB,kBAAO;IACV,UAAU,ELpYE,OAAO;IKqYnB,KAAK,EL9ZE,IAAI;EKgaR,iBAAM;IACT,UAAU,ELhZA,OAAO;IKiZjB,KAAK,ELlaE,IAAI;EKoaR,qBAAQ;IACX,OAAO,EAAE,MAAM;IACf,WAAW,EAAE,GAAG;IAChB,aAAa,EAAE,WAAW;EAGvB,gCAAqB;IACxB,UAAU,ELhZI,OAAO;EKkZlB,+BAAoB;IACvB,UAAU,EL3ZE,OAAO;EK8ZhB,+BAAoB;IACvB,WAAW,EAAE,GAAG;IAEhB,kCAAE;MACE,OAAO,EAAE,IAAI;;AAMlB,uCAAuC;AACvC,YAAa;EACT,MAAM,EAAE,CAAC;EACT,UAAU,EAAE,MAAM;EAClB,WAAW,EAAE,GAAG;EAChB,YAAY,EAAE,KAAK;EACnB,UAAU,EL9ZL,OAAO;;AM9ChB,sBAAsB;AACtB,KAAM;EAEF,UAAU,ENQN,IAAI;EFPR,kBAAkB,EAAE,qBAAuB;EAC3C,eAAe,EAAE,qBAAuB;EACxC,aAAa,EAAE,qBAAuB;EACtC,UAAU,EAAE,qBAAuB;EQAnC,WAAO;IACV,UAAU,ENaI,OAAO;IMXrB,2CAAkC;MAC9B,UAAU,ENUA,OAAO;EMLlB,aAAS;IACZ,iBAAiB,ENiCL,OAAW;IMhCvB,UAAU,ENPH,IAAI;EMSR,cAAU;IACb,UAAU,ENGC,OAAO;IMAlB,oBAAO;MACH,UAAU,ENAG,OAAO;IMGxB,8CAAkC;MAC9B,UAAU,ENJG,OAAO;IMSpB,4BAAC;MACJ,KAAK,ENZa,OAAO;IMiBtB,8BAAC;MACJ,KAAK,ENlBa,OAAO;IMqB1B,yBAAU;MACN,KAAK,EAAE,SAAgC;EAIxC,cAAW;IACd,UAAU,ENRC,OAAO;IMSlB,iBAAiB,ENVT,OAAO;IFrCZ,kBAAkB,EAAE,qBAAuB;IAC3C,eAAe,EAAE,qBAAuB;IACxC,aAAa,EAAE,qBAAuB;IACtC,UAAU,EAAE,qBAAuB;IQgDtC,8CAAkC;MAC9B,UAAU,ENdH,OAAO;EMmBlB,gBAAC;IACG,KAAK,ENrDQ,OAAO;IMsDpB,OAAO,EAAE,IAAI;EAGjB,uBAAS;IACL,OAAO,EAAE,GAAG;EAGb,WAAM;IACT,SAAS,EAAE,OAAO;IAClB,KAAK,EN/DY,OAAO;IMgExB,OAAO,EAAE,IAAI;EAGV,aAAQ;IACX,SAAS,EAAE,IAAI;IACf,UAAU,EAAE,MAAM;;AAInB,YAAa;EACT,SAAS,EAAE,IAAI;EACf,MAAM,EAAE,OAAO;EACf,UAAU,EAAE,iBAAqB;EAEjC,mBAAO;IACV,SAAS,EAAE,IAAI;;ACxFhB,oBAAoB;AACpB,oBAAoB;AAWnB,8DAA2C;EACvC,WAAW,EAAE,IAAI;AAIrB,gCAAU;EACN,UAAU,EAAE,IAAI;EAChB,OAAO,EAAE,MAAM;EAEf,wCAAO;IACV,WAAW,EAAE,MAAM;IACnB,eAAe,EAAE,IAAI;IACrB,UAAU,EAAE,IAAI;IAChB,SAAS,EAAE,IAAI;IACf,cAAc,EAAE,SAAS;IACzB,cAAc,EAAE,GAAG;IACnB,KAAK,EPZK,OAAO;IOcjB,sEAAgC;MAC5B,QAAQ,EAAE,QAAQ;MAClB,GAAG,EAAE,OAAO;MACZ,KAAK,EAAE,CAAC;MACR,WAAW,EAAE,MAAM;MACnB,UAAU,EPfH,OAAO;MOgBd,aAAa,EAAE,IAAI;MACnB,OAAO,EAAE,SAAS;MAClB,MAAM,EAAE,gBAAgB;MACxB,UAAU,EAAE,MAAM;IAEtB,8CAAO;MACH,KAAK,EPQE,OAAW;AOHvB,kCAAY;EACR,cAAc,EAAE,MAAM;EACtB,6CAAW;IAEd,SAAS,EAAE,IAAI;IACf,OAAO,EAAE,aAAa;IAEtB,+CAAC;MACG,KAAK,EP9CO,OAAO;MO+CnB,WAAW,EAAE,GAAG;MAEhB,qDAAO;QACV,KAAK,EPTK,OAAW;QOUrB,eAAe,EAAE,IAAI;;AAUxB,QAAQ;EACJ,UAAU,EAAE,mBAAmB;;AAEnC,MAAM;EACF,GAAG,EAAE,IAAI;EACT,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,IAAI;EACZ,IAAI,EAAE,IAAI;EACV,aAAa,EAAE,GAAG;;AC9EtB,oBAAoB;AACpB,oBAAoB;AACpB,oBAAqB;EACpB,OAAO,EAAE,QAAQ;EACjB,UAAU,ERgBE,OAAO;EQfnB,KAAK,ERIY,OAAO;EQHxB,MAAM,EAAE,IAAI;;AAEb,4BAA6B;EAC5B,MAAM,EAAE,QAAQ;EAChB,SAAS,EAAE,GAAG;EACd,KAAK,ERMa,OAAO;;ASjB1B,4BAA4B;AAC5B,KAAM;EACF,OAAO,EAAE,SAAS;EAClB,SAAS,EAAE,IAAI;EAEf,UAAK;IACR,MAAM,EAAE,MAAM;IAGd,2BAAgB;MACZ,aAAa,EAAE,MAAM;EAiBtB,aAAU;IACb,SAAS,EAAE,KAAK;EAGb,kBAAM;IACT,SAAS,EAAE,IAAI;IACf,UAAU,EAAE,OAAO;IACnB,WAAW,EAAE,GAAG;IAChB,WAAW,EAAE,KAAK;IAElB,KAAK,ET5BY,OAAO;ES+BrB,kBAAY;IACf,OAAO,EAAE,YAAY;IAErB,SAAS,EAAE,IAAI;IACf,WAAW,EAAE,IAAI;IACjB,MAAM,EAAE,CAAC;IACT,OAAO,EAAE,cAAc;IACvB,SAAS,EAAE,IAAI;IACf,cAAc,EAAE,MAAM;IACtB,MAAM,EAAE,OAAO;IACf,QAAQ,EAAE,MAAM;IAChB,UAAU,ET9BI,OAAO;IS+BrB,MAAM,EAAE,iBAA4B;IACpC,aAAa,EAAE,GAAG;IAElB,KAAK,ETvCM,OAAO;ISyClB,wBAAO;MACH,eAAe,EAAE,IAAI;MACrB,UAAU,ETTF,OAAW;MSUnB,KAAK,EAAE,KAAK;MACZ,MAAM,EAAE,iBAAqB;;AAQlC,OAAO;EACH,WAAW,EAAE,IAAI;EAEjB,kBAAkB,EAAE,mCAAgC;EACpD,eAAe,EAAE,mCAAgC;EACjD,UAAU,EAAE,mCAAgC;;AAI5C,4BAAW;EACd,UAAU,EAAE,mBAAmB;EAC/B,MAAM,EAAE,CAAC;EACT,OAAO,EAAE,IAAI;EACb,KAAK,EAAE,KAAK;EAEZ,yCAAY;IACR,KAAK,EAAE,IAAI;IACX,GAAG,EAAE,IAAI;;ACtFd,aAAa;AACb,aAAa;AACb,QAAS;EACR,QAAQ,EAAE,MAAM;EAChB,MAAM,EAAE,iBAA2B;;AAEpC,IAAK;EACJ,MAAM,EAAE,MAAM;EACd,OAAO,EAAE,MAAM;EACf,QAAQ,EAAE,IAAI;EACd,SAAS,EAAE,MAAM;EACjB,UAAU,EVUI,OAAO;EUTrB,KAAK,EVIM,OAAO;;AUDnB,YAAa;EACZ,MAAM,EAAE,UAAU;EAClB,OAAO,EAAE,QAAQ;EACjB,aAAa,EAAE,IAAI;;AAEpB,kBAAmB;EAClB,UAAU,EVOA,OAAO;EUNjB,KAAK,EAAE,IAAI;;AAEZ,oBAAqB;EACpB,UAAU,EVOE,OAAO;;AULpB,mBAAoB;EACnB,UAAU,EVRE,OAAO;;AUUpB,kBAAmB;EAClB,UAAU,EVzBS,OAAO;EU0B1B,KAAK,EVrBE,IAAI;;AWXZ,mBAAmB;AACnB,mBAAmB;AACnB,KAAM;EACL,MAAM,EAAE,WAAW;;AAGpB;;QAES;EACR,MAAM,EAAE,IAAI;;AAEb;gBACiB;EAChB,aAAa,EAAE,iBAA4B;;AAG5C,wBAAyB;EACxB,MAAM,EAAE,OAAO;;AAEhB,8BAA+B;EAC9B,QAAQ,EAAE,MAAM;EAChB,WAAW,EAAE,MAAM;EACnB,aAAa,EAAE,QAAQ;;AAExB,0CAA2C;EAC1C,KAAK,EAAE,KAAK;;ACzBb,eAAe;AACf,eAAe;AACf,yBAAyB;EAKxB,YAAK;IACD,KAAK,EAAE,IAAI;IAEX,gBAAG;MACN,OAAO,EAAE,IAAI;IAEV,cAAC;MACJ,OAAO,EAAE,YAAY;MACrB,OAAO,EAAE,qBAAqB;MAC9B,KAAK,EZ6BU,OAAO;MY5BtB,KAAK,EAAE,IAAI;MAEX,UAAU,EAAE,gEAAqE;Mdf/E,kBAAkB,EAAE,oBAAuB;MAC3C,eAAe,EAAE,oBAAuB;MACxC,aAAa,EAAE,oBAAuB;MACtC,UAAU,EAAE,oBAAuB;McgBrC,2CACQ;QACJ,UAAU,EAAE,gEAAkE;QAC9E,eAAe,EAAE,IAAI;QACrB,KAAK,EZhBH,IAAI;;EYuBR,MAAO;Id9BP,kBAAkB,EAAE,oBAAuB;IAC3C,eAAe,EAAE,oBAAuB;IACxC,aAAa,EAAE,oBAAuB;IACtC,UAAU,EAAE,oBAAuB;Ic8BtC,iBAAa;MACT,OAAO,EAAE,CAAC;IAGd,oDAA6C;MACzC,OAAO,EAAE,WAAW;;EAGrB;eACc;IACjB,OAAO,EAAE,KAAK;IACd,KAAK,EAAE,IAAI;IACX,MAAM,EAAE,IAAI;IACZ,WAAW,EAAE,IAAI;IACjB,UAAU,EAAE,MAAM;IAClB,UAAU,EZlDM,OAAO;;EYqDpB,OAAO;IACV,OAAO,EAAE,MAAM;IAEX,mBAAO;MACV,OAAO,EAAE,IAAI;IAIb,0BAAK;MACD,KAAK,EAAE,GAAG;MACV,MAAM,EAAE,MAAM;MAEd,gCAAO;QACV,KAAK,EAAE,IAAI;IAIZ,yBAAI;MACA,UAAU,EAAE,IAAI;MAChB,OAAO,EAAE,WAAW;IAGrB,uBAAW;MACd,KAAK,EAAE,OAAO;MACd,GAAG,EAAE,QAAQ;MAET,sCAAI;QACP,OAAO,EAAE,QAAQ;;EAQnB,cAAK;IACD,MAAM,EAAE,CAAC;IACT,OAAO,EAAE,eAAe;EAE5B,gBAAO;IACH,MAAM,EAAE,aAAa;IAErB,qBAAI;MACP,MAAM,EAAE,CAAC;MACT,OAAO,EAAE,eAAe;MAExB,8BAAU;QACN,OAAO,EAAE,eAAe;EAI7B,iBAAQ;IACJ,OAAO,EAAE,IAAI;IACb,SAAS,EAAE,GAAG;IAEd,wBAAM;MAET,SAAS,EAAE,GAAG;MACd,KAAK,EAAE,IAAI;MAEX,+BAAQ;QACL,KAAK,EAAE,KAAK;;EAOZ,0BAAY;IACf,OAAO,EAAE,QAAQ;;EAOf,IAAI;IACP,UAAU,EAAE,MAAM;IAClB,OAAO,EAAE,MAAM;IACf,UAAM;MAEF,OAAO,EAAE,KAAK;MACd,OAAO,EAAE,CAAC;MACV,KAAK,EAAE,IAAI;MACX,WAAW,EAAE,MAAM;MACnB,aAAa,EAAE,IAAI;;EAKpB,WAAY;IACf,MAAM,EAAE,SAAS;;EAGd,YAAY;IACf,WAAW,EAAE,MAAM;;EAGhB,aAAc;IACjB,aAAa,EAAE,CAAC;IAEhB,qBAAQ;MACJ,OAAO,EAAE,KAAK;MACd,IAAI,EAAE,CAAC;MACP,UAAU,EAAE,WAAW;IAE3B,2BAAc;MACV,OAAO,EAAE,GAAG;IAEhB,2BAAc;MACV,OAAO,EAAE,IAAI;AC9IlB,UAAU;EACN,WAAW,EAAE,wCAAwC;EACrD,SAAS,EAAE,QAAQ;;AAKvB,gBAAgB;AAChB,gBAAgB;AAChB,UAAW;EACV,MAAM,EAAE,IAAI;EACZ,UAAU,EbdE,OAAO;;AaiBpB,cAAc;AACd,iBAAkB;EACd,OAAO,EAAE,IAAI;EACb,KAAK,EbUI,OAAW", -"sources": ["_fonts.scss","_mixins.scss","_forms.scss","_variables.scss","_tables.scss","_components.scss","_divers.scss","_sidebar.scss","_layout.scss","_list-view.scss","_global-view.scss","_reader-view.scss","_configuration.scss","_logs.scss","_stats.scss","_mobile.scss","ansum.scss"], -"names": [], -"file": "ansum.css" -} diff --git a/p/themes/Ansum/ansum.scss b/p/themes/Ansum/ansum.scss index 1a538b50a..337cc5f97 100644 --- a/p/themes/Ansum/ansum.scss +++ b/p/themes/Ansum/ansum.scss @@ -1,51 +1,48 @@ @import "fonts"; + @import "mixins"; + @import "variables"; @import "forms"; + @import "tables"; + @import "components"; @import "divers"; @import "sidebar"; + @import "layout"; + @import "list-view"; + @import "global-view"; + @import "reader-view"; @import "configuration"; @import "logs"; + @import "stats"; @import "mobile"; -html, body{ - font-family: "lato", "Helvetica", "Arial", sans-serif; - font-size: 0.875rem; -} - @charset "UTF-8"; /*=== GENERAL */ /*============*/ html, body { - height: 100%; background: $grey-light; + height: 100%; + font-family: "lato", "Helvetica", "Arial", sans-serif; + font-size: 0.875rem; } /*=== Links */ a, button.as-link { - outline: none; - color: $main-first; + outline: none; + color: $main-first; } - - - - - - - - - diff --git a/p/themes/BlueLagoon/BlueLagoon.css b/p/themes/BlueLagoon/BlueLagoon.css index 93c0ba213..7e9fdf40a 100644 --- a/p/themes/BlueLagoon/BlueLagoon.css +++ b/p/themes/BlueLagoon/BlueLagoon.css @@ -3,72 +3,77 @@ /*=== GENERAL */ /*============*/ html, body { + background: #fafafa; height: 100%; font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", "PingFang SC", "Microsoft YaHei", sans-serif; - background: #fafafa; font-size: 92%; } /*=== Links */ a, button.as-link { - color: #0062BE; + color: #0062be; outline: none; } /*=== Forms */ -.form-group{ - width: 100%; +.form-group { + display: inline-block; float: left; + width: 100%; height: auto; - display: inline-block; } + legend { margin: 20px 0 5px; padding: 5px 0; - border-bottom: 1px solid #ddd; font-size: 1.4em; + border-bottom: 1px solid #ddd; } + label { min-height: 25px; padding: 5px 0; cursor: pointer; } + textarea { width: 360px; height: 100px; } + input, select, textarea { - min-height: 25px; padding: 5px; background: #fff; + color: #222; border: 1px solid #ccc; border-radius: 3px; - color: #222; + box-shadow: 0 1px 2px #ccc inset, 0 1px #fff; + min-height: 25px; line-height: 25px; vertical-align: middle; - box-shadow: 0 1px 2px #ccc inset, 0 1px #fff; } + option { padding: 0 .5em; } + input:focus, select:focus, textarea:focus { - color: #0F0F0F; - box-shadow: 0 0 3px #0062BF; - border: solid 1px #0062BF; + color: #0f0f0f; + border: solid 1px #0062bf; + box-shadow: 0 0 3px #0062bf; } + input:invalid, select:invalid { border-color: #f00; box-shadow: 0 0 2px 2px #fdd inset; } + input:disabled, select:disabled { background: #eee; } + input.extend { transition: width 200ms linear; - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; } /*=== Tables */ @@ -80,9 +85,11 @@ tr, th, td { padding: 0.5em; border: 1px solid #ddd; } + th { background: #f6f6f6; } + form td, form th { font-weight: normal; @@ -97,49 +104,60 @@ form th { background: #f4f4f4; border-top: 1px solid #ddd; } + .form-group.form-actions .btn { margin: 0 10px; border-radius: 4px; - box-shadow:0 1px rgba(255,255,255,0.08) inset; + box-shadow: 0 1px rgba(255,255,255,0.08) inset; } + .form-group .group-name { padding: 10px 0; text-align: right; } + .form-group .group-controls { min-height: 25px; padding: 5px 0; } + .form-group table { margin: 10px 0 0 220px; } /*=== Buttons */ button.as-link[disabled] { - color:#555 !important; + color: #555 !important; } .dropdown-menu .input select, .dropdown-menu .input input { - background:#444; - color:#fff; - box-shadow:0 2px 2px #222 inset, 0px 1px rgba(255, 255, 255, 0.08); - border:solid 1px #171717; + margin: 0 auto 5px; + padding: 2px 5px; + background: #444; + color: #fff; + border: solid 1px #171717; + border-radius: 3px; + box-shadow: 0 2px 2px #222 inset, 0px 1px rgba(255, 255, 255, 0.08); } .stick { vertical-align: middle; font-size: 0; } + .stick input, .stick .btn { border-radius: 0; } + .stick .btn:first-child,.stick input:first-child { border-radius: 6px 0 0 6px; } + .stick .btn:last-child, .stick input:last-child { border-radius: 0 6px 6px 0; } + .stick .btn + .btn, .stick .btn + input, .stick .btn + .dropdown > .btn, @@ -151,103 +169,113 @@ button.as-link[disabled] { .stick .dropdown + .dropdown > .btn { border-left: none; } + .stick .btn + .dropdown > .btn { border-left: none; border-radius: 0 3px 3px 0; } .btn { - display: inline-block; - min-height: 37px; - min-width: 15px; margin: 0; padding: 5px 10px; - color:#222; + background: linear-gradient(0deg, #ede7de 0%, #fff 100%) #ede7de; + background: -webkit-linear-gradient(bottom, #ede7de 0%, #fff 100%); + display: inline-block; + color: #222; + font-size: 0.9rem; border: solid 1px #ccc; border-radius: 4px; - background: linear-gradient(0deg, #EDE7DE 0%, #FFF 100%) #EDE7DE; - background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #FFF 100%); + min-height: 37px; + min-width: 15px; text-shadow: 0px -1px rgba(255,255,255,0.08); - font-size: 0.9rem; vertical-align: middle; cursor: pointer; overflow: hidden; } + a.btn { min-height: 25px; line-height: 25px; } + .btn:hover { text-shadow: 0 0 2px #fff; - text-decoration:none; + text-decoration: none; } + .btn.active,.btn:active,.dropdown-target:target ~ .btn.dropdown-toggle { - background: linear-gradient(180deg, #EDE7DE 0%, #FFF 100%) #EDE7DE; - background: -webkit-linear-gradient(top, #EDE7DE 0%, #FFF 100%); + background: linear-gradient(180deg, #ede7de 0%, #fff 100%) #ede7de; + background: -webkit-linear-gradient(top, #ede7de 0%, #fff 100%); } -.nav_menu .btn.active, .nav_menu .btn:active, .nav_menu .dropdown-target:target ~ .btn.dropdown-toggle{ - box-shadow: 0 1px #fff; - border-radius: 4px; - background: linear-gradient(180deg, #EDE7DE 0%, #F6F6F6 100%) #EDE7DE; - background: -webkit-linear-gradient(top, #EDE7DE 0%, #F6F6F6 100%); +.nav_menu .btn.active, .nav_menu .btn:active, .nav_menu .dropdown-target:target ~ .btn.dropdown-toggle { + background: linear-gradient(180deg, #ede7de 0%, #f6f6f6 100%) #ede7de; + background: -webkit-linear-gradient(top, #ede7de 0%, #f6f6f6 100%); border: solid 1px #ccc; + border-radius: 4px; + box-shadow: 0 1px #fff; } + .nav_menu .btn { + background: transparent; border: 0; - background:transparent; } .read_all { - color:#222; + color: #222; } -.btn.dropdown-toggle[href="#dropdown-configure"]{ - background: linear-gradient(0deg, #EDE7DE 0%, #FFF 100%) #EDE7DE; - background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #FFF 100%); - border-radius: 4px; + +.btn.dropdown-toggle[href="#dropdown-configure"] { + background: linear-gradient(0deg, #ede7de 0%, #fff 100%) #ede7de; + background: -webkit-linear-gradient(bottom, #ede7de 0%, #fff 100%); border: solid 1px #ccc; + border-radius: 4px; box-shadow: 0 1px #fff; } + .btn.dropdown-toggle:active { - background:transparent; + background: transparent; } + .btn-important { - background: linear-gradient(180deg, #0090FF 0%, #0062BE 100%) #E4992C; - background: -webkit-linear-gradient(top, #0090FF 0%, #0062BE 100%); - color: #FFF; - box-shadow: 0 1px rgba(255,255,255,0.08) inset; + background: linear-gradient(180deg, #0090ff 0%, #0062be 100%) #e4992c; + background: -webkit-linear-gradient(top, #0090ff 0%, #0062be 100%); + color: #fff; border-radius: 4px; + box-shadow: 0 1px rgba(255,255,255,0.08) inset; text-shadow: 0px -1px rgba(255,255,255,0.08); font-weight: normal; } -.btn-important:hover { -} + .btn-important:active { - background: linear-gradient(0deg, #E4992C 0%, #D18114 100%) #E4992C; - background: -webkit-linear-gradient(bottom, #E4992C 0%, #D18114 100%); + background: linear-gradient(0deg, #e4992c 0%, #d18114 100%) #e4992c; + background: -webkit-linear-gradient(bottom, #e4992c 0%, #d18114 100%); } .btn-attention { - background: #E95B57; - background: linear-gradient(to bottom, #E95B57, #BD362F); - background: -webkit-linear-gradient(top, #E95B57 0%, #BD362F 100%); + background: #e95b57; + background: linear-gradient(to bottom, #e95b57, #bd362f); + background: -webkit-linear-gradient(top, #e95b57 0%, #bd362f 100%); color: #fff; - border: 1px solid #C44742; + border: 1px solid #c44742; text-shadow: 0px -1px 0px #666; } + .btn-attention:hover { - background: linear-gradient(to bottom, #D14641, #BD362F); - background: -webkit-linear-gradient(top, #D14641 0%, #BD362F 100%); + background: linear-gradient(to bottom, #d14641, #bd362f); + background: -webkit-linear-gradient(top, #d14641 0%, #bd362f 100%); } + .btn-attention:active { - background: #BD362F; + background: #bd362f; box-shadow: none; } -.btn[type="reset"]{ + +.btn[type="reset"] { + background: linear-gradient(180deg, #222 0%, #171717 100%) #171717; + background: -webkit-linear-gradient(top, #222 0%, #171717 100%); color: #fff; - background:linear-gradient(180deg, #222 0%, #171717 100%) #171717; - background: -webkit-linear-gradient(top, #222 0%, #171717 100%); - box-shadow:0 -1px rgba(255,255,255,0.08) inset; + box-shadow: 0 -1px rgba(255,255,255,0.08) inset; } /*=== Navigation */ .nav-list .nav-header, @@ -256,55 +284,64 @@ a.btn { line-height: 2.5em; font-size: 0.9rem; } + .nav-list .item:hover { text-shadow: 0 0 2px rgba(255,255,255,0.28); - color:#fff; + color: #fff; } .nav-list .item.active { - background: linear-gradient(180deg, #0090FF 0%, #0062BE 100%) #E4992C; - background: -webkit-linear-gradient(top, #0090FF 0%, #0062BE 100%); + margin: 0; + background: linear-gradient(180deg, #0090ff 0%, #0062be 100%) #e4992c; + background: -webkit-linear-gradient(top, #0090ff 0%, #0062be 100%); + box-shadow: -1px 2px 2px #171717, 0px 1px rgba(255, 255, 255, 0.08) inset; border-width: medium medium 1px; border-style: none none solid; border-color: -moz-use-text-color -moz-use-text-color #171717; - box-shadow: -1px 2px 2px #171717, 0px 1px rgba(255, 255, 255, 0.08) inset; - margin: 0; } + .nav-list .item.active a { color: #fff; } + .nav-list .disable { - color: #aaa; background: #fafafa; + color: #aaa; text-align: center; } + .nav-list .item > a { padding: 0 10px; - color:#ccc; + color: #ccc; } + .nav-list a:hover { text-decoration: none; } + .nav-list .item.empty a { color: #f39c12; } + .nav-list .item.active.empty a { + background: linear-gradient(180deg, #e4992c 0%, #d18114 100%) #e4992c; + background: -webkit-linear-gradient(180deg, #e4992c 0%, #d18114 100%); color: #fff; - background: linear-gradient(180deg, #E4992C 0%, #D18114 100%) #E4992C; - background: -webkit-linear-gradient(180deg, #E4992C 0%, #D18114 100%); } + .nav-list .item.error a { - color: #BD362F; + color: #bd362f; } + .nav-list .item.active.error a { + background: #bd362f; color: #fff; - background: #BD362F; } .nav-list .nav-header { padding: 0 10px; - color: #222; background: transparent; + color: #222; } .nav-list .nav-form { @@ -314,10 +351,11 @@ a.btn { .nav-head { margin: 0; - background: linear-gradient(0deg, #EDE7DE 0%, #FFF 100%) #EDE7DE; - background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #FFF 100%); + background: linear-gradient(0deg, #ede7de 0%, #fff 100%) #ede7de; + background: -webkit-linear-gradient(bottom, #ede7de 0%, #fff 100%); text-align: right; } + .nav-head .item { padding: 5px 10px; font-size: 0.9rem; @@ -329,6 +367,7 @@ a.btn { margin: 0; padding: 0; } + .horizontal-list .item { vertical-align: middle; } @@ -337,32 +376,32 @@ a.btn { .dropdown-menu { margin: 5px 0 0; padding: 5px 0; + background: #222; + font-size: 0.8rem; border: 1px solid #171717; border-radius: 4px; box-shadow: 0 0 3px #000; - font-size: 0.8rem; text-align: left; - background: #222; } + .dropdown-menu::after { - content: ""; - position: absolute; - top: -6px; - right: 13px; + background: #222; width: 10px; height: 10px; - background: #222; border-top: 1px solid #171717; border-left: 1px solid #171717; + content: ""; + position: absolute; + top: -6px; + right: 13px; z-index: -10; transform: rotate(45deg); - -moz-transform: rotate(45deg); - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); } + .dropdown-header { - display:none; + display: none; } + .dropdown-menu > .item > a, .dropdown-menu > .item > span, .dropdown-menu > .item > .as-link { @@ -371,28 +410,26 @@ a.btn { color: #ccc; font-size: 0.8rem; } + .dropdown-menu > .item > label { color: #ccc; } + .dropdown-menu > .item:hover { - background: linear-gradient(180deg, #0090FF 0%, #0062BE 100%) #E4992C; - background: -webkit-linear-gradient(top, #0090FF 0%, #0062BE 100%); + background: linear-gradient(180deg, #0090ff 0%, #0062be 100%) #e4992c; + background: -webkit-linear-gradient(top, #0090ff 0%, #0062be 100%); color: #fff; } + .dropdown-menu > .item[aria-checked="true"] > a::before { font-weight: bold; margin: 0 0 0 -14px; } + .dropdown-menu > .item:hover > a { color: #fff; text-decoration: none; } -.dropdown-menu .input select, -.dropdown-menu .input input { - margin: 0 auto 5px; - padding: 2px 5px; - border-radius: 3px; -} .separator { margin: 5px 0; @@ -405,35 +442,40 @@ a.btn { margin: 15px auto; padding: 10px 15px; background: #f4f4f4; + color: #aaa; + font-size: 0.9em; border: 1px solid #ccc; border-right: 1px solid #aaa; border-bottom: 1px solid #aaa; border-radius: 5px; - color: #aaa; text-shadow: 0 0 1px #eee; - font-size: 0.9em; } + .alert-head { font-size: 1.15em; } + .alert > a { color: inherit; text-decoration: underline; } + .alert-warn { background: #ffe; - border: 1px solid #eeb; color: #c95; + border: 1px solid #eeb; } + .alert-success { background: #dfd; - border: 1px solid #cec; color: #484; + border: 1px solid #cec; } + .alert-error { background: #fdd; - border: 1px solid #ecc; color: #844; + border: 1px solid #ecc; } /*=== Pagination */ @@ -443,14 +485,17 @@ a.btn { color: #333; font-size: 0.8em; } + .content .pagination { margin: 0; padding: 0; } + .pagination .item.pager-current { font-weight: bold; font-size: 1.5em; } + .pagination .item a { display: block; color: #333; @@ -458,12 +503,15 @@ a.btn { line-height: 3em; text-decoration: none; } + .pagination .item a:hover { background: #ddd; } + .pagination:first-child .item { border-bottom: 1px solid #aaa; } + .pagination:last-child .item { border-top: 1px solid #ddd; } @@ -471,28 +519,30 @@ a.btn { .pagination .loading, .pagination a:hover.loading { background: url("loader.gif") center center no-repeat #fff; - font-size: 0; - height:55px + height: 55px; + font-size: 0 } /*=== Boxes */ .box { - background: #F9F7F4; + background: #f9f7f4; border-radius: 4px; box-shadow: 0 1px #fff; } + .box .box-title { margin: 0; padding: 5px 10px; - background: linear-gradient(0deg, #EDE7DE 0%, #fff 100%) #171717; - background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #fff 100%); - box-shadow: 0px -1px #fff inset,0 -2px #ccc inset; + background: linear-gradient(0deg, #ede7de 0%, #fff 100%) #171717; + background: -webkit-linear-gradient(bottom, #ede7de 0%, #fff 100%); color: #888; - text-shadow: 0 1px #ccc; - border-radius: 4px 4px 0 0; font-size: 1.1rem; + border-radius: 4px 4px 0 0; + box-shadow: 0px -1px #fff inset,0 -2px #ccc inset; + text-shadow: 0 1px #ccc; font-weight: normal; } + .box .box-content { max-height: 260px; } @@ -507,6 +557,7 @@ a.btn { .box .box-content .item .configure { visibility: hidden; } + .box .box-title:hover .configure, .box .box-content .item:hover .configure { visibility: visible; @@ -516,54 +567,64 @@ a.btn { .tree { margin: 10px 0; } + .tree-folder-title { position: relative; padding: 0 10px; line-height: 2.5rem; font-size: 0.9rem; } + .tree-folder-title .title { background: inherit; color: #fff; } + .tree-folder-title .title:hover { text-decoration: none; } + .tree-folder.active .tree-folder-title { background: linear-gradient(180deg, #222 0%, #171717 100%) #171717; background: -webkit-linear-gradient(top, #222 0%, #171717 100%); + color: #fff; box-shadow: 0px 1px #171717, 0px 1px rgba(255, 255, 255, 0.08) inset; text-shadow: 0 0 2px rgba(255,255,255,0.28); - color: #fff; } -.tree-folder.active > .tree-folder-title > a.title{ - color: #0090FF; + +.tree-folder.active > .tree-folder-title > a.title { + color: #0090ff; text-shadow: 0 1px rgba(255,255,255,0.08); } + .tree-folder-items { - background: #171717; - padding: 8px 0; + padding: 8px 0; + background: #171717; box-shadow: 0 4px 4px #171717 inset, 0 1px rgba(255,255,255,0.08),0 -1px rgba(255,255,255,0.08); } + .tree-folder-items > .item { padding: 0 10px; line-height: 2.5rem; font-size: 0.8rem; } + .tree-folder-items > .item.active { + margin: 0px 8px; background: linear-gradient(180deg, #222 0%, #171717 100%) #171717; background: -webkit-linear-gradient(top, #222 0%, #171717 100%); border-radius: 4px; - margin: 0px 8px; box-shadow: 0px 1px #171717, 0px 1px rgba(255, 255, 255, 0.08) inset, 0 2px 2px #111; } + .tree-folder-items > .item > a { text-decoration: none; color: #fff; font-size: 0.92em; } + .tree-folder-items > .item.active > a { - color: #0090FF + color: #0090ff } /*=== Scrollbar */ @@ -572,14 +633,17 @@ a.btn { #sidebar { scrollbar-color: rgba(255, 255, 255, 0.05) rgba(0, 0, 0, 0.0); } + #sidebar:hover { scrollbar-color: rgba(255, 255, 255, 0.3) rgba(0, 0, 0, 0.0); } } + @supports not (scrollbar-width: thin) { #sidebar::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.1); } + #sidebar:hover::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.3); } @@ -589,54 +653,64 @@ a.btn { /*===============*/ /*=== Header */ .header { - background: linear-gradient(0deg, #EDE7DE 0%, #FFF 100%) #EDE7DE; - background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #FFF 100%); - border-bottom: solid 1px #BDB7AE; + background: linear-gradient(0deg, #ede7de 0%, #fff 100%) #ede7de; + background: -webkit-linear-gradient(bottom, #ede7de 0%, #fff 100%); + border-bottom: solid 1px #bdb7ae; box-shadow: 0 -1px rgba(255,255,255,0.28) inset; } + .header > .item { padding: 0; vertical-align: middle; text-align: center; } + .header > .item.title .logo { - height: 40px; width: 40px; + height: 40px; } -.header > .item.title{ + +.header > .item.title { width: 250px; } + .header > .item.title h1 { margin: 10px 0; } + .header > .item.title h1 a { - text-decoration: none; + color: #222; font-size: 28px; - color:#222; + text-decoration: none; text-shadow: 0 1px #fff; } + .header > .item.search input { width: 230px; } + .header .item.search input:focus { width: 350px; } /*=== Body */ #global { - background:#F9F7F4; + background: #f9f7f4; /* Header : 60px + 1px border bottom */ height: calc(100% - 61px); } + .aside { - box-shadow: 0 2px 2px #171717 inset; background: #222; width: 235px; + box-shadow: 0 2px 2px #171717 inset; } + .aside.aside_feed { padding: 10px 0; text-align: center; } + .aside.aside_feed .tree { margin: 10px 0 50px; } @@ -649,6 +723,7 @@ a.btn { color: #fff; text-shadow: 0 1px rgba(255,255,255,0.08); } + .aside_feed .btn-important { border: none; } @@ -658,13 +733,16 @@ a.btn { .feed.item.empty > a { color: #e67e22; } + .feed.item.error, .feed.item.error > a { - color: #BD362F; + color: #bd362f; } + .aside_feed .tree-folder-items .dropdown-menu::after { left: 2px; } + .aside_feed .tree-folder-items .item .dropdown-target:target ~ .dropdown-toggle > .icon, .aside_feed .tree-folder-items .item:hover .dropdown-toggle > .icon, .aside_feed .tree-folder-items .item.active .dropdown-toggle > .icon { @@ -676,9 +754,11 @@ a.btn { padding: 10px 50px; font-size: 0.9em; } + .post form { margin: 10px 0; } + .post.content { max-width: 550px; } @@ -689,44 +769,53 @@ a.btn { padding: 14px 0px; text-shadow: 0 1px rgba(255,255,255,0.08); } + .prompt label { text-align: left; } + .prompt form { margin: 10px auto 20px auto; width: 180px; } + .prompt input { margin: 5px auto; width: 100%; } + .prompt p { margin: 20px 0; } -.prompt input#username,.prompt input#passwordPlain{ - border:solid 1px #ccc; + +.prompt input#username,.prompt input#passwordPlain { + background: #fff; + border: solid 1px #ccc; box-shadow: 0 4px -4px #ccc inset,0px 1px rgba(255, 255, 255, 0.08); - background:#fff; } -.prompt input#username:focus,.prompt input#passwordPlain:focus{ - border: solid 1px #0062BE; - box-shadow: 0 0 3px #0062BE; + +.prompt input#username:focus,.prompt input#passwordPlain:focus { + border: solid 1px #0062be; + box-shadow: 0 0 3px #0062be; } /*=== New article notification */ #new-article { - background: #0084CC; + background: #0084cc; text-align: center; font-size: 0.9em; } + #new-article:hover { - background: #0066CC; + background: #06c; } + #new-article > a { line-height: 3em; color: #fff; font-weight: bold; } + #new-article > a:hover { text-decoration: none; } @@ -734,110 +823,131 @@ a.btn { /*=== Day indication */ .day { padding: 0 10px; - font-style:italic; - line-height: 3em; - box-shadow: 0 -1px #ccc, 0 -1px rgba(255,255,255,0.28) inset; - background: #F9F7F4; + background: #f9f7f4; color: #666; + box-shadow: 0 -1px #ccc, 0 -1px rgba(255,255,255,0.28) inset; + font-style: italic; + line-height: 3em; text-shadow: 0 1px rgba(255,255,255,0.28); text-align: center; } + #new-article + .day { border-top: none; } + .day .name { display: none; } /*=== Index menu */ .nav_menu { - background: linear-gradient(0deg, #EDE7DE 0%, #C2BCB3 100%) #EDE7DE; - background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #C2BCB3 100%); + padding: 5px 0; + background: linear-gradient(0deg, #ede7de 0%, #c2bcb3 100%) #ede7de; + background: -webkit-linear-gradient(bottom, #ede7de 0%, #c2bcb3 100%); border-bottom: 1px solid #ccc; - box-shadow:0 -1px rgba(255, 255, 255, 0.28) inset; + box-shadow: 0 -1px rgba(255, 255, 255, 0.28) inset; text-align: center; - padding: 5px 0; } -#panel >.nav_menu{ - background: linear-gradient(0deg, #EDE7DE 0%, #FFF 100%) #EDE7DE; - background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #FFF 100%); + +#panel >.nav_menu { + background: linear-gradient(0deg, #ede7de 0%, #fff 100%) #ede7de; + background: -webkit-linear-gradient(bottom, #ede7de 0%, #fff 100%); } -#panel > .nav_menu > #nav_menu_read_all{ - background: linear-gradient(0deg, #EDE7DE 0%, #FFF 100%) #EDE7DE; - background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #FFF 100%); + +#panel > .nav_menu > #nav_menu_read_all { + background: linear-gradient(0deg, #ede7de 0%, #fff 100%) #ede7de; + background: -webkit-linear-gradient(bottom, #ede7de 0%, #fff 100%); + border: 1px solid #ccc; border-radius: 4px; - border: 1px solid #CCC; - box-shadow: 0px 1px #FFF; + box-shadow: 0px 1px #fff; } + #panel > .nav_menu > #nav_menu_read_all .dropdown > .btn.dropdown-toggle { - border-radius: 0 4px 4px 0; - border:none; + border: none; border-left: solid 1px #ccc; + border-radius: 0 4px 4px 0; } /*=== Feed articles */ .flux_content { - background: #FFF; + background: #fff; } + .flux { - background: #F9F7F4; + background: #f9f7f4; } + .flux:hover { - background: #F9F7F4; + background: #f9f7f4; } + .flux:not(.current):hover .item.title { - background: #F9F7F4; + background: #f9f7f4; } + .flux.current .flux .item.title a { - text-shadow:0 0 2px #ccc; + text-shadow: 0 0 2px #ccc; } + .flux.not_read:not(.current):hover .item.title { - opacity:0.85; + opacity: 0.85; } + .flux.favorite { - background: #FFF6DA; + background: #fff6da; } -.flux.favorite:not(.current):hover{ - background: #F9F7F4; + +.flux.favorite:not(.current):hover { + background: #f9f7f4; } + .flux.favorite:not(.current):hover .item.title { - background: #F9F7F4; + background: #f9f7f4; } + .flux.current { - background: linear-gradient(0deg, #DAD4CB 0%, #FFF 100%) #DAD4CB; - background: -webkit-linear-gradient(bottom, #DAD4CB 0%, #FFF 100%); + background: linear-gradient(0deg, #dad4cb 0%, #fff 100%) #dad4cb; + background: -webkit-linear-gradient(bottom, #dad4cb 0%, #fff 100%); + border-left: solid 4px #0062bf; box-shadow: 0 -1px #fff inset, 0 2px #ccc; - border-left: solid 4px #0062BF; } .flux .item.title { -opacity: 0.35; + opacity: 0.35; } + .flux.favorite .item.title { -opacity: 1; + opacity: 1; } + .flux.not_read .item.title { -opacity: 1; + opacity: 1; } + .flux.current .item.title a { color: #0f0f0f; } + .flux .item.title a { color: #333; } .flux_header { - border-top: 1px solid #ddd; font-size: 0.8rem; - cursor: pointer; + border-top: 1px solid #ddd; box-shadow: 0 -1px rgba(255,255,255,0.28) inset; + cursor: pointer; } + .flux_header .title { font-size: 0.9rem; } + .flux .website .favicon { padding: 5px; } + .flux .date { color: #666; font-size: 0.7rem; @@ -852,14 +962,15 @@ opacity: 1; .content { padding: 20px 10px; } + .content > h1.title > a { color: #000; } .content hr { margin: 30px 10px; - height: 1px; background: #ddd; + height: 1px; border: 0; box-shadow: 0 2px 5px #ccc; } @@ -873,13 +984,15 @@ opacity: 1; font-size: 0.9rem; border-radius: 3px; } + .content code { padding: 2px 5px; - color: #dd1144; background: #fafafa; + color: #d14; border: 1px solid #eee; border-radius: 3px; } + .content pre code { background: transparent; color: #fff; @@ -887,14 +1000,15 @@ opacity: 1; } .content blockquote { - display: block; margin: 0; padding: 5px 20px; - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; background: #fafafa; + display: block; color: #333; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; } + .content blockquote p { margin: 0; } @@ -902,49 +1016,55 @@ opacity: 1; /*=== Notification and actualize notification */ .notification { padding: 0 0 0 5px; - text-align: center; - background:#222; + background: #222; + color: #fff; + font-size: 0.9em; border: none; border-radius: 0 0 6px 6px; box-shadow: 0px 0px 4px rgba(0,0,0,0.45), 0 -1px rgba(255,255,255,0.08) inset, 0 2px 2px #171717 inset; - color:#fff; + text-align: center; font-weight: bold; - font-size: 0.9em; line-height: 3em; - position:absolute; - top:0; + position: absolute; + top: 0; z-index: 10; vertical-align: middle; } + .notification.good { color: #fff; } + .notification.bad { - background: #222222; - color: #EB2901; + background: #222; + color: #eb2901; } + .notification a.close { padding: 0 15px; line-height: 3em; } + .notification#actualizeProgress { line-height: 2em; } /*=== "Load more" part */ #bigMarkAsRead { + background: #f9f7f4; + color: #666; + box-shadow: 0 1px rgba(255,255,255,0.28)inset; text-align: center; text-decoration: none; text-shadow: 0 -1px 0 #aaa; - color: #666; - background: #F9F7F4; - box-shadow: 0 1px rgba(255,255,255,0.28)inset; } + #bigMarkAsRead:hover { + background: #f9f7f4; + background: radial-gradient(circle at 50% -25% , #ccc 0%, #f9f7f4 50%); color: #000; - background: #F9F7F4; - background: radial-gradient(circle at 50% -25% , #ccc 0%, #F9F7F4 50%); } + #bigMarkAsRead:hover .bigTick { text-shadow: 0 0 10px #666; } @@ -953,26 +1073,27 @@ opacity: 1; #nav_entries { background: linear-gradient(180deg, #222 0%, #171717 100%) #222; background: -webkit-linear-gradient(top, #222 0%, #171717 100%); + width: 235px; border-top: 1px solid #171717; + box-shadow: 0 1px rgba(255,255,255,0.08) inset, 0 -2px 2px #171717; text-align: center; line-height: 3em; table-layout: fixed; - box-shadow: 0 1px rgba(255,255,255,0.08) inset, 0 -2px 2px #171717; - width:235px; } /*=== READER VIEW */ /*================*/ #stream.reader .flux { padding: 0 0 50px; - border: none; background: #f0f0f0; color: #333; + border: none; } + #stream.reader .flux .author { margin: 0 0 10px; - font-size: 90%; color: #666; + font-size: 90%; } /*=== GLOBAL VIEW */ @@ -982,39 +1103,39 @@ opacity: 1; } .box.category .box-title { - background: linear-gradient(0deg, #EDE7DE 0%, #fff 100%) #171717; - background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #fff 100%); - box-shadow: 0px -1px #fff inset,0 -2px #ccc inset; + background: linear-gradient(0deg, #ede7de 0%, #fff 100%) #171717; + font-size: 1.2rem; border-radius: none; + box-shadow: 0px -1px #fff inset,0 -2px #ccc inset; line-height: 2em; - font-size: 1.2rem; - text-shadow:0 1px #ccc; + text-shadow: 0 1px #ccc; } + .box.category .box-title .title { font-weight: normal; text-decoration: none; text-align: left; color: #888; } -.box.category:not([data-unread="0"]) .box-title { -} -.box.category:not([data-unread="0"]) .box-title:active { -} + .box.category:not([data-unread="0"]) .box-title .title { color: #222; font-weight: bold; } + .box.category .title:not([data-unread="0"])::after { + background: none; + border: 0; position: absolute; top: 5px; right: 10px; - border: 0; - background: none; font-weight: bold; } + .box.category .item.feed { padding: 2px 10px; font-size: 0.8rem; } + .box.category .item.feed:not(.empty):not(.error) .item-title { color: #222; } @@ -1022,18 +1143,20 @@ opacity: 1; /*=== PANEL */ /*===========*/ #panel { - box-shadow: 0px 0px 4px #000; + background: #f9f7f4; border-radius: 8px; - background:#F9F7F4; + box-shadow: 0px 0px 4px #000; } /*=== DIVERS */ /*===========*/ .aside.aside_feed .nav-form input,.aside.aside_feed .nav-form select { width: 130px; } + .aside.aside_feed .nav-form .dropdown .dropdown-menu { right: -20px; } + .aside.aside_feed .nav-form .dropdown .dropdown-menu::after { right: 33px; } @@ -1049,21 +1172,24 @@ opacity: 1; .stat tr { border: none; } + .stat > table td, .stat > table th { - border-bottom: 1px solid #ccc; background: rgba(255,255,255,0.38); + border-bottom: 1px solid #ccc; box-shadow: 0 1px #fff; } .stat > .horizontal-list { margin: 0 0 5px; } + .stat > .horizontal-list .item { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } + .stat > .horizontal-list .item:first-child { width: 250px; } @@ -1075,50 +1201,60 @@ opacity: 1; border-radius: 5px; overflow: hidden; } + .log { padding: 5px 10px; background: #fafafa; color: #333; font-size: 0.8rem; } + .log+.log { border-top: 1px solid #aaa; } + .log .date { display: block; font-weight: bold; } + .log.error { background: #fdd; color: #844; } + .log.warning { background: #ffe; color: #c95; } + .log.notice { background: #f4f4f4; color: #aaa; } + .log.debug { background: #333; color: #eee; } #slider.active { - box-shadow: -4px 0 4px rgba(15, 15, 15, 0.55); - background: #F8F8F8; + background: #f8f8f8; + box-shadow: -4px 0 4px rgba(15, 15, 15, 0.55); } + #close-slider.active { - background: rgba(15, 15, 15, 0.35); + background: rgba(15, 15, 15, 0.35); } /*=== MOBILE */ /*===========*/ + @media screen and (max-width: 840px) { .header { display: table; } + .nav-login { display: none; } @@ -1128,27 +1264,26 @@ opacity: 1; border-top: none; box-shadow: 3px 0 3px #000; transition: width 200ms linear; - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; } + .aside:target { width: 235px; } + .aside .toggle_aside, #panel .close { + background: #171717; display: block; width: 100%; height: 40px; line-height: 40px; text-align: center; - background: #171717; box-shadow: 0 1px rgba(255,255,255,0.08); } + .aside .btn-important { - display: inline-block; margin: 20px 0 0; + display: inline-block; } .aside.aside_feed { @@ -1158,20 +1293,24 @@ opacity: 1; .nav_menu .btn { margin: 5px 10px; } + .nav_menu .stick { margin: 0 10px; } + .nav_menu .stick .btn { margin: 5px 0; } + .nav_menu .search { - display: inline-block; - max-width: 97%; + display: none; } + .nav_menu .search input { max-width: 97%; width: 90px; } + .nav_menu .search input:focus { width: 400px; } @@ -1185,19 +1324,18 @@ opacity: 1; } .notification a.close { + background: transparent; display: block; left: 0; - background: transparent; } + .notification a.close:hover { opacity: 0.5; } + .notification a.close .icon { display: none; } - .nav_menu .search { - display: none; - } #nav_entries { width: 100%; @@ -1205,16 +1343,19 @@ opacity: 1; } @media (max-width: 700px) { - .header{ + .header { display: none; } + .nav-login { display: inline-block; width: 100%; } + .nav_menu .search { display: inline-block; } + .aside .btn-important { display: none; } diff --git a/p/themes/Dark/dark.css b/p/themes/Dark/dark.css index c82c36644..8322179f4 100644 --- a/p/themes/Dark/dark.css +++ b/p/themes/Dark/dark.css @@ -3,15 +3,15 @@ /*=== GENERAL */ /*============*/ html, body { - height: 100%; - font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", "PingFang SC", "Microsoft YaHei", sans-serif; background: #1c1c1c; + height: 100%; color: #888; + font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", "PingFang SC", "Microsoft YaHei", sans-serif; } /*=== Links */ a, button.as-link { - color: #6986B2; + color: #6986b2; outline: none; } @@ -28,47 +28,51 @@ legend { font-size: 1.4em; border-bottom: 1px solid #2f2f2f; } + label { min-height: 25px; padding: 5px 0; cursor: pointer; } + textarea { width: 360px; height: 100px; } + input, select, textarea { - min-height: 25px; padding: 5px; - line-height: 25px; - vertical-align: middle; background: #333; + color: #999; border: 1px solid #000; border-radius: 3px; - color: #999; box-shadow: 0 2px 2px #1d1d1d inset; + min-height: 25px; + line-height: 25px; + vertical-align: middle; } + option { padding: 0 .5em; } + input:focus, select:focus, textarea:focus { color: #6986b2; border-color: #2f2f2f; } + input:invalid, select:invalid { border-color: #f00; box-shadow: 0 0 2px 1px #f00; } + input:disabled, select:disabled { background: #666; color: #aaa; } + input.extend { transition: width 200ms linear; - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; } /*=== Tables */ @@ -80,9 +84,11 @@ tr, th, td { padding: 0.5em; border: 1px solid #333; } + th { background: #222; } + form td, form th { font-weight: normal; @@ -97,44 +103,54 @@ form th { background: #1a1a1a; border-top: 1px solid #2f2f2f; } + .form-group.form-actions .btn { margin: 0 10px; } + .form-group .group-name { padding: 10px 0; text-align: right; } + .form-group .group-controls { min-height: 25px; padding: 5px 0; } + .form-group table { margin: 10px 0 0 220px; } /*=== Buttons */ button.as-link[disabled] { - color:#445 !important; + color: #445 !important; } + .stick { vertical-align: middle; font-size: 0; } + .stick input, .stick .btn { border-radius: 0; } + .stick .btn:first-child, .stick input:first-child { border-radius: 3px 0 0 3px; } + .stick .btn-important:first-child { border-right: 1px solid #000; } + .stick .btn:last-child, .stick input:last-child { border-radius: 0 3px 3px 0; } + .stick .btn + .btn, .stick .btn + input, .stick .btn + .dropdown > .btn, @@ -146,80 +162,79 @@ button.as-link[disabled] { .stick .dropdown + .dropdown > .btn { border-left: none; } + .stick input:focus+input { border-left: 1px solid #000; } + .stick input+input:focus { border-left: 1px solid #333; } + .stick .btn + .dropdown > .btn { border-left: none; border-radius: 0 3px 3px 0; } .btn { + margin: 0; + padding: 5px 10px; + background: #111; display: inline-block; + color: #888; + font-size: 0.9rem; + border: 1px solid #000; + border-radius: 3px; min-height: 37px; min-width: 15px; line-height: 25px; - margin: 0; - padding: 5px 10px; - font-size: 0.9rem; vertical-align: middle; cursor: pointer; overflow: hidden; - background: #111; - border-radius: 3px; - border: 1px solid #000; - color: #888; } + a.btn { min-height: 25px; line-height: 25px; } + .btn:hover { text-decoration: none; - background: -moz-linear-gradient(top, #4A5D7A 0%, #26303F 100%); - background: -moz-linear-gradient(top, #4A5D7A 0%, #26303F 100%); - background: -webkit-linear-gradient(top, #4A5D7A 0%, #26303F 100%); - background: -o-linear-gradient(top, #4A5D7A 0%, #26303F 100%); - background: -ms-linear-gradient(top, #4A5D7A 0%, #26303F 100%); + background: linear-gradient(to top, #4a5d7a 0%, #26303f 100%); } + .btn.active, .dropdown-target:target ~ .btn.dropdown-toggle { background: #333; } + .btn:active { - background: #26303F; + background: #26303f; } .btn-important { font-weight: normal; - background: #26303F; + background: #26303f; } + .btn-important:hover { - background: linear-gradient(top, #4A5D7A 0%, #26303F 100%); - background: -moz-linear-gradient(top, #4A5D7A 0%, #26303F 100%); - background: -webkit-linear-gradient(top, #4A5D7A 0%, #26303F 100%); - background: -o-linear-gradient(top, #4A5D7A 0%, #26303F 100%); - background: -ms-linear-gradient(top, #4A5D7A 0%, #26303F 100%); + background: linear-gradient(to top, #4a5d7a 0%, #26303f 100%); } + .btn-important:active { - background: #26303F; + background: #26303f; } .btn-attention { - background: #880011; + background: #801; } + .btn-attention:hover { - background: linear-gradient(top, #cc0044 0%, #880011 100%); - background: -moz-linear-gradient(top, #cc0044 0%, #880011 100%); - background: -webkit-linear-gradient(top, #cc0044 0%, #880011 100%); - background: -o-linear-gradient(top, #cc0044 0%, #880011 100%); - background: -ms-linear-gradient(top, #cc0044 0%, #880011 100%); + background: linear-gradient(to top, #c04 0%, #801 100%); } + .btn-attention:active { - background: #880011; + background: #801; } /*=== Navigation */ @@ -229,42 +244,52 @@ a.btn { line-height: 2.5em; font-size: 0.9rem; } + .nav-list .item:hover { - background: #26303F; + background: #26303f; } + .nav-list .item.active { background: #333; } + .nav-list .item:hover a, .nav-list .item.active a { color: #888; } + .nav-list .disable { - text-align: center; - color: #aaa; background: #fafafa; + color: #aaa; + text-align: center; } + .nav-list .item > a { padding: 0 10px; } + .nav-list a:hover { text-decoration: none; } + .nav-list .item.empty a { color: #c95; } + .nav-list .item:hover.empty a, .nav-list .item.active.empty a { - color: #fff; background: #c95; + color: #fff; } + .nav-list .item.error a { color: #a44; } + .nav-list .item:hover.error a, .nav-list .item.active.error a { - color: #fff; background: #a44; + color: #fff; } .nav-list .nav-header { @@ -285,6 +310,7 @@ a.btn { background: #1c1c1c; border-bottom: 1px solid #333; } + .nav-head .item { padding: 5px 10px; font-size: 0.9rem; @@ -296,6 +322,7 @@ a.btn { margin: 0; padding: 0; } + .horizontal-list .item { vertical-align: middle; } @@ -304,34 +331,34 @@ a.btn { .dropdown-menu { margin: 5px 0 0; padding: 5px 0; - font-size: 0.8rem; - text-align: left; background: #1a1a1a; + font-size: 0.8rem; border: 1px solid #888; border-radius: 5px; + text-align: left; } + .dropdown-menu::after { + background: #1a1a1a; + width: 10px; + height: 10px; + border-top: 1px solid #888; + border-left: 1px solid #888; content: ""; position: absolute; top: -6px; right: 13px; - width: 10px; - height: 10px; z-index: -10; transform: rotate(45deg); - -moz-transform: rotate(45deg); - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); - background: #1a1a1a; - border-top: 1px solid #888; - border-left: 1px solid #888; } + .dropdown-header { padding: 0 5px 5px; font-weight: bold; text-align: left; color: #888; } + .dropdown-menu > .item > a, .dropdown-menu > .item > span, .dropdown-menu > .item > .as-link { @@ -339,18 +366,22 @@ a.btn { line-height: 2.5em; font-size: 0.8rem; } + .dropdown-menu > .item:hover { - background: #26303F; + background: #26303f; color: #888; } + .dropdown-menu > .item[aria-checked="true"] > a::before { font-weight: bold; margin: 0 0 0 -14px; } + .dropdown-menu > .item:hover > a { text-decoration: none; color: #888; } + .dropdown-menu .input select, .dropdown-menu .input input { margin: 0 auto 5px; @@ -367,48 +398,56 @@ a.btn { .alert { margin: 15px auto; padding: 10px 15px; - font-size: 0.9em; background: #111; + color: #aaa; + font-size: 0.9em; border: 1px solid #888; border-radius: 5px; - color: #aaa; } + .alert-head { font-size: 1.15em; } + .alert > a { text-decoration: underline; color: inherit; } + .alert-warn { - border: 1px solid #c95; color: #c95; + border: 1px solid #c95; } + .alert-success { - border: 1px solid #484; color: #484; + border: 1px solid #484; } + .alert-error { - border: 1px solid #a44; color: #a44; + border: 1px solid #a44; } /*=== Pagination */ .pagination { - text-align: center; - font-size: 0.8em; background: #1c1c1c; color: #888; + font-size: 0.8em; + text-align: center; } + .content .pagination { margin: 0; padding: 0; } + .pagination .item.pager-current { - font-weight: bold; - font-size: 1.5em; background: #111; + font-size: 1.5em; + font-weight: bold; } + .pagination .item a { display: block; font-style: italic; @@ -416,14 +455,17 @@ a.btn { text-decoration: none; color: #666; } + .pagination .item a:hover { background-color: #111; } + .pagination:first-child .item { border-bottom: 1px solid #333; } + .pagination:last-child .item { - border-top: 1px solid #333; + border-top: 1px solid #333; } .pagination .loading, @@ -436,13 +478,15 @@ a.btn { border: 1px solid #000; border-radius: 5px; } + .box .box-title { margin: 0; padding: 5px 10px; - background: #26303F; + background: #26303f; border-bottom: 1px solid #000; border-radius: 5px 5px 0 0; } + .box .box-content { max-height: 260px; } @@ -457,6 +501,7 @@ a.btn { .box .box-content .item .configure { visibility: hidden; } + .box .box-title:hover .configure, .box .box-content .item:hover .configure { visibility: visible; @@ -466,57 +511,70 @@ a.btn { .tree { margin: 10px 0; } + .tree-folder-title { - position: relative; padding: 0 10px; - line-height: 2.5rem; - font-size: 1rem; background: #1c1c1c; + font-size: 1rem; + position: relative; + line-height: 2.5rem; } + .tree-folder-title .title { background: inherit; color: #888; } + .tree-folder-title .title:hover { text-decoration: none; } + .tree-folder.active .tree-folder-title { background: #2c2c2c; font-weight: bold; } + .tree-folder-items { + background: #161616; border-top: 1px solid #222; border-bottom: 1px solid #222; - background: #161616; } + .tree-folder-items > .item { padding: 0 10px; line-height: 2.5rem; font-size: 0.8rem; } + .tree-folder-items > .item.active { background: #1c1c1c; } + .tree-folder-items > .item > a { text-decoration: none; } + .tree-folder-items > .item.active > a { color: #888; } /*=== Scrollbar */ + @supports (scrollbar-width: thin) { #sidebar { scrollbar-color: rgba(255, 255, 255, 0.05) rgba(0, 0, 0, 0.0); } + #sidebar:hover { scrollbar-color: rgba(255, 255, 255, 0.3) rgba(0, 0, 0, 0.0); } } + @supports not (scrollbar-width: thin) { #sidebar::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.1); } + #sidebar:hover::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.3); } @@ -528,24 +586,30 @@ a.btn { .header { height: 85px; } + .header > .item { padding: 10px; vertical-align: middle; text-align: center; border-bottom: 1px solid #333; } -.header > .item.title{ + +.header > .item.title { width: 230px; } + .header > .item.title h1 { margin: 0.5em 0; } + .header > .item.title h1 a { text-decoration: none; } + .header > .item.search input { width: 230px; } + .header .item.search input:focus { width: 350px; } @@ -554,55 +618,65 @@ a.btn { #global { height: calc(100% - 85px); } + .aside { - border-right: 1px solid #333; background: #1c1c1c; + border-right: 1px solid #333; } + .aside.aside_feed { padding: 10px 0; text-align: center; } + .aside.aside_feed .tree { margin: 10px 0 50px; } /*=== Aside main page (categories) */ .aside_feed .tree-folder-title > .title:not([data-unread="0"])::after { - position: absolute; - right: 0; margin: 10px 0; padding: 0 10px; + background: inherit; font-size: 0.9rem; + position: absolute; + right: 0; line-height: 1.5rem; - background: inherit; } /*=== Aside main page (feeds) */ .feed.item.empty.active { background: #c95; } + .feed.item.error.active { background: #a44; } + .feed.item.empty, .feed.item.empty > a { color: #c95; } + .feed.item.error, .feed.item.error > a { color: #a44; } + .feed.item.empty.active, .feed.item.empty.active > a { color: #111; } + .feed.item.error.active, .feed.item.error.active > a { color: #fff; } + .aside_feed .tree-folder-items .dropdown-menu::after { left: 2px; } + .aside_feed .tree-folder-items .item .dropdown-target:target ~ .dropdown-toggle > .icon, .aside_feed .tree-folder-items .item:hover .dropdown-toggle > .icon, .aside_feed .tree-folder-items .item.active .dropdown-toggle > .icon { @@ -615,9 +689,11 @@ a.btn { padding: 10px 50px; font-size: 0.9em; } + .post form { margin: 10px 0; } + .post.content { max-width: 550px; } @@ -626,35 +702,42 @@ a.btn { .prompt { text-align: center; } + .prompt label { text-align: left; } + .prompt form { margin: 10px auto 20px auto; width: 200px; } + .prompt input { margin: 5px auto; width: 100%; } + .prompt p { margin: 20px 0; } /*=== New article notification */ #new-article { - text-align: center; + background: #26303f; font-size: 0.9em; - background: #26303F; + text-align: center; } + #new-article:hover { - background: #4A5D7A; + background: #4a5d7a; } + #new-article > a { line-height: 3em; font-weight: bold; color: #fff; } + #new-article > a:hover { text-decoration: none; } @@ -667,13 +750,14 @@ a.btn { border-top: 1px solid #333; border-bottom: 1px solid #333; } + .day .name { padding: 0 10px 0 0; + color: #aab; font-size: 1.8em; opacity: 0.3; font-style: italic; text-align: right; - color: #aab; text-shadow: 0px -1px 0px #333; } @@ -688,18 +772,22 @@ a.btn { .flux { border-left: 2px solid #2f2f2f; } + .flux:hover { background: #111; } + .flux.current { - border-left: 2px solid #0062BE; background: #111; + border-left: 2px solid #0062be; } + .flux.not_read { - border-left: 2px solid #FF5300; + border-left: 2px solid #ff5300; } + .flux.favorite { - border-left: 2px solid #FFC300; + border-left: 2px solid #ffc300; } @@ -707,19 +795,24 @@ a.btn { font-size: 0.8rem; cursor: pointer; } + .flux_header .title { font-size: 0.9rem; } + .flux_header .item.title a { color: #888; } + .flux .website .favicon { margin: 5px; } + .flux .date { - font-size: 0.7rem; color: #666; + font-size: 0.7rem; } + .flux:not(.current):hover .item.title { background: #111; } @@ -733,14 +826,15 @@ a.btn { .content { padding: 20px 10px; } + .content > h1.title > a { color: #888; } .content hr { margin: 30px 10px; - height: 1px; background: #666; + height: 1px; border: 0; box-shadow: 0 2px 5px #666; } @@ -748,20 +842,22 @@ a.btn { .content pre { margin: 10px auto; padding: 10px 20px; - overflow: auto; background: #222; color: #fff; - border: 1px solid #000; font-size: 0.9rem; + border: 1px solid #000; border-radius: 3px; + overflow: auto; } + .content code { padding: 2px 5px; - color: #dd1144; background: #000; + color: #d14; border: 1px solid #333; border-radius: 3px; } + .content pre code { background: transparent; color: #fff; @@ -769,14 +865,15 @@ a.btn { } .content blockquote { - display: block; margin: 0; padding: 5px 20px; - border-top: 1px solid #444; - border-bottom: 1px solid #444; background: #222; + display: block; color: #999; + border-top: 1px solid #444; + border-bottom: 1px solid #444; } + .content blockquote p { margin: 0; } @@ -784,37 +881,43 @@ a.btn { /*=== Notification and actualize notification */ .notification { padding: 0 0 0 5px; + background: #111; + color: #c95; + font-size: 0.9em; + border: 1px solid #c95; + border-radius: 5px; + box-shadow: 0 0 5px #666; text-align: center; font-weight: bold; - font-size: 0.9em; line-height: 3em; z-index: 10; vertical-align: middle; - border-radius: 5px; - box-shadow: 0 0 5px #666; - background: #111; - color: #c95; - border: 1px solid #c95; } + .notification.good { border-color: #484; color: #484; } + .notification.bad { border-color: #a44; color: #a44; } + .notification a.close { padding: 0 15px; line-height: 3em; } + .notification a.close:hover { background: #222; border-radius: 0 3px 3px 0; } + .notification.good a.close:hover { background: #484; } + .notification.bad a.close:hover { background: #a44; } @@ -828,6 +931,7 @@ a.btn { text-align: center; text-decoration: none; } + #bigMarkAsRead:hover { background: #111; color: #aaa; @@ -847,13 +951,14 @@ a.btn { /*================*/ #stream.reader .flux { padding: 0 0 50px; - border: none; background: #111; + border: none; } + #stream.reader .flux .author { margin: 0 0 10px; - font-size: 90%; color: #666; + font-size: 90%; } /*=== GLOBAL VIEW */ @@ -864,25 +969,30 @@ a.btn { text-align: left; color: #888; } + .box.category:not([data-unread="0"]) .box-title { - background: #34495E; + background: #34495e; } + .box.category:not([data-unread="0"]) .box-title:active { - background: #26303F; + background: #26303f; } + .box.category:not([data-unread="0"]) .box-title .title { color: #fff; font-weight: bold; } + .box.category .title:not([data-unread="0"])::after { + background: none; + border: 0; position: absolute; top: 5px; right: 10px; - border: 0; - background: none; font-weight: bold; box-shadow: none; text-shadow: none; } + .box.category .item.feed { padding: 2px 10px; font-size: 0.8rem; @@ -907,9 +1017,11 @@ a.btn { .aside.aside_feed .nav-form select { width: 140px; } + .aside.aside_feed .nav-form .dropdown .dropdown-menu { right: -20px; } + .aside.aside_feed .nav-form .dropdown .dropdown-menu::after { right: 33px; } @@ -925,6 +1037,7 @@ a.btn { .stat tr { border: none; } + .stat > table td, .stat > table th { border-bottom: 1px solid #333; @@ -933,11 +1046,13 @@ a.btn { .stat > .horizontal-list { margin: 0 0 5px; } + .stat > .horizontal-list .item { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } + .stat > .horizontal-list .item:first-child { width: 270px; } @@ -948,31 +1063,38 @@ a.btn { overflow: hidden; border: 1px solid #333; } + .log { padding: 5px 10px; - font-size: 0.8rem; background: #111; color: #888; + font-size: 0.8rem; } + .log+.log { border-top: 1px solid #333; } + .log .date { display: block; font-weight: bold; } + .log.error { background: #a44; color: #fff; } + .log.warning { background: #c95; color: #fff; } + .log.notice { background: #ec9; color: #000; } + .log.debug { background: #111; color: #eee; @@ -980,23 +1102,21 @@ a.btn { /*=== MOBILE */ /*===========*/ -@media(max-width: 840px) { + +@media (max-width: 840px) { .aside { transition: width 200ms linear; - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; } + .aside .toggle_aside, #panel .close { + background: #111; display: block; width: 100%; height: 50px; + border-bottom: 1px solid #333; line-height: 50px; text-align: center; - background: #111; - border-bottom: 1px solid #333; } .aside.aside_feed { @@ -1006,20 +1126,25 @@ a.btn { .nav_menu .btn { margin: 5px 10px; } + .nav_menu .stick { margin: 0 10px; } + .nav_menu .stick .btn { margin: 5px 0; } + .nav_menu .search { display: inline-block; max-width: 97%; } + .nav_menu .search input { max-width: 97%; width: 90px; } + .nav_menu .search input:focus { width: 400px; } @@ -1038,13 +1163,16 @@ a.btn { border-left: none; border-radius: 0; } + .notification a.close { display: block; left: 0; } + .notification a.close:hover { opacity: 0.5; } + .notification a.close .icon { display: none; } diff --git a/p/themes/Flat/flat.css b/p/themes/Flat/flat.css index 9132a013b..f7159b46f 100644 --- a/p/themes/Flat/flat.css +++ b/p/themes/Flat/flat.css @@ -3,9 +3,9 @@ /*=== GENERAL */ /*============*/ html, body { + background: #fafafa; height: 100%; font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", "PingFang SC", "Microsoft YaHei", sans-serif; - background: #fafafa; } /*=== Links */ @@ -16,58 +16,62 @@ a, button.as-link { /*=== Forms */ legend { - display: inline-block; - width: auto; margin: 20px 0 5px; padding: 5px 20px; - font-size: 1.4em; - clear: both; background: #ecf0f1; + display: inline-block; + width: auto; + font-size: 1.4em; border-radius: 20px; + clear: both; } + label { min-height: 25px; padding: 5px 0; cursor: pointer; color: #444; } + textarea { width: 360px; height: 100px; } + input, select, textarea { - min-height: 25px; padding: 5px; - line-height: 25px; - vertical-align: middle; background: #fff; + color: #666; border: none; border-bottom: 3px solid #ddd; border-left-color: #ddd; - color: #666; border-radius: 5px; + min-height: 25px; + line-height: 25px; + vertical-align: middle; } + option { padding: 0 .5em; } + input:focus, select:focus, textarea:focus { color: #333; border-color: #2980b9; } + input:invalid, select:invalid { color: #f00; border-color: #f00; box-shadow: none; } + input:disabled, select:disabled { background: #eee; } + input.extend { transition: width 200ms linear; - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; } /*=== Tables */ @@ -79,9 +83,11 @@ tr, th, td { padding: 0.5em; border: 1px solid #ddd; } + th { background: #f6f6f6; } + form td, form th { font-weight: normal; @@ -96,17 +102,19 @@ form th { border: 1px solid transparent; border-radius: 3px; } + .form-group::after { content: ""; display: block; clear: both; } + .form-group:hover { background: #fff; border: 1px solid #eee; border-radius: 3px; - border: 1px solid #eee; } + .form-group.form-actions { margin: 15px 0 25px; padding: 5px 0; @@ -114,20 +122,25 @@ form th { border-top: 3px solid #bdc3c7; border-radius: 5px 5px 0 0; } + .form-group.form-actions .btn { margin: 0 10px; } + .form-group .group-name { padding: 10px 0; text-align: right; } + .form-group .group-controls { min-height: 25px; padding: 5px 0; } + .form-group .group-controls .control { line-height: 2.0em; } + .form-group table { margin: 10px 0 0 220px; } @@ -137,19 +150,23 @@ form th { vertical-align: middle; font-size: 0; } + .stick input, .stick .btn { border-radius: 0; } + .stick .btn:first-child, .stick input:first-child { border-radius: 5px 0 0 5px; } + .stick .btn:last-child, .stick input:last-child, .stick .btn + .dropdown > .btn { border-radius: 0 5px 5px 0; } + .stick .btn + .btn, .stick .btn + input, .stick .btn + .dropdown > .btn, @@ -164,30 +181,33 @@ form th { } .btn { + margin: 0; + padding: 5px 10px; + background: #3498db; display: inline-block; + color: #fff; + font-size: 0.9rem; + border: none; + border-bottom: 3px solid #2980b9; + border-left-color: #2980b9; + border-radius: 5px; min-height: 38px; min-width: 15px; line-height: 25px; - margin: 0; - padding: 5px 10px; - font-size: 0.9rem; vertical-align: middle; cursor: pointer; overflow: hidden; - background: #3498db; - border-radius: 5px; - border: none; - border-bottom: 3px solid #2980b9; - border-left-color: #2980b9; - color: #fff; } + a.btn { min-height: 25px; line-height: 25px; } + .btn:hover { text-decoration: none; } + .btn.active, .btn:active, .btn:hover, @@ -202,6 +222,7 @@ a.btn { border-bottom: 3px solid #d35400; border-left-color: #d35400; } + .btn-important:hover, .btn-important:active { background: #d35400; @@ -213,6 +234,7 @@ a.btn { border-bottom: 3px solid #c0392b; border-left-color: #c0392b; } + .btn-attention:hover, .btn-attention:active { background: #c0392b; @@ -225,41 +247,50 @@ a.btn { line-height: 2.5em; font-size: 0.9rem; } + .nav-list .item:hover, .nav-list .item.active { background: #2980b9; color: #fff; } + .nav-list .item:hover a, .nav-list .item.active a { color: #fff; } + .nav-list .disable { text-align: center; background: #fafafa; color: #aaa; } + .nav-list .item > a { padding: 0 10px; } + .nav-list a:hover { text-decoration: none; } + .nav-list .item.empty a { color: #f39c12; } + .nav-list .item:hover.empty a, .nav-list .item.active.empty a { - color: #fff; background: #f39c12; + color: #fff; } + .nav-list .item.error a { color: #bd362f; } + .nav-list .item:hover.error a, .nav-list .item.active.error a { - color: #fff; background: #bd362f; + color: #fff; } .nav-list .nav-header { @@ -280,9 +311,11 @@ a.btn { background: #34495e; color: #fff; } + .nav-head a { color: #fff; } + .nav-head .item { padding: 5px 10px; font-size: 0.9rem; @@ -294,42 +327,43 @@ a.btn { margin: 0; padding: 0; } + .horizontal-list .item { vertical-align: middle; } /*=== Dropdown */ .dropdown-menu { - background: #fafafa; margin: 5px 0 0; padding: 5px 0; + background: #fafafa; font-size: 0.8rem; - text-align: left; border: 1px solid #95a5a6; border-radius: 3px; + text-align: left; } + .dropdown-menu::after { + background: #fff; + width: 10px; + height: 10px; + border-top: 1px solid #95a5a6; + border-left: 1px solid #95a5a6; content: ""; position: absolute; top: -6px; right: 13px; - width: 10px; - height: 10px; z-index: -10; transform: rotate(45deg); - -moz-transform: rotate(45deg); - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); - background: #fff; - border-top: 1px solid #95a5a6; - border-left: 1px solid #95a5a6; } + .dropdown-header { padding: 0 5px 5px; font-weight: bold; text-align: left; color: #34495e; } + .dropdown-menu > .item > a, .dropdown-menu > .item > span, .dropdown-menu > .item > .as-link { @@ -337,18 +371,22 @@ a.btn { line-height: 2.5em; font-size: 0.8rem; } + .dropdown-menu > .item:hover { background: #2980b9; color: #fff; } + .dropdown-menu > .item[aria-checked="true"] > a::before { font-weight: bold; margin: 0 0 0 -14px; } + .dropdown-menu > .item:hover > a { text-decoration: none; color: #fff; } + .dropdown-menu .input select, .dropdown-menu .input input { margin: 0 auto 5px; @@ -365,55 +403,63 @@ a.btn { .alert { margin: 15px auto; padding: 10px 15px; - font-size: 0.9em; background: #f4f4f4; + color: #aaa; + font-size: 0.9em; border: 1px solid #ccc; border-right: 1px solid #aaa; border-bottom: 1px solid #aaa; border-radius: 5px; - color: #aaa; text-shadow: 0 0 1px #eee; } + .alert-head { font-size: 1.15em; } + .alert > a { text-decoration: underline; color: inherit; } + .alert-warn { background: #ffe; - border: 1px solid #eeb; color: #c95; + border: 1px solid #eeb; } + .alert-success { background: #dfd; - border: 1px solid #cec; color: #484; + border: 1px solid #cec; } + .alert-error { background: #fdd; - border: 1px solid #ecc; color: #844; + border: 1px solid #ecc; } /*=== Pagination */ .pagination { - text-align: center; - font-size: 0.8em; background: #ecf0f1; color: #000; + font-size: 0.8em; + text-align: center; } + .content .pagination { margin: 0; padding: 0; } + .pagination .item.pager-current { - font-weight: bold; - font-size: 1.5em; background: #34495e; color: #ecf0f1; + font-size: 1.5em; + font-weight: bold; } + .pagination .item a { display: block; font-style: italic; @@ -421,6 +467,7 @@ a.btn { text-decoration: none; color: #000; } + .pagination .item a:hover { background: #34495e; color: #ecf0f1; @@ -428,8 +475,8 @@ a.btn { .pagination .loading, .pagination a:hover.loading { - font-size: 0; background: url("loader.gif") center center no-repeat #34495e; + font-size: 0; } /*=== Boxes */ @@ -437,6 +484,7 @@ a.btn { border: 1px solid #ddd; border-radius: 5px; } + .box .box-title { margin: 0; padding: 5px 10px; @@ -445,6 +493,7 @@ a.btn { border-bottom: 1px solid #ddd; border-radius: 5px 5px 0 0; } + .box .box-content { max-height: 260px; } @@ -459,11 +508,13 @@ a.btn { .box .box-content .item .configure { visibility: hidden; } + .box .box-content .item .configure .icon { vertical-align: middle; background-color: #95a5a6; border-radius: 3px; } + .box .box-title:hover .configure, .box .box-content .item:hover .configure { visibility: visible; @@ -473,6 +524,7 @@ a.btn { .tree { margin: 10px 0; } + .tree-folder-title { position: relative; padding: 0 10px; @@ -480,46 +532,57 @@ a.btn { line-height: 2.5rem; font-size: 1rem; } + .tree-folder-title .title { background: inherit; color: #fff; } + .tree-folder-title .title:hover { text-decoration: none; } + .tree-folder.active .tree-folder-title { background: #2980b9; font-weight: bold; } + .tree-folder-items { background: #2c3e50; } + .tree-folder-items > .item { padding: 0 10px; line-height: 2.5rem; font-size: 0.8rem; } + .tree-folder-items > .item.active { background: #2980b9; } + .tree-folder-items > .item > a { text-decoration: none; color: #fff; } /*=== Scrollbar */ + @supports (scrollbar-width: thin) { #sidebar { scrollbar-color: rgba(255, 255, 255, 0.05) rgba(0, 0, 0, 0.0); } + #sidebar:hover { scrollbar-color: rgba(255, 255, 255, 0.3) rgba(0, 0, 0, 0.0); } } + @supports not (scrollbar-width: thin) { #sidebar::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.1); } + #sidebar:hover::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.3); } @@ -529,26 +592,32 @@ a.btn { /*===============*/ /*=== Header */ .header { - height: 85px; background: #ecf0f1; + height: 85px; } + .header > .item { padding: 10px; vertical-align: middle; text-align: center; } -.header > .item.title{ + +.header > .item.title { width: 230px; } + .header > .item.title h1 { margin: 0.5em 0; } + .header > .item.title h1 a { text-decoration: none; } + .header > .item.search input { width: 230px; } + .header .item.search input:focus { width: 350px; } @@ -557,54 +626,63 @@ a.btn { #global { height: calc(100% - 85px); } + .aside { background: #ecf0f1; } + .aside.aside_feed { padding: 10px 0; text-align: center; background: #34495e; border-radius: 0 10px 0 0; } + .aside.aside_feed .tree { margin: 10px 0 50px; } /*=== Aside main page (categories) */ .aside_feed .tree-folder-title > .title:not([data-unread="0"])::after { - position: absolute; - right: 0; margin: 10px 0; padding: 0 10px; + background: inherit; font-size: 0.9rem; + position: absolute; + right: 0; line-height: 1.5rem; - background: inherit; } /*=== Aside main page (feeds) */ .feed.item.empty.active { background: #f39c12; } + .feed.item.error.active { background: #bd362f; } + .feed.item.empty, .feed.item.empty > a { color: #e67e22; } + .feed.item.error, .feed.item.error > a { color: #bd362f; } + .feed.item.empty.active, .feed.item.error.active, .feed.item.empty.active > a, .feed.item.error.active > a { color: #fff; } + .aside_feed .tree-folder-items .dropdown-menu::after { left: 2px; } + .aside_feed .tree-folder-items .item .dropdown-target:target ~ .dropdown-toggle > .icon, .aside_feed .tree-folder-items .item:hover .dropdown-toggle > .icon, .aside_feed .tree-folder-items .item.active .dropdown-toggle > .icon { @@ -616,9 +694,11 @@ a.btn { padding: 10px 50px; font-size: 0.9em; } + .post form { margin: 10px 0; } + .post.content { max-width: 550px; } @@ -627,35 +707,42 @@ a.btn { .prompt { text-align: center; } + .prompt label { text-align: left; } + .prompt form { margin: 10px auto 20px auto; width: 200px; } + .prompt input { margin: 5px auto; width: 100%; } + .prompt p { margin: 20px 0; } /*=== New article notification */ #new-article { - text-align: center; - font-size: 0.9em; background: #3498db; + font-size: 0.9em; + text-align: center; } + #new-article:hover { background: #2980b9; } + #new-article > a { line-height: 3em; font-weight: bold; color: #fff; } + #new-article > a:hover { text-decoration: none; } @@ -667,13 +754,14 @@ a.btn { line-height: 3em; border-left: 2px solid #ecf0f1; } + .day .name { padding: 0 10px 0 0; + color: #aab; font-size: 1.8em; opacity: 0.3; font-style: italic; text-align: right; - color: #aab; } /*=== Index menu */ @@ -692,45 +780,54 @@ a.btn { .flux { border-left: 2px solid #ecf0f1; } + .flux:hover { background: #fff; } + .flux.current { + background: #fff; border-left-color: #3498db; } + .flux.not_read { - background: #FFF3ED; - border-left-color: #FF5300; + border-left-color: #ff5300; } + +.flux.not_read:not(.current) { + background: #fff3ed; +} + .flux.not_read:not(.current):hover .item.title { - background: #FFF3ED; + background: inherit; } + .flux.favorite { - background: #FFF6DA; - border-left-color: #FFC300; + background: #fff6da; + border-left-color: #ffc300; } + .flux.favorite:not(.current):hover .item.title { - background: #FFF6DA; -} -.flux.current { - background: #fff; + background: #fff6da; } - .flux_header { font-size: 0.8rem; cursor: pointer; border-top: 1px solid #ecf0f1; } + .flux_header .title { font-size: 0.9rem; } + .flux .website .favicon { padding: 5px; } + .flux .date { - font-size: 0.7rem; color: #666; + font-size: 0.7rem; } .flux .bottom { @@ -742,14 +839,15 @@ a.btn { .content { padding: 20px 10px; } + .content > h1.title > a { color: #000; } .content hr { margin: 30px 10px; - height: 1px; background: #ddd; + height: 1px; border: 0; box-shadow: 0 2px 5px #ccc; } @@ -763,13 +861,15 @@ a.btn { font-size: 0.9rem; border-radius: 3px; } + .content code { padding: 2px 5px; - color: #dd1144; background: #fafafa; + color: #d14; border: 1px solid #eee; border-radius: 3px; } + .content pre code { background: transparent; color: #fff; @@ -777,14 +877,15 @@ a.btn { } .content blockquote { - display: block; margin: 0; padding: 5px 20px; - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; background: #fafafa; + display: block; color: #333; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; } + .content blockquote p { margin: 0; } @@ -792,33 +893,38 @@ a.btn { /*=== Notification and actualize notification */ .notification { padding: 0 0 0 5px; + background: #ddd; + color: #666; + font-size: 0.9em; + border: none; + border-radius: 3px; text-align: center; font-weight: bold; - font-size: 0.9em; line-height: 3em; z-index: 10; vertical-align: middle; - background: #ddd; - color: #666; - border-radius: 3px; - border: none; } + .notification.good { background: #1abc9c; color: #fff; } + .notification.bad { background: #e74c3c; color: #fff; } + .notification a.close { padding: 0 15px; line-height: 3em; border-radius: 0 3px 3px 0; } + .notification.good a.close:hover { background: #16a085; } + .notification.bad a.close:hover { background: #c0392b; } @@ -833,6 +939,7 @@ a.btn { text-decoration: none; background: #ecf0f1; } + #bigMarkAsRead:hover { background: #34495e; color: #fff; @@ -855,10 +962,11 @@ a.btn { color: #34495e; border: none; } + #stream.reader .flux .author { margin: 0 0 10px; - font-size: 90%; color: #999; + font-size: 90%; } /*=== GLOBAL VIEW */ @@ -868,25 +976,30 @@ a.btn { text-decoration: none; text-align: left; } + .box.category:not([data-unread="0"]) .box-title { background: #3498db; } + .box.category:not([data-unread="0"]) .box-title:active { background: #2980b9; } + .box.category:not([data-unread="0"]) .box-title .title { font-weight: bold; color: #fff; } + .box.category .title:not([data-unread="0"])::after { + background: none; + border: 0; position: absolute; top: 5px; right: 10px; - border: 0; - background: none; font-weight: bold; box-shadow: none; text-shadow: none; } + .box.category .item.feed { padding: 2px 10px; font-size: 0.8rem; @@ -898,9 +1011,11 @@ a.btn { .aside.aside_feed .nav-form select { width: 140px; } + .aside.aside_feed .nav-form .dropdown .dropdown-menu { right: -20px; } + .aside.aside_feed .nav-form .dropdown .dropdown-menu::after { right: 33px; } @@ -916,6 +1031,7 @@ a.btn { .stat tr { border: none; } + .stat > table td, .stat > table th { border-bottom: 1px solid #ddd; @@ -924,11 +1040,13 @@ a.btn { .stat > .horizontal-list { margin: 0 0 5px; } + .stat > .horizontal-list .item { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } + .stat > .horizontal-list .item:first-child { width: 270px; } @@ -939,13 +1057,14 @@ a.btn { overflow: hidden; border: 1px solid #aaa; } + .log { margin: 10px 0; padding: 5px 2%; - overflow: auto; - font-size: 0.8rem; background: #fafafa; color: #666; + font-size: 0.8rem; + overflow: auto; } .log > .date { @@ -953,16 +1072,20 @@ a.btn { padding: 5px 10px; border-radius: 20px; } + .log.error > .date { background: #e74c3c; color: #fff; } + .log.warning > .date { background: #f39c12; } + .log.notice > .date { background: #ecf0f1; } + .log.debug > .date { background: #111; color: #eee; @@ -970,22 +1093,20 @@ a.btn { /*=== MOBILE */ /*===========*/ -@media(max-width: 840px) { + +@media (max-width: 840px) { .aside { transition: width 200ms linear; - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; } + .aside .toggle_aside, #panel .close { + background: #2c3e50; display: block; width: 100%; height: 50px; line-height: 50px; text-align: center; - background: #2c3e50; } .aside.aside_feed { @@ -995,20 +1116,25 @@ a.btn { .nav_menu .btn { margin: 5px 10px; } + .nav_menu .stick { margin: 0 10px; } + .nav_menu .stick .btn { margin: 5px 0; } + .nav_menu .search { display: inline-block; max-width: 97%; } + .nav_menu .search input { max-width: 97%; width: 90px; } + .nav_menu .search input:focus { width: 400px; } @@ -1024,14 +1150,17 @@ a.btn { .notification { border-radius: 0; } + .notification a.close { + background: transparent; display: block; left: 0; - background: transparent; } + .notification a.close:hover { opacity: 0.5; } + .notification a.close .icon { display: none; } diff --git a/p/themes/Mapco/_components.scss b/p/themes/Mapco/_components.scss index 342a428f5..79b2effa4 100644 --- a/p/themes/Mapco/_components.scss +++ b/p/themes/Mapco/_components.scss @@ -5,119 +5,118 @@ /*=== Horizontal-list */ .horizontal-list { - margin: 0; - padding: 0.1rem 0; + margin: 0; + padding: 0.1rem 0; - .item{ - vertical-align: middle; + .item { + vertical-align: middle; - &:first-child{ - padding-left: 0.5rem; - } + &:first-child { + padding-left: 0.5rem; + } - } + } } /*=== Dropdown */ .dropdown-menu { - background: $grey-lighter; - margin: 0; - font-size: 1rem; - text-align: left; - padding: 0.5rem 0 1rem 0; - border: none; - border-radius: 3px; - - -webkit-box-shadow: 0px 6px 8px 0px rgba(0,0,0,0.35); - -moz-box-shadow: 0px 6px 8px 0px rgba(0,0,0,0.35); - box-shadow: 0px 6px 8px 0px rgba(0,0,0,0.35); - - &::after { - content: ""; - position: absolute; - top: -4px; - right: 13px; - width: 10px; - height: 10px; - z-index: -10; - transform: rotate(45deg); - -moz-transform: rotate(45deg); - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); - background: white; - // border-top: 1px solid #95a5a6; - // border-left: 1px solid #95a5a6; - } - - .dropdown-header { - // padding: 0 5px 5px; - margin: 1.75rem 0 0.5rem 2rem; - font-weight: bold; + margin: 0; + padding: 0.5rem 0 1rem 0; + background: $grey-lighter; + font-size: 1rem; + border: none; + border-radius: 3px; + box-shadow: 0px 6px 8px 0px rgba(0,0,0,0.35); text-align: left; - color: $grey-dark; - text-transform: uppercase; - letter-spacing: 1px; - - - } - - .item{ - @include transition(all, 0.075s, ease-in-out); - a, span, .as-link{ - padding: 0 2rem; - line-height: 2.5em; - font-size: 1rem; - color: $main-font-color; - } - &:hover{ - background: $main-first; - color: $white; - a, button{ - text-decoration: none; - color: $white; - } + &::after { + background: white; + width: 10px; + height: 10px; + content: ""; + position: absolute; + top: -4px; + right: 13px; + z-index: -10; + transform: rotate(45deg); + // border-top: 1px solid #95a5a6; + // border-left: 1px solid #95a5a6; } - &[aria-checked="true"]{ - a::before{ + + .dropdown-header { + // padding: 0 5px 5px; + margin: 1.75rem 0 0.5rem 2rem; font-weight: bold; - margin: 0 0 0 -14px; - } + text-align: left; + color: $grey-dark; + text-transform: uppercase; + letter-spacing: 1px; + } - } - .input{ - select, input{ - margin: 0 auto 5px; - padding: 2px 5px; - border-radius: 3px; + + .item { + + @include transition(all, 0.075s, ease-in-out); + + a, span, .as-link { + padding: 0 2rem; + color: $main-font-color; + font-size: 1rem; + line-height: 2.5em; + } + + &:hover { + background: $main-first; + color: $white; + + a, button { + text-decoration: none; + color: $white; + } + } + + &[aria-checked="true"] { + a::before { + margin: 0 0 0 -14px; + font-weight: bold; + } + } } - } - .separator { - margin: 0.75rem 0; - border-bottom: 1px solid $grey-light; - // display: none; - } -} -.tree .tree-folder .tree-folder-items .dropdown-menu, -.tree .tree-folder .tree-folder-items .dropdown-menu{ - // tout ça sert à restaurer l'apparence du dropdown dans un contexte de sidebar sombre - - .item{ - padding: 0; - - a, - button{ - color: $main-font-color; - - &:hover{ - color: $white; - } + .input { + select, input { + margin: 0 auto 5px; + padding: 2px 5px; + border-radius: 3px; + } } - &:hover{ - background: $main-first; + + .separator { + margin: 0.75rem 0; + border-bottom: 1px solid $grey-light; + // display: none; + } + +} + +.tree .tree-folder .tree-folder-items .dropdown-menu { + // tout ça sert à restaurer l'apparence du dropdown dans un contexte de sidebar sombre + .item { + padding: 0; + + a, + button { + color: $main-font-color; + + &:hover { + color: $white; + } + } + + &:hover { + background: $main-first; + } } - } } @@ -125,255 +124,274 @@ /*=== Alerts */ .alert { - margin: 1rem 0; - // width: 100%; - padding: 1rem; - font-size: 1rem; - background: $grey-lighter; - border: 1px solid $grey-medium; - border-radius: 3px; - color: $grey-dark; - text-shadow: 0 0 1px $grey-light; + margin: 1rem 0; + // width: 100%; + padding: 1rem; + background: $grey-lighter; + color: $grey-dark; + font-size: 1rem; + border: 1px solid $grey-medium; + border-radius: 3px; + text-shadow: 0 0 1px $grey-light; } + .alert-head { - font-size: 1.15em; + font-size: 1.15em; } + .alert > a { - text-decoration: underline; - color: inherit; + text-decoration: underline; + color: inherit; } + .alert-warn { - background: $warning-light; - border: 1px solid unquote($warning-text+'33'); // on ajoute l'opacité à la fin - color: $warning-text; + background: $warning-light; + color: $warning-text; + border: 1px solid unquote($warning-text+'33'); // on ajoute l'opacité à la fin } + .alert-success { - background: $success-light; - border: 1px solid unquote($success-text+'33'); - color: $success-text; + background: $success-light; + color: $success-text; + border: 1px solid unquote($success-text+'33'); } + .alert-error { - background: $alert-light; - border: 1px solid unquote($alert-text+'33'); - color: $alert-text; + background: $alert-light; + color: $alert-text; + border: 1px solid unquote($alert-text+'33'); } /*=== Pagination */ .pagination { - text-align: center; - font-size: 0.8em; - background: $grey-light; - color: $main-font-color; - - .item{ - &.pager-current { - font-weight: bold; - font-size: 1.5em; - background: $sid-bg; - color: $grey-light; - } - a { - display: block; - font-style: italic; - line-height: 3em; - text-decoration: none; - color: $main-font-color; - - &:hover{ - background: $main-font-color; - color: $grey-light; - } + background: $grey-light; + color: $main-font-color; + font-size: 0.8em; + text-align: center; + + .item { + &.pager-current { + background: $sid-bg; + color: $grey-light; + font-size: 1.5em; + font-weight: bold; + } + + a { + display: block; + color: $main-font-color; + font-style: italic; + line-height: 3em; + text-decoration: none; + + &:hover { + background: $main-font-color; + color: $grey-light; + } + } } - } - .loading, - a:hover.loading { - font-size: 0; - background: url("loader.gif") center center no-repeat #34495e; - } + .loading, + a:hover.loading { + background: url("loader.gif") center center no-repeat #34495e; + font-size: 0; + } } + .content .pagination { - margin: 0; - padding: 0; + margin: 0; + padding: 0; } /*=== Boxes */ .box { - // border: 1px solid #ddd; - border: none; - border-radius: 3px; - background: $white; - - -webkit-box-shadow: 0px 2px 2px 0px rgba(0,0,0,0.25); - -moz-box-shadow: 0px 2px 2px 0px rgba(0,0,0,0.25); - box-shadow: 0px 2px 2px 0px rgba(0,0,0,0.25); + background: $white; + // border: 1px solid #ddd; + border: none; + border-radius: 3px; + + box-shadow: 0px 2px 2px 0px rgba(0,0,0,0.25); + + .box-title { + margin: 0; + padding: 5px 10px; + background: $grey-light; + color: $main-font-color; + // border-bottom: 1px solid #ddd; + border-radius: 2px 2px 0 0; + + img { + margin-right: 0.75rem; + } - .box-title { - margin: 0; - padding: 5px 10px; - background: $grey-light; - color: $main-font-color; - // border-bottom: 1px solid #ddd; - border-radius: 2px 2px 0 0; - img{ - margin-right: 0.75rem; - } + &:hover { + .configure { + background: url("icons/cog.svg") no-repeat 4px 4px; + display: block; + float: left; + width: 1.75rem; + height: 1.75rem; + border-radius: 2px; + visibility: visible; + margin-right: 0.5rem; + + .icon { + display: none; + border-radius: 3px; + vertical-align: middle; + } + + &:hover { + background: url("icons/cog-white.svg") no-repeat 4px 4px $main-first; + } + } + } - &:hover{ .configure { - visibility: visible; - background: url("icons/cog.svg") no-repeat 4px 4px; - width: 1.75rem; - height: 1.75rem; - display: block; - border-radius: 2px; - float: left; - margin-right: 0.5rem; - .icon { - vertical-align: middle; - border-radius: 3px; - display: none; + visibility: hidden; + } + + form { + input { + width: 85%; } - &:hover { - background: url("icons/cog-white.svg") no-repeat 4px 4px $main-first; + + .dropdown { + float: right; + + a.dropdown-toggle { + padding: 0; + // float: right; + border-radius: 0; + background-image: url(icons/more.svg); + background-repeat: no-repeat; + background-position: right 8px; + + img { + display: none; + } + } } } } - .configure { - visibility: hidden; - } - form{ - input{ - width: 85%; - } - .dropdown{ - float: right; - a.dropdown-toggle{ - padding: 0; - background-image: url(icons/more.svg); - background-repeat: no-repeat; - background-position: right 8px; - // float: right; - border-radius: 0; - img{ - display: none; - } - } - } - } - } - .box-content { - // max-height: 260px; + .box-content { + // max-height: 260px; + .item { + padding: 0.5rem 0.75rem; + color: $main-font-color; + font-size: 1rem; + border-bottom: 1px solid $grey-light; + line-height: 1.7em; + + img { + margin-right: 0.75rem; + } - .item { - padding: 0.5rem 0.75rem; - font-size: 1rem; - color: $main-font-color; - line-height: 1.7em; - border-bottom: 1px solid $grey-light; - - img{ - margin-right: 0.75rem; - } - - .configure { - visibility: hidden; - width: 1.75rem; - height: 1.75rem; - display: block; - border-radius: 2px; - float: left; - margin-right: 0.5rem; - background: url("icons/cog.svg") no-repeat 4px 4px; - - .icon { - vertical-align: middle; - border-radius: 3px; - display: none; + .configure { + background: url("icons/cog.svg") no-repeat 4px 4px; + display: block; + float: left; + width: 1.75rem; + height: 1.75rem; + border-radius: 2px; + visibility: hidden; + margin-right: 0.5rem; + + .icon { + display: none; + border-radius: 3px; + vertical-align: middle; + } + + &:hover { + // background: $main-first; + background: url("icons/cog-white.svg") no-repeat 4px 4px $main-first; + } + } + + &:hover .configure { + visibility: visible; + } } - &:hover{ - // background: $main-first; - background: url("icons/cog-white.svg") no-repeat 4px 4px $main-first; + + .item:last-child { + border-bottom: none; } - } - &:hover .configure { - visibility: visible; - } } - .item:last-child{ - border-bottom: none; - } - } } /*=== "Load more" part */ #bigMarkAsRead { - text-align: center; - text-decoration: none; - background: $main-first-light; - color: $main-first; + text-align: center; + text-decoration: none; + background: $main-first-light; + color: $main-first; - @include transition(all, 0.15s, ease-in-out); + @include transition(all, 0.15s, ease-in-out); - &:hover { - background: $main-first; - color: #fff; + &:hover { + background: $main-first; + color: #fff; - .bigTick{ - background: url(icons/tick-white.svg) center no-repeat; + .bigTick { + background: url(icons/tick-white.svg) center no-repeat; + } + } + + .bigTick { + margin: 0.5rem 0; + background: url(icons/tick-color.svg) center no-repeat; + display: inline-block; + width: 64px; + height: 64px; + text-indent: -9999px; + white-space: nowrap; } - } - .bigTick{ - margin: 0.5rem 0; - display: inline-block; - text-indent: -9999px; - background: url(icons/tick-color.svg) center no-repeat; - height: 64px; - width: 64px; - white-space: nowrap; - } } // page de login -.formLogin{ - background: $sid-bg; - - .header{ - .configure{ - padding-right: 1rem; - img{ - margin-right: 0.5rem; - } - - a.signin{ +.formLogin { + background: $sid-bg; + + .header { + .configure { + padding-right: 1rem; + + img { + margin-right: 0.5rem; + } + + a.signin { + color: $white; + } + } + } + + h1 { color: $white; - } } - } - - h1{ - color: $white; - } - form#crypto-form{ - div{ - margin-bottom: 1rem; - - label{ - font-size: 1rem; - color: $grey-medium; - - - } - input{ - background: $main-first-darker; - - &:focus{ - background: $grey-lighter; - color: $main-font-color; + + form#crypto-form { + div { + margin-bottom: 1rem; + + label { + color: $grey-medium; + font-size: 1rem; + + + } + + input { + background: $main-first-darker; + + &:focus { + background: $grey-lighter; + color: $main-font-color; + } + } } - } - } - } + } } diff --git a/p/themes/Mapco/_configuration.scss b/p/themes/Mapco/_configuration.scss index c3c6fd417..78e471f19 100644 --- a/p/themes/Mapco/_configuration.scss +++ b/p/themes/Mapco/_configuration.scss @@ -1,90 +1,89 @@ /*=== Configuration pages */ .post { - padding: 1rem 2rem; - font-size: 1rem; - - form { - margin: 1rem 0; - - // Gestion des extensions - .horizontal-list{ - margin-bottom: 0.5rem; - - .item{ - .stick{ - // width: 65%; - // margin-right: 1rem; - // display:flex; - - } - .btn{ - // width: 8rem; - // flex-grow: 1; + padding: 1rem 2rem; + font-size: 1rem; + + form { + margin: 1rem 0; + + // Gestion des extensions + .horizontal-list { + margin-bottom: 0.5rem; + + .item { + .stick { + // width: 65%; + // margin-right: 1rem; + // display:flex; + + } + + .btn { + // width: 8rem; + // flex-grow: 1; + } + } + } - } - } - } - &.content { - max-width: 550px; - } - - h1, h2{ // pages titles - font-size: 3rem; - margin-top: 1.75rem; - font-weight: 300; - line-height: 1.2em; - // font-family: "spectral"; - color: $main-font-color; - } - - a[href="./"]{ // C'est le bouton "Retour à vos flux" - display: inline-block; - // min-height: 38px; - min-width: 15px; - line-height: 25px; - margin: 0; - padding: 0.75rem 1.5rem; - font-size: 1rem; - vertical-align: middle; - cursor: pointer; - overflow: hidden; - background: $grey-lighter; - border: 1px solid $grey-medium-light; - border-radius: 5px; - // border: none; - color: $grey-dark; - - &:hover{ - text-decoration: none; - background: $main-first; - color: white; - border: 1px solid $main-first; + + &.content { + max-width: 550px; + } + + h1, h2 { // pages titles + // font-family: "spectral"; + color: $main-font-color; + font-size: 3rem; + margin-top: 1.75rem; + font-weight: 300; + line-height: 1.2em; } - } - + + a[href="./"] { // C'est le bouton "Retour à vos flux" + margin: 0; + padding: 0.75rem 1.5rem; + background: $grey-lighter; + display: inline-block; + // border: none; + color: $grey-dark; + font-size: 1rem; + border: 1px solid $grey-medium-light; + border-radius: 5px; + // min-height: 38px; + min-width: 15px; + line-height: 25px; + vertical-align: middle; + cursor: pointer; + overflow: hidden; + + &:hover { + background: $main-first; + color: white; + border: 1px solid $main-first; + text-decoration: none; + } + } + } -#slider{ - border-left: none; - - -webkit-box-shadow: 0px 6px 8px 0px rgba(0,0,0,0.35); - -moz-box-shadow: 0px 6px 8px 0px rgba(0,0,0,0.35); - box-shadow: 0px 6px 8px 0px rgba(0,0,0,0.35); +#slider { + border-left: none; + box-shadow: 0px 6px 8px 0px rgba(0,0,0,0.35); } -.slide-container{ - .properties{ - background: rgba(0, 0, 0, 0.75); - border: 0; - padding: 1rem; - color: white; +.slide-container { + .properties { + padding: 1rem; + background: rgba(0, 0, 0, 0.75); + color: white; + border: 0; - .page-number{ - right: 1rem; - top: 1rem; + .page-number { + right: 1rem; + top: 1rem; + } } - } } diff --git a/p/themes/Mapco/_divers.scss b/p/themes/Mapco/_divers.scss index 7d122f1b4..c6fb1f283 100644 --- a/p/themes/Mapco/_divers.scss +++ b/p/themes/Mapco/_divers.scss @@ -4,9 +4,11 @@ .aside.aside_feed .nav-form select { width: 140px; } + .aside.aside_feed .nav-form .dropdown .dropdown-menu { right: -20px; } + .aside.aside_feed .nav-form .dropdown .dropdown-menu::after { right: 33px; } diff --git a/p/themes/Mapco/_fonts.scss b/p/themes/Mapco/_fonts.scss index 5891be834..da1cf4ac7 100644 --- a/p/themes/Mapco/_fonts.scss +++ b/p/themes/Mapco/_fonts.scss @@ -1,56 +1,63 @@ @font-face { - font-family: "lato"; - font-style: normal; - font-stretch: normal; - font-weight: 400; - src: local("Lato"), url("../fonts/LatoLatin-Regular.woff") format("woff"); -} -@font-face { - font-family: "lato"; - font-style: italic; - font-stretch: normal; - font-weight: 400; - src: local("Lato"), url("../fonts/LatoLatin-Italic.woff") format("woff"); -} -@font-face { - font-family: "lato"; - font-style: normal; - font-stretch: normal; - font-weight: 700; - src: local("Lato"), url("../fonts/LatoLatin-Bold.woff") format("woff"); -} -@font-face { - font-family: "lato"; - font-style: italic; - font-stretch: normal; - font-weight: 700; - src: local("Lato"), url("../fonts/LatoLatin-BoldItalic.woff") format("woff"); -} -@font-face { - font-family: "spectral"; - font-style: normal; - font-stretch: normal; - font-weight: 400; - src: local("Spectral"), url("../fonts/Spectral-Regular.woff") format("woff"); -} -@font-face { - font-family: "spectral"; - font-style: italic; - font-stretch: normal; - font-weight: 400; - src: local("Spectral"), url("../fonts/Spectral-Italic.woff") format("woff"); -} -@font-face { - font-family: "spectral"; - font-style: normal; - font-stretch: normal; - font-weight: 700; - src: local("Spectral"), url("../fonts/Spectral-Bold.woff") format("woff"); -} -@font-face { - font-family: "spectral"; - font-style: italic; - font-stretch: normal; - font-weight: 700; - src: local("Spectral"), url("../fonts/Spectral-BoldItalic.woff") format("woff"); + font-family: "lato"; + font-style: normal; + font-stretch: normal; + font-weight: 400; + src: local("Lato"), url("../fonts/LatoLatin-Regular.woff") format("woff"); +} + +@font-face { + font-family: "lato"; + font-style: italic; + font-stretch: normal; + font-weight: 400; + src: local("Lato"), url("../fonts/LatoLatin-Italic.woff") format("woff"); +} + +@font-face { + font-family: "lato"; + font-style: normal; + font-stretch: normal; + font-weight: 700; + src: local("Lato"), url("../fonts/LatoLatin-Bold.woff") format("woff"); +} + +@font-face { + font-family: "lato"; + font-style: italic; + font-stretch: normal; + font-weight: 700; + src: local("Lato"), url("../fonts/LatoLatin-BoldItalic.woff") format("woff"); +} + +@font-face { + font-family: "spectral"; + font-style: normal; + font-stretch: normal; + font-weight: 400; + src: local("Spectral"), url("../fonts/Spectral-Regular.woff") format("woff"); +} + +@font-face { + font-family: "spectral"; + font-style: italic; + font-stretch: normal; + font-weight: 400; + src: local("Spectral"), url("../fonts/Spectral-Italic.woff") format("woff"); +} + +@font-face { + font-family: "spectral"; + font-style: normal; + font-stretch: normal; + font-weight: 700; + src: local("Spectral"), url("../fonts/Spectral-Bold.woff") format("woff"); +} + +@font-face { + font-family: "spectral"; + font-style: italic; + font-stretch: normal; + font-weight: 700; + src: local("Spectral"), url("../fonts/Spectral-BoldItalic.woff") format("woff"); } diff --git a/p/themes/Mapco/_forms.scss b/p/themes/Mapco/_forms.scss index 8a388aa8a..ff1691894 100644 --- a/p/themes/Mapco/_forms.scss +++ b/p/themes/Mapco/_forms.scss @@ -1,152 +1,160 @@ /* btns */ - .btn { - display: inline-block; - min-height: 38px; - min-width: 15px; - line-height: 25px; - margin: 0; - padding: 0.5rem 1.5rem; - font-size: 1rem; - vertical-align: middle; - cursor: pointer; - overflow: hidden; - background: $grey-lighter; - border-radius: 5px; - border: none; - color: $grey-dark; - - @include transition(all, 0.15s, ease-in-out); - - &.btn-important { - background: $main-first; - color: $white; - - // @include transition(all, 0.15s, ease-in-out); - - &:hover, - &:active { - background: $main-first-alt; + margin: 0; + padding: 0.5rem 1.5rem; + background: $grey-lighter; + display: inline-block; + color: $grey-dark; + font-size: 1rem; + border: none; + border-radius: 5px; + min-height: 38px; + min-width: 15px; + line-height: 25px; + vertical-align: middle; + cursor: pointer; + overflow: hidden; + + @include transition(all, 0.15s, ease-in-out); + + &.btn-important { + background: $main-first; + color: $white; + + // @include transition(all, 0.15s, ease-in-out); + &:hover, + &:active { + background: $main-first-alt; + } } - } - &.btn-attention { - background: $alert-bg; - color: #fff; + &.btn-attention { + background: $alert-bg; + color: #fff; - &:hover, - &:active { - background: $alert-text; + &:hover, + &:active { + background: $alert-text; + } } - } - &:hover { - text-decoration: none; - } + &:hover { + text-decoration: none; + } } a.btn { - min-height: 25px; - line-height: 25px; + min-height: 25px; + line-height: 25px; } /*=== Forms */ legend { - display: inline-block; - width: auto; - margin: 2rem 0 1rem 0; - padding: 0; - font-size: 1rem; - clear: both; - text-transform: uppercase; - letter-spacing: 1px; - font-weight: 700; + margin: 2rem 0 1rem 0; + padding: 0; + display: inline-block; + width: auto; + font-size: 1rem; + clear: both; + text-transform: uppercase; + letter-spacing: 1px; + font-weight: 700; } + label { - min-height: 25px; - padding: 5px 0; - cursor: pointer; - color: $grey-dark; + min-height: 25px; + padding: 5px 0; + cursor: pointer; + color: $grey-dark; } + textarea { - width: 360px; - height: 100px; + width: 360px; + height: 100px; } + input, select, textarea, button { - font-family: "lato", "Helvetica", "Arial", sans-serif; - min-height: 25px; - padding: 5px 10px; - line-height: 25px; - vertical-align: middle; - background: $white; - border: 1px solid $grey-light; - font-size: 1rem; - color: $grey-dark; - border-radius: 2px; + padding: 5px 10px; + background: $white; + color: $grey-dark; + font-family: "lato", "Helvetica", "Arial", sans-serif; + font-size: 1rem; + border: 1px solid $grey-light; + border-radius: 2px; + min-height: 25px; + line-height: 25px; + vertical-align: middle; } + option { - padding: 0 .5em; + padding: 0 .5em; } + input:focus, select:focus, textarea:focus { - color: $main-font-color; - border-color: $main-first; + color: $main-font-color; + border-color: $main-first; } + input:invalid, select:invalid { - color: $alert-bg; - border-color: $alert-bg; - box-shadow: none; + color: $alert-bg; + border-color: $alert-bg; + box-shadow: none; } + input:disabled, select:disabled { - background: $grey-light; + background: $grey-light; } + input.extend { - transition: width 200ms linear; - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; + transition: width 200ms linear; } .form-group { - padding: 5px; - border-radius: 3px; + padding: 5px; + border-radius: 3px; - &::after { - content: ""; - display: block; - clear: both; - } - &:hover { - // background: #fff; - // border: 1px solid #eee; - // border-radius: 3px; - // border: 1px solid #eee; - } - .group-name { - padding: 10px 0; - text-align: right; - } - .group-controls { - min-height: 25px; - padding: 5px 0; - } - .group-controls .control { - line-height: 2.0em; - } - table { - margin: 10px 0 0 220px; - } - - &.form-actions { - margin: 15px 0 25px; - padding: 5px 0; - // background: #333; - } - &.form-actions .btn { - margin: 0 0.5rem 0 0; - } + &::after { + content: ""; + display: block; + clear: both; + } + + &:hover { + // background: #fff; + // border: 1px solid #eee; + // border-radius: 3px; + // border: 1px solid #eee; + } + + .group-name { + padding: 10px 0; + text-align: right; + } + + .group-controls { + min-height: 25px; + padding: 5px 0; + } + + .group-controls .control { + line-height: 2.0em; + } + + table { + margin: 10px 0 0 220px; + } + + &.form-actions { + margin: 15px 0 25px; + padding: 5px 0; + // background: #333; + } + + &.form-actions .btn { + margin: 0 0.5rem 0 0; + } } diff --git a/p/themes/Mapco/_global-view.scss b/p/themes/Mapco/_global-view.scss index 30979bd6c..7d24a9109 100644 --- a/p/themes/Mapco/_global-view.scss +++ b/p/themes/Mapco/_global-view.scss @@ -1,80 +1,83 @@ /*=== GLOBAL VIEW */ /*================*/ +#stream { + .box.category { + &:not([data-unread="0"]) .box-title { + // background: #3498db; + } -#stream{ - .box.category{ - - &:not([data-unread="0"]) .box-title { - // background: #3498db; - } - &:not([data-unread="0"]) .box-title:active { - // background: #2980b9; - } - &:not([data-unread="0"]) .box-title .title { - font-weight: bold; - // color: #fff; - } - - .box-title{ - background: none; - padding: 1.5rem; - - a.title{ - font-weight: normal; - text-decoration: none; - text-align: left; - font-size: 1rem; - text-transform: uppercase; - letter-spacing: 1px; - color: $grey-dark; + &:not([data-unread="0"]) .box-title:active { + // background: #2980b9; + } - &:not([data-unread="0"])::after { - position: absolute; - top: 1.75rem; - right: 0; - line-height: 1.5rem; - background: $grey-light; - border-radius: 12px; - padding: 0 0.75rem; - margin: -0.5rem 1rem 0 0; - text-align: center; + &:not([data-unread="0"]) .box-title .title { + font-weight: bold; + // color: #fff; } - &:hover{ - color: $main-first; + + .box-title { + padding: 1.5rem; + background: none; + + a.title { + color: $grey-dark; + font-size: 1rem; + font-weight: normal; + text-decoration: none; + text-align: left; + text-transform: uppercase; + letter-spacing: 1px; + + &:not([data-unread="0"])::after { + margin: -0.5rem 1rem 0 0; + padding: 0 0.75rem; + background: $grey-light; + border-radius: 12px; + position: absolute; + top: 1.75rem; + right: 0; + line-height: 1.5rem; + text-align: center; + } + + &:hover { + color: $main-first; + } + } } - } - } - .box-content{ - padding-bottom: 0.5rem; - .item.feed { - // padding: 2px 10px; - font-size: 1rem; - padding: 0.5rem 1.5rem; - - a{ - color: $main-font-color; - font-weight: 400; + .box-content { + padding-bottom: 0.5rem; + + .item.feed { + padding: 0.5rem 1.5rem; + // padding: 2px 10px; + font-size: 1rem; - &:hover{ - color: $main-first; - text-decoration: none; - } + a { + color: $main-font-color; + font-weight: 400; + + &:hover { + color: $main-first; + text-decoration: none; + } + } + } } - } - } - } + } } // le panel qui apparait en overlay pour afficher les flux -#overlay{ - background: rgba(0, 0, 0, 0.65); +#overlay { + background: rgba(0, 0, 0, 0.65); } -#panel{ - top: 3rem; - right: 3rem; - bottom: 3rem; - left: 3rem; - border-radius: 3px; + +#panel { + top: 3rem; + right: 3rem; + bottom: 3rem; + left: 3rem; + border-radius: 3px; } diff --git a/p/themes/Mapco/_layout.scss b/p/themes/Mapco/_layout.scss index b62618d81..c7c0849b4 100644 --- a/p/themes/Mapco/_layout.scss +++ b/p/themes/Mapco/_layout.scss @@ -2,118 +2,117 @@ /*===============*/ /*=== Header */ .header { - background: $sid-bg; - padding: 0.5rem 1.35rem; - display: block; - table-layout: none; - width: auto; - - .item{ - vertical-align: middle; - // text-align: center; - // display: flex; - // justify-content: space-between; - // flex-direction: row; - - &.title{ - - font-weight: 400; - width: 280px; - - h1{ - a{ - text-decoration: none; - color: $sid-font-color; - font-size: 1rem; - text-transform: uppercase; - letter-spacing: 1px; - - img{ - margin-right: 0.5rem; - - } + padding: 0.5rem 1.35rem; + background: $sid-bg; + display: block; + width: auto; + table-layout: none; + + .item { + vertical-align: middle; + // text-align: center; + // display: flex; + // justify-content: space-between; + // flex-direction: row; + &.title { + width: 280px; + + font-weight: 400; + + h1 { + a { + text-decoration: none; + color: $sid-font-color; + font-size: 1rem; + text-transform: uppercase; + letter-spacing: 1px; + + img { + margin-right: 0.5rem; + + } + } + } } - } - } - &.search{ - // text-align: center; - // width: 50%; - input{ - width: 230px; - border-radius: 2px 0 0 2px; - background-color: $sid-bg-alt; - color: $sid-font-color; - border: none; + &.search { + // text-align: center; + // width: 50%; + input { + width: 230px; + color: $sid-font-color; + border: none; + border-radius: 2px 0 0 2px; + background-color: $sid-bg-alt; - @include transition(all, 0.15s, ease-in-out); + @include transition(all, 0.15s, ease-in-out); - &:hover{ - background-color: $sid-bg-dark; - } - - &:focus{ - width: 350px; + &:hover { + background-color: $sid-bg-dark; + } - background-color: $white; - color: $grey-dark; - } - } - .btn{ - img{display: none;} - border-radius: 0 2px 2px 0; + &:focus { + width: 350px; + color: $grey-dark; - background-color: $main-first; - background-position: center; - background-repeat: no-repeat; - background-image: url(icons/magnifier.svg); - - border-left-width: 0; + background-color: $white; + } + } - width: 3rem; - min-height: 35px; + .btn { + img {display: none;} - &:hover{ - background-color: $main-first-alt; - } - } - } - &.configure{ - width: 2rem; - position: absolute; - right: 1rem; - top: 1.25rem; - text-align: center; - // float: right; - - .btn{ - img{display: none;} - // border-radius: 0 2px 2px 0; - - background-color: transparent; - background-position: center; - background-repeat: no-repeat; - background-image: url(icons/cog-white.svg); + width: 3rem; + border-radius: 0 2px 2px 0; + + background-color: $main-first; + background-position: center; + background-repeat: no-repeat; + background-image: url(icons/magnifier.svg); - padding: 0 0.5rem; - - // border-left-width: 0; + border-left-width: 0; + min-height: 35px; - // width: 3rem; + &:hover { + background-color: $main-first-alt; + } + } + } - &:hover{ - // background-color: $main-first-alt; + &.configure { + width: 2rem; + position: absolute; + right: 1rem; + top: 1.25rem; + text-align: center; + // float: right; + .btn { + img {display: none;} + + padding: 0 0.5rem; + // border-radius: 0 2px 2px 0; + + background-color: transparent; + background-position: center; + background-repeat: no-repeat; + background-image: url(icons/cog-white.svg); + + // border-left-width: 0; + + // width: 3rem; + &:hover { + // background-color: $main-first-alt; + } + } } - } } - } - + } /*=== Body */ #global { - height: calc(100% - 85px); + height: calc(100% - 85px); } @@ -121,342 +120,352 @@ /*=== Prompt (centered) */ .prompt { - text-align: center; + text-align: center; } + .prompt label { - text-align: left; + text-align: left; } + .prompt form { - margin: 10px auto 20px auto; - width: 200px; + margin: 10px auto 20px auto; + width: 200px; } + .prompt input { - margin: 5px auto; - width: 100%; + margin: 5px auto; + width: 100%; } + .prompt p { - margin: 20px 0; + margin: 20px 0; } /*=== New article notification */ #new-article { - text-align: center; - font-size: 1rem; - background: $main-first; + background: $main-first; + font-size: 1rem; + text-align: center; } + #new-article:hover { - background: $main-first-alt; + background: $main-first-alt; } + #new-article > a { - line-height: 3em; - font-weight: bold; - color: $white; + line-height: 3em; + font-weight: bold; + color: $white; } + #new-article > a:hover { - text-decoration: none; + text-decoration: none; } /*=== Day indication */ .day { - padding: 1rem 0 0 1.25rem; - font-weight: 700; - line-height: 3em; - letter-spacing: 1px; - text-transform: uppercase; - font-size: 0.875rem; - color: $light-font-color; - // border-left: 2px solid #ecf0f1; - - .name{ - padding: 0 1rem 0 1rem; + padding: 1rem 0 0 1.25rem; + color: $light-font-color; font-size: 0.875rem; - // font-weight: 700; - color: $main-font-color; - position: relative; - left: 0; - - // letter-spacing: 1px; + font-weight: 700; + line-height: 3em; + letter-spacing: 1px; text-transform: uppercase; - } + // border-left: 2px solid #ecf0f1; + .name { + padding: 0 1rem 0 1rem; + // font-weight: 700; + color: $main-font-color; + font-size: 0.875rem; + position: relative; + left: 0; + + // letter-spacing: 1px; + text-transform: uppercase; + } } /*=== Index menu */ .nav_menu { - text-align: center; - padding: 5px 0; - - .btn{ - border-left-width: 0; - padding: 0.5rem 1rem; - background-color: $grey-lighter; - background-position: center; - background-repeat: no-repeat; - - &:hover{ - background-color: $grey-light; - } - } - - .stick{ - background: $grey-lighter; - - .btn{ - border-left-width: 0; - padding: 0.5rem 1rem; - background-color: $grey-lighter; - background-position: center; - background-repeat: no-repeat; - @include transition(all, 0.15s, ease-in-out); - - &:hover{ - background-color: $grey-medium-light; - } - - &.active{ - background-color: $main-first; - } - - img.icon{display: none;} // on efface pour afficher nos icones, mouhahaha !! - - - // actions - &#toggle-read{ - background-image: url(icons/read.svg); - } - &#toggle-read.active{ - background-image: url(icons/read-white.svg); - } - - &#toggle-unread{ - background-image: url(icons/unread.svg); - } - &#toggle-unread.active{ - background-image: url(icons/unread-white.svg); - } - - &#toggle-starred{ - background-image: url(icons/starred.svg); - } - &#toggle-starred.active{ - background-image: url(icons/starred-white.svg); - } - - &#toggle-non-starred{ - background-image: url(icons/non-starred.svg); - } - &#toggle-non-starred.active{ - background-image: url(icons/non-starred-white.svg); - } - - // read all - &.read_all{ + text-align: center; + padding: 5px 0; + + .btn { + border-left-width: 0; + padding: 0.5rem 1rem; background-color: $grey-lighter; - // min-height: 0; - color:$main-font-color; - padding: 5px 16px; - @include transition(all, 0.15s, ease-in-out); + background-position: center; + background-repeat: no-repeat; - &:hover{ - background-color: $grey-medium-light; + &:hover { + background-color: $grey-light; } - } - - // views - &.view-normal{ - background-image: url(icons/view-list.svg); - } - &.view-normal.active{ - background-image: url(icons/view-list-white.svg); - } - - &.view-global{ - background-image: url(icons/view-global.svg); - } - &.view-global.active{ - background-image: url(icons/view-global-white.svg); - } - - &.view-reader{ - background-image: url(icons/view-reader.svg); - } - &.view-reader.active{ - background-image: url(icons/view-reader-white.svg); - } - - &.view-rss{ - background-image: url(icons/rss.svg); - } - - } - .dropdown { - a.dropdown-toggle{ - border-left-width: 0; - background-image: url(icons/more.svg); - } - } - - &#nav_menu_action{ - } - &#nav_menu_read_all{ - - } - &#nav_menu_views{ - + + .stick { + background: $grey-lighter; + + .btn { + border-left-width: 0; + padding: 0.5rem 1rem; + background-color: $grey-lighter; + background-position: center; + background-repeat: no-repeat; + + @include transition(all, 0.15s, ease-in-out); + + &:hover { + background-color: $grey-medium-light; + } + + &.active { + background-color: $main-first; + } + + img.icon {display: none;} // on efface pour afficher nos icones, mouhahaha !! + + + // actions + &#toggle-read { + background-image: url(icons/read.svg); + } + + &#toggle-read.active { + background-image: url(icons/read-white.svg); + } + + &#toggle-unread { + background-image: url(icons/unread.svg); + } + + &#toggle-unread.active { + background-image: url(icons/unread-white.svg); + } + + &#toggle-starred { + background-image: url(icons/starred.svg); + } + + &#toggle-starred.active { + background-image: url(icons/starred-white.svg); + } + + &#toggle-non-starred { + background-image: url(icons/non-starred.svg); + } + + &#toggle-non-starred.active { + background-image: url(icons/non-starred-white.svg); + } + + // read all + &.read_all { + padding: 5px 16px; + // min-height: 0; + color: $main-font-color; + background-color: $grey-lighter; + + @include transition(all, 0.15s, ease-in-out); + + &:hover { + background-color: $grey-medium-light; + } + } + + // views + &.view-normal { + background-image: url(icons/view-list.svg); + } + + &.view-normal.active { + background-image: url(icons/view-list-white.svg); + } + + &.view-global { + background-image: url(icons/view-global.svg); + } + + &.view-global.active { + background-image: url(icons/view-global-white.svg); + } + + &.view-reader { + background-image: url(icons/view-reader.svg); + } + + &.view-reader.active { + background-image: url(icons/view-reader-white.svg); + } + + &.view-rss { + background-image: url(icons/rss.svg); + } + + } + + .dropdown { + a.dropdown-toggle { + border-left-width: 0; + background-image: url(icons/more.svg); + } + } } - } } #dropdown-query ~ .dropdown-menu .dropdown-header .icon { - vertical-align: middle; - background-color: $grey-medium-dark; - border-radius: 3px; + vertical-align: middle; + background-color: $grey-medium-dark; + border-radius: 3px; } /*=== Content of feed articles */ .content, .content.thin { - padding: 20px 10px; + padding: 20px 10px; - font-size: 1.125rem; - line-height: 1.8rem; + font-size: 1.125rem; + line-height: 1.8rem; - h1.title, h1{ + h1.title, h1 { + a { + color: $main-font-color; + font-family: "spectral", serif; + font-size: 2rem; - a{ - color: $main-font-color; - font-family: "spectral"; - font-size: 2rem; + &:hover { + color: $main-first; + text-decoration: none; + } + } + } - &:hover{ - color: $main-first; - text-decoration: none; - } + .author { + color: $light-font-color; + font-size: 1.125rem; + } + + p, ul { + font-size: 1.125rem; + line-height: 1.8rem; + } + + .content hr { + margin: 30px 10px; + background: $grey-medium-light; + height: 1px; + border: 0; + box-shadow: 0 2px 5px #ccc; + } + + pre { + margin: 10px auto; + padding: 10px 20px; + overflow: auto; + background: $main-first-darker; + color: $white; + font-size: 0.9rem; + border-radius: 3px; + + code { + background: transparent; + color: $white; + border: none; + } } - } - .author{ - font-size: 1.125rem; - color: $light-font-color; - } - p, ul{ - font-size: 1.125rem; - line-height: 1.8rem; - } - hr{ - } - .content hr { - margin: 30px 10px; - height: 1px; - background: $grey-medium-light; - border: 0; - box-shadow: 0 2px 5px #ccc; - } - - pre { - margin: 10px auto; - padding: 10px 20px; - overflow: auto; - background: $main-first-darker; - color: $white; - font-size: 0.9rem; - border-radius: 3px; code { - background: transparent; - color: $white; - border: none; + padding: 2px 5px; + background: $grey-lighter; + color: $grey-light; + border: 1px solid $grey-light; + border-radius: 3px; } - } - code { - padding: 2px 5px; - color: $grey-light; - background: $grey-lighter; - border: 1px solid $grey-light; - border-radius: 3px; - } - blockquote { - display: block; - margin: 0; - padding: 5px 20px; - border-top: 1px solid $grey-medium-light; - border-bottom: 1px solid $grey-medium-light; - background: $grey-lighter; - color: $main-font-color; - - p { - margin: 0; + blockquote { + margin: 0; + padding: 5px 20px; + background: $grey-lighter; + display: block; + color: $main-font-color; + border-top: 1px solid $grey-medium-light; + border-bottom: 1px solid $grey-medium-light; + + p { + margin: 0; + } } - } } /*=== Notification and actualize notification */ .notification { - position: fixed; - top: auto; - bottom: 0; - left: 0; - right: 0; - width: 100%; - height: 3rem; - - padding: 1rem 0; - text-align: center; - // font-weight: bold; - font-size: 1em; - line-height: 3em; - z-index: 10; - vertical-align: middle; - background: $grey-medium-light; - color: $grey-dark; - // border-radius: 3px; - border: none; - - .msg{ - font-size: 1rem; - display: inline-block; - } - - &.good { - background: $success-bg; - color: $white; - } - &.bad { - background: $alert-bg; - color: $white; - } - a.close { - padding: 0 15px; + + padding: 1rem 0; + background: $grey-medium-light; + width: 100%; + height: 3rem; + color: $grey-dark; + // font-weight: bold; + font-size: 1em; + // border-radius: 3px; + border: none; + position: fixed; + top: auto; + bottom: 0; + left: 0; + right: 0; + text-align: center; line-height: 3em; - border-radius: 0 3px 3px 0; - } - - &.good a.close:hover { - background: $success-text; - } - &.bad a.close:hover { - background: $alert-text; - } - - &#actualizeProgress { - line-height: 2em; - - br{ - display: none; + z-index: 10; + vertical-align: middle; + + .msg { + display: inline-block; + font-size: 1rem; + } + + &.good { + background: $success-bg; + color: $white; + } + + &.bad { + background: $alert-bg; + color: $white; + } + + a.close { + padding: 0 15px; + border-radius: 0 3px 3px 0; + line-height: 3em; + } + + &.good a.close:hover { + background: $success-text; + } + + &.bad a.close:hover { + background: $alert-text; + } + + &#actualizeProgress { + line-height: 2em; + + br { + display: none; + } } - } } /*=== Navigation menu (for articles) */ #nav_entries { - margin: 0; - text-align: center; - line-height: 3em; - table-layout: fixed; - background: $sid-bg; + margin: 0; + text-align: center; + line-height: 3em; + table-layout: fixed; + background: $sid-bg; } diff --git a/p/themes/Mapco/_list-view.scss b/p/themes/Mapco/_list-view.scss index 69cd8a81d..ab3001ee4 100644 --- a/p/themes/Mapco/_list-view.scss +++ b/p/themes/Mapco/_list-view.scss @@ -1,92 +1,97 @@ /*=== Feed articles */ .flux { - // border-left: 2px solid #ecf0f1; - background: $white; + // border-left: 2px solid #ecf0f1; + background: $white; - @include transition(all, 0.15s, ease-in-out); - - &:hover{ - background: $grey-lighter; + @include transition(all, 0.15s, ease-in-out); - &:not(.current):hover .item.title { - background: $grey-lighter; + &:hover { + background: $grey-lighter; - - } - } - &.current{ - border-left-color: $main-first; - background: $grey-lighter; - } - &.not_read{ - background: $unread-bg; //-------------------- - // border-left-color: #FF5300; - - &:hover{ - background: $unread-bg-light; //-------------------- - } - - &:not(.current):hover .item.title { - background: $unread-bg-light; + &:not(.current):hover .item.title { + background: $grey-lighter; - + + } } - .item.title{ - a{ - color: $unread-font-color; //-------------------- - } - + + &.current { + background: $grey-lighter; + border-left-color: $main-first; } - .item.website{ - a{ - color: $unread-font-color; //-------------------- - } + + &.not_read { + background: $unread-bg; //-------------------- + // border-left-color: #FF5300; + &:hover { + background: $unread-bg-light; //-------------------- + } + + &:not(.current):hover .item.title { + background: $unread-bg-light; + + + } + + .item.title { + a { + color: $unread-font-color; //-------------------- + } + + } + + .item.website { + a { + color: $unread-font-color; //-------------------- + } + } + + .item.date { + color: unquote($unread-font-color+"99"); //-------------------- + } } - .item.date{ - color: unquote($unread-font-color+"99"); //-------------------- + + &.favorite { + background: $fav-light; + border-left-color: $fav-bg; + + @include transition(all, 0.15s, ease-in-out); + + &:not(.current):hover .item.title { + background: $fav-light; + } } -} - - &.favorite { - background: $fav-light; - border-left-color: $fav-bg; - @include transition(all, 0.15s, ease-in-out); + .website { + a { + color: $main-font-color; + opacity: 0.75; + } - &:not(.current):hover .item.title { - background: $fav-light; + .favicon { + padding: 5px; + } } - } - .website{ - a{ - color: $main-font-color; - opacity: 0.75; + .date { + color: $main-font-color; + font-size: 0.85rem; + opacity: 0.75; } - - .favicon { - padding: 5px; + + .bottom { + font-size: 1rem; + text-align: center; } - } - .date { - font-size: 0.85rem; - color: $main-font-color; - opacity: 0.75; - } - - .bottom { - font-size: 1rem; - text-align: center; - } } .flux_header { - font-size: 1rem; - cursor: pointer; - border-top: 1px solid $grey-light; - - .title { font-size: 1rem; - } + cursor: pointer; + border-top: 1px solid $grey-light; + + .title { + font-size: 1rem; + } } diff --git a/p/themes/Mapco/_logs.scss b/p/themes/Mapco/_logs.scss index ae1e24c6c..82eead3c9 100644 --- a/p/themes/Mapco/_logs.scss +++ b/p/themes/Mapco/_logs.scss @@ -4,13 +4,14 @@ overflow: hidden; border: 1px solid $grey-medium-dark; } + .log { margin: 10px 0; padding: 5px 2%; - overflow: auto; - font-size: 0.8rem; background: $grey-lighter; color: $grey-dark; + font-size: 0.8rem; + overflow: auto; } .log > .date { @@ -18,16 +19,20 @@ padding: 5px 10px; border-radius: 20px; } + .log.error > .date { background: $alert-bg; color: #fff; } + .log.warning > .date { background: $warning-bg; } + .log.notice > .date { background: $grey-light; } + .log.debug > .date { background: $main-first-darker; color: $white; diff --git a/p/themes/Mapco/_mixins.scss b/p/themes/Mapco/_mixins.scss index 3542ade6b..9dbed6945 100644 --- a/p/themes/Mapco/_mixins.scss +++ b/p/themes/Mapco/_mixins.scss @@ -1,59 +1,71 @@ +/* stylelint-disable property-no-vendor-prefix */ + /* FUNCTIONS */ //animation + @mixin transition($target, $duration, $ease) { - -webkit-transition: $target $duration $ease; - -moz-transition: $target $duration $ease; - -o-transition: $target $duration $ease; - -ms-transition: $target $duration $ease; - transition: $target $duration $ease; + -webkit-transition: $target $duration $ease; + -moz-transition: $target $duration $ease; + -o-transition: $target $duration $ease; + -ms-transition: $target $duration $ease; + transition: $target $duration $ease; } //animation + @mixin animation-delay($delay) { - -webkit-animation-delay: $delay; - /* Safari 4.0 - 8.0 */ - animation-delay: $delay; + -webkit-animation-delay: $delay; + /* Safari 4.0 - 8.0 */ + animation-delay: $delay; } //animation + @mixin animation($animate...) { - $max: length($animate); - $animations: ''; - @for $i from 1 through $max { - $animations: #{$animations + nth($animate, $i)}; - @if $i < $max { - $animations: #{$animations + ", "}; - } - } - -webkit-animation: $animations; - -moz-animation: $animations; - -o-animation: $animations; - -ms-transition: $animations; - animation: $animations; + $max: length($animate); + $animations: ''; + + @for $i from 1 through $max { + $animations: #{$animations + nth($animate, $i)}; + + @if $i < $max { + $animations: #{$animations + ", "}; + } + } + -webkit-animation: $animations; + -moz-animation: $animations; + -o-animation: $animations; + -ms-transition: $animations; + animation: $animations; } //keyframes + @mixin keyframes($animationName) { - @-webkit-keyframes #{$animationName} { - @content; - } - @-moz-keyframes #{$animationName} { - @content; - } - @-o-keyframes #{$animationName} { - @content; - } - @keyframes #{$animationName} { - @content; - } + + @-webkit-keyframes #{$animationName} { + @content; + } + + @-moz-keyframes #{$animationName} { + @content; + } + + @-o-keyframes #{$animationName} { + @content; + } + + @keyframes #{$animationName} { + @content; + } } -@mixin border-radius($radius: 4px){ - -moz-border-radius: $radius; - -webkit-border-radius: $radius; - -ms-border-radius: $radius; - -o-border-radius: $radius; - -khtml-border-radius: $radius; - border-radius: $radius; +@mixin border-radius($radius: 4px) { + -moz-border-radius: $radius; + -webkit-border-radius: $radius; + -ms-border-radius: $radius; + -o-border-radius: $radius; + -khtml-border-radius: $radius; + border-radius: $radius; } diff --git a/p/themes/Mapco/_mobile.scss b/p/themes/Mapco/_mobile.scss index dcf4e74f2..d646f3469 100644 --- a/p/themes/Mapco/_mobile.scss +++ b/p/themes/Mapco/_mobile.scss @@ -1,169 +1,185 @@ /*=== MOBILE */ /*===========*/ -@media(max-width: 840px) { - html, body{ - // font-size: 1rem; - } - ul.nav{ - .item{ - width: 100%; - - img{ - display: none; - } - a{ - display: inline-block; - padding: 1rem 1rem 1rem 2.5rem; - color: $sid-font-color; - width: 100%; - - background: url("../../themes/icons/logout.svg") no-repeat $sid-bg-dark 3% center; + +@media (max-width: 840px) { + html, body { + // font-size: 1rem; + } + + ul.nav { + .item { + width: 100%; + + img { + display: none; + } + + a { + padding: 1rem 1rem 1rem 2.5rem; + + background: url("../../themes/icons/logout.svg") no-repeat $sid-bg-dark 3% center; + display: inline-block; + width: 100%; + color: $sid-font-color; + + @include transition(all, 0.2s, ease-in-out); + + &:hover, + &:active { + background: url("../../themes/icons/logout.svg") no-repeat $alert-bg 3% center; + text-decoration: none; + } + } + + } + + } + + .aside { @include transition(all, 0.2s, ease-in-out); - &:hover, - &:active{ - background: url("../../themes/icons/logout.svg") no-repeat $alert-bg 3% center; - text-decoration: none; + &.aside_feed { + padding: 0; } - } + .tree .tree-folder .tree-folder-items .item a { + padding: 0.5rem 1rem; + } } - - } - .aside { - @include transition(all, 0.2s, ease-in-out); - - &.aside_feed { - padding: 0; + + .aside .toggle_aside, + #panel .close { + background: $main-first-alt; + display: block; + width: 100%; + height: 50px; + line-height: 50px; + text-align: center; } - .tree .tree-folder .tree-folder-items .item a{ - padding: 0.5rem 1rem; + .header { + padding: 0.5rem; + + .item { + &.title { + display: none; + } + + &.search { + input { + width: 90%; + height: 3.5rem; + + &:focus { + width: 100%; + + } + } + + .btn { + min-height: 49px; + padding: 0.5rem 2rem; + } + } + + &.configure { + width: 2.75rem; + top: 3.125rem; + + .dropdown { + .btn { + padding: 1.125rem; + } + } + } + } } - } - .aside .toggle_aside, - #panel .close { - display: block; - width: 100%; - height: 50px; - line-height: 50px; - text-align: center; - background: $main-first-alt; - } - - .header{ - padding: 0.5rem; - .item{ - &.title{ - display: none; - } - - &.search{ - input{ - width: 90%; - height: 3.5rem; - - &:focus{ - width: 100%; - } + .nav_menu { + .btn { + margin: 0; + padding: 0.85rem 1.25rem; } - .btn{ - min-height: 49px; - padding: 0.5rem 2rem; + + .stick { + margin: 0.5rem 0.5rem; + + .btn { + margin: 0; + padding: 0.85rem 1.25rem; + + &.read_all { + padding: 0.85rem 1.25rem; + } + } } - } - &.configure{ - width: 2.75rem; - top: 3.125rem; - .dropdown{ - .btn{ - padding: 1.125rem; - } + + .search { + display: none; + max-width: 97%; + + .input { + + max-width: 97%; + width: 90px; + + &:focus { + width: 400px; + } + } } - } - } - } - - .nav_menu{ - .btn { - margin: 0; - padding: 0.85rem 1.25rem; } - .stick { - margin: 0.5rem 0.5rem; - .btn{ - margin: 0; - padding: 0.85rem 1.25rem; - - &.read_all{ - padding: 0.85rem 1.25rem; + #stream { + .flux { + .flux_header { + padding: 0.5rem 0; + } } - } } - .search { - display: none; - max-width: 97%; - .input{ - max-width: 97%; - width: 90px; - &::focus{ - width: 400px; + .day { + text-align: center; + padding: 1rem 0; + + .name { + padding: 0; + // font-size: 1.1rem; + display: block; + width: 100%; + line-height: 1.5rem; + margin-bottom: 1rem; } - } + } - } - #stream{ - .flux{ - .flux_header{ - padding: 0.5rem 0; - } + + .pagination { + margin: 0 0 3.5em; } - } - - - - .day{ - text-align: center; - padding: 1rem 0; - .name { - // font-size: 1.1rem; - display: block; - padding: 0; - width: 100%; - line-height: 1.5rem; - margin-bottom: 1rem; + + #nav_entries { + line-height: 4.5rem; } - } + .notification { + border-radius: 0; - .pagination { - margin: 0 0 3.5em; - } + a.close { + background: transparent; + display: block; + left: 0; + } - #nav_entries{ - line-height: 4.5rem; - } + a.close:hover { + opacity: 0.5; + } - .notification { - border-radius: 0; + a.close .icon { + display: none; - a.close { - display: block; - left: 0; - background: transparent; - } - a.close:hover { - opacity: 0.5; - } - a.close .icon { - display: none; - + } } - } } diff --git a/p/themes/Mapco/_reader-view.scss b/p/themes/Mapco/_reader-view.scss index 4368908d6..e75e43d00 100644 --- a/p/themes/Mapco/_reader-view.scss +++ b/p/themes/Mapco/_reader-view.scss @@ -6,8 +6,9 @@ color: $main-font-color; border: none; } + #stream.reader .flux .author { margin: 0 0 10px; - font-size: 90%; color: $grey-medium-dark; + font-size: 90%; } diff --git a/p/themes/Mapco/_sidebar.scss b/p/themes/Mapco/_sidebar.scss index 62ec1c1cf..a26ff647c 100644 --- a/p/themes/Mapco/_sidebar.scss +++ b/p/themes/Mapco/_sidebar.scss @@ -1,134 +1,139 @@ /*=== Tree */ .tree { - margin: 10px 0; - - &#sidebar{ - scrollbar-color: rgba(255,255, 0, 0.1) rgba(0, 0, 0, 0.05); - scrollbar-color: unquote($sid-font-color+"33") unquote($sid-font-color+"22"); - } - - - .tree-folder{ - border-bottom: 1px solid $sid-sep; - - .tree-folder-title { - position: relative; - background: $sid-bg; - font-size: 0.85rem; - letter-spacing: 1px; - padding: 12px 16px; - font-weight: 700; - text-transform: uppercase; - - .title { - background: inherit; - color: $sid-font-color; - &:hover{ - text-decoration: none; - } - } - } - &.active { - .tree-folder-title { - background: $sid-bg; - font-weight: bold; - } + margin: 10px 0; + + &#sidebar { + scrollbar-color: rgba(255,255, 0, 0.1) rgba(0, 0, 0, 0.05); + scrollbar-color: unquote($sid-font-color+"33") unquote($sid-font-color+"22"); } - .tree-folder-items { - background: $sid-bg-alt; - .item{ - padding: 0 1rem; - line-height: 2.5rem; - font-size: 1rem; - font-weight: 400; - @include transition(all, 0.15s, ease-in-out); - &.active{ - background: $sid-active; + .tree-folder { + border-bottom: 1px solid $sid-sep; - .dropdown li a{ - color: $main-font-color; + .tree-folder-title { + padding: 12px 16px; + background: $sid-bg; + position: relative; + font-size: 0.85rem; + letter-spacing: 1px; + font-weight: 700; + text-transform: uppercase; - &:hover{ - color: $sid-font-color; - } - } + .title { + background: inherit; + color: $sid-font-color; - a{ - color: $sid-active-font; - } + &:hover { + text-decoration: none; + } + } } - &:hover{ - background: $sid-bg-dark; + &.active { + .tree-folder-title { + background: $sid-bg; + font-weight: bold; + } } - a{ - text-decoration: none; - color: $sid-font-color; + .tree-folder-items { + background: $sid-bg-alt; + + .item { + padding: 0 1rem; + line-height: 2.5rem; + font-size: 1rem; + font-weight: 400; + + @include transition(all, 0.15s, ease-in-out); + + &.active { + background: $sid-active; + + .dropdown li a { + color: $main-font-color; + + &:hover { + color: $sid-font-color; + } + } + + a { + color: $sid-active-font; + } + } + + &:hover { + background: $sid-bg-dark; + } + + a { + text-decoration: none; + color: $sid-font-color; + } + } + + .feed .item-title:not([data-unread="0"])::before { + margin: 11px 6px 0 4px; + padding: 3px 4px; + background: $sid-pills; + display: block; + float: left; + font-size: 0.75rem; + border-radius: 12px; + content: attr(data-unread); + text-align: center; + line-height: 0.75rem; + } } - } - - .feed .item-title:not([data-unread="0"])::before { - content: attr(data-unread); - background: $sid-pills; - font-size: 0.75rem; - display: block; - float: left; - padding: 3px 4px; - text-align:center; - border-radius: 12px; - margin: 11px 6px 0 4px; - line-height: 0.75rem; - } - .feed .item-title:not([data-unread="0"]) { - - } } - } } /*=== Buttons */ .stick { - vertical-align: middle; - font-size: 0; - - input, .btn { - border-radius: 0; - } - .btn:first-child, - input:first-child { - border-radius: 5px 0 0 5px; - } - .btn:last-child, input:last-child, .btn + .dropdown > .btn { - border-radius: 0 5px 5px 0; - } - .btn + .btn, - .btn + input, - .btn + .dropdown > .btn, - input + .btn, - input + input, - input + .dropdown > .btn, - .dropdown + .btn, - .dropdown + input, - .dropdown + .dropdown > .btn { - border-left: 1px solid $grey-medium-light; - } + vertical-align: middle; + font-size: 0; + + input, .btn { + border-radius: 0; + } + + .btn:first-child, + input:first-child { + border-radius: 5px 0 0 5px; + } + + .btn:last-child, input:last-child, .btn + .dropdown > .btn { + border-radius: 0 5px 5px 0; + } + + .btn + .btn, + .btn + input, + .btn + .dropdown > .btn, + input + .btn, + input + input, + input + .dropdown > .btn, + .dropdown + .btn, + .dropdown + input, + .dropdown + .dropdown > .btn { + border-left: 1px solid $grey-medium-light; + } } .aside { - background: $sid-bg; - - &.aside_feed { - padding: 10px 0; - text-align: center; background: $sid-bg; - } - &.aside_feed .tree { - margin: 10px 0 50px; - } + + &.aside_feed { + padding: 10px 0; + text-align: center; + background: $sid-bg; + } + + &.aside_feed .tree { + margin: 10px 0 50px; + } } @@ -138,154 +143,171 @@ /*=== Navigation */ - -.nav-list{ - .nav-header, - .item{ - height: 2.5em; - line-height: 2.5em; - font-size: 1rem; - } - .item{ - background: $sid-bg; - @include transition(all, 0.15s, ease-in-out); - a{ - padding: 0 1rem; - color: $sid-font-color; - } - .error{ - a{ - color: $alert-bg; - } +.nav-list { + .nav-header, + .item { + height: 2.5em; + line-height: 2.5em; + font-size: 1rem; } - &:hover{ - background: $sid-bg-dark; - color: $sid-font-color; - - .error{ - a{ - color: $sid-font-color; - background: $main-first; + + .item { + background: $sid-bg; + + @include transition(all, 0.15s, ease-in-out); + + a { + padding: 0 1rem; + color: $sid-font-color; } - } - .empty{ - a{ - color: $sid-font-color; - background: $warning-bg; + + .error { + a { + color: $alert-bg; + } } - } - - a{ - color: $sid-font-color; - text-decoration: none; - } - } - &.active{ - background: $main-first; - color: $white; - - .error{ - a{ - color: $white; - background: $main-first; + + &:hover { + background: $sid-bg-dark; + color: $sid-font-color; + + .error { + a { + background: $main-first; + color: $sid-font-color; + } + } + + .empty { + a { + background: $warning-bg; + color: $sid-font-color; + } + } + + a { + color: $sid-font-color; + text-decoration: none; + } } - } - .empty{ - a{ - color: $white; - background: $warning-bg; + &.active { + background: $main-first; + color: $white; + + .error { + a { + background: $main-first; + color: $white; + } + } + + .empty { + a { + background: $warning-bg; + color: $white; + } + } + + a { + color: $white; + text-decoration: none; + } } - } - - a{ - color: $white; - text-decoration: none; - } + } - - } - &.empty{ - a{ - color: $warning-bg; + + &.empty { + a { + color: $warning-bg; + } } - } - .disable{ - text-align: center; - background: $grey-lighter; - color: $grey-medium-dark; - } - .nav-header { - padding: 0 10px; - font-weight: bold; - color: $grey-dark; - text-transform: uppercase; - letter-spacing: 1px; - margin-top: 1rem; - } - - .nav-form { - padding: 3px; - text-align: center; - } - - .nav-head { - margin: 0; - text-align: right; - // background: #34495e; - color: $white; - a { - color: $white; - } - .item { - padding: 5px 10px; - font-size: 0.9rem; - line-height: 1.5rem; - } - } + .disable { + text-align: center; + background: $grey-lighter; + color: $grey-medium-dark; + } + + .nav-header { + padding: 0 10px; + font-weight: bold; + color: $grey-dark; + text-transform: uppercase; + letter-spacing: 1px; + margin-top: 1rem; + } + + .nav-form { + padding: 3px; + text-align: center; + } + + .nav-head { + margin: 0; + text-align: right; + // background: #34495e; + color: $white; + + a { + color: $white; + } + + .item { + padding: 5px 10px; + font-size: 0.9rem; + line-height: 1.5rem; + } + } } /*=== Aside main page (categories) */ .aside_feed .tree-folder-title > .title:not([data-unread="0"])::after { - position: absolute; - right: 0; - line-height: 1.5rem; - background: $sid-pills; - border-radius: 12px; - padding: 0 0.75rem; - margin: -0.5rem 1rem 0 0; - text-align: center; + margin: -0.5rem 1rem 0 0; + padding: 0 0.75rem; + background: $sid-pills; + border-radius: 12px; + position: absolute; + right: 0; + line-height: 1.5rem; + text-align: center; } .feed.item.empty.active { - background: $grey-dark; + background: $grey-dark; } + .feed.item.error.active { - background: $grey-dark; + background: $grey-dark; } + .feed.item.empty, .feed.item.empty > a { - color: $grey-dark; + color: $grey-dark; } + .feed.item.error, .feed.item.error > a { - color: $grey-dark; + color: $grey-dark; } + .feed.item.empty.active, .feed.item.error.active, .feed.item.empty.active > a, .feed.item.error.active > a { - color: $white; + color: $white; } + .aside_feed .tree-folder-items .dropdown-menu::after { - left: 2px; + left: 2px; } + .aside_feed .tree-folder-items .item .dropdown-target:target ~ .dropdown-toggle > .icon, .aside_feed .tree-folder-items .item:hover .dropdown-toggle > .icon, .aside_feed .tree-folder-items .item.active .dropdown-toggle > .icon { - border-radius: 3px; + border-radius: 3px; } -.aside_feed .stick #btn-importExport{ - border-left-color: $sid-bg; + +.aside_feed .stick #btn-importExport { + border-left-color: $sid-bg; } diff --git a/p/themes/Mapco/_stats.scss b/p/themes/Mapco/_stats.scss index f287efa08..592419166 100644 --- a/p/themes/Mapco/_stats.scss +++ b/p/themes/Mapco/_stats.scss @@ -9,6 +9,7 @@ .stat tr { border: none; } + .stat > table td, .stat > table th { border-bottom: 1px solid $grey-medium-light; @@ -17,11 +18,13 @@ .stat > .horizontal-list { margin: 0 0 5px; } + .stat > .horizontal-list .item { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } + .stat > .horizontal-list .item:first-child { width: 270px; } diff --git a/p/themes/Mapco/_tables.scss b/p/themes/Mapco/_tables.scss index 608e19aca..2d0311608 100644 --- a/p/themes/Mapco/_tables.scss +++ b/p/themes/Mapco/_tables.scss @@ -7,9 +7,11 @@ tr, th, td { padding: 0.5em; border: 1px solid $grey-medium-light; } + th { background: $grey-lighter; } + form td, form th { font-weight: normal; diff --git a/p/themes/Mapco/_variables.scss b/p/themes/Mapco/_variables.scss index b01d9bd67..993d263bf 100644 --- a/p/themes/Mapco/_variables.scss +++ b/p/themes/Mapco/_variables.scss @@ -1,6 +1,6 @@ // La couleur principale du thème -$main-first: #3366cc; // couleur principale -$main-first-alt: #2255cc; // var pour les hovers +$main-first: #36c; // couleur principale +$main-first-alt: #25c; // var pour les hovers $main-first-light: #effcfd; // var light 1 $main-first-lighter: #f7fdfe; // var light 2 $main-first-dark: #35363c; // var pour les hovers @@ -15,11 +15,11 @@ $white: #fff; // le blanc (des fois qu'on aurait envie de le teinter un peu) // on essaiera de teinter ces gris suivant la couleur principale $grey-darker: #3b3f4d; $grey-dark: #5b6871; -$grey-medium-dark: #a6a7ae; +$grey-medium-dark: #a6a7ae; $grey-medium: #c5ced3; -$grey-medium-light: #d5d8db; -$grey-light: #eff0f2; -$grey-lighter: #f9fafb; +$grey-medium-light: #d5d8db; +$grey-light: #eff0f2; +$grey-lighter: #f9fafb; $unread-font-color: $main-first; $unread-bg: #f2f6f8; @@ -39,15 +39,15 @@ $success-light: #cffde7; $success-text: #0c7540; // les favoris -$fav-bg: #FFC300; -$fav-light: #FFF6DA; +$fav-bg: #ffc300; +$fav-light: #fff6da; // la couleur de sidebar, utile si on a envie d'un thème qui aurait une sidebar foncé, e.g. -$sid-font-color: #FFFFFF; // la couleur de fond de la barre de gauche et du header +$sid-font-color: #fff; // la couleur de fond de la barre de gauche et du header $sid-bg: #303136; // le background général de la barre de gauche, et du header $sid-bg-alt: #26272a; // le background de l'intérieur des groupes $sid-bg-dark: #17181a; // les hovers $sid-sep: #3f3f3f; // les séparateurs $sid-active: $main-first; // la couleur active -$sid-active-font: #FFFFFF; // la couleur active +$sid-active-font: #fff; // la couleur active $sid-pills: rgba(0,0,0, 0.25); // les gélules diff --git a/p/themes/Mapco/mapco.css b/p/themes/Mapco/mapco.css index a69a4f960..b144dca58 100644 --- a/p/themes/Mapco/mapco.css +++ b/p/themes/Mapco/mapco.css @@ -46,33 +46,34 @@ font-stretch: normal; font-weight: 700; src: local("Spectral"), url("../fonts/Spectral-BoldItalic.woff") format("woff"); } +/* stylelint-disable property-no-vendor-prefix */ /* FUNCTIONS */ /* btns */ .btn { + margin: 0; + padding: 0.5rem 1.5rem; + background: #f9fafb; display: inline-block; + color: #5b6871; + font-size: 1rem; + border: none; + border-radius: 5px; min-height: 38px; min-width: 15px; line-height: 25px; - margin: 0; - padding: 0.5rem 1.5rem; - font-size: 1rem; vertical-align: middle; cursor: pointer; overflow: hidden; - background: #f9fafb; - border-radius: 5px; - border: none; - color: #5b6871; -webkit-transition: all 0.15s ease-in-out; -moz-transition: all 0.15s ease-in-out; -o-transition: all 0.15s ease-in-out; -ms-transition: all 0.15s ease-in-out; transition: all 0.15s ease-in-out; } .btn.btn-important { - background: #3366cc; + background: #36c; color: #fff; } .btn.btn-important:hover, .btn.btn-important:active { - background: #2255cc; } + background: #25c; } .btn.btn-attention { background: #f5633e; color: #fff; } @@ -87,10 +88,10 @@ a.btn { /*=== Forms */ legend { - display: inline-block; - width: auto; margin: 2rem 0 1rem 0; padding: 0; + display: inline-block; + width: auto; font-size: 1rem; clear: both; text-transform: uppercase; @@ -108,23 +109,23 @@ textarea { height: 100px; } input, select, textarea, button { - font-family: "lato", "Helvetica", "Arial", sans-serif; - min-height: 25px; padding: 5px 10px; - line-height: 25px; - vertical-align: middle; background: #fff; - border: 1px solid #eff0f2; - font-size: 1rem; color: #5b6871; - border-radius: 2px; } + font-family: "lato", "Helvetica", "Arial", sans-serif; + font-size: 1rem; + border: 1px solid #eff0f2; + border-radius: 2px; + min-height: 25px; + line-height: 25px; + vertical-align: middle; } option { padding: 0 .5em; } input:focus, select:focus, textarea:focus { color: #303136; - border-color: #3366cc; } + border-color: #36c; } input:invalid, select:invalid { color: #f5633e; @@ -135,11 +136,7 @@ input:disabled, select:disabled { background: #eff0f2; } input.extend { - transition: width 200ms linear; - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; } + transition: width 200ms linear; } .form-group { padding: 5px; @@ -194,29 +191,24 @@ form th { /*=== Dropdown */ .dropdown-menu { - background: #f9fafb; margin: 0; - font-size: 1rem; - text-align: left; padding: 0.5rem 0 1rem 0; + background: #f9fafb; + font-size: 1rem; border: none; border-radius: 3px; - -webkit-box-shadow: 0px 6px 8px 0px rgba(0, 0, 0, 0.35); - -moz-box-shadow: 0px 6px 8px 0px rgba(0, 0, 0, 0.35); - box-shadow: 0px 6px 8px 0px rgba(0, 0, 0, 0.35); } + box-shadow: 0px 6px 8px 0px rgba(0, 0, 0, 0.35); + text-align: left; } .dropdown-menu::after { + background: white; + width: 10px; + height: 10px; content: ""; position: absolute; top: -4px; right: 13px; - width: 10px; - height: 10px; z-index: -10; - transform: rotate(45deg); - -moz-transform: rotate(45deg); - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); - background: white; } + transform: rotate(45deg); } .dropdown-menu .dropdown-header { margin: 1.75rem 0 0.5rem 2rem; font-weight: bold; @@ -232,18 +224,18 @@ form th { transition: all 0.075s ease-in-out; } .dropdown-menu .item a, .dropdown-menu .item span, .dropdown-menu .item .as-link { padding: 0 2rem; - line-height: 2.5em; + color: #303136; font-size: 1rem; - color: #303136; } + line-height: 2.5em; } .dropdown-menu .item:hover { - background: #3366cc; + background: #36c; color: #fff; } .dropdown-menu .item:hover a, .dropdown-menu .item:hover button { text-decoration: none; color: #fff; } .dropdown-menu .item[aria-checked="true"] a::before { - font-weight: bold; - margin: 0 0 0 -14px; } + margin: 0 0 0 -14px; + font-weight: bold; } .dropdown-menu .input select, .dropdown-menu .input input { margin: 0 auto 5px; padding: 2px 5px; @@ -252,32 +244,26 @@ form th { margin: 0.75rem 0; border-bottom: 1px solid #eff0f2; } -.tree .tree-folder .tree-folder-items .dropdown-menu .item, .tree .tree-folder .tree-folder-items .dropdown-menu .item { padding: 0; } .tree .tree-folder .tree-folder-items .dropdown-menu .item a, - .tree .tree-folder .tree-folder-items .dropdown-menu .item button, - .tree .tree-folder .tree-folder-items .dropdown-menu .item a, .tree .tree-folder .tree-folder-items .dropdown-menu .item button { color: #303136; } .tree .tree-folder .tree-folder-items .dropdown-menu .item a:hover, - .tree .tree-folder .tree-folder-items .dropdown-menu .item button:hover, - .tree .tree-folder .tree-folder-items .dropdown-menu .item a:hover, .tree .tree-folder .tree-folder-items .dropdown-menu .item button:hover { color: #fff; } - .tree .tree-folder .tree-folder-items .dropdown-menu .item:hover, .tree .tree-folder .tree-folder-items .dropdown-menu .item:hover { - background: #3366cc; } + background: #36c; } /*=== Alerts */ .alert { margin: 1rem 0; padding: 1rem; - font-size: 1rem; background: #f9fafb; + color: #5b6871; + font-size: 1rem; border: 1px solid #c5ced3; border-radius: 3px; - color: #5b6871; text-shadow: 0 0 1px #eff0f2; } .alert-head { @@ -289,43 +275,43 @@ form th { .alert-warn { background: #fdfde0; - border: 1px solid #73762f33; - color: #73762f; } + color: #73762f; + border: 1px solid #73762f33; } .alert-success { background: #cffde7; - border: 1px solid #0c754033; - color: #0c7540; } + color: #0c7540; + border: 1px solid #0c754033; } .alert-error { background: #fde0d8; - border: 1px solid #73341f33; - color: #73341f; } + color: #73341f; + border: 1px solid #73341f33; } /*=== Pagination */ .pagination { - text-align: center; - font-size: 0.8em; background: #eff0f2; - color: #303136; } + color: #303136; + font-size: 0.8em; + text-align: center; } .pagination .item.pager-current { - font-weight: bold; - font-size: 1.5em; background: #303136; - color: #eff0f2; } + color: #eff0f2; + font-size: 1.5em; + font-weight: bold; } .pagination .item a { display: block; + color: #303136; font-style: italic; line-height: 3em; - text-decoration: none; - color: #303136; } + text-decoration: none; } .pagination .item a:hover { background: #303136; color: #eff0f2; } .pagination .loading, .pagination a:hover.loading { - font-size: 0; - background: url("loader.gif") center center no-repeat #34495e; } + background: url("loader.gif") center center no-repeat #34495e; + font-size: 0; } .content .pagination { margin: 0; @@ -333,11 +319,9 @@ form th { /*=== Boxes */ .box { + background: #fff; border: none; border-radius: 3px; - background: #fff; - -webkit-box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.25); - -moz-box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.25); box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.25); } .box .box-title { margin: 0; @@ -348,20 +332,20 @@ form th { .box .box-title img { margin-right: 0.75rem; } .box .box-title:hover .configure { - visibility: visible; background: url("icons/cog.svg") no-repeat 4px 4px; + display: block; + float: left; width: 1.75rem; height: 1.75rem; - display: block; border-radius: 2px; - float: left; + visibility: visible; margin-right: 0.5rem; } .box .box-title:hover .configure .icon { - vertical-align: middle; + display: none; border-radius: 3px; - display: none; } + vertical-align: middle; } .box .box-title:hover .configure:hover { - background: url("icons/cog-white.svg") no-repeat 4px 4px #3366cc; } + background: url("icons/cog-white.svg") no-repeat 4px 4px #36c; } .box .box-title .configure { visibility: hidden; } .box .box-title form input { @@ -370,35 +354,35 @@ form th { float: right; } .box .box-title form .dropdown a.dropdown-toggle { padding: 0; + border-radius: 0; background-image: url(icons/more.svg); background-repeat: no-repeat; - background-position: right 8px; - border-radius: 0; } + background-position: right 8px; } .box .box-title form .dropdown a.dropdown-toggle img { display: none; } .box .box-content .item { padding: 0.5rem 0.75rem; - font-size: 1rem; color: #303136; - line-height: 1.7em; - border-bottom: 1px solid #eff0f2; } + font-size: 1rem; + border-bottom: 1px solid #eff0f2; + line-height: 1.7em; } .box .box-content .item img { margin-right: 0.75rem; } .box .box-content .item .configure { - visibility: hidden; + background: url("icons/cog.svg") no-repeat 4px 4px; + display: block; + float: left; width: 1.75rem; height: 1.75rem; - display: block; border-radius: 2px; - float: left; - margin-right: 0.5rem; - background: url("icons/cog.svg") no-repeat 4px 4px; } + visibility: hidden; + margin-right: 0.5rem; } .box .box-content .item .configure .icon { - vertical-align: middle; + display: none; border-radius: 3px; - display: none; } + vertical-align: middle; } .box .box-content .item .configure:hover { - background: url("icons/cog-white.svg") no-repeat 4px 4px #3366cc; } + background: url("icons/cog-white.svg") no-repeat 4px 4px #36c; } .box .box-content .item:hover .configure { visibility: visible; } .box .box-content .item:last-child { @@ -409,24 +393,24 @@ form th { text-align: center; text-decoration: none; background: #effcfd; - color: #3366cc; + color: #36c; -webkit-transition: all 0.15s ease-in-out; -moz-transition: all 0.15s ease-in-out; -o-transition: all 0.15s ease-in-out; -ms-transition: all 0.15s ease-in-out; transition: all 0.15s ease-in-out; } #bigMarkAsRead:hover { - background: #3366cc; + background: #36c; color: #fff; } #bigMarkAsRead:hover .bigTick { background: url(icons/tick-white.svg) center no-repeat; } #bigMarkAsRead .bigTick { margin: 0.5rem 0; - display: inline-block; - text-indent: -9999px; background: url(icons/tick-color.svg) center no-repeat; - height: 64px; + display: inline-block; width: 64px; + height: 64px; + text-indent: -9999px; white-space: nowrap; } .formLogin { @@ -442,8 +426,8 @@ form th { .formLogin form#crypto-form div { margin-bottom: 1rem; } .formLogin form#crypto-form div label { - font-size: 1rem; - color: #c5ced3; } + color: #c5ced3; + font-size: 1rem; } .formLogin form#crypto-form div input { background: #1d1e22; } .formLogin form#crypto-form div input:focus { @@ -467,20 +451,20 @@ form th { margin: 10px 0; } .tree#sidebar { scrollbar-color: rgba(255, 255, 0, 0.1) rgba(0, 0, 0, 0.05); - scrollbar-color: #FFFFFF33 #FFFFFF22; } + scrollbar-color: #fff33 #fff22; } .tree .tree-folder { border-bottom: 1px solid #3f3f3f; } .tree .tree-folder .tree-folder-title { - position: relative; + padding: 12px 16px; background: #303136; + position: relative; font-size: 0.85rem; letter-spacing: 1px; - padding: 12px 16px; font-weight: 700; text-transform: uppercase; } .tree .tree-folder .tree-folder-title .title { background: inherit; - color: #FFFFFF; } + color: #fff; } .tree .tree-folder .tree-folder-title .title:hover { text-decoration: none; } .tree .tree-folder.active .tree-folder-title { @@ -499,28 +483,28 @@ form th { -ms-transition: all 0.15s ease-in-out; transition: all 0.15s ease-in-out; } .tree .tree-folder .tree-folder-items .item.active { - background: #3366cc; } + background: #36c; } .tree .tree-folder .tree-folder-items .item.active .dropdown li a { color: #303136; } .tree .tree-folder .tree-folder-items .item.active .dropdown li a:hover { - color: #FFFFFF; } + color: #fff; } .tree .tree-folder .tree-folder-items .item.active a { - color: #FFFFFF; } + color: #fff; } .tree .tree-folder .tree-folder-items .item:hover { background: #17181a; } .tree .tree-folder .tree-folder-items .item a { text-decoration: none; - color: #FFFFFF; } + color: #fff; } .tree .tree-folder .tree-folder-items .feed .item-title:not([data-unread="0"])::before { - content: attr(data-unread); + margin: 11px 6px 0 4px; + padding: 3px 4px; background: rgba(0, 0, 0, 0.25); - font-size: 0.75rem; display: block; float: left; - padding: 3px 4px; - text-align: center; + font-size: 0.75rem; border-radius: 12px; - margin: 11px 6px 0 4px; + content: attr(data-unread); + text-align: center; line-height: 0.75rem; } /*=== Buttons */ @@ -570,30 +554,30 @@ form th { transition: all 0.15s ease-in-out; } .nav-list .item a { padding: 0 1rem; - color: #FFFFFF; } + color: #fff; } .nav-list .item .error a { color: #f5633e; } .nav-list .item:hover { background: #17181a; - color: #FFFFFF; } + color: #fff; } .nav-list .item:hover .error a { - color: #FFFFFF; - background: #3366cc; } + background: #36c; + color: #fff; } .nav-list .item:hover .empty a { - color: #FFFFFF; - background: #f4f762; } + background: #f4f762; + color: #fff; } .nav-list .item:hover a { - color: #FFFFFF; + color: #fff; text-decoration: none; } .nav-list .item.active { - background: #3366cc; + background: #36c; color: #fff; } .nav-list .item.active .error a { - color: #fff; - background: #3366cc; } + background: #36c; + color: #fff; } .nav-list .item.active .empty a { - color: #fff; - background: #f4f762; } + background: #f4f762; + color: #fff; } .nav-list .item.active a { color: #fff; text-decoration: none; } @@ -626,13 +610,13 @@ form th { /*=== Aside main page (categories) */ .aside_feed .tree-folder-title > .title:not([data-unread="0"])::after { + margin: -0.5rem 1rem 0 0; + padding: 0 0.75rem; + background: rgba(0, 0, 0, 0.25); + border-radius: 12px; position: absolute; right: 0; line-height: 1.5rem; - background: rgba(0, 0, 0, 0.25); - border-radius: 12px; - padding: 0 0.75rem; - margin: -0.5rem 1rem 0 0; text-align: center; } .feed.item.empty.active { @@ -670,19 +654,19 @@ form th { /*===============*/ /*=== Header */ .header { - background: #303136; padding: 0.5rem 1.35rem; + background: #303136; display: block; - table-layout: none; - width: auto; } + width: auto; + table-layout: none; } .header .item { vertical-align: middle; } .header .item.title { - font-weight: 400; - width: 280px; } + width: 280px; + font-weight: 400; } .header .item.title h1 a { text-decoration: none; - color: #FFFFFF; + color: #fff; font-size: 1rem; text-transform: uppercase; letter-spacing: 1px; } @@ -690,10 +674,10 @@ form th { margin-right: 0.5rem; } .header .item.search input { width: 230px; + color: #fff; + border: none; border-radius: 2px 0 0 2px; background-color: #26272a; - color: #FFFFFF; - border: none; -webkit-transition: all 0.15s ease-in-out; -moz-transition: all 0.15s ease-in-out; -o-transition: all 0.15s ease-in-out; @@ -703,21 +687,21 @@ form th { background-color: #17181a; } .header .item.search input:focus { width: 350px; - background-color: #fff; - color: #5b6871; } + color: #5b6871; + background-color: #fff; } .header .item.search .btn { + width: 3rem; border-radius: 0 2px 2px 0; - background-color: #3366cc; + background-color: #36c; background-position: center; background-repeat: no-repeat; background-image: url(icons/magnifier.svg); border-left-width: 0; - width: 3rem; min-height: 35px; } .header .item.search .btn img { display: none; } .header .item.search .btn:hover { - background-color: #2255cc; } + background-color: #25c; } .header .item.configure { width: 2rem; position: absolute; @@ -725,11 +709,11 @@ form th { top: 1.25rem; text-align: center; } .header .item.configure .btn { + padding: 0 0.5rem; background-color: transparent; background-position: center; background-repeat: no-repeat; - background-image: url(icons/cog-white.svg); - padding: 0 0.5rem; } + background-image: url(icons/cog-white.svg); } .header .item.configure .btn img { display: none; } @@ -757,12 +741,12 @@ form th { /*=== New article notification */ #new-article { - text-align: center; + background: #36c; font-size: 1rem; - background: #3366cc; } + text-align: center; } #new-article:hover { - background: #2255cc; } + background: #25c; } #new-article > a { line-height: 3em; @@ -775,16 +759,16 @@ form th { /*=== Day indication */ .day { padding: 1rem 0 0 1.25rem; + color: #5b6871; + font-size: 0.875rem; font-weight: 700; line-height: 3em; letter-spacing: 1px; - text-transform: uppercase; - font-size: 0.875rem; - color: #5b6871; } + text-transform: uppercase; } .day .name { padding: 0 1rem 0 1rem; - font-size: 0.875rem; color: #303136; + font-size: 0.875rem; position: relative; left: 0; text-transform: uppercase; } @@ -817,7 +801,7 @@ form th { .nav_menu .stick .btn:hover { background-color: #d5d8db; } .nav_menu .stick .btn.active { - background-color: #3366cc; } + background-color: #36c; } .nav_menu .stick .btn img.icon { display: none; } .nav_menu .stick .btn#toggle-read { @@ -837,9 +821,9 @@ form th { .nav_menu .stick .btn#toggle-non-starred.active { background-image: url(icons/non-starred-white.svg); } .nav_menu .stick .btn.read_all { - background-color: #f9fafb; - color: #303136; padding: 5px 16px; + color: #303136; + background-color: #f9fafb; -webkit-transition: all 0.15s ease-in-out; -moz-transition: all 0.15s ease-in-out; -o-transition: all 0.15s ease-in-out; @@ -877,21 +861,21 @@ form th { line-height: 1.8rem; } .content h1.title a, .content h1 a, .content.thin h1.title a, .content.thin h1 a { color: #303136; - font-family: "spectral"; + font-family: "spectral", serif; font-size: 2rem; } .content h1.title a:hover, .content h1 a:hover, .content.thin h1.title a:hover, .content.thin h1 a:hover { - color: #3366cc; + color: #36c; text-decoration: none; } .content .author, .content.thin .author { - font-size: 1.125rem; - color: #5b6871; } + color: #5b6871; + font-size: 1.125rem; } .content p, .content ul, .content.thin p, .content.thin ul { font-size: 1.125rem; line-height: 1.8rem; } .content .content hr, .content.thin .content hr { margin: 30px 10px; - height: 1px; background: #d5d8db; + height: 1px; border: 0; box-shadow: 0 2px 5px #ccc; } .content pre, .content.thin pre { @@ -908,42 +892,42 @@ form th { border: none; } .content code, .content.thin code { padding: 2px 5px; - color: #eff0f2; background: #f9fafb; + color: #eff0f2; border: 1px solid #eff0f2; border-radius: 3px; } .content blockquote, .content.thin blockquote { - display: block; margin: 0; padding: 5px 20px; - border-top: 1px solid #d5d8db; - border-bottom: 1px solid #d5d8db; background: #f9fafb; - color: #303136; } + display: block; + color: #303136; + border-top: 1px solid #d5d8db; + border-bottom: 1px solid #d5d8db; } .content blockquote p, .content.thin blockquote p { margin: 0; } /*=== Notification and actualize notification */ .notification { + padding: 1rem 0; + background: #d5d8db; + width: 100%; + height: 3rem; + color: #5b6871; + font-size: 1em; + border: none; position: fixed; top: auto; bottom: 0; left: 0; right: 0; - width: 100%; - height: 3rem; - padding: 1rem 0; text-align: center; - font-size: 1em; line-height: 3em; z-index: 10; - vertical-align: middle; - background: #d5d8db; - color: #5b6871; - border: none; } + vertical-align: middle; } .notification .msg { - font-size: 1rem; - display: inline-block; } + display: inline-block; + font-size: 1rem; } .notification.good { background: #10f587; color: #fff; } @@ -952,8 +936,8 @@ form th { color: #fff; } .notification a.close { padding: 0 15px; - line-height: 3em; - border-radius: 0 3px 3px 0; } + border-radius: 0 3px 3px 0; + line-height: 3em; } .notification.good a.close:hover { background: #0c7540; } .notification.bad a.close:hover { @@ -984,8 +968,8 @@ form th { .flux:hover:not(.current):hover .item.title { background: #f9fafb; } .flux.current { - border-left-color: #3366cc; - background: #f9fafb; } + background: #f9fafb; + border-left-color: #36c; } .flux.not_read { background: #f2f6f8; } .flux.not_read:hover { @@ -993,29 +977,29 @@ form th { .flux.not_read:not(.current):hover .item.title { background: #fdfdfe; } .flux.not_read .item.title a { - color: #3366cc; } + color: #36c; } .flux.not_read .item.website a { - color: #3366cc; } + color: #36c; } .flux.not_read .item.date { - color: #3366cc99; } + color: #36c99; } .flux.favorite { - background: #FFF6DA; - border-left-color: #FFC300; + background: #fff6da; + border-left-color: #ffc300; -webkit-transition: all 0.15s ease-in-out; -moz-transition: all 0.15s ease-in-out; -o-transition: all 0.15s ease-in-out; -ms-transition: all 0.15s ease-in-out; transition: all 0.15s ease-in-out; } .flux.favorite:not(.current):hover .item.title { - background: #FFF6DA; } + background: #fff6da; } .flux .website a { color: #303136; opacity: 0.75; } .flux .website .favicon { padding: 5px; } .flux .date { - font-size: 0.85rem; color: #303136; + font-size: 0.85rem; opacity: 0.75; } .flux .bottom { font-size: 1rem; @@ -1033,38 +1017,38 @@ form th { #stream .box.category:not([data-unread="0"]) .box-title .title { font-weight: bold; } #stream .box.category .box-title { - background: none; - padding: 1.5rem; } + padding: 1.5rem; + background: none; } #stream .box.category .box-title a.title { + color: #5b6871; + font-size: 1rem; font-weight: normal; text-decoration: none; text-align: left; - font-size: 1rem; text-transform: uppercase; - letter-spacing: 1px; - color: #5b6871; } + letter-spacing: 1px; } #stream .box.category .box-title a.title:not([data-unread="0"])::after { + margin: -0.5rem 1rem 0 0; + padding: 0 0.75rem; + background: #eff0f2; + border-radius: 12px; position: absolute; top: 1.75rem; right: 0; line-height: 1.5rem; - background: #eff0f2; - border-radius: 12px; - padding: 0 0.75rem; - margin: -0.5rem 1rem 0 0; text-align: center; } #stream .box.category .box-title a.title:hover { - color: #3366cc; } + color: #36c; } #stream .box.category .box-content { padding-bottom: 0.5rem; } #stream .box.category .box-content .item.feed { - font-size: 1rem; - padding: 0.5rem 1.5rem; } + padding: 0.5rem 1.5rem; + font-size: 1rem; } #stream .box.category .box-content .item.feed a { color: #303136; font-weight: 400; } #stream .box.category .box-content .item.feed a:hover { - color: #3366cc; + color: #36c; text-decoration: none; } #overlay { @@ -1087,8 +1071,8 @@ form th { #stream.reader .flux .author { margin: 0 0 10px; - font-size: 90%; - color: #a6a7ae; } + color: #a6a7ae; + font-size: 90%; } /*=== Configuration pages */ .post { @@ -1101,42 +1085,40 @@ form th { .post.content { max-width: 550px; } .post h1, .post h2 { + color: #303136; font-size: 3rem; margin-top: 1.75rem; font-weight: 300; - line-height: 1.2em; - color: #303136; } + line-height: 1.2em; } .post a[href="./"] { - display: inline-block; - min-width: 15px; - line-height: 25px; margin: 0; padding: 0.75rem 1.5rem; - font-size: 1rem; - vertical-align: middle; - cursor: pointer; - overflow: hidden; background: #f9fafb; + display: inline-block; + color: #5b6871; + font-size: 1rem; border: 1px solid #d5d8db; border-radius: 5px; - color: #5b6871; } + min-width: 15px; + line-height: 25px; + vertical-align: middle; + cursor: pointer; + overflow: hidden; } .post a[href="./"]:hover { - text-decoration: none; - background: #3366cc; + background: #36c; color: white; - border: 1px solid #3366cc; } + border: 1px solid #36c; + text-decoration: none; } #slider { border-left: none; - -webkit-box-shadow: 0px 6px 8px 0px rgba(0, 0, 0, 0.35); - -moz-box-shadow: 0px 6px 8px 0px rgba(0, 0, 0, 0.35); box-shadow: 0px 6px 8px 0px rgba(0, 0, 0, 0.35); } .slide-container .properties { - background: rgba(0, 0, 0, 0.75); - border: 0; padding: 1rem; - color: white; } + background: rgba(0, 0, 0, 0.75); + color: white; + border: 0; } .slide-container .properties .page-number { right: 1rem; top: 1rem; } @@ -1150,10 +1132,10 @@ form th { .log { margin: 10px 0; padding: 5px 2%; - overflow: auto; - font-size: 0.8rem; background: #f9fafb; - color: #5b6871; } + color: #5b6871; + font-size: 0.8rem; + overflow: auto; } .log > .date { margin: 0 10px 0 0; @@ -1207,11 +1189,11 @@ form th { ul.nav .item img { display: none; } ul.nav .item a { - display: inline-block; padding: 1rem 1rem 1rem 2.5rem; - color: #FFFFFF; - width: 100%; background: url("../../themes/icons/logout.svg") no-repeat #17181a 3% center; + display: inline-block; + width: 100%; + color: #fff; -webkit-transition: all 0.2s ease-in-out; -moz-transition: all 0.2s ease-in-out; -o-transition: all 0.2s ease-in-out; @@ -1234,12 +1216,12 @@ form th { .aside .toggle_aside, #panel .close { + background: #25c; display: block; width: 100%; height: 50px; line-height: 50px; - text-align: center; - background: #2255cc; } + text-align: center; } .header { padding: 0.5rem; } @@ -1275,7 +1257,7 @@ form th { .nav_menu .search .input { max-width: 97%; width: 90px; } - .nav_menu .search .input::focus { + .nav_menu .search .input:focus { width: 400px; } #stream .flux .flux_header { @@ -1285,8 +1267,8 @@ form th { text-align: center; padding: 1rem 0; } .day .name { - display: block; padding: 0; + display: block; width: 100%; line-height: 1.5rem; margin-bottom: 1rem; } @@ -1300,26 +1282,24 @@ form th { .notification { border-radius: 0; } .notification a.close { + background: transparent; display: block; - left: 0; - background: transparent; } + left: 0; } .notification a.close:hover { opacity: 0.5; } .notification a.close .icon { display: none; } } -html, body { - font-family: "lato", "Helvetica", "Arial", sans-serif; - font-size: 0.875rem; } - /*=== GENERAL */ /*============*/ html, body { + background: #eff0f2; height: 100%; - background: #eff0f2; } + font-family: "lato", "Helvetica", "Arial", sans-serif; + font-size: 0.875rem; } /*=== Links */ a, button.as-link { outline: none; - color: #3366cc; } + color: #36c; } /*# sourceMappingURL=mapco.css.map */ diff --git a/p/themes/Mapco/mapco.css.map b/p/themes/Mapco/mapco.css.map deleted file mode 100644 index b4bb0bebc..000000000 --- a/p/themes/Mapco/mapco.css.map +++ /dev/null @@ -1,7 +0,0 @@ -{ -"version": 3, -"mappings": "AAAA,UAMC;EALG,WAAW,EAAE,MAAM;EACnB,UAAU,EAAE,MAAM;EAClB,YAAY,EAAE,MAAM;EACpB,WAAW,EAAE,GAAG;EAChB,GAAG,EAAE,oEAAoE;AAE7E,UAMC;EALG,WAAW,EAAE,MAAM;EACnB,UAAU,EAAE,MAAM;EAClB,YAAY,EAAE,MAAM;EACpB,WAAW,EAAE,GAAG;EAChB,GAAG,EAAE,mEAAmE;AAE5E,UAMC;EALG,WAAW,EAAE,MAAM;EACnB,UAAU,EAAE,MAAM;EAClB,YAAY,EAAE,MAAM;EACpB,WAAW,EAAE,GAAG;EAChB,GAAG,EAAE,iEAAiE;AAE1E,UAMC;EALG,WAAW,EAAE,MAAM;EACnB,UAAU,EAAE,MAAM;EAClB,YAAY,EAAE,MAAM;EACpB,WAAW,EAAE,GAAG;EAChB,GAAG,EAAE,uEAAuE;AAEhF,UAMC;EALG,WAAW,EAAE,UAAU;EACvB,UAAU,EAAE,MAAM;EAClB,YAAY,EAAE,MAAM;EACpB,WAAW,EAAE,GAAG;EAChB,GAAG,EAAE,uEAAuE;AAEhF,UAMC;EALG,WAAW,EAAE,UAAU;EACvB,UAAU,EAAE,MAAM;EAClB,YAAY,EAAE,MAAM;EACpB,WAAW,EAAE,GAAG;EAChB,GAAG,EAAE,sEAAsE;AAE/E,UAMC;EALG,WAAW,EAAE,UAAU;EACvB,UAAU,EAAE,MAAM;EAClB,YAAY,EAAE,MAAM;EACpB,WAAW,EAAE,GAAG;EAChB,GAAG,EAAE,oEAAoE;AAE7E,UAMC;EALG,WAAW,EAAE,UAAU;EACvB,UAAU,EAAE,MAAM;EAClB,YAAY,EAAE,MAAM;EACpB,WAAW,EAAE,GAAG;EAChB,GAAG,EAAE,0EAA0E;ACtDnF,eAAe;ACAf,UAAU;AAEV,IAAK;EACD,OAAO,EAAE,YAAY;EACrB,UAAU,EAAE,IAAI;EAChB,SAAS,EAAE,IAAI;EACf,WAAW,EAAE,IAAI;EACjB,MAAM,EAAE,CAAC;EACT,OAAO,EAAE,aAAa;EACtB,SAAS,EAAE,IAAI;EACf,cAAc,EAAE,MAAM;EACtB,MAAM,EAAE,OAAO;EACf,QAAQ,EAAE,MAAM;EAChB,UAAU,ECQC,OAAO;EDPlB,aAAa,EAAE,GAAG;EAClB,MAAM,EAAE,IAAI;EACZ,KAAK,ECAG,OAAO;EFZf,kBAAkB,EAAE,qBAAuB;EAC3C,eAAe,EAAE,qBAAuB;EACxC,aAAa,EAAE,qBAAuB;EACtC,cAAc,EAAE,qBAAuB;EACvC,UAAU,EAAE,qBAAuB;ECYnC,kBAAgB;IACnB,UAAU,EC6BE,OAAW;ID5BvB,KAAK,ECXE,IAAI;IDeX,mDACS;MACL,UAAU,EC1BE,OAAO;ED+BpB,kBAAgB;IACnB,UAAU,ECNA,OAAO;IDOjB,KAAK,EAAE,IAAI;IAEX,mDACS;MACL,UAAU,ECTF,OAAO;EDahB,UAAQ;IACX,eAAe,EAAE,IAAI;;AAItB,KAAM;EACF,UAAU,EAAE,IAAI;EAChB,WAAW,EAAE,IAAI;;AAGrB,cAAc;AACd,MAAO;EACH,OAAO,EAAE,YAAY;EACrB,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,aAAa;EACrB,OAAO,EAAE,CAAC;EACV,SAAS,EAAE,IAAI;EACf,KAAK,EAAE,IAAI;EACX,cAAc,EAAE,SAAS;EACzB,cAAc,EAAE,GAAG;EACnB,WAAW,EAAE,GAAG;;AAEpB,KAAM;EACF,UAAU,EAAE,IAAI;EAChB,OAAO,EAAE,KAAK;EACd,MAAM,EAAE,OAAO;EACf,KAAK,ECrDG,OAAO;;ADuDnB,QAAS;EACL,KAAK,EAAE,KAAK;EACZ,MAAM,EAAE,KAAK;;AAEjB,+BAAgC;EAC5B,WAAW,EAAE,wCAAwC;EACrD,UAAU,EAAE,IAAI;EAChB,OAAO,EAAE,QAAQ;EACjB,WAAW,EAAE,IAAI;EACjB,cAAc,EAAE,MAAM;EACtB,UAAU,ECtEN,IAAI;EDuER,MAAM,EAAE,iBAAqB;EAC7B,SAAS,EAAE,IAAI;EACf,KAAK,ECpEG,OAAO;EDqEf,aAAa,EAAE,GAAG;;AAEtB,MAAO;EACH,OAAO,EAAE,MAAM;;AAEnB,yCAA0C;EACtC,KAAK,EClFS,OAAO;EDmFrB,YAAY,EC1CH,OAAW;;AD4CxB,6BAA8B;EAC1B,KAAK,ECnEE,OAAO;EDoEd,YAAY,ECpEL,OAAO;EDqEd,UAAU,EAAE,IAAI;;AAEpB,+BAAgC;EAC5B,UAAU,EChFD,OAAO;;ADkFpB,YAAa;EACT,UAAU,EAAE,kBAAkB;EAC9B,eAAe,EAAE,kBAAkB;EACnC,kBAAkB,EAAE,kBAAkB;EACtC,aAAa,EAAE,kBAAkB;EACjC,cAAc,EAAE,kBAAkB;;AAItC,WAAY;EACR,OAAO,EAAE,GAAG;EACZ,aAAa,EAAE,GAAG;EAElB,kBAAS;IACZ,OAAO,EAAE,EAAE;IACX,OAAO,EAAE,KAAK;IACd,KAAK,EAAE,IAAI;EAQR,uBAAY;IACf,OAAO,EAAE,MAAM;IACf,UAAU,EAAE,KAAK;EAEd,2BAAgB;IACnB,UAAU,EAAE,IAAI;IAChB,OAAO,EAAE,KAAK;EAEX,oCAAyB;IAC5B,WAAW,EAAE,KAAK;EAEf,iBAAM;IACT,MAAM,EAAE,cAAc;EAGnB,wBAAe;IAClB,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,KAAK;EAGX,6BAAoB;IACvB,MAAM,EAAE,YAAY;;AEnJrB,eAAe;AACf,KAAM;EACL,eAAe,EAAE,QAAQ;;AAG1B,UAAW;EACV,OAAO,EAAE,KAAK;EACd,MAAM,EAAE,iBAA4B;;AAErC,EAAG;EACF,UAAU,EDWI,OAAO;;ACTtB;OACQ;EACP,WAAW,EAAE,MAAM;EACnB,UAAU,EAAE,MAAM;;ACfnB,mBAAmB;AACnB,mBAAmB;AACnB,cAAc;AAGd,wBAAwB;AACxB,gBAAiB;EACb,MAAM,EAAE,CAAC;EACT,OAAO,EAAE,QAAQ;EAEjB,sBAAK;IACR,cAAc,EAAE,MAAM;IAEtB,kCAAa;MACT,YAAY,EAAE,MAAM;;AAMzB,iBAAiB;AACjB,cAAe;EACX,UAAU,EFDC,OAAO;EEElB,MAAM,EAAE,CAAC;EACT,SAAS,EAAE,IAAI;EACf,UAAU,EAAE,IAAI;EAChB,OAAO,EAAE,eAAe;EACxB,MAAM,EAAE,IAAI;EACZ,aAAa,EAAE,GAAG;EAElB,kBAAkB,EAAE,mCAAgC;EACpD,eAAe,EAAE,mCAAgC;EACjD,UAAU,EAAE,mCAAgC;EAE5C,qBAAS;IACZ,OAAO,EAAE,EAAE;IACX,QAAQ,EAAE,QAAQ;IAClB,GAAG,EAAE,IAAI;IACT,KAAK,EAAE,IAAI;IACX,KAAK,EAAE,IAAI;IACX,MAAM,EAAE,IAAI;IACZ,OAAO,EAAE,GAAG;IACZ,SAAS,EAAE,aAAa;IACxB,cAAc,EAAE,aAAa;IAC7B,iBAAiB,EAAE,aAAa;IAChC,aAAa,EAAE,aAAa;IAC5B,UAAU,EAAE,KAAK;EAKd,+BAAiB;IAEpB,MAAM,EAAE,qBAAqB;IAC7B,WAAW,EAAE,IAAI;IACjB,UAAU,EAAE,IAAI;IAChB,KAAK,EFxCM,OAAO;IEyClB,cAAc,EAAE,SAAS;IACzB,cAAc,EAAE,GAAG;EAKhB,oBAAK;IJ3DL,kBAAkB,EAAE,sBAAuB;IAC3C,eAAe,EAAE,sBAAuB;IACxC,aAAa,EAAE,sBAAuB;IACtC,cAAc,EAAE,sBAAuB;IACvC,UAAU,EAAE,sBAAuB;IIyDtC,gFAAiB;MACb,OAAO,EAAE,MAAM;MACf,WAAW,EAAE,KAAK;MAClB,SAAS,EAAE,IAAI;MACf,KAAK,EF5DQ,OAAO;IE8DxB,0BAAO;MACH,UAAU,EFtBF,OAAW;MEuBnB,KAAK,EF9DF,IAAI;MEgEP,+DAAS;QACZ,eAAe,EAAE,IAAI;QACrB,KAAK,EFlEC,IAAI;IEsEP,mDAAS;MACZ,WAAW,EAAE,IAAI;MACjB,MAAM,EAAE,WAAW;EAKpB,yDAAa;IACT,MAAM,EAAE,UAAU;IAClB,OAAO,EAAE,OAAO;IAChB,aAAa,EAAE,GAAG;EAGnB,yBAAW;IACd,MAAM,EAAE,SAAS;IACjB,aAAa,EAAE,iBAAqB;;AASjC;0DAAK;EACR,OAAO,EAAE,CAAC;EAEV;;;mEACM;IACF,KAAK,EFrGQ,OAAO;IEuGpB;;;2EAAO;MACV,KAAK,EFtGC,IAAI;EEyGX;kEAAO;IACH,UAAU,EFnEF,OAAW;;AE2ExB,eAAe;AACf,MAAO;EACH,MAAM,EAAE,MAAM;EAEd,OAAO,EAAE,IAAI;EACb,SAAS,EAAE,IAAI;EACf,UAAU,EF9GC,OAAO;EE+GlB,MAAM,EAAE,iBAAsB;EAC9B,aAAa,EAAE,GAAG;EAClB,KAAK,EFtHG,OAAO;EEuHf,WAAW,EAAE,eAAmB;;AAEpC,WAAY;EACR,SAAS,EAAE,MAAM;;AAErB,UAAW;EACP,eAAe,EAAE,SAAS;EAC1B,KAAK,EAAE,OAAO;;AAElB,WAAY;EACR,UAAU,EFhHE,OAAO;EEiHnB,MAAM,EAAE,mBAAqC;EAC7C,KAAK,EFjHM,OAAO;;AEmHtB,cAAe;EACX,UAAU,EFjHE,OAAO;EEkHnB,MAAM,EAAE,mBAAqC;EAC7C,KAAK,EFlHM,OAAO;;AEoHtB,YAAa;EACT,UAAU,EF9HA,OAAO;EE+HjB,MAAM,EAAE,mBAAmC;EAC3C,KAAK,EF/HI,OAAO;;AEkIpB,mBAAmB;AACnB,WAAY;EACR,UAAU,EAAE,MAAM;EAClB,SAAS,EAAE,KAAK;EAChB,UAAU,EFhJD,OAAO;EEiJhB,KAAK,EF5JS,OAAO;EE+JxB,+BAAgB;IACZ,WAAW,EAAE,IAAI;IACjB,SAAS,EAAE,KAAK;IAChB,UAAU,EF7HN,OAAO;IE8HX,KAAK,EFxJG,OAAO;EE0JnB,mBAAE;IACE,OAAO,EAAE,KAAK;IACd,UAAU,EAAE,MAAM;IAClB,WAAW,EAAE,GAAG;IAChB,eAAe,EAAE,IAAI;IACrB,KAAK,EF1KQ,OAAO;IE4KpB,yBAAO;MACV,UAAU,EF7KM,OAAO;ME8KvB,KAAK,EFnKM,OAAO;EEwKhB;6BACgB;IACnB,SAAS,EAAE,CAAC;IACZ,UAAU,EAAE,iDAAiD;;AAG9D,oBAAqB;EACjB,MAAM,EAAE,CAAC;EACT,OAAO,EAAE,CAAC;;AAId,cAAc;AACd,IAAK;EAED,MAAM,EAAE,IAAI;EACZ,aAAa,EAAE,GAAG;EAClB,UAAU,EFlMN,IAAI;EEoMR,kBAAkB,EAAE,mCAAgC;EACpD,eAAe,EAAE,mCAAgC;EACjD,UAAU,EAAE,mCAAgC;EAE5C,eAAW;IACd,MAAM,EAAE,CAAC;IACT,OAAO,EAAE,QAAQ;IACjB,UAAU,EFlME,OAAO;IEmMnB,KAAK,EF9MY,OAAO;IEgNxB,aAAa,EAAE,WAAW;IAC1B,mBAAG;MACF,YAAY,EAAE,OAAO;IAIrB,gCAAW;MACV,UAAU,EAAE,OAAO;MACnB,UAAU,EAAE,sCAAsC;MAClD,KAAK,EAAE,OAAO;MACd,MAAM,EAAE,OAAO;MACf,OAAO,EAAE,KAAK;MACd,aAAa,EAAE,GAAG;MAClB,KAAK,EAAE,IAAI;MACX,YAAY,EAAE,MAAM;MACpB,sCAAM;QACL,cAAc,EAAE,MAAM;QACtB,aAAa,EAAE,GAAG;QAClB,OAAO,EAAE,IAAI;MAEd,sCAAQ;QACP,UAAU,EAAE,oDAAwD;IAIvE,0BAAW;MACV,UAAU,EAAE,MAAM;IAIf,0BAAK;MACR,KAAK,EAAE,GAAG;IAEP,8BAAS;MACZ,KAAK,EAAE,KAAK;MACZ,gDAAiB;QACb,OAAO,EAAE,CAAC;QACV,gBAAgB,EAAE,mBAAmB;QACrC,iBAAiB,EAAE,SAAS;QAC5B,mBAAmB,EAAE,SAAS;QAE9B,aAAa,EAAE,CAAC;QAChB,oDAAG;UACN,OAAO,EAAE,IAAI;EASf,uBAAM;IACF,OAAO,EAAE,cAAc;IACvB,SAAS,EAAE,IAAI;IACf,KAAK,EFvQQ,OAAO;IEwQpB,WAAW,EAAE,KAAK;IAClB,aAAa,EAAE,iBAAqB;IAEpC,2BAAG;MACN,YAAY,EAAE,OAAO;IAGlB,kCAAW;MACd,UAAU,EAAE,MAAM;MAClB,KAAK,EAAE,OAAO;MACd,MAAM,EAAE,OAAO;MACf,OAAO,EAAE,KAAK;MACd,aAAa,EAAE,GAAG;MAClB,KAAK,EAAE,IAAI;MACX,YAAY,EAAE,MAAM;MACpB,UAAU,EAAE,sCAAsC;MAElD,wCAAM;QACF,cAAc,EAAE,MAAM;QACtB,aAAa,EAAE,GAAG;QAClB,OAAO,EAAE,IAAI;MAEjB,wCAAO;QAEH,UAAU,EAAE,oDAAwD;IAGrE,wCAAmB;MACtB,UAAU,EAAE,OAAO;EAGpB,kCAAgB;IACZ,aAAa,EAAE,IAAI;;AAKxB,yBAAyB;AACzB,cAAe;EACX,UAAU,EAAE,MAAM;EAClB,eAAe,EAAE,IAAI;EACrB,UAAU,EFvTK,OAAO;EEwTtB,KAAK,EFzQI,OAAW;EF9CpB,kBAAkB,EAAE,qBAAuB;EAC3C,eAAe,EAAE,qBAAuB;EACxC,aAAa,EAAE,qBAAuB;EACtC,cAAc,EAAE,qBAAuB;EACvC,UAAU,EAAE,qBAAuB;EIuTnC,oBAAQ;IACX,UAAU,EF9QE,OAAW;IE+QvB,KAAK,EAAE,IAAI;IAEX,6BAAQ;MACJ,UAAU,EAAE,0CAA0C;EAGvD,uBAAQ;IACX,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,YAAY;IACrB,WAAW,EAAE,OAAO;IACpB,UAAU,EAAE,0CAA0C;IACtD,MAAM,EAAE,IAAI;IACZ,KAAK,EAAE,IAAI;IACX,WAAW,EAAE,MAAM;;AAKpB,UAAU;EACN,UAAU,EFtSL,OAAO;EEySf,6BAAU;IACN,aAAa,EAAE,IAAI;IACnB,iCAAG;MACN,YAAY,EAAE,MAAM;IAGjB,sCAAQ;MACX,KAAK,EFnVC,IAAI;EEwVR,aAAE;IACL,KAAK,EFzVE,IAAI;EE4VX,+BAAG;IACC,aAAa,EAAE,IAAI;IAEnB,qCAAK;MACR,SAAS,EAAE,IAAI;MACf,KAAK,EF1VO,OAAO;IE8VhB,qCAAK;MACR,UAAU,EF3WQ,OAAO;ME6WzB,2CAAO;QACH,UAAU,EF/VD,OAAO;QEgWhB,KAAK,EF5WO,OAAO;;AGTzB,eAAe;AACf,eAAe;AACf;kCACmC;EAClC,KAAK,EAAE,KAAK;;AAEb,oDAAqD;EACpD,KAAK,EAAE,KAAK;;AAEb,2DAA4D;EAC3D,KAAK,EAAE,IAAI;;ACVZ,aAAa;AACb,KAAM;EACF,MAAM,EAAE,MAAM;EAEd,aAAS;IACZ,eAAe,EAAE,0CAAyC;IAC1D,eAAe,EAAE,mBAA2D;EAIzE,kBAAY;IACf,aAAa,EAAE,iBAAkB;IAEjC,qCAAmB;MACf,QAAQ,EAAE,QAAQ;MAClB,UAAU,EJ+BN,OAAO;MI9BX,SAAS,EAAE,OAAO;MAClB,cAAc,EAAE,GAAG;MACnB,OAAO,EAAE,SAAS;MAClB,WAAW,EAAE,GAAG;MAChB,cAAc,EAAE,SAAS;MAEzB,4CAAO;QACV,UAAU,EAAE,OAAO;QACnB,KAAK,EJqBU,OAAO;QIpBtB,kDAAO;UACH,eAAe,EAAE,IAAI;IAKtB,4CAAmB;MACtB,UAAU,EJcH,OAAO;MIbd,WAAW,EAAE,IAAI;IAGlB,qCAAmB;MACf,UAAU,EJUF,OAAO;MIRf,2CAAK;QACR,OAAO,EAAE,MAAM;QACf,WAAW,EAAE,MAAM;QACnB,SAAS,EAAE,IAAI;QACf,WAAW,EAAE,GAAG;QNvCd,kBAAkB,EAAE,qBAAuB;QAC3C,eAAe,EAAE,qBAAuB;QACxC,aAAa,EAAE,qBAAuB;QACtC,cAAc,EAAE,qBAAuB;QACvC,UAAU,EAAE,qBAAuB;QMsCrC,kDAAQ;UACJ,UAAU,EJGH,OAAW;UIDlB,iEAAc;YACjB,KAAK,EJzCU,OAAO;YI2CtB,uEAAO;cACH,KAAK,EJRK,OAAO;UIYlB,oDAAC;YACJ,KAAK,EJPU,OAAO;QIWvB,iDAAO;UACH,UAAU,EJfF,OAAO;QIkBnB,6CAAC;UACG,eAAe,EAAE,IAAI;UACrB,KAAK,EJvBM,OAAO;MI2BnB,sFAAiD;QACpD,OAAO,EAAE,iBAAiB;QAC1B,UAAU,EJtBA,mBAAiB;QIuB3B,SAAS,EAAE,OAAO;QAClB,OAAO,EAAE,KAAK;QACd,KAAK,EAAE,IAAI;QACX,OAAO,EAAE,OAAO;QAChB,UAAU,EAAC,MAAM;QACjB,aAAa,EAAE,IAAI;QACnB,MAAM,EAAE,cAAc;QACtB,WAAW,EAAE,OAAO;;AAStB,gBAAgB;AAChB,MAAO;EACH,cAAc,EAAE,MAAM;EACtB,SAAS,EAAE,CAAC;EAEZ,yBAAY;IACf,aAAa,EAAE,CAAC;EAEb;0BACkB;IACrB,aAAa,EAAE,WAAW;EAEvB,+EAA2D;IAC9D,aAAa,EAAE,WAAW;EAEvB;;;;;;;;qCAQ6B;IAChC,WAAW,EAAE,iBAA4B;;AAK1C,MAAO;EACH,UAAU,EJ3EL,OAAO;EI6EZ,iBAAa;IAChB,OAAO,EAAE,MAAM;IACf,UAAU,EAAE,MAAM;IAClB,UAAU,EJhFF,OAAO;EIkFZ,uBAAmB;IACtB,MAAM,EAAE,WAAW;;AAMpB,wCAAwC;AAIxC,mBAAmB;AAGf;eACK;EACR,MAAM,EAAE,KAAK;EACb,WAAW,EAAE,KAAK;EAClB,SAAS,EAAE,IAAI;AAEZ,eAAK;EACR,UAAU,EJvGF,OAAO;EF1CZ,kBAAkB,EAAE,qBAAuB;EAC3C,eAAe,EAAE,qBAAuB;EACxC,aAAa,EAAE,qBAAuB;EACtC,cAAc,EAAE,qBAAuB;EACvC,UAAU,EAAE,qBAAuB;EM+ItC,iBAAC;IACG,OAAO,EAAE,MAAM;IACf,KAAK,EJ5GO,OAAO;EI+GnB,wBAAC;IACJ,KAAK,EJjII,OAAO;EIoIjB,qBAAO;IACH,UAAU,EJjHD,OAAO;IIkHhB,KAAK,EJrHO,OAAO;IIwHtB,8BAAC;MACG,KAAK,EJzHM,OAAO;MI0HlB,UAAU,EJrHH,OAAW;IIyHtB,8BAAC;MACG,KAAK,EJ/HM,OAAO;MIgIlB,UAAU,EJ7IH,OAAO;IIiJf,uBAAC;MACJ,KAAK,EJrIU,OAAO;MIsItB,eAAe,EAAE,IAAI;EAGtB,sBAAQ;IACJ,UAAU,EJrIF,OAAW;IIsInB,KAAK,EJ7KF,IAAI;IIgLV,+BAAC;MACG,KAAK,EJjLH,IAAI;MIkLN,UAAU,EJ3IH,OAAW;IIgJtB,+BAAC;MACG,KAAK,EJxLH,IAAI;MIyLN,UAAU,EJpKH,OAAO;IIwKf,wBAAC;MACJ,KAAK,EJ9LC,IAAI;MI+LV,eAAe,EAAE,IAAI;AAMtB,iBAAC;EACG,KAAK,EJjLG,OAAO;AIoLhB,kBAAQ;EACX,UAAU,EAAE,MAAM;EAClB,UAAU,EJjMI,OAAO;EIkMrB,KAAK,EJtMa,OAAO;AIyMtB,qBAAY;EACf,OAAO,EAAE,MAAM;EACf,WAAW,EAAE,IAAI;EACjB,KAAK,EJ7MM,OAAO;EI8MlB,cAAc,EAAE,SAAS;EACzB,cAAc,EAAE,GAAG;EACnB,UAAU,EAAE,IAAI;AAGZ,mBAAU;EACb,OAAO,EAAE,GAAG;EACZ,UAAU,EAAE,MAAM;AAGf,mBAAU;EACb,MAAM,EAAE,CAAC;EACT,UAAU,EAAE,KAAK;EAEjB,KAAK,EJjOC,IAAI;EIkOV,qBAAE;IACE,KAAK,EJnOH,IAAI;EIqOV,yBAAM;IACF,OAAO,EAAE,QAAQ;IACjB,SAAS,EAAE,MAAM;IACjB,WAAW,EAAE,MAAM;;AAKzB,qCAAqC;AACrC,qEAAsE;EAClE,QAAQ,EAAE,QAAQ;EAClB,KAAK,EAAE,CAAC;EACR,WAAW,EAAE,MAAM;EACnB,UAAU,EJzMF,mBAAiB;EI0MzB,aAAa,EAAE,IAAI;EACnB,OAAO,EAAE,SAAS;EAClB,MAAM,EAAE,gBAAgB;EACxB,UAAU,EAAE,MAAM;;AAGtB,uBAAwB;EACpB,UAAU,EJrPF,OAAO;;AIuPnB,uBAAwB;EACpB,UAAU,EJxPF,OAAO;;AI0PnB;oBACqB;EACjB,KAAK,EJ5PG,OAAO;;AI8PnB;oBACqB;EACjB,KAAK,EJhQG,OAAO;;AIkQnB;;;2BAG4B;EACxB,KAAK,EJ3QD,IAAI;;AI6QZ,oDAAqD;EACjD,IAAI,EAAE,GAAG;;AAEb;;oEAEqE;EACjE,aAAa,EAAE,GAAG;;AAEtB,oCAAoC;EAChC,iBAAiB,EJnPZ,OAAO;;AK9ChB,kBAAkB;AAClB,mBAAmB;AACnB,eAAe;AACf,OAAQ;EACJ,UAAU,EL0CL,OAAO;EKzCZ,OAAO,EAAE,cAAc;EACvB,OAAO,EAAE,KAAK;EACd,YAAY,EAAE,IAAI;EAClB,KAAK,EAAE,IAAI;EAEX,aAAK;IACR,cAAc,EAAE,MAAM;IAMtB,mBAAO;MAEH,WAAW,EAAE,GAAG;MAChB,KAAK,EAAE,KAAK;MAGf,wBAAC;QACG,eAAe,EAAE,IAAI;QACrB,KAAK,ELoBM,OAAO;QKnBlB,SAAS,EAAE,IAAI;QACf,cAAc,EAAE,SAAS;QACzB,cAAc,EAAE,GAAG;QAEnB,4BAAG;UACN,YAAY,EAAE,MAAM;IAUlB,0BAAK;MACR,KAAK,EAAE,KAAK;MACZ,aAAa,EAAE,WAAW;MAC1B,gBAAgB,ELGL,OAAO;MKFlB,KAAK,ELAU,OAAO;MKCtB,MAAM,EAAE,IAAI;MP1CV,kBAAkB,EAAE,qBAAuB;MAC3C,eAAe,EAAE,qBAAuB;MACxC,aAAa,EAAE,qBAAuB;MACtC,cAAc,EAAE,qBAAuB;MACvC,UAAU,EAAE,qBAAuB;MO0CrC,gCAAO;QACH,gBAAgB,ELHR,OAAO;MKMnB,gCAAO;QACH,KAAK,EAAE,KAAK;QAEZ,gBAAgB,EL9Cd,IAAI;QK+CN,KAAK,EL1CC,OAAO;IK6Cd,yBAAI;MAEP,aAAa,EAAE,WAAW;MAE1B,gBAAgB,ELfL,OAAW;MKgBtB,mBAAmB,EAAE,MAAM;MAC3B,iBAAiB,EAAE,SAAS;MAC5B,gBAAgB,EAAE,wBAAwB;MAE1C,iBAAiB,EAAE,CAAC;MAEpB,KAAK,EAAE,IAAI;MACX,UAAU,EAAE,IAAI;MAXhB,6BAAG;QAAC,OAAO,EAAE,IAAI;MAajB,+BAAO;QACH,gBAAgB,EL1EL,OAAO;IK8EvB,uBAAW;MACP,KAAK,EAAE,IAAI;MACX,QAAQ,EAAE,QAAQ;MAClB,KAAK,EAAE,IAAI;MACX,GAAG,EAAE,OAAO;MACZ,UAAU,EAAE,MAAM;MAGlB,4BAAI;QAIP,gBAAgB,EAAE,WAAW;QAC7B,mBAAmB,EAAE,MAAM;QAC3B,iBAAiB,EAAE,SAAS;QAC5B,gBAAgB,EAAE,wBAAwB;QAE1C,OAAO,EAAE,QAAQ;QARjB,gCAAG;UAAC,OAAO,EAAE,IAAI;;AAwBnB,aAAa;AACb,OAAQ;EACJ,MAAM,EAAE,iBAAiB;;AAM7B,0BAA0B;AAC1B,OAAQ;EACJ,UAAU,EAAE,MAAM;;AAEtB,aAAc;EACV,UAAU,EAAE,IAAI;;AAEpB,YAAa;EACT,MAAM,EAAE,mBAAmB;EAC3B,KAAK,EAAE,KAAK;;AAEhB,aAAc;EACV,MAAM,EAAE,QAAQ;EAChB,KAAK,EAAE,IAAI;;AAEf,SAAU;EACN,MAAM,EAAE,MAAM;;AAGlB,iCAAiC;AACjC,YAAa;EACT,UAAU,EAAE,MAAM;EAClB,SAAS,EAAE,IAAI;EACf,UAAU,EL9FD,OAAW;;AKgGxB,kBAAmB;EACf,UAAU,ELjJG,OAAO;;AKmJxB,gBAAiB;EACb,WAAW,EAAE,GAAG;EAChB,WAAW,EAAE,IAAI;EACjB,KAAK,EL7ID,IAAI;;AK+IZ,sBAAuB;EACnB,eAAe,EAAE,IAAI;;AAGzB,uBAAuB;AACvB,IAAK;EACD,OAAO,EAAE,gBAAgB;EACzB,WAAW,EAAE,GAAG;EAChB,WAAW,EAAE,GAAG;EAChB,cAAc,EAAE,GAAG;EACnB,cAAc,EAAE,SAAS;EACzB,SAAS,EAAE,QAAQ;EACnB,KAAK,EL5JU,OAAO;EK+JtB,UAAK;IACR,OAAO,EAAE,aAAa;IACtB,SAAS,EAAE,QAAQ;IAEnB,KAAK,ELpKY,OAAO;IKqKxB,QAAQ,EAAE,QAAQ;IAClB,IAAI,EAAE,CAAC;IAGP,cAAc,EAAE,SAAS;;AAI1B,mBAAmB;AACnB,SAAU;EACN,UAAU,EAAE,MAAM;EAClB,OAAO,EAAE,KAAK;EAEd,cAAI;IACP,iBAAiB,EAAE,CAAC;IACpB,OAAO,EAAE,WAAW;IACpB,gBAAgB,ELzKF,OAAO;IK0KrB,mBAAmB,EAAE,MAAM;IAC3B,iBAAiB,EAAE,SAAS;IAE5B,oBAAO;MACH,gBAAgB,EL/KR,OAAO;EKmLhB,gBAAM;IACT,UAAU,ELnLI,OAAO;IKqLrB,qBAAI;MACA,iBAAiB,EAAE,CAAC;MACpB,OAAO,EAAE,WAAW;MACpB,gBAAgB,ELxLN,OAAO;MKyLjB,mBAAmB,EAAE,MAAM;MAC3B,iBAAiB,EAAE,SAAS;MP3M7B,kBAAkB,EAAE,qBAAuB;MAC3C,eAAe,EAAE,qBAAuB;MACxC,aAAa,EAAE,qBAAuB;MACtC,cAAc,EAAE,qBAAuB;MACvC,UAAU,EAAE,qBAAuB;MO0MlC,2BAAO;QACV,gBAAgB,ELhME,OAAO;MKmMtB,4BAAQ;QACX,gBAAgB,ELrKL,OAAW;MKwKnB,8BAAQ;QAAC,OAAO,EAAE,IAAI;MAItB,iCAAa;QAChB,gBAAgB,EAAE,mBAAmB;MAElC,wCAAoB;QACvB,gBAAgB,EAAE,yBAAyB;MAGxC,mCAAe;QAClB,gBAAgB,EAAE,qBAAqB;MAEpC,0CAAsB;QACzB,gBAAgB,EAAE,2BAA2B;MAG1C,oCAAgB;QACnB,gBAAgB,EAAE,sBAAsB;MAErC,2CAAuB;QAC1B,gBAAgB,EAAE,4BAA4B;MAG3C,wCAAoB;QACvB,gBAAgB,EAAE,0BAA0B;MAEzC,+CAA2B;QAC9B,gBAAgB,EAAE,gCAAgC;MAI/C,8BAAU;QACb,gBAAgB,ELvOH,OAAO;QKyOpB,KAAK,ELrPW,OAAO;QKsPvB,OAAO,EAAE,QAAQ;QP3Pf,kBAAkB,EAAE,qBAAuB;QAC3C,eAAe,EAAE,qBAAuB;QACxC,aAAa,EAAE,qBAAuB;QACtC,cAAc,EAAE,qBAAuB;QACvC,UAAU,EAAE,qBAAuB;QO0PrC,oCAAO;UACH,gBAAgB,ELhPF,OAAO;MKqPtB,iCAAa;QAChB,gBAAgB,EAAE,wBAAwB;MAEvC,wCAAoB;QACvB,gBAAgB,EAAE,8BAA8B;MAG7C,iCAAa;QAChB,gBAAgB,EAAE,0BAA0B;MAEzC,wCAAoB;QACvB,gBAAgB,EAAE,gCAAgC;MAG/C,iCAAa;QAChB,gBAAgB,EAAE,0BAA0B;MAEzC,wCAAoB;QACvB,gBAAgB,EAAE,gCAAgC;MAG/C,8BAAU;QACb,gBAAgB,EAAE,kBAAkB;IAKjC,4CAAiB;MACpB,iBAAiB,EAAE,CAAC;MACpB,gBAAgB,EAAE,mBAAmB;;AAiBvC,uDAAwD;EACpD,cAAc,EAAE,MAAM;EACtB,gBAAgB,ELvSD,OAAO;EKwStB,aAAa,EAAE,GAAG;;AAItB,iCAAiC;AACjC,uBAAwB;EACpB,OAAO,EAAE,SAAS;EAElB,SAAS,EAAE,QAAQ;EACnB,WAAW,EAAE,MAAM;EAItB,gFAAC;IACG,KAAK,EL9TQ,OAAO;IK+TpB,WAAW,EAAE,UAAU;IACvB,SAAS,EAAE,IAAI;IAEf,wGAAO;MACV,KAAK,EL1RM,OAAW;MK2RtB,eAAe,EAAE,IAAI;EAInB,uCAAO;IACV,SAAS,EAAE,QAAQ;IACnB,KAAK,ELzUa,OAAO;EK2UtB,0DAAK;IACR,SAAS,EAAE,QAAQ;IACnB,WAAW,EAAE,MAAM;EAIhB,+CAAY;IACf,MAAM,EAAE,SAAS;IACjB,MAAM,EAAE,GAAG;IACX,UAAU,EL3US,OAAO;IK4U1B,MAAM,EAAE,CAAC;IACT,UAAU,EAAE,cAAc;EAGvB,+BAAI;IACP,MAAM,EAAE,SAAS;IACjB,OAAO,EAAE,SAAS;IAClB,QAAQ,EAAE,IAAI;IACd,UAAU,ELjWS,OAAO;IKkW1B,KAAK,EL7VE,IAAI;IK8VX,SAAS,EAAE,MAAM;IACjB,aAAa,EAAE,GAAG;IAElB,yCAAK;MACD,UAAU,EAAE,WAAW;MACvB,KAAK,ELnWF,IAAI;MKoWP,MAAM,EAAE,IAAI;EAGb,iCAAK;IACR,OAAO,EAAE,OAAO;IAChB,KAAK,ELhWO,OAAO;IKiWnB,UAAU,ELhWI,OAAO;IKiWrB,MAAM,EAAE,iBAAqB;IAC7B,aAAa,EAAE,GAAG;EAIf,6CAAW;IACd,OAAO,EAAE,KAAK;IACd,MAAM,EAAE,CAAC;IACT,OAAO,EAAE,QAAQ;IACjB,UAAU,EAAE,iBAA4B;IACxC,aAAa,EAAE,iBAA4B;IAC3C,UAAU,EL5WI,OAAO;IK6WrB,KAAK,ELzXY,OAAO;IK2XxB,iDAAE;MACE,MAAM,EAAE,CAAC;;AAOd,gDAAgD;AAChD,aAAc;EACV,QAAQ,EAAE,KAAK;EACf,GAAG,EAAE,IAAI;EACT,MAAM,EAAE,CAAC;EACT,IAAI,EAAE,CAAC;EACP,KAAK,EAAE,CAAC;EACR,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,IAAI;EAEZ,OAAO,EAAE,MAAM;EACf,UAAU,EAAE,MAAM;EAElB,SAAS,EAAE,GAAG;EACd,WAAW,EAAE,GAAG;EAChB,OAAO,EAAE,EAAE;EACX,cAAc,EAAE,MAAM;EACtB,UAAU,EL1YM,OAAO;EK2YvB,KAAK,EL9YG,OAAO;EKgZf,MAAM,EAAE,IAAI;EAEZ,kBAAI;IACP,SAAS,EAAE,IAAI;IACf,OAAO,EAAE,YAAY;EAGlB,kBAAO;IACV,UAAU,ELpYE,OAAO;IKqYnB,KAAK,EL9ZE,IAAI;EKgaR,iBAAM;IACT,UAAU,ELhZA,OAAO;IKiZjB,KAAK,ELlaE,IAAI;EKoaR,qBAAQ;IACX,OAAO,EAAE,MAAM;IACf,WAAW,EAAE,GAAG;IAChB,aAAa,EAAE,WAAW;EAGvB,gCAAqB;IACxB,UAAU,ELhZI,OAAO;EKkZlB,+BAAoB;IACvB,UAAU,EL3ZE,OAAO;EK8ZhB,+BAAoB;IACvB,WAAW,EAAE,GAAG;IAEhB,kCAAE;MACE,OAAO,EAAE,IAAI;;AAMlB,uCAAuC;AACvC,YAAa;EACT,MAAM,EAAE,CAAC;EACT,UAAU,EAAE,MAAM;EAClB,WAAW,EAAE,GAAG;EAChB,YAAY,EAAE,KAAK;EACnB,UAAU,EL9ZL,OAAO;;AM9ChB,sBAAsB;AACtB,KAAM;EAEF,UAAU,ENQN,IAAI;EFPR,kBAAkB,EAAE,qBAAuB;EAC3C,eAAe,EAAE,qBAAuB;EACxC,aAAa,EAAE,qBAAuB;EACtC,cAAc,EAAE,qBAAuB;EACvC,UAAU,EAAE,qBAAuB;EQDnC,WAAO;IACV,UAAU,ENaI,OAAO;IMXrB,2CAAkC;MAC9B,UAAU,ENUA,OAAO;EMLlB,aAAS;IACZ,iBAAiB,ENiCL,OAAW;IMhCvB,UAAU,ENGI,OAAO;EMDlB,cAAU;IACb,UAAU,ENGC,OAAO;IMAlB,oBAAO;MACH,UAAU,ENAG,OAAO;IMGxB,8CAAkC;MAC9B,UAAU,ENJG,OAAO;IMSpB,4BAAC;MACJ,KAAK,ENeM,OAAW;IMVnB,8BAAC;MACJ,KAAK,ENSM,OAAW;IMNvB,yBAAU;MACN,KAAK,EAAE,SAAgC;EAIxC,cAAW;IACd,UAAU,ENRC,OAAO;IMSlB,iBAAiB,ENVT,OAAO;IFrCZ,kBAAkB,EAAE,qBAAuB;IAC3C,eAAe,EAAE,qBAAuB;IACxC,aAAa,EAAE,qBAAuB;IACtC,cAAc,EAAE,qBAAuB;IACvC,UAAU,EAAE,qBAAuB;IQ+CtC,8CAAkC;MAC9B,UAAU,ENdH,OAAO;EMmBlB,gBAAC;IACG,KAAK,ENrDQ,OAAO;IMsDpB,OAAO,EAAE,IAAI;EAGjB,uBAAS;IACL,OAAO,EAAE,GAAG;EAGb,WAAM;IACT,SAAS,EAAE,OAAO;IAClB,KAAK,EN/DY,OAAO;IMgExB,OAAO,EAAE,IAAI;EAGV,aAAQ;IACX,SAAS,EAAE,IAAI;IACf,UAAU,EAAE,MAAM;;AAInB,YAAa;EACT,SAAS,EAAE,IAAI;EACf,MAAM,EAAE,OAAO;EACf,UAAU,EAAE,iBAAqB;EAEjC,mBAAO;IACV,SAAS,EAAE,IAAI;;ACxFhB,oBAAoB;AACpB,oBAAoB;AAWnB,8DAA2C;EACvC,WAAW,EAAE,IAAI;AAIrB,gCAAU;EACN,UAAU,EAAE,IAAI;EAChB,OAAO,EAAE,MAAM;EAEf,wCAAO;IACV,WAAW,EAAE,MAAM;IACnB,eAAe,EAAE,IAAI;IACrB,UAAU,EAAE,IAAI;IAChB,SAAS,EAAE,IAAI;IACf,cAAc,EAAE,SAAS;IACzB,cAAc,EAAE,GAAG;IACnB,KAAK,EPZK,OAAO;IOcjB,sEAAgC;MAC5B,QAAQ,EAAE,QAAQ;MAClB,GAAG,EAAE,OAAO;MACZ,KAAK,EAAE,CAAC;MACR,WAAW,EAAE,MAAM;MACnB,UAAU,EPfH,OAAO;MOgBd,aAAa,EAAE,IAAI;MACnB,OAAO,EAAE,SAAS;MAClB,MAAM,EAAE,gBAAgB;MACxB,UAAU,EAAE,MAAM;IAEtB,8CAAO;MACH,KAAK,EPQE,OAAW;AOHvB,kCAAY;EACR,cAAc,EAAE,MAAM;EACtB,6CAAW;IAEd,SAAS,EAAE,IAAI;IACf,OAAO,EAAE,aAAa;IAEtB,+CAAC;MACG,KAAK,EP9CO,OAAO;MO+CnB,WAAW,EAAE,GAAG;MAEhB,qDAAO;QACV,KAAK,EPTK,OAAW;QOUrB,eAAe,EAAE,IAAI;;AAUxB,QAAQ;EACJ,UAAU,EAAE,mBAAmB;;AAEnC,MAAM;EACF,GAAG,EAAE,IAAI;EACT,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,IAAI;EACZ,IAAI,EAAE,IAAI;EACV,aAAa,EAAE,GAAG;;AC9EtB,oBAAoB;AACpB,oBAAoB;AACpB,oBAAqB;EACpB,OAAO,EAAE,QAAQ;EACjB,UAAU,ERgBE,OAAO;EQfnB,KAAK,ERIY,OAAO;EQHxB,MAAM,EAAE,IAAI;;AAEb,4BAA6B;EAC5B,MAAM,EAAE,QAAQ;EAChB,SAAS,EAAE,GAAG;EACd,KAAK,ERMa,OAAO;;ASjB1B,4BAA4B;AAC5B,KAAM;EACF,OAAO,EAAE,SAAS;EAClB,SAAS,EAAE,IAAI;EAEf,UAAK;IACR,MAAM,EAAE,MAAM;IAGd,2BAAgB;MACZ,aAAa,EAAE,MAAM;EAiBtB,aAAU;IACb,SAAS,EAAE,KAAK;EAGb,kBAAM;IACT,SAAS,EAAE,IAAI;IACf,UAAU,EAAE,OAAO;IACnB,WAAW,EAAE,GAAG;IAChB,WAAW,EAAE,KAAK;IAElB,KAAK,ET5BY,OAAO;ES+BrB,kBAAY;IACf,OAAO,EAAE,YAAY;IAErB,SAAS,EAAE,IAAI;IACf,WAAW,EAAE,IAAI;IACjB,MAAM,EAAE,CAAC;IACT,OAAO,EAAE,cAAc;IACvB,SAAS,EAAE,IAAI;IACf,cAAc,EAAE,MAAM;IACtB,MAAM,EAAE,OAAO;IACf,QAAQ,EAAE,MAAM;IAChB,UAAU,ET9BI,OAAO;IS+BrB,MAAM,EAAE,iBAA4B;IACpC,aAAa,EAAE,GAAG;IAElB,KAAK,ETvCM,OAAO;ISyClB,wBAAO;MACH,eAAe,EAAE,IAAI;MACrB,UAAU,ETTF,OAAW;MSUnB,KAAK,EAAE,KAAK;MACZ,MAAM,EAAE,iBAAqB;;AAQlC,OAAO;EACH,WAAW,EAAE,IAAI;EAEjB,kBAAkB,EAAE,mCAAgC;EACpD,eAAe,EAAE,mCAAgC;EACjD,UAAU,EAAE,mCAAgC;;AAI5C,4BAAW;EACd,UAAU,EAAE,mBAAmB;EAC/B,MAAM,EAAE,CAAC;EACT,OAAO,EAAE,IAAI;EACb,KAAK,EAAE,KAAK;EAEZ,yCAAY;IACR,KAAK,EAAE,IAAI;IACX,GAAG,EAAE,IAAI;;ACtFd,aAAa;AACb,aAAa;AACb,QAAS;EACR,QAAQ,EAAE,MAAM;EAChB,MAAM,EAAE,iBAA2B;;AAEpC,IAAK;EACJ,MAAM,EAAE,MAAM;EACd,OAAO,EAAE,MAAM;EACf,QAAQ,EAAE,IAAI;EACd,SAAS,EAAE,MAAM;EACjB,UAAU,EVUI,OAAO;EUTrB,KAAK,EVIM,OAAO;;AUDnB,YAAa;EACZ,MAAM,EAAE,UAAU;EAClB,OAAO,EAAE,QAAQ;EACjB,aAAa,EAAE,IAAI;;AAEpB,kBAAmB;EAClB,UAAU,EVOA,OAAO;EUNjB,KAAK,EAAE,IAAI;;AAEZ,oBAAqB;EACpB,UAAU,EVOE,OAAO;;AULpB,mBAAoB;EACnB,UAAU,EVRE,OAAO;;AUUpB,kBAAmB;EAClB,UAAU,EVzBS,OAAO;EU0B1B,KAAK,EVrBE,IAAI;;AWXZ,mBAAmB;AACnB,mBAAmB;AACnB,KAAM;EACL,MAAM,EAAE,WAAW;;AAGpB;;QAES;EACR,MAAM,EAAE,IAAI;;AAEb;gBACiB;EAChB,aAAa,EAAE,iBAA4B;;AAG5C,wBAAyB;EACxB,MAAM,EAAE,OAAO;;AAEhB,8BAA+B;EAC9B,QAAQ,EAAE,MAAM;EAChB,WAAW,EAAE,MAAM;EACnB,aAAa,EAAE,QAAQ;;AAExB,0CAA2C;EAC1C,KAAK,EAAE,KAAK;;ACzBb,eAAe;AACf,eAAe;AACf,yBAAyB;EAKxB,YAAK;IACD,KAAK,EAAE,IAAI;IAEX,gBAAG;MACN,OAAO,EAAE,IAAI;IAEV,cAAC;MACJ,OAAO,EAAE,YAAY;MACrB,OAAO,EAAE,qBAAqB;MAC9B,KAAK,EZ6BU,OAAO;MY5BtB,KAAK,EAAE,IAAI;MAEX,UAAU,EAAE,gEAAqE;Mdf/E,kBAAkB,EAAE,oBAAuB;MAC3C,eAAe,EAAE,oBAAuB;MACxC,aAAa,EAAE,oBAAuB;MACtC,cAAc,EAAE,oBAAuB;MACvC,UAAU,EAAE,oBAAuB;McerC,2CACQ;QACJ,UAAU,EAAE,gEAAkE;QAC9E,eAAe,EAAE,IAAI;;EAOvB,MAAO;Id7BP,kBAAkB,EAAE,oBAAuB;IAC3C,eAAe,EAAE,oBAAuB;IACxC,aAAa,EAAE,oBAAuB;IACtC,cAAc,EAAE,oBAAuB;IACvC,UAAU,EAAE,oBAAuB;Ic4BtC,iBAAa;MACT,OAAO,EAAE,CAAC;IAGd,oDAA6C;MACzC,OAAO,EAAE,WAAW;;EAGrB;eACc;IACjB,OAAO,EAAE,KAAK;IACd,KAAK,EAAE,IAAI;IACX,MAAM,EAAE,IAAI;IACZ,WAAW,EAAE,IAAI;IACjB,UAAU,EAAE,MAAM;IAClB,UAAU,EZjDM,OAAO;;EYoDpB,OAAO;IACV,OAAO,EAAE,MAAM;IAEX,mBAAO;MACV,OAAO,EAAE,IAAI;IAIb,0BAAK;MACD,KAAK,EAAE,GAAG;MACV,MAAM,EAAE,MAAM;MAEd,gCAAO;QACV,KAAK,EAAE,IAAI;IAIZ,yBAAI;MACA,UAAU,EAAE,IAAI;MAChB,OAAO,EAAE,WAAW;IAGrB,uBAAW;MACd,KAAK,EAAE,OAAO;MACd,GAAG,EAAE,QAAQ;MAET,sCAAI;QACP,OAAO,EAAE,QAAQ;;EAQnB,cAAK;IACD,MAAM,EAAE,CAAC;IACT,OAAO,EAAE,eAAe;EAE5B,gBAAO;IACH,MAAM,EAAE,aAAa;IAErB,qBAAI;MACP,MAAM,EAAE,CAAC;MACT,OAAO,EAAE,eAAe;MAExB,8BAAU;QACN,OAAO,EAAE,eAAe;EAI7B,iBAAQ;IACJ,OAAO,EAAE,IAAI;IACb,SAAS,EAAE,GAAG;IAEd,wBAAM;MAET,SAAS,EAAE,GAAG;MACd,KAAK,EAAE,IAAI;MAEX,+BAAQ;QACL,KAAK,EAAE,KAAK;;EAOZ,0BAAY;IACf,OAAO,EAAE,QAAQ;;EAOf,IAAI;IACP,UAAU,EAAE,MAAM;IAClB,OAAO,EAAE,MAAM;IACf,UAAM;MAEF,OAAO,EAAE,KAAK;MACd,OAAO,EAAE,CAAC;MACV,KAAK,EAAE,IAAI;MACX,WAAW,EAAE,MAAM;MACnB,aAAa,EAAE,IAAI;;EAKpB,WAAY;IACf,MAAM,EAAE,SAAS;;EAGd,YAAY;IACf,WAAW,EAAE,MAAM;;EAGhB,aAAc;IACjB,aAAa,EAAE,CAAC;IAEhB,qBAAQ;MACJ,OAAO,EAAE,KAAK;MACd,IAAI,EAAE,CAAC;MACP,UAAU,EAAE,WAAW;IAE3B,2BAAc;MACV,OAAO,EAAE,GAAG;IAEhB,2BAAc;MACV,OAAO,EAAE,IAAI;AC7IlB,UAAU;EACN,WAAW,EAAE,wCAAwC;EACrD,SAAS,EAAE,QAAQ;;AAKvB,gBAAgB;AAChB,gBAAgB;AAChB,UAAW;EACV,MAAM,EAAE,IAAI;EACZ,UAAU,EbdE,OAAO;;AaiBpB,cAAc;AACd,iBAAkB;EACd,OAAO,EAAE,IAAI;EACb,KAAK,EbUI,OAAW", -"sources": ["_fonts.scss","_mixins.scss","_forms.scss","_variables.scss","_tables.scss","_components.scss","_divers.scss","_sidebar.scss","_layout.scss","_list-view.scss","_global-view.scss","_reader-view.scss","_configuration.scss","_logs.scss","_stats.scss","_mobile.scss","mapco.scss"], -"names": [], -"file": "mapco.css" -} diff --git a/p/themes/Mapco/mapco.scss b/p/themes/Mapco/mapco.scss index 1a538b50a..337cc5f97 100644 --- a/p/themes/Mapco/mapco.scss +++ b/p/themes/Mapco/mapco.scss @@ -1,51 +1,48 @@ @import "fonts"; + @import "mixins"; + @import "variables"; @import "forms"; + @import "tables"; + @import "components"; @import "divers"; @import "sidebar"; + @import "layout"; + @import "list-view"; + @import "global-view"; + @import "reader-view"; @import "configuration"; @import "logs"; + @import "stats"; @import "mobile"; -html, body{ - font-family: "lato", "Helvetica", "Arial", sans-serif; - font-size: 0.875rem; -} - @charset "UTF-8"; /*=== GENERAL */ /*============*/ html, body { - height: 100%; background: $grey-light; + height: 100%; + font-family: "lato", "Helvetica", "Arial", sans-serif; + font-size: 0.875rem; } /*=== Links */ a, button.as-link { - outline: none; - color: $main-first; + outline: none; + color: $main-first; } - - - - - - - - - diff --git a/p/themes/Origine-compact/origine-compact.css b/p/themes/Origine-compact/origine-compact.css index e8046e0b1..af8e66d17 100644 --- a/p/themes/Origine-compact/origine-compact.css +++ b/p/themes/Origine-compact/origine-compact.css @@ -3,9 +3,9 @@ /*=== GENERAL */ /*============*/ html, body { + background: #fafafa; height: 100%; font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", "PingFang SC", "Microsoft YaHei", sans-serif; - background: #fafafa; } /*=== Links */ @@ -18,50 +18,54 @@ a, button.as-link { legend { margin: 20px 0 5px; padding: 5px 0; - border-bottom: 1px solid #ddd; font-size: 1.4em; + border-bottom: 1px solid #ddd; } + label { min-height: 25px; padding: 5px 0; cursor: pointer; } + textarea { width: 360px; height: 100px; } + input, select, textarea { - min-height: 25px; padding: 3px 5px 2px 5px; background: #fdfdfd; + color: #666; border: 1px solid #bbb; border-radius: 3px; - color: #666; + box-shadow: 0 2px 2px #eee inset; + min-height: 25px; line-height: 25px; vertical-align: middle; - box-shadow: 0 2px 2px #eee inset; } + option { padding: 0 .5em; } + input:focus, select:focus, textarea:focus { color: #0062be; - border-color: #33bbff; - box-shadow: 0 2px 2px #ddddff inset; + border-color: #3bf; + box-shadow: 0 2px 2px #ddf inset; } + input:invalid, select:invalid { border-color: #f00; box-shadow: 0 0 2px 2px #fdd inset; } + input:disabled, select:disabled { background: #eee; } + input.extend { transition: width 200ms linear; - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; } /*=== Tables */ @@ -73,9 +77,11 @@ tr, th, td { padding: 0.5em; border: 1px solid #ddd; } + th { background: #f6f6f6; } + form td, form th { font-weight: normal; @@ -90,17 +96,21 @@ form th { background: #f4f4f4; border-top: 1px solid #ddd; } + .form-group.form-actions .btn { margin: 0 10px; } + .form-group .group-name { padding: 10px 0; text-align: right; } + .form-group .group-controls { min-height: 25px; padding: 8px 0; } + .form-group table { margin: 10px 0 0 220px; } @@ -110,21 +120,26 @@ form th { vertical-align: middle; font-size: 0; } + .stick input, .stick .btn { border-radius: 0; } + .stick .btn:first-child, .stick input:first-child { border-radius: 3px 0 0 3px; } + .stick .btn-important:first-child { border-right: 1px solid #06f; } + .stick .btn:last-child, .stick input:last-child { border-radius: 0 3px 3px 0; } + .stick .btn + .btn, .stick .btn + input, .stick .btn + .dropdown > .btn, @@ -136,18 +151,17 @@ form th { .stick .dropdown + .dropdown > .btn { border-left: none; } + .stick input + .btn { border-top: 1px solid #bbb; } + .stick .btn + .dropdown > .btn { border-left: none; border-radius: 0 3px 3px 0; } .btn { - display: inline-block; - min-height: 32px; - min-width: 15px; margin: 0; padding: 5px 10px; background: #fff; @@ -156,22 +170,27 @@ form th { background: -webkit-linear-gradient(top, #fff 0%, #eee 100%); background: -o-linear-gradient(top, #fff 0%, #eee 100%); background: -ms-linear-gradient(top, #fff 0%, #eee 100%); - border-radius: 3px; + display: inline-block; + color: #666; + font-size: 0.9rem; border: 1px solid #ddd; - border-bottom: 1px solid #aaa; border-right: 1px solid #aaa; - color: #666; + border-bottom: 1px solid #aaa; + border-radius: 3px; + min-height: 32px; + min-width: 15px; text-shadow: 0px -1px 0 #ddd; - font-size: 0.9rem; vertical-align: middle; cursor: pointer; overflow: hidden; } + a.btn, .stick .btn { min-height: 20px; line-height: 20px; } + .btn:hover { background: #f0f0f0; background: linear-gradient(to bottom, #f8f8f8, #f0f0f0); @@ -181,57 +200,62 @@ a.btn, background: -ms-linear-gradient(top, #f8f8f8 0%, #f0f0f0 100%); text-decoration: none; } + .btn.active, .btn:active, .dropdown-target:target ~ .btn.dropdown-toggle { - box-shadow: 0px 2px 4px #e0e0e0 inset, 0px 1px 2px #fafafa; background: #eee; + box-shadow: 0px 2px 4px #e0e0e0 inset, 0px 1px 2px #fafafa; } .btn-important { - background: #0084CC; - background: linear-gradient(to bottom, #0084CC, #0045CC); - background: -moz-linear-gradient(top, #0084CC 0%, #0045CC 100%); - background: -webkit-linear-gradient(top, #0084CC 0%, #0045CC 100%); - background: -o-linear-gradient(top, #0084CC 0%, #0045CC 100%); - background: -ms-linear-gradient(top, #0084CC 0%, #0045CC 100%); + background: #0084cc; + background: linear-gradient(to bottom, #0084cc, #0045cc); + background: -moz-linear-gradient(top, #0084cc 0%, #0045cc 100%); + background: -webkit-linear-gradient(top, #0084cc 0%, #0045cc 100%); + background: -o-linear-gradient(top, #0084cc 0%, #0045cc 100%); + background: -ms-linear-gradient(top, #0084cc 0%, #0045cc 100%); color: #fff; - border: 1px solid #0062B7; + border: 1px solid #0062b7; text-shadow: 0px -1px 0 #aaa; font-weight: normal; } + .btn-important:hover { - background: linear-gradient(to bottom, #0066CC, #0045CC); - background: -moz-linear-gradient(top, #0066CC 0%, #0045CC 100%); - background: -webkit-linear-gradient(top, #0066CC 0%, #0045CC 100%); - background: -o-linear-gradient(top, #0066CC 0%, #0045CC 100%); - background: -ms-linear-gradient(top, #0066CC 0%, #0045CC 100%); + background: linear-gradient(to bottom, #06c, #0045cc); + background: -moz-linear-gradient(top, #06c 0%, #0045cc 100%); + background: -webkit-linear-gradient(top, #06c 0%, #0045cc 100%); + background: -o-linear-gradient(top, #06c 0%, #0045cc 100%); + background: -ms-linear-gradient(top, #06c 0%, #0045cc 100%); } + .btn-important:active { - background: #0044CB; + background: #0044cb; box-shadow: none; } .btn-attention { - background: #E95B57; - background: linear-gradient(to bottom, #E95B57, #BD362F); - background: -moz-linear-gradient(top, #E95B57 0%, #BD362F 100%); - background: -webkit-linear-gradient(top, #E95B57 0%, #BD362F 100%); - background: -o-linear-gradient(top, #E95B57 0%, #BD362F 100%); - background: -ms-linear-gradient(top, #E95B57 0%, #BD362F 100%); + background: #e95b57; + background: linear-gradient(to bottom, #e95b57, #bd362f); + background: -moz-linear-gradient(top, #e95b57 0%, #bd362f 100%); + background: -webkit-linear-gradient(top, #e95b57 0%, #bd362f 100%); + background: -o-linear-gradient(top, #e95b57 0%, #bd362f 100%); + background: -ms-linear-gradient(top, #e95b57 0%, #bd362f 100%); color: #fff; - border: 1px solid #C44742; + border: 1px solid #c44742; text-shadow: 0px -1px 0px #666; } + .btn-attention:hover { - background: linear-gradient(to bottom, #D14641, #BD362F); - background: -moz-linear-gradient(top, #D14641 0%, #BD362F 100%); - background: -webkit-linear-gradient(top, #D14641 0%, #BD362F 100%); - background: -o-linear-gradient(top, #D14641 0%, #BD362F 100%); - background: -ms-linear-gradient(top, #D14641 0%, #BD362F 100%); + background: linear-gradient(to bottom, #d14641, #bd362f); + background: -moz-linear-gradient(top, #d14641 0%, #bd362f 100%); + background: -webkit-linear-gradient(top, #d14641 0%, #bd362f 100%); + background: -o-linear-gradient(top, #d14641 0%, #bd362f 100%); + background: -ms-linear-gradient(top, #d14641 0%, #bd362f 100%); } + .btn-attention:active { - background: #BD362F; + background: #bd362f; box-shadow: none; } @@ -242,49 +266,60 @@ a.btn, line-height: 2.5em; font-size: 0.9rem; } + .nav-list .item:hover { background: #fafafa; } + .nav-list .item:hover a { - color: #003388; + color: #038; } + .nav-list .item.active { - background: #0062BE; + background: #0062be; color: #fff; } + .nav-list .item.active a { color: #fff; } + .nav-list .disable { - color: #aaa; background: #fafafa; + color: #aaa; text-align: center; } + .nav-list .item > a { padding: 0 10px; } + .nav-list a:hover { text-decoration: none; } + .nav-list .item.empty a { color: #f39c12; } + .nav-list .item.active.empty a { - color: #fff; background: #f39c12; + color: #fff; } + .nav-list .item.error a { - color: #BD362F; + color: #bd362f; } + .nav-list .item.active.error a { + background: #bd362f; color: #fff; - background: #BD362F; } .nav-list .nav-header { padding: 0 10px; - color: #888; background: #f4f4f4; + color: #888; border-bottom: 1px solid #ddd; font-weight: bold; text-shadow: 0 0 1px #ddd; @@ -306,6 +341,7 @@ a.btn, border-bottom: 1px solid #ddd; text-align: right; } + .nav-head .item { padding: 5px 10px; font-size: 0.9rem; @@ -318,6 +354,7 @@ a.btn, padding: 0; font-size: 0.9rem; } + .horizontal-list .item { vertical-align: middle; line-height: 30px; @@ -327,34 +364,34 @@ a.btn, .dropdown-menu { margin: 5px 0 0; padding: 5px 0; + font-size: 0.8rem; border: 1px solid #ddd; border-radius: 5px; box-shadow: 3px 3px 3px #ddd; - font-size: 0.8rem; text-align: left; } + .dropdown-menu::after { - content: ""; - position: absolute; - top: -6px; - right: 13px; + background: #fff; width: 10px; height: 10px; - background: #fff; border-top: 1px solid #ddd; border-left: 1px solid #ddd; + content: ""; + position: absolute; + top: -6px; + right: 13px; z-index: -10; transform: rotate(45deg); - -moz-transform: rotate(45deg); - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); } + .dropdown-header { padding: 0 5px 5px; color: #888; font-weight: bold; text-align: left; } + .dropdown-menu > .item > a, .dropdown-menu > .item > span, .dropdown-menu > .item > .as-link { @@ -362,18 +399,22 @@ a.btn, line-height: 2.5em; font-size: 0.8rem; } + .dropdown-menu > .item:hover { - background: #0062BE; + background: #0062be; color: #fff; } + .dropdown-menu > .item[aria-checked="true"] > a::before { font-weight: bold; margin: 0 0 0 -14px; } + .dropdown-menu > .item:hover > a { color: #fff; text-decoration: none; } + .dropdown-menu .input select, .dropdown-menu .input input { margin: 0 auto 5px; @@ -391,35 +432,40 @@ a.btn, margin: 15px auto; padding: 10px 15px; background: #f4f4f4; + color: #aaa; + font-size: 0.9em; border: 1px solid #ccc; border-right: 1px solid #aaa; border-bottom: 1px solid #aaa; border-radius: 5px; - color: #aaa; text-shadow: 0 0 1px #eee; - font-size: 0.9em; } + .alert-head { font-size: 1.15em; } + .alert > a { color: inherit; text-decoration: underline; } + .alert-warn { background: #ffe; - border: 1px solid #eeb; color: #c95; + border: 1px solid #eeb; } + .alert-success { background: #dfd; - border: 1px solid #cec; color: #484; + border: 1px solid #cec; } + .alert-error { background: #fdd; - border: 1px solid #ecc; color: #844; + border: 1px solid #ecc; } /*=== Pagination */ @@ -429,14 +475,17 @@ a.btn, color: #333; font-size: 0.8em; } + .content .pagination { margin: 0; padding: 0; } + .pagination .item.pager-current { font-weight: bold; font-size: 1.5em; } + .pagination .item a { display: block; color: #333; @@ -444,12 +493,15 @@ a.btn, line-height: 3em; text-decoration: none; } + .pagination .item a:hover { background: #ddd; } + .pagination:first-child .item { border-bottom: 1px solid #aaa; } + .pagination:last-child .item { border-top: 1px solid #aaa; } @@ -466,6 +518,7 @@ a.btn, border-radius: 5px; box-shadow: 0 0 3px #bbb; } + .box .box-title { margin: 0; padding: 5px 10px; @@ -473,6 +526,7 @@ a.btn, border-bottom: 1px solid #ddd; border-radius: 5px 5px 0 0; } + .box .box-content { min-height: 2.5em; max-height: 260px; @@ -488,6 +542,7 @@ a.btn, .box .box-content .item .configure { visibility: hidden; } + .box .box-title:hover .configure, .box .box-content .item:hover .configure { visibility: visible; @@ -497,6 +552,7 @@ a.btn, .tree { margin: 10px 0; } + .tree-folder-title { position: relative; padding: 0 5px; @@ -504,36 +560,45 @@ a.btn, line-height: 2rem; font-size: 0.9rem; } + .tree-folder-title .title { background: inherit; color: #444; } + .tree-folder-title .title:hover { text-decoration: none; } + .tree-folder.active .tree-folder-title { background: #f0f0f0; font-weight: bold; } + .tree-folder.active .tree-folder-title .title { - color: #0062BE; + color: #0062be; } + .tree-folder-items { + background: #f6f6f6; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; - background: #f6f6f6; } + .tree-folder-items > .item { padding: 0 10px; line-height: 2.2rem; font-size: 0.8rem; } + .tree-folder-items > .item.active { background: #0062be; } + .tree-folder-items > .item > a { text-decoration: none; } + .tree-folder-items > .item.active > a { color: #fff; } @@ -542,40 +607,47 @@ a.btn, /*===============*/ /*=== Header */ .header { - height: 40px; background: #f4f4f4; + height: 40px; } + .header > .item { padding: 0px; border-bottom: 1px solid #aaa; vertical-align: middle; text-align: center; } -.header > .item.title{ + +.header > .item.title { width: 230px; } + .header > .item.title h1 { margin: 0; font-size: 1em; } + .header > .item.title h1 a { text-decoration: none; } + .header .item.configure .btn, .header .item.search .btn { min-height: 18px; padding: 4px 10px; line-height: 18px; } + .header > .item.title .logo { - height: 25px; width: 25px; + height: 25px; } .header > .item.search input { - width: 230px; padding: 1px 5px; + width: 230px; } + .header .item.search input:focus { width: 350px; } @@ -584,54 +656,63 @@ a.btn, #global { height: calc(100% - 85px); } + .aside { - border-right: 1px solid #aaa; background: #fff; + border-right: 1px solid #aaa; } + .aside.aside_feed { padding: 10px 0; text-align: center; background: #fff; } + .aside.aside_feed .tree { margin: 10px 0 50px; } /*=== Aside main page (categories) */ .aside_feed .category .title:not([data-unread="0"])::after { - position: absolute; - right: 0; margin: 10px 0; padding: 0 10px; + background: inherit; font-size: 0.8rem; + position: absolute; + right: 0; line-height: 0.9rem; - background: inherit; } /*=== Aside main page (feeds) */ .feed.item.empty.active { background: #e67e22; } + .feed.item.error.active { background: #bd362f; } + .feed.item.empty, .feed.item.empty > a { color: #e67e22; } + .feed.item.error, .feed.item.error > a { color: #bd362f; } + .feed.item.empty.active, .feed.item.error.active, .feed.item.empty.active > a, .feed.item.error.active > a { color: #fff; } + .aside_feed .tree-folder-items .dropdown-menu::after { left: 2px; } + .aside_feed .tree-folder-items .item .dropdown-target:target ~ .dropdown-toggle > .icon, .aside_feed .tree-folder-items .item.active .dropdown-toggle > .icon { background-color: #fff; @@ -643,9 +724,11 @@ a.btn, padding: 10px 50px; font-size: 0.9em; } + .post form { margin: 10px 0; } + .post.content { max-width: 550px; } @@ -654,52 +737,61 @@ a.btn, .prompt { text-align: center; } + .prompt label { text-align: left; } + .prompt form { margin: 10px auto 20px auto; width: 200px; } + .prompt input { margin: 5px auto; width: 100%; } + .prompt p { margin: 20px 0; } /*=== New article notification */ #new-article { - background: #0084CC; + background: #0084cc; text-align: center; font-size: 0.9em; } + #new-article:hover { - background: #0066CC; + background: #06c; } + #new-article > a { line-height: 3em; color: #fff; font-weight: bold; } + #new-article > a:hover { text-decoration: none; } /*=== Day indication */ .day { - font-size: 0.9rem; padding: 0 10px; - font-weight: bold; - line-height: 2em; background: #fff; + font-size: 0.9rem; border-top: 1px solid #aaa; border-bottom: 1px solid #aaa; + font-weight: bold; + line-height: 2em; } + #new-article + .day { border-top: none; } + .day .name { padding: 0 10px 0 0; color: #aab; @@ -711,53 +803,62 @@ a.btn, /*=== Index menu */ .nav_menu { + padding: 5px 0; background: #fafafa; border-bottom: 1px solid #aaa; text-align: center; - padding: 5px 0; } /*=== Feed articles */ .flux { - border-left: 2px solid #aaa; background: #fafafa; + border-left: 2px solid #aaa; } + .flux:hover { background: #fff; } + .flux.current { - border-left: 2px solid #0062BE; + background: #fff; + border-left: 2px solid #0062be; } + .flux.not_read { - border-left: 2px solid #FF5300; - background: #FFF3ED; + border-left-color: #ff5300; +} + +.flux.not_read:not(.current) { + background: #fff3ed; } + .flux.not_read:not(.current):hover .item.title { - background: #FFF3ED; + background: inherit; } + .flux.favorite { - border-left: 2px solid #FFC300; - background: #FFF6DA; + background: #fff6da; + border-left: 2px solid #ffc300; } + .flux.favorite:not(.current):hover .item.title { - background: #FFF6DA; -} -.flux.current { - background: #fff; + background: #fff6da; } - .flux_header { - border-top: 1px solid #ddd; font-size: 0.8rem; + border-top: 1px solid #ddd; cursor: pointer; } + .flux_header .title { font-size: 0.8rem; } + .flux .website .favicon { padding: 5px; } + .flux .date { color: #666; font-size: 0.7rem; @@ -772,17 +873,19 @@ a.btn, .content { padding: 10px 10px; } + #stream.normal .content > h1.title { - display:none; + display: none; } + .content > h1.title > a { color: #000; } .content hr { margin: 30px 10px; - height: 1px; background: #ddd; + height: 1px; border: 0; box-shadow: 0 2px 5px #ccc; } @@ -796,13 +899,15 @@ a.btn, font-size: 0.9rem; border-radius: 3px; } + .content code { padding: 2px 5px; - color: #dd1144; background: #fafafa; + color: #d14; border: 1px solid #eee; border-radius: 3px; } + .content pre code { background: transparent; color: #fff; @@ -810,14 +915,15 @@ a.btn, } .content blockquote { - display: block; margin: 0; padding: 5px 20px; - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; background: #fafafa; + display: block; color: #333; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; } + .content blockquote p { margin: 0; } @@ -825,33 +931,38 @@ a.btn, /*=== Notification and actualize notification */ .notification { padding: 0 0 0 5px; - text-align: center; + font-size: 0.9em; border: 1px solid #eeb; border-radius: 3px; box-shadow: 0 0 5px #ddd; + text-align: center; font-weight: bold; - font-size: 0.9em; line-height: 3em; z-index: 10; vertical-align: middle; } + .notification.good { background: #ffe; - border: 1px solid #eeb; color: #c95; + border: 1px solid #eeb; } + .notification.bad { background: #fdd; - border: 1px solid #ecc; color: #844; + border: 1px solid #ecc; } + .notification a.close { padding: 0 15px; line-height: 3em; } + .notification.good a.close:hover { background: #eeb; } + .notification.bad a.close:hover { background: #ecc; } @@ -862,20 +973,23 @@ a.btn, /*=== "Load more" part */ #bigMarkAsRead { - text-align: center; - text-decoration: none; - color: #666; background: #fafafa; + color: #666; font-size: 1.2em; + text-align: center; + text-decoration: none; } + #bigMarkAsRead:hover { - color: #0062be; background: #fff; + color: #0062be; box-shadow: 0 -5px 10px #eee inset; } + #bigMarkAsRead .bigTick { font-size: 3em; } + #bigMarkAsRead:hover .bigTick { text-shadow: 0 0 5px #0062be; } @@ -894,14 +1008,15 @@ a.btn, /*================*/ #stream.reader .flux { padding: 0 0 50px; - border: none; background: #f0f0f0; color: #333; + border: none; } + #stream.reader .flux .author { margin: 0 0 10px; - font-size: 90%; color: #666; + font-size: 90%; } /*=== GLOBAL VIEW */ @@ -911,26 +1026,31 @@ a.btn, text-decoration: none; text-align: left; } + .box.category:not([data-unread="0"]) .box-title { - background: #0084CC; + background: #0084cc; } + .box.category:not([data-unread="0"]) .box-title:active { background: #3498db; } + .box.category:not([data-unread="0"]) .box-title .title { color: #fff; font-weight: bold; } + .box.category .title:not([data-unread="0"])::after { - position: absolute; - top: 5px; right: 10px; - border: 0; background: none; color: #fff; - font-weight: bold; + border: 0; box-shadow: none; + position: absolute; + top: 5px; right: 10px; + font-weight: bold; text-shadow: none; } + .box.category .item.feed { padding: 2px 10px; font-size: 0.8rem; @@ -942,9 +1062,11 @@ a.btn, .aside.aside_feed .nav-form select { width: 140px; } + .aside.aside_feed .nav-form .dropdown .dropdown-menu { right: -20px; } + .aside.aside_feed .nav-form .dropdown .dropdown-menu::after { right: 33px; } @@ -960,6 +1082,7 @@ a.btn, .stat tr { border: none; } + .stat > table td, .stat > table th { border-bottom: 1px solid #ddd; @@ -968,11 +1091,13 @@ a.btn, .stat > .horizontal-list { margin: 0 0 5px; } + .stat > .horizontal-list .item { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } + .stat > .horizontal-list .item:first-child { width: 270px; } @@ -984,31 +1109,38 @@ a.btn, border-radius: 5px; overflow: hidden; } + .log { padding: 5px 10px; background: #fafafa; color: #333; font-size: 0.8rem; } + .log+.log { border-top: 1px solid #aaa; } + .log .date { display: block; font-weight: bold; } + .log.error { background: #fdd; color: #844; } + .log.warning { background: #ffe; color: #c95; } + .log.notice { background: #f4f4f4; color: #aaa; } + .log.debug { background: #333; color: #eee; @@ -1016,24 +1148,22 @@ a.btn, /*=== MOBILE */ /*===========*/ -@media(max-width: 840px) { + +@media (max-width: 840px) { .aside { box-shadow: 3px 0 3px #aaa; transition: width 200ms linear; - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; } + .aside .toggle_aside, #panel .close { + background: #f6f6f6; display: block; width: 100%; height: 50px; + border-bottom: 1px solid #ddd; line-height: 50px; text-align: center; - background: #f6f6f6; - border-bottom: 1px solid #ddd; } .aside.aside_feed { @@ -1043,20 +1173,25 @@ a.btn, .nav_menu .btn { margin: 5px 10px; } + .nav_menu .stick { margin: 0 10px; } + .nav_menu .stick .btn { margin: 5px 0; } + .nav_menu .search { display: inline-block; max-width: 97%; } + .nav_menu .search input { max-width: 97%; width: 90px; } + .nav_menu .search input:focus { width: 400px; } @@ -1071,13 +1206,15 @@ a.btn, } .notification a.close { + background: transparent; display: block; left: 0; - background: transparent; } + .notification a.close:hover { opacity: 0.5; } + .notification a.close .icon { display: none; } diff --git a/p/themes/Origine/origine.css b/p/themes/Origine/origine.css index 11dec90ac..7e826dd5f 100644 --- a/p/themes/Origine/origine.css +++ b/p/themes/Origine/origine.css @@ -3,9 +3,9 @@ /*=== GENERAL */ /*============*/ html, body { + background: #fafafa; height: 100%; font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", "PingFang SC", "Microsoft YaHei", sans-serif; - background: #fafafa; } /*=== Links */ @@ -18,50 +18,54 @@ a, button.as-link { legend { margin: 20px 0 5px; padding: 5px 0; - border-bottom: 1px solid #ddd; font-size: 1.4em; + border-bottom: 1px solid #ddd; } + label { min-height: 25px; padding: 5px 0; cursor: pointer; } + textarea { width: 360px; height: 100px; } + input, select, textarea { - min-height: 25px; padding: 5px; background: #fdfdfd; + color: #666; border: 1px solid #bbb; border-radius: 3px; - color: #666; + box-shadow: 0 2px 2px #eee inset; + min-height: 25px; line-height: 25px; vertical-align: middle; - box-shadow: 0 2px 2px #eee inset; } + option { padding: 0 .5em; } + input:focus, select:focus, textarea:focus { color: #0062be; - border-color: #33bbff; - box-shadow: 0 2px 2px #ddddff inset; + border-color: #3bf; + box-shadow: 0 2px 2px #ddf inset; } + input:invalid, select:invalid { border-color: #f00; box-shadow: 0 0 2px 2px #fdd inset; } + input:disabled, select:disabled { background: #eee; } + input.extend { transition: width 200ms linear; - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; } /*=== Tables */ @@ -73,9 +77,11 @@ tr, th, td { padding: 0.5em; border: 1px solid #ddd; } + th { background: #f6f6f6; } + form td, form th { font-weight: normal; @@ -90,17 +96,21 @@ form th { background: #f4f4f4; border-top: 1px solid #ddd; } + .form-group.form-actions .btn { margin: 0 10px; } + .form-group .group-name { padding: 10px 0; text-align: right; } + .form-group .group-controls { min-height: 25px; padding: 5px 0; } + .form-group table { margin: 10px 0 0 220px; } @@ -110,21 +120,26 @@ form th { vertical-align: middle; font-size: 0; } + .stick input, .stick .btn { border-radius: 0; } + .stick .btn:first-child, .stick input:first-child { border-radius: 3px 0 0 3px; } + .stick .btn-important:first-child { border-right: 1px solid #06f; } + .stick .btn:last-child, .stick input:last-child { border-radius: 0 3px 3px 0; } + .stick .btn + .btn, .stick .btn + input, .stick .btn + .dropdown > .btn, @@ -136,102 +151,87 @@ form th { .stick .dropdown + .dropdown > .btn { border-left: none; } + .stick input + .btn { border-top: 1px solid #bbb; } + .stick .btn + .dropdown > .btn { border-left: none; border-radius: 0 3px 3px 0; } .btn { - display: inline-block; - min-height: 37px; - min-width: 15px; - line-height: 25px; margin: 0; padding: 5px 10px; background: #fff; background: linear-gradient(to bottom, #fff 0%, #eee 100%); - background: -moz-linear-gradient(top, #fff 0%, #eee 100%); - background: -webkit-linear-gradient(top, #fff 0%, #eee 100%); - background: -o-linear-gradient(top, #fff 0%, #eee 100%); - background: -ms-linear-gradient(top, #fff 0%, #eee 100%); - border-radius: 3px; + display: inline-block; + color: #666; + font-size: 0.9rem; border: 1px solid #ddd; - border-bottom: 1px solid #aaa; border-right: 1px solid #aaa; - color: #666; + border-bottom: 1px solid #aaa; + border-radius: 3px; + min-height: 37px; + min-width: 15px; + line-height: 25px; text-shadow: 0px -1px 0 #ddd; - font-size: 0.9rem; vertical-align: middle; cursor: pointer; overflow: hidden; } + a.btn { min-height: 25px; line-height: 25px; } + .btn:hover { background: #f0f0f0; background: linear-gradient(to bottom, #f8f8f8, #f0f0f0); - background: -moz-linear-gradient(top, #f8f8f8 0%, #f0f0f0 100%); - background: -webkit-linear-gradient(top, #f8f8f8 0%, #f0f0f0 100%); - background: -o-linear-gradient(top, #f8f8f8 0%, #f0f0f0 100%); - background: -ms-linear-gradient(top, #f8f8f8 0%, #f0f0f0 100%); text-decoration: none; } + .btn.active, .btn:active, .dropdown-target:target ~ .btn.dropdown-toggle { - box-shadow: 0px 2px 4px #e0e0e0 inset, 0px 1px 2px #fafafa; background: #eee; + box-shadow: 0px 2px 4px #e0e0e0 inset, 0px 1px 2px #fafafa; } .btn-important { - background: #0084CC; - background: linear-gradient(to bottom, #0084CC, #0045CC); - background: -moz-linear-gradient(top, #0084CC 0%, #0045CC 100%); - background: -webkit-linear-gradient(top, #0084CC 0%, #0045CC 100%); - background: -o-linear-gradient(top, #0084CC 0%, #0045CC 100%); - background: -ms-linear-gradient(top, #0084CC 0%, #0045CC 100%); + background: #0084cc; + background: linear-gradient(to bottom, #0084cc, #0045cc); color: #fff; - border: 1px solid #0062B7; + border: 1px solid #0062b7; text-shadow: 0px -1px 0 #aaa; font-weight: normal; } + .btn-important:hover { - background: linear-gradient(to bottom, #0066CC, #0045CC); - background: -moz-linear-gradient(top, #0066CC 0%, #0045CC 100%); - background: -webkit-linear-gradient(top, #0066CC 0%, #0045CC 100%); - background: -o-linear-gradient(top, #0066CC 0%, #0045CC 100%); - background: -ms-linear-gradient(top, #0066CC 0%, #0045CC 100%); + background: linear-gradient(to bottom, #06c, #0045cc); } + .btn-important:active { - background: #0044CB; + background: #0044cb; box-shadow: none; } .btn-attention { - background: #E95B57; - background: linear-gradient(to bottom, #E95B57, #BD362F); - background: -moz-linear-gradient(top, #E95B57 0%, #BD362F 100%); - background: -webkit-linear-gradient(top, #E95B57 0%, #BD362F 100%); - background: -o-linear-gradient(top, #E95B57 0%, #BD362F 100%); - background: -ms-linear-gradient(top, #E95B57 0%, #BD362F 100%); + background: #e95b57; + background: linear-gradient(to bottom, #e95b57, #bd362f); color: #fff; - border: 1px solid #C44742; + border: 1px solid #c44742; text-shadow: 0px -1px 0px #666; } + .btn-attention:hover { - background: linear-gradient(to bottom, #D14641, #BD362F); - background: -moz-linear-gradient(top, #D14641 0%, #BD362F 100%); - background: -webkit-linear-gradient(top, #D14641 0%, #BD362F 100%); - background: -o-linear-gradient(top, #D14641 0%, #BD362F 100%); - background: -ms-linear-gradient(top, #D14641 0%, #BD362F 100%); + background: linear-gradient(to bottom, #d14641, #bd362f); } + .btn-attention:active { - background: #BD362F; + background: #bd362f; box-shadow: none; } @@ -242,49 +242,60 @@ a.btn { line-height: 2.5em; font-size: 0.9rem; } + .nav-list .item:hover { background: #fafafa; } + .nav-list .item:hover a { - color: #003388; + color: #038; } + .nav-list .item.active { - background: #0062BE; + background: #0062be; color: #fff; } + .nav-list .item.active a { color: #fff; } + .nav-list .disable { - color: #aaa; background: #fafafa; + color: #aaa; text-align: center; } + .nav-list .item > a { padding: 0 10px; } + .nav-list a:hover { text-decoration: none; } + .nav-list .item.empty a { color: #f39c12; } + .nav-list .item.active.empty a { - color: #fff; background: #f39c12; + color: #fff; } + .nav-list .item.error a { - color: #BD362F; + color: #bd362f; } + .nav-list .item.active.error a { + background: #bd362f; color: #fff; - background: #BD362F; } .nav-list .nav-header { padding: 0 10px; - color: #888; background: #f4f4f4; + color: #888; border-bottom: 1px solid #ddd; font-weight: bold; text-shadow: 0 0 1px #ddd; @@ -299,13 +310,10 @@ a.btn { margin: 0; background: #fff; background: linear-gradient(to bottom, #fff, #f0f0f0); - background: -moz-linear-gradient(top, #fff 0%, #f0f0f0 100%); - background: -webkit-linear-gradient(top, #fff 0%, #f0f0f0 100%); - background: -o-linear-gradient(top, #fff 0%, #f0f0f0 100%); - background: -ms-linear-gradient(top, #fff 0%, #f0f0f0 100%); border-bottom: 1px solid #ddd; text-align: right; } + .nav-head .item { padding: 5px 10px; font-size: 0.9rem; @@ -317,6 +325,7 @@ a.btn { margin: 0; padding: 0; } + .horizontal-list .item { vertical-align: middle; } @@ -325,34 +334,34 @@ a.btn { .dropdown-menu { margin: 5px 0 0; padding: 5px 0; + font-size: 0.8rem; border: 1px solid #ddd; border-radius: 5px; box-shadow: 3px 3px 3px #ddd; - font-size: 0.8rem; text-align: left; } + .dropdown-menu::after { - content: ""; - position: absolute; - top: -6px; - right: 13px; + background: #fff; width: 10px; height: 10px; - background: #fff; border-top: 1px solid #ddd; border-left: 1px solid #ddd; + content: ""; + position: absolute; + top: -6px; + right: 13px; z-index: -10; transform: rotate(45deg); - -moz-transform: rotate(45deg); - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); } + .dropdown-header { padding: 0 5px 5px; color: #888; font-weight: bold; text-align: left; } + .dropdown-menu > .item > a, .dropdown-menu > .item > span, .dropdown-menu > .item > .as-link { @@ -360,18 +369,22 @@ a.btn { line-height: 2.5em; font-size: 0.8rem; } + .dropdown-menu > .item:hover { - background: #0062BE; + background: #0062be; color: #fff; } + .dropdown-menu > .item[aria-checked="true"] > a::before { font-weight: bold; margin: 0 0 0 -14px; } + .dropdown-menu > .item:hover > a { color: #fff; text-decoration: none; } + .dropdown-menu .input select, .dropdown-menu .input input { margin: 0 auto 5px; @@ -389,35 +402,40 @@ a.btn { margin: 15px auto; padding: 10px 15px; background: #f4f4f4; + color: #aaa; + font-size: 0.9em; border: 1px solid #ccc; border-right: 1px solid #aaa; border-bottom: 1px solid #aaa; border-radius: 5px; - color: #aaa; text-shadow: 0 0 1px #eee; - font-size: 0.9em; } + .alert-head { font-size: 1.15em; } + .alert > a { color: inherit; text-decoration: underline; } + .alert-warn { background: #ffe; - border: 1px solid #eeb; color: #c95; + border: 1px solid #eeb; } + .alert-success { background: #dfd; - border: 1px solid #cec; color: #484; + border: 1px solid #cec; } + .alert-error { background: #fdd; - border: 1px solid #ecc; color: #844; + border: 1px solid #ecc; } /*=== Pagination */ @@ -427,14 +445,17 @@ a.btn { color: #333; font-size: 0.8em; } + .content .pagination { margin: 0; padding: 0; } + .pagination .item.pager-current { font-weight: bold; font-size: 1.5em; } + .pagination .item a { display: block; color: #333; @@ -442,12 +463,15 @@ a.btn { line-height: 3em; text-decoration: none; } + .pagination .item a:hover { background: #ddd; } + .pagination:first-child .item { border-bottom: 1px solid #aaa; } + .pagination:last-child .item { border-top: 1px solid #aaa; } @@ -464,6 +488,7 @@ a.btn { border-radius: 5px; box-shadow: 0 0 3px #bbb; } + .box .box-title { margin: 0; padding: 5px 10px; @@ -471,6 +496,7 @@ a.btn { border-bottom: 1px solid #ddd; border-radius: 5px 5px 0 0; } + .box .box-content { min-height: 2.5em; max-height: 260px; @@ -486,6 +512,7 @@ a.btn { .box .box-content .item .configure { visibility: hidden; } + .box .box-title:hover .configure, .box .box-content .item:hover .configure { visibility: visible; @@ -495,6 +522,7 @@ a.btn { .tree { margin: 10px 0; } + .tree-folder-title { position: relative; padding: 0 10px; @@ -502,36 +530,45 @@ a.btn { line-height: 2.5rem; font-size: 1rem; } + .tree-folder-title .title { background: inherit; color: #444; } + .tree-folder-title .title:hover { text-decoration: none; } + .tree-folder.active .tree-folder-title { background: #f0f0f0; font-weight: bold; } + .tree-folder.active .tree-folder-title .title { - color: #0062BE; + color: #0062be; } + .tree-folder-items { + background: #f6f6f6; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; - background: #f6f6f6; } + .tree-folder-items > .item { padding: 0 10px; line-height: 2.5rem; font-size: 0.8rem; } + .tree-folder-items > .item.active { background: #0062be; } + .tree-folder-items > .item > a { text-decoration: none; } + .tree-folder-items > .item.active > a { color: #fff; } @@ -540,28 +577,34 @@ a.btn { /*===============*/ /*=== Header */ .header { - height: 85px; background: #f4f4f4; + height: 85px; } + .header > .item { padding: 10px; border-bottom: 1px solid #aaa; vertical-align: middle; text-align: center; } -.header > .item.title{ + +.header > .item.title { width: 230px; } + .header > .item.title h1 { margin: 0.5em 0; text-shadow: 1px -1px 0 #ccc; } + .header > .item.title h1 a { text-decoration: none; } + .header > .item.search input { width: 230px; } + .header .item.search input:focus { width: 350px; } @@ -570,54 +613,63 @@ a.btn { #global { height: calc(100% - 85px); } + .aside { - border-right: 1px solid #aaa; background: #fff; + border-right: 1px solid #aaa; } + .aside.aside_feed { padding: 10px 0; text-align: center; background: #fff; } + .aside.aside_feed .tree { margin: 10px 0 50px; } /*=== Aside main page (categories) */ .aside_feed .category .title:not([data-unread="0"])::after { - position: absolute; - right: 0; margin: 10px 0; padding: 0 10px; + background: inherit; font-size: 0.9rem; + position: absolute; + right: 0; line-height: 1.5rem; - background: inherit; } /*=== Aside main page (feeds) */ .feed.item.empty.active { background: #e67e22; } + .feed.item.error.active { background: #bd362f; } + .feed.item.empty, .feed.item.empty > a { color: #e67e22; } + .feed.item.error, .feed.item.error > a { color: #bd362f; } + .feed.item.empty.active, .feed.item.error.active, .feed.item.empty.active > a, .feed.item.error.active > a { color: #fff; } + .aside_feed .tree-folder-items .dropdown-menu::after { left: 2px; } + .aside_feed .tree-folder-items .item .dropdown-target:target ~ .dropdown-toggle > .icon, .aside_feed .tree-folder-items .item.active .dropdown-toggle > .icon { background-color: #fff; @@ -629,9 +681,11 @@ a.btn { padding: 10px 50px; font-size: 0.9em; } + .post form { margin: 10px 0; } + .post.content { max-width: 550px; } @@ -640,35 +694,42 @@ a.btn { .prompt { text-align: center; } + .prompt label { text-align: left; } + .prompt form { margin: 10px auto 20px auto; width: 200px; } + .prompt input { margin: 5px auto; width: 100%; } + .prompt p { margin: 20px 0; } /*=== New article notification */ #new-article { - background: #0084CC; + background: #0084cc; text-align: center; font-size: 0.9em; } + #new-article:hover { - background: #0066CC; + background: #06c; } + #new-article > a { line-height: 3em; color: #fff; font-weight: bold; } + #new-article > a:hover { text-decoration: none; } @@ -682,9 +743,11 @@ a.btn { border-top: 1px solid #aaa; border-bottom: 1px solid #aaa; } + #new-article + .day { border-top: none; } + .day .name { padding: 0 10px 0 0; color: #aab; @@ -697,53 +760,62 @@ a.btn { /*=== Index menu */ .nav_menu { + padding: 5px 0; background: #fafafa; border-bottom: 1px solid #aaa; text-align: center; - padding: 5px 0; } /*=== Feed articles */ .flux { - border-left: 2px solid #aaa; background: #fafafa; + border-left: 2px solid #aaa; } + .flux:hover { background: #fff; } + .flux.current { - border-left: 2px solid #0062BE; + background: #fff; + border-left: 2px solid #0062be; } + .flux.not_read { - border-left: 2px solid #FF5300; - background: #FFF3ED; + border-left: 2px solid #ff5300; } + +.flux.not_read:not(.current) { + background: #fff3ed; +} + .flux.not_read:not(.current):hover .item.title { - background: #FFF3ED; + background: inherit; } + .flux.favorite { - border-left: 2px solid #FFC300; - background: #FFF6DA; + background: #fff6da; + border-left: 2px solid #ffc300; } + .flux.favorite:not(.current):hover .item.title { - background: #FFF6DA; -} -.flux.current { - background: #fff; + background: #fff6da; } - .flux_header { - border-top: 1px solid #ddd; font-size: 0.8rem; + border-top: 1px solid #ddd; cursor: pointer; } + .flux_header .title { font-size: 0.9rem; } + .flux .website .favicon { padding: 5px; } + .flux .date { color: #666; font-size: 0.7rem; @@ -758,14 +830,15 @@ a.btn { .content { padding: 20px 10px; } + .content > h1.title > a { color: #000; } .content hr { margin: 30px 10px; - height: 1px; background: #ddd; + height: 1px; border: 0; box-shadow: 0 2px 5px #ccc; } @@ -779,13 +852,15 @@ a.btn { font-size: 0.9rem; border-radius: 3px; } + .content code { padding: 2px 5px; - color: #dd1144; background: #fafafa; + color: #d14; border: 1px solid #eee; border-radius: 3px; } + .content pre code { background: transparent; color: #fff; @@ -793,14 +868,15 @@ a.btn { } .content blockquote { - display: block; margin: 0; padding: 5px 20px; - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; background: #fafafa; + display: block; color: #333; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; } + .content blockquote p { margin: 0; } @@ -808,33 +884,38 @@ a.btn { /*=== Notification and actualize notification */ .notification { padding: 0 0 0 5px; - text-align: center; + font-size: 0.9em; border: 1px solid #eeb; border-radius: 3px; box-shadow: 0 0 5px #ddd; + text-align: center; font-weight: bold; - font-size: 0.9em; line-height: 3em; z-index: 10; vertical-align: middle; } + .notification.good { background: #ffe; - border: 1px solid #eeb; color: #c95; + border: 1px solid #eeb; } + .notification.bad { background: #fdd; - border: 1px solid #ecc; color: #844; + border: 1px solid #ecc; } + .notification a.close { padding: 0 15px; line-height: 3em; } + .notification.good a.close:hover { background: #eeb; } + .notification.bad a.close:hover { background: #ecc; } @@ -845,17 +926,19 @@ a.btn { /*=== "Load more" part */ #bigMarkAsRead { + background: #fafafa; + color: #666; text-align: center; text-decoration: none; text-shadow: 0 -1px 0 #aaa; - color: #666; - background: #fafafa; } + #bigMarkAsRead:hover { - color: #0062be; background: #fff; + color: #0062be; box-shadow: 0 -5px 10px #eee inset; } + #bigMarkAsRead:hover .bigTick { text-shadow: 0 0 5px #0062be; } @@ -874,14 +957,15 @@ a.btn { /*================*/ #stream.reader .flux { padding: 0 0 50px; - border: none; background: #f0f0f0; color: #333; + border: none; } + #stream.reader .flux .author { margin: 0 0 10px; - font-size: 90%; color: #666; + font-size: 90%; } /*=== GLOBAL VIEW */ @@ -891,26 +975,31 @@ a.btn { text-decoration: none; text-align: left; } + .box.category:not([data-unread="0"]) .box-title { - background: #0084CC; + background: #0084cc; } + .box.category:not([data-unread="0"]) .box-title:active { background: #3498db; } + .box.category:not([data-unread="0"]) .box-title .title { color: #fff; font-weight: bold; } + .box.category .title:not([data-unread="0"])::after { - position: absolute; - top: 5px; right: 10px; - border: 0; background: none; color: #fff; - font-weight: bold; + border: 0; box-shadow: none; + position: absolute; + top: 5px; right: 10px; + font-weight: bold; text-shadow: none; } + .box.category .item.feed { padding: 2px 10px; font-size: 0.8rem; @@ -922,9 +1011,11 @@ a.btn { .aside.aside_feed .nav-form select { width: 140px; } + .aside.aside_feed .nav-form .dropdown .dropdown-menu { right: -20px; } + .aside.aside_feed .nav-form .dropdown .dropdown-menu::after { right: 33px; } @@ -940,6 +1031,7 @@ a.btn { .stat tr { border: none; } + .stat > table td, .stat > table th { border-bottom: 1px solid #ddd; @@ -948,11 +1040,13 @@ a.btn { .stat > .horizontal-list { margin: 0 0 5px; } + .stat > .horizontal-list .item { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } + .stat > .horizontal-list .item:first-child { width: 270px; } @@ -964,31 +1058,38 @@ a.btn { border-radius: 5px; overflow: hidden; } + .log { padding: 5px 10px; background: #fafafa; color: #333; font-size: 0.8rem; } + .log+.log { border-top: 1px solid #aaa; } + .log .date { display: block; font-weight: bold; } + .log.error { background: #fdd; color: #844; } + .log.warning { background: #ffe; color: #c95; } + .log.notice { background: #f4f4f4; color: #aaa; } + .log.debug { background: #333; color: #eee; @@ -996,24 +1097,22 @@ a.btn { /*=== MOBILE */ /*===========*/ -@media(max-width: 840px) { + +@media (max-width: 840px) { .aside { box-shadow: 3px 0 3px #aaa; transition: width 200ms linear; - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; } + .aside .toggle_aside, #panel .close { + background: #f6f6f6; display: block; width: 100%; height: 50px; + border-bottom: 1px solid #ddd; line-height: 50px; text-align: center; - background: #f6f6f6; - border-bottom: 1px solid #ddd; } .aside.aside_feed { @@ -1023,20 +1122,25 @@ a.btn { .nav_menu .btn { margin: 5px 10px; } + .nav_menu .stick { margin: 0 10px; } + .nav_menu .stick .btn { margin: 5px 0; } + .nav_menu .search { display: inline-block; max-width: 97%; } + .nav_menu .search input { max-width: 97%; width: 90px; } + .nav_menu .search input:focus { width: 400px; } @@ -1051,13 +1155,15 @@ a.btn { } .notification a.close { + background: transparent; display: block; left: 0; - background: transparent; } + .notification a.close:hover { opacity: 0.5; } + .notification a.close .icon { display: none; } diff --git a/p/themes/Pafat/pafat.css b/p/themes/Pafat/pafat.css index 46425cd5c..8b6bc5f62 100644 --- a/p/themes/Pafat/pafat.css +++ b/p/themes/Pafat/pafat.css @@ -3,10 +3,10 @@ /*=== GENERAL */ /*============*/ html, body { + background: #fafafa; height: 100%; + color: #666; font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", "PingFang SC", "Microsoft YaHei", sans-serif; - background: #fafafa; - color : #666; } /*=== Links */ @@ -19,25 +19,28 @@ a { legend { margin: 20px 0 5px; padding: 5px 0; - border-bottom: 1px solid #ddd; font-size: 1.4em; + border-bottom: 1px solid #ddd; } + label { min-height: 25px; padding: 5px 0; cursor: pointer; } + textarea { width: 360px; height: 100px; } + input, select, textarea { - min-height: 25px; padding: 1px; background: #fdfdfd; + color: #666; border: 1px solid #bbb; border-radius: 3px; - color: #666; + min-height: 25px; line-height: 21px; vertical-align: middle; } @@ -45,24 +48,23 @@ input, select, textarea { option { padding: 0 .5em; } + input:focus, select:focus, textarea:focus { - outline-color: #aaa; + outline-color: #aaa; } input:invalid, select:invalid { border-color: #f00; box-shadow: 0 0 2px 2px #fdd inset; - outline-color: #fdd; + outline-color: #fdd; } + input:disabled, select:disabled { background: #eee; } + input.extend { transition: width 200ms linear; - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; } /*=== Tables */ @@ -74,9 +76,11 @@ tr, th, td { padding: 0.5em; border: 1px solid #ddd; } + th { background: #f6f6f6; } + form td, form th { font-weight: normal; @@ -91,19 +95,23 @@ form th { background: #f4f4f4; border-top: 1px solid #ddd; } + .form-group.form-actions .btn { margin: 0 10px; } + .form-group .group-name { padding: 10px 0; text-align: right; } + .form-group .group-controls { - min-height: 25px; + margin: 10px 0 10px 220px; padding: 5px 0; - margin : 10px 0 10px 220px; + min-height: 25px; } + .form-group table { margin: 10px 0 0 220px; } @@ -114,21 +122,26 @@ form th { font-size: 0; min-width: 215px; } + .stick input, .stick .btn { border-radius: 0; } + .stick .btn:first-child, .stick input:first-child { border-radius: 3px 0 0 3px; } + .stick .btn-important:first-child { - width:176px; + width: 176px; } + .stick .btn:last-child, .stick input:last-child { border-radius: 0 3px 3px 0; } + .stick .btn + .btn, .stick .btn + input, .stick .btn + .dropdown > .btn, @@ -148,17 +161,17 @@ form th { } .btn { - display: inline-block; - min-height: 29px; - min-width: 15px; - line-height: 25px; margin: 0; padding: 1px 5px; background: #fff; - border-radius: 3px; - border: 1px solid #aaa; + display: inline-block; color: #666; font-size: 0.9rem; + border: 1px solid #aaa; + border-radius: 3px; + min-height: 29px; + min-width: 15px; + line-height: 25px; vertical-align: middle; cursor: pointer; overflow: hidden; @@ -170,7 +183,7 @@ a.btn { } .read_all.btn { - height:29px; + height: 29px; } .btn:hover { @@ -190,9 +203,10 @@ a.btn { border-color: #5cb85c; font-weight: normal; } + .btn-important:hover, .btn-important:active { - background:#47a447; - border-color : #47a447; + background: #47a447; + border-color: #47a447; box-shadow: none; } @@ -200,12 +214,14 @@ a.btn { background: #d9534f; color: #fff; border: 1px solid #d9534f; - outline-color : #aaa; + outline-color: #aaa; } + .btn-attention:hover { background: #d2322d; - border-color : #d2322d; + border-color: #d2322d; } + .btn-attention:active { background: #d2322d; box-shadow: none; @@ -218,49 +234,60 @@ a.btn { line-height: 2.5em; font-size: 0.9rem; } + .nav-list .item:hover { background: #fafafa; } + .nav-list .item:hover a { - color: #003388; + color: #038; } + .nav-list .item.active { - background: #3498DB; + background: #3498db; color: #fff; } + .nav-list .item.active a { color: #fff; } + .nav-list .disable { - color: #aaa; background: #fafafa; + color: #aaa; text-align: center; } + .nav-list .item > a { padding: 0 10px; } + .nav-list a:hover { text-decoration: none; } + .nav-list .item.empty a { color: #f39c12; } + .nav-list .item.active.empty a { - color: #fff; background: #f39c12; + color: #fff; } + .nav-list .item.error a { - color: #BD362F; + color: #bd362f; } + .nav-list .item.active.error a { + background: #bd362f; color: #fff; - background: #BD362F; } .nav-list .nav-header { padding: 0 10px; - color: #888; background: #f4f4f4; + color: #888; border-bottom: 1px solid #ddd; font-weight: bold; } @@ -274,13 +301,9 @@ a.btn { margin: 0; background: #fff; background: linear-gradient(to bottom, #fff, #f0f0f0); - background: -moz-linear-gradient(top, #fff 0%, #f0f0f0 100%); - background: -webkit-linear-gradient(top, #fff 0%, #f0f0f0 100%); - background: -o-linear-gradient(top, #fff 0%, #f0f0f0 100%); - background: -ms-linear-gradient(top, #fff 0%, #f0f0f0 100%); - border-bottom: 1px solid #ddd; text-align: right; } + .nav-head .item { padding: 5px 10px; font-size: 0.9rem; @@ -292,6 +315,7 @@ a.btn { margin: 0; padding: 0; } + .horizontal-list .item { vertical-align: middle; } @@ -300,27 +324,26 @@ a.btn { .dropdown-menu { margin: 5px 0 0; padding: 5px 0; + font-size: 0.8rem; border: 1px solid #aaa; border-radius: 5px; - font-size: 0.8rem; text-align: left; } + .dropdown-menu::after { - content: ""; - position: absolute; - top: -6px; - right: 13px; + background: #fff; width: 10px; height: 10px; - background: #fff; border-top: 1px solid #aaa; border-left: 1px solid #aaa; + content: ""; + position: absolute; + top: -6px; + right: 13px; z-index: -10; transform: rotate(45deg); - -moz-transform: rotate(45deg); - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); } + .dropdown-header { padding: 0 5px 5px; color: #888; @@ -341,14 +364,17 @@ a.btn { background: #eee; color: #666; } + .dropdown-menu > .item[aria-checked="true"] > a::before { font-weight: bold; margin: 0 0 0 -14px; } + .dropdown-menu > .item:hover > a { color: #666; text-decoration: none; } + .dropdown-menu .input select, .dropdown-menu .input input { margin: 0 auto 5px; @@ -366,34 +392,39 @@ a.btn { margin: 15px auto; padding: 10px 15px; background: #f4f4f4; + color: #aaa; + font-size: 0.9em; border: 1px solid #ccc; border-right: 1px solid #aaa; border-bottom: 1px solid #aaa; border-radius: 5px; - color: #aaa; - font-size: 0.9em; } + .alert-head { font-size: 1.15em; } + .alert > a { color: inherit; text-decoration: underline; } + .alert-warn { background: #ffe; - border: 1px solid #eeb; color: #c95; + border: 1px solid #eeb; } + .alert-success { background: #dfd; - border: 1px solid #cec; color: #484; + border: 1px solid #cec; } + .alert-error { background: #fdd; - border: 1px solid #ecc; color: #844; + border: 1px solid #ecc; } /*=== Pagination */ @@ -403,14 +434,17 @@ a.btn { color: #41444f; font-size: 0.8em; } + .content .pagination { margin: 0; padding: 0; } + .pagination .item.pager-current { font-weight: bold; font-size: 1.5em; } + .pagination .item a { display: block; color: #41444f; @@ -418,12 +452,15 @@ a.btn { line-height: 3em; text-decoration: none; } + .pagination .item a:hover { background: #ddd; } + .pagination:first-child .item { border-bottom: 1px solid #aaa; } + .pagination:last-child .item { border-top: 1px solid #aaa; } @@ -439,6 +476,7 @@ a.btn { border: 1px solid #aaa; border-radius: 5px; } + .box .box-title { margin: 0; padding: 5px 10px; @@ -446,6 +484,7 @@ a.btn { border-bottom: 1px solid #aaa; border-radius: 5px 5px 0 0; } + .box .box-content { max-height: 260px; } @@ -460,6 +499,7 @@ a.btn { .box .box-content .item .configure { visibility: hidden; } + .box .box-title:hover .configure, .box .box-content .item:hover .configure { visibility: visible; @@ -469,25 +509,29 @@ a.btn { .tree { margin: 10px 0; } + .tree-folder-title { - position: relative; margin: 5px; padding: 0 10px; - line-height: 2rem; - font-size: 0.9rem; background: #5bc0de; color: #fff; + font-size: 0.9rem; border-top: 1px solid transparent; border-bottom: 1px solid transparent; border-radius: 5px; + position: relative; + line-height: 2rem; } + .tree-folder-title .title { background: inherit; color: #fff; } + .tree-folder-title .title:hover { text-decoration: none; } + .tree-folder.active .tree-folder-title { background: #39b3d7; font-weight: bold; @@ -495,17 +539,21 @@ a.btn { border-top: 1px solid #666; border-bottom: 1px solid #666; } + .tree-folder-items > .item { padding: 0 10px; line-height: 2.5rem; font-size: 0.8rem; } + .tree-folder-items > .item.active { background: #5cb85c; } + .tree-folder-items > .item > a { text-decoration: none; } + .tree-folder-items > .item.active > a { color: #fff; } @@ -514,40 +562,39 @@ a.btn { /*===============*/ /*=== Header */ .header { - height: 85px; background: #41444f; + height: 85px; } + .header > .item { padding: 10px; border-bottom: 1px solid #aaa; vertical-align: middle; text-align: center; } -.header > .item.title{ + +.header > .item.title { width: 230px; } + .header > .item.title h1 { margin: 0.5em 0; } .header > .item.title h1 a, a.signin { text-decoration: none; - color : #C5C6CA; + color: #c5c6ca; } .header > .item.search input { width: 230px; - height : 29px; - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; + height: 29px; box-sizing: border-box; } .header > .item.search button { - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; box-sizing: border-box; - height : 29px; + height: 29px; } .header .item.search input:focus { @@ -558,37 +605,44 @@ a.btn { #global { height: calc(100% - 85px); } + .aside { - border-right: 1px solid #aaa; background: #fff; + border-right: 1px solid #aaa; } + .aside.aside_feed { padding: 10px 0; text-align: center; } + .aside.aside_feed .tree { margin: 10px 0 50px; } /*=== Aside main page (categories) */ .aside_feed .tree-folder-title > .title:not([data-unread="0"])::after { - position: absolute; - top: 0.25rem; right: 3px; padding: 0px 5px; + font-size: 0.8rem; border: 1px solid #fff; border-radius: 3px; - font-size: 0.8rem; + position: absolute; + top: 0.25rem; right: 3px; line-height: 1.5rem; } + .aside_feed .tree-folder.all .tree-folder-title { background: #428bca; } + .aside_feed .tree-folder.all.active .tree-folder-title { background: #3276b1; } + .aside_feed .tree-folder.favorites .tree-folder-title { background: #f0ad4e; } + .aside_feed .tree-folder.favorites.active .tree-folder-title { background: #ed9c28; } @@ -597,26 +651,32 @@ a.btn { .feed.item.empty.active { background: #e67e22; } + .feed.item.error.active { background: #bd362f; } + .feed.item.empty, .feed.item.empty > a { color: #e67e22; } + .feed.item.error, .feed.item.error > a { color: #bd362f; } + .feed.item.empty.active, .feed.item.error.active, .feed.item.empty.active > a, .feed.item.error.active > a { color: #fff; } + .aside_feed .tree-folder-items .dropdown-menu::after { left: 2px; } + .aside_feed .tree-folder-items .item .dropdown-target:target ~ .dropdown-toggle > .icon, .aside_feed .tree-folder-items .item:hover .dropdown-toggle > .icon, .aside_feed .tree-folder-items .item.active .dropdown-toggle > .icon { @@ -629,9 +689,11 @@ a.btn { padding: 10px 50px; font-size: 0.9em; } + .post form { margin: 10px 0; } + .post.content { max-width: 550px; } @@ -640,17 +702,21 @@ a.btn { .prompt { text-align: center; } + .prompt label { text-align: left; } + .prompt form { margin: 10px auto 20px auto; width: 200px; } + .prompt input { margin: 5px auto; width: 100%; } + .prompt p { margin: 20px 0; } @@ -661,14 +727,17 @@ a.btn { text-align: center; font-size: 0.9em; } + #new-article:hover { background: #3276b1; } + #new-article > a { line-height: 3em; color: #fff; font-weight: bold; } + #new-article > a:hover { text-decoration: none; } @@ -676,19 +745,21 @@ a.btn { /*=== Day indication */ .day { padding: 0 10px; - font-weight: bold; - line-height: 3em; background: #fff; + color: #666; border-top: 1px solid #aaa; border-bottom: 1px solid #aaa; - color : #666; + font-weight: bold; + line-height: 3em; } + #new-article + .day { border-top: none; } + .day .name { padding: 0 10px 0 0; - color : #666; + color: #666; font-size: 1.8em; opacity: 0.3; font-style: italic; @@ -697,52 +768,58 @@ a.btn { /*=== Index menu */ .nav_menu { + padding: 5px 0; background: #fafafa; border-bottom: 1px solid #aaa; text-align: center; - padding: 5px 0; } /*=== Feed articles */ .flux { - border-left: 3px solid #5cb85c; background: #fafafa; + border-left: 3px solid #5cb85c; } + .flux:hover { background: #fff; } + .flux.current { + background: #fff; border-left: 3px solid #39b3d7; } + .flux.not_read { border-left: 3px solid #d9534f; } + .flux .item.title a, .flux.not_read:not(.current):hover .item.title { - color : #333; + color: #333; } + .flux.favorite { + background: #fff6da; border-left: 2px solid #428bca; - background: #FFF6DA; } + .flux.favorite:not(.current):hover .item.title { - background: #FFF6DA; -} -.flux.current { - background: #fff; + background: #fff6da; } - .flux_header { - border-top: 1px solid #ddd; font-size: 0.8rem; + border-top: 1px solid #ddd; cursor: pointer; } + .flux_header .title { font-size: 0.9rem; } + .flux .website .favicon { padding: 5px; } + .flux .date { color: #666; font-size: 0.7rem; @@ -757,14 +834,15 @@ a.btn { .content { padding: 20px 10px; } + .content > h1.title > a { color: #333; } .content hr { margin: 30px 10px; - height: 1px; background: #ddd; + height: 1px; border: 0; box-shadow: 0 2px 5px #ccc; } @@ -778,13 +856,15 @@ a.btn { font-size: 0.9rem; border-radius: 3px; } + .content code { padding: 2px 5px; - color: #dd1144; background: #fafafa; + color: #d14; border: 1px solid #eee; border-radius: 3px; } + .content pre code { background: transparent; color: #fff; @@ -792,14 +872,15 @@ a.btn { } .content blockquote { - display: block; margin: 0; padding: 5px 20px; - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; background: #fafafa; + display: block; color: #41444f; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; } + .content blockquote p { margin: 0; } @@ -807,33 +888,38 @@ a.btn { /*=== Notification and actualize notification */ .notification { padding: 0 0 0 5px; - text-align: center; + font-size: 0.9em; border: 1px solid #eeb; border-radius: 3px; box-shadow: 0 0 5px #ddd; + text-align: center; font-weight: bold; - font-size: 0.9em; line-height: 3em; z-index: 10; vertical-align: middle; } + .notification.good { background: #ffe; - border: 1px solid #eeb; color: #c95; + border: 1px solid #eeb; } + .notification.bad { background: #fdd; - border: 1px solid #ecc; color: #844; + border: 1px solid #ecc; } + .notification a.close { padding: 0 15px; line-height: 3em; } + .notification.good a.close:hover { background: #eeb; } + .notification.bad a.close:hover { background: #ecc; } @@ -844,18 +930,19 @@ a.btn { /*=== "Load more" part */ #bigMarkAsRead { + background: #fafafa; + color: #666; text-align: center; text-decoration: none; - color: #666; - background: #fafafa; } + #bigMarkAsRead:hover { - color: #000; background: #f0f0f0; + color: #000; } #bigMarkAsRead:hover .bigTick { -/* text-shadow: 0 0 10px #666;*/ + /* text-shadow: 0 0 10px #666;*/ } /*=== Navigation menu (for articles) */ @@ -869,20 +956,21 @@ a.btn { } #nav_entries .item:hover { - background:#eee ; + background: #eee ; } /*=== READER VIEW */ /*================*/ #stream.reader .flux { padding: 0 0 50px; - border: none; background: #f0f0f0; color: #41444f; + border: none; } + #stream.reader .flux .author { margin: 0 0 10px; - font-size: 90%; color: #666; + font-size: 90%; } /*=== GLOBAL VIEW */ @@ -892,24 +980,28 @@ a.btn { text-decoration: none; text-align: left; } + .box.category:not([data-unread="0"]) .box-title { - background: #5BC0DE; + background: #5bc0de; } + .box.category:not([data-unread="0"]) .box-title .title { font-weight: bold; color: #fff; } + .box.category .title:not([data-unread="0"])::after { + background: none; + font-size: 0.8rem; + border: 0; + box-shadow: none; position: absolute; top: 5px; right: 10px; - border: 0; - background: none; font-weight: bold; - box-shadow: none; text-shadow: none; - font-size: 0.8rem; line-height: 1.6rem; } + .box.category .item.feed { padding: 2px 10px; font-size: 0.8rem; @@ -921,9 +1013,11 @@ a.btn { .aside.aside_feed .nav-form select { width: 140px; } + .aside.aside_feed .nav-form .dropdown .dropdown-menu { right: -20px; } + .aside.aside_feed .nav-form .dropdown .dropdown-menu::after { right: 33px; } @@ -939,19 +1033,23 @@ a.btn { .stat tr { border: none; } + .stat > table td, .stat > table th { border-bottom: 1px solid #ddd; text-align: center; } + .stat > .horizontal-list { margin: 0 0 5px; } + .stat > .horizontal-list .item { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } + .stat > .horizontal-list .item:first-child { width: 250px; } @@ -963,31 +1061,38 @@ a.btn { border-radius: 5px; overflow: hidden; } + .log { padding: 5px 10px; background: #fafafa; color: #41444f; font-size: 0.8rem; } + .log+.log { border-top: 1px solid #aaa; } + .log .date { display: block; font-weight: bold; } + .log.error { background: #fdd; color: #844; } + .log.warning { background: #ffe; color: #c95; } + .log.notice { background: #f4f4f4; color: #aaa; } + .log.debug { background: #41444f; color: #eee; @@ -995,24 +1100,22 @@ a.btn { /*=== MOBILE */ /*===========*/ -@media(max-width: 840px) { + +@media (max-width: 840px) { .aside { box-shadow: 3px 0 3px #aaa; transition: width 200ms linear; - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; } + .aside .toggle_aside, #panel .close { + background: #f6f6f6; display: block; width: 100%; height: 40px; + border-bottom: 1px solid #ddd; line-height: 40px; text-align: center; - background: #f6f6f6; - border-bottom: 1px solid #ddd; } .aside.aside_feed { @@ -1022,20 +1125,25 @@ a.btn { .nav_menu .btn { margin: 5px 10px; } + .nav_menu .stick { margin: 0 10px; } + .nav_menu .stick .btn { margin: 5px 0; } + .nav_menu .search { display: inline-block; max-width: 97%; } + .nav_menu .search input { max-width: 97%; width: 90px; } + .nav_menu .search input:focus { width: 400px; } @@ -1050,13 +1158,15 @@ a.btn { } .notification a.close { + background: transparent; display: block; left: 0; - background: transparent; } + .notification a.close:hover { opacity: 0.5; } + .notification a.close .icon { display: none; } diff --git a/p/themes/Screwdriver/screwdriver.css b/p/themes/Screwdriver/screwdriver.css index 3a78bdf50..d397f473d 100644 --- a/p/themes/Screwdriver/screwdriver.css +++ b/p/themes/Screwdriver/screwdriver.css @@ -3,72 +3,77 @@ /*=== GENERAL */ /*============*/ html, body { + background: #fafafa; height: 100%; font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", "PingFang SC", "Microsoft YaHei", sans-serif; - background: #fafafa; font-size: 92%; } /*=== Links */ a, button.as-link { - color: #D18114; + color: #d18114; outline: none; } /*=== Forms */ -.form-group{ - width: 100%; +.form-group { + display: inline-block; float: left; + width: 100%; height: auto; - display: inline-block; } + legend { margin: 20px 0 5px; padding: 5px 0; - border-bottom: 1px solid #ddd; font-size: 1.4em; + border-bottom: 1px solid #ddd; } + label { min-height: 25px; padding: 5px 0; cursor: pointer; } + textarea { width: 360px; height: 100px; } + input, select, textarea { - min-height: 25px; padding: 5px; background: #fff; + color: #222; border: 1px solid #ccc; border-radius: 3px; - color: #222; + box-shadow: 0 1px 2px #ccc inset, 0 1px #fff; + min-height: 25px; line-height: 25px; vertical-align: middle; - box-shadow: 0 1px 2px #ccc inset, 0 1px #fff; } + option { padding: 0 .5em; } + input:focus, select:focus, textarea:focus { - color: #0F0F0F; - box-shadow: 0 0 3px #E7AB34; - border: solid 1px #E7AB34; + color: #0f0f0f; + border: solid 1px #e7ab34; + box-shadow: 0 0 3px #e7ab34; } + input:invalid, select:invalid { border-color: #f00; box-shadow: 0 0 2px 2px #fdd inset; } + input:disabled, select:disabled { background: #eee; } + input.extend { transition: width 200ms linear; - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; } /*=== Tables */ @@ -80,9 +85,11 @@ tr, th, td { padding: 0.5em; border: 1px solid #ddd; } + th { background: #f6f6f6; } + form td, form th { font-weight: normal; @@ -97,51 +104,60 @@ form th { background: #f4f4f4; border-top: 1px solid #ddd; } + .form-group.form-actions .btn { margin: 0 10px; border-radius: 4px; - box-shadow:0 1px rgba(255,255,255,0.08) inset; + box-shadow: 0 1px rgba(255,255,255,0.08) inset; } + .form-group .group-name { padding: 10px 0; text-align: right; } + .form-group .group-controls { min-height: 25px; padding: 5px 0; } + .form-group table { margin: 10px 0 0 220px; } /*=== Buttons */ button.as-link[disabled] { - color:#555 !important; + color: #555 !important; } .dropdown-menu .input select, .dropdown-menu .input input { - background:#444; - color:#fff; - box-shadow:0 2px 2px #222 inset, 0px 1px rgba(255, 255, 255, 0.08); - border:solid 1px #171717; + margin: 0 auto 5px; + padding: 2px 5px; + background: #444; + color: #fff; + border: solid 1px #171717; + border-radius: 3px; + box-shadow: 0 2px 2px #222 inset, 0px 1px rgba(255, 255, 255, 0.08); } .stick { vertical-align: middle; font-size: 0; } + .stick input, .stick .btn { border-radius: 0; } + .stick .btn:first-child,.stick input:first-child { border-radius: 6px 0 0 6px; } -.stick .btn-important:first-child { -} + .stick .btn:last-child, .stick input:last-child { border-radius: 0 6px 6px 0; } + .stick .btn + .btn, .stick .btn + input, .stick .btn + .dropdown > .btn, @@ -153,103 +169,113 @@ button.as-link[disabled] { .stick .dropdown + .dropdown > .btn { border-left: none; } + .stick .btn + .dropdown > .btn { border-left: none; border-radius: 0 3px 3px 0; } .btn { - display: inline-block; - min-height: 37px; - min-width: 15px; margin: 0; padding: 5px 10px; - color:#222; + background: linear-gradient(0deg, #ede7de 0%, #fff 100%) #ede7de; + background: -webkit-linear-gradient(bottom, #ede7de 0%, #fff 100%); + display: inline-block; + color: #222; + font-size: 0.9rem; border: solid 1px #ccc; border-radius: 4px; - background: linear-gradient(0deg, #EDE7DE 0%, #FFF 100%) #EDE7DE; - background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #FFF 100%); + min-height: 37px; + min-width: 15px; text-shadow: 0px -1px rgba(255,255,255,0.08); - font-size: 0.9rem; vertical-align: middle; cursor: pointer; overflow: hidden; } + a.btn { min-height: 25px; line-height: 25px; } + .btn:hover { text-shadow: 0 0 2px #fff; - text-decoration:none; + text-decoration: none; } + .btn.active,.btn:active,.dropdown-target:target ~ .btn.dropdown-toggle { - background: linear-gradient(180deg, #EDE7DE 0%, #FFF 100%) #EDE7DE; - background: -webkit-linear-gradient(top, #EDE7DE 0%, #FFF 100%); + background: linear-gradient(180deg, #ede7de 0%, #fff 100%) #ede7de; + background: -webkit-linear-gradient(top, #ede7de 0%, #fff 100%); } -.nav_menu .btn.active, .nav_menu .btn:active, .nav_menu .dropdown-target:target ~ .btn.dropdown-toggle{ - box-shadow: 0 1px #fff; - border-radius: 4px; - background: linear-gradient(180deg, #EDE7DE 0%, #F6F6F6 100%) #EDE7DE; - background: -webkit-linear-gradient(top, #EDE7DE 0%, #F6F6F6 100%); +.nav_menu .btn.active, .nav_menu .btn:active, .nav_menu .dropdown-target:target ~ .btn.dropdown-toggle { + background: linear-gradient(180deg, #ede7de 0%, #f6f6f6 100%) #ede7de; + background: -webkit-linear-gradient(top, #ede7de 0%, #f6f6f6 100%); border: solid 1px #ccc; + border-radius: 4px; + box-shadow: 0 1px #fff; } + .nav_menu .btn { + background: transparent; border: 0; - background:transparent; } .read_all { - color:#222; + color: #222; } -.btn.dropdown-toggle[href="#dropdown-configure"]{ - background: linear-gradient(0deg, #EDE7DE 0%, #FFF 100%) #EDE7DE; - background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #FFF 100%); - border-radius: 4px; + +.btn.dropdown-toggle[href="#dropdown-configure"] { + background: linear-gradient(0deg, #ede7de 0%, #fff 100%) #ede7de; + background: -webkit-linear-gradient(bottom, #ede7de 0%, #fff 100%); border: solid 1px #ccc; + border-radius: 4px; box-shadow: 0 1px #fff; } + .btn.dropdown-toggle:active { - background:transparent; + background: transparent; } + .btn-important { - background: linear-gradient(180deg, #E4992C 0%, #D18114 100%) #E4992C; - background: -webkit-linear-gradient(top, #E4992C 0%, #D18114 100%); - color: #FFF; - box-shadow: 0 1px rgba(255,255,255,0.08) inset; + background: linear-gradient(180deg, #e4992c 0%, #d18114 100%) #e4992c; + background: -webkit-linear-gradient(top, #e4992c 0%, #d18114 100%); + color: #fff; border-radius: 4px; + box-shadow: 0 1px rgba(255,255,255,0.08) inset; text-shadow: 0px -1px rgba(255,255,255,0.08); font-weight: normal; } -.btn-important:hover { -} + .btn-important:active { - background: linear-gradient(0deg, #E4992C 0%, #D18114 100%) #E4992C; - background: -webkit-linear-gradient(bottom, #E4992C 0%, #D18114 100%); + background: linear-gradient(0deg, #e4992c 0%, #d18114 100%) #e4992c; + background: -webkit-linear-gradient(bottom, #e4992c 0%, #d18114 100%); } .btn-attention { - background: #E95B57; - background: linear-gradient(to bottom, #E95B57, #BD362F); - background: -webkit-linear-gradient(top, #E95B57 0%, #BD362F 100%); + background: #e95b57; + background: linear-gradient(to bottom, #e95b57, #bd362f); + background: -webkit-linear-gradient(top, #e95b57 0%, #bd362f 100%); color: #fff; - border: 1px solid #C44742; + border: 1px solid #c44742; text-shadow: 0px -1px 0px #666; } + .btn-attention:hover { - background: linear-gradient(to bottom, #D14641, #BD362F); - background: -webkit-linear-gradient(top, #D14641 0%, #BD362F 100%); + background: linear-gradient(to bottom, #d14641, #bd362f); + background: -webkit-linear-gradient(top, #d14641 0%, #bd362f 100%); } + .btn-attention:active { - background: #BD362F; + background: #bd362f; box-shadow: none; } -.btn[type="reset"]{ + +.btn[type="reset"] { + background: linear-gradient(180deg, #222 0%, #171717 100%) #171717; + background: -webkit-linear-gradient(top, #222 0%, #171717 100%); color: #fff; - background:linear-gradient(180deg, #222 0%, #171717 100%) #171717; - background: -webkit-linear-gradient(top, #222 0%, #171717 100%); - box-shadow:0 -1px rgba(255,255,255,0.08) inset; + box-shadow: 0 -1px rgba(255,255,255,0.08) inset; } /*=== Navigation */ .nav-list .nav-header, @@ -258,55 +284,64 @@ a.btn { line-height: 2.5em; font-size: 0.9rem; } + .nav-list .item:hover { text-shadow: 0 0 2px rgba(255,255,255,0.28); - color:#fff; + color: #fff; } .nav-list .item.active { + margin: 0; background: linear-gradient(180deg, #222 0%, #171717 100%) repeat scroll 0% 0% #171717; background: -webkit-linear-gradient(180deg, #222 0%, #171717 100%); + box-shadow: -1px 2px 2px #171717, 0px 1px rgba(255, 255, 255, 0.08) inset; border-width: medium medium 1px; border-style: none none solid; border-color: -moz-use-text-color -moz-use-text-color #171717; - box-shadow: -1px 2px 2px #171717, 0px 1px rgba(255, 255, 255, 0.08) inset; - margin: 0; } + .nav-list .item.active a { - color: #D18114; + color: #d18114; } + .nav-list .disable { - color: #aaa; background: #fafafa; + color: #aaa; text-align: center; } + .nav-list .item > a { padding: 0 10px; - color:#ccc; + color: #ccc; } + .nav-list a:hover { text-decoration: none; } + .nav-list .item.empty a { color: #f39c12; } + .nav-list .item.active.empty a { + background: linear-gradient(180deg, #e4992c 0%, #d18114 100%) #e4992c; + background: -webkit-linear-gradient(180deg, #e4992c 0%, #d18114 100%); color: #fff; - background: linear-gradient(180deg, #E4992C 0%, #D18114 100%) #E4992C; - background: -webkit-linear-gradient(180deg, #E4992C 0%, #D18114 100%); } + .nav-list .item.error a { - color: #BD362F; + color: #bd362f; } + .nav-list .item.active.error a { + background: #bd362f; color: #fff; - background: #BD362F; } .nav-list .nav-header { padding: 0 10px; - color: #222; background: transparent; + color: #222; } .nav-list .nav-form { @@ -316,10 +351,11 @@ a.btn { .nav-head { margin: 0; - background: linear-gradient(0deg, #EDE7DE 0%, #FFF 100%) #EDE7DE; - background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #FFF 100%); + background: linear-gradient(0deg, #ede7de 0%, #fff 100%) #ede7de; + background: -webkit-linear-gradient(bottom, #ede7de 0%, #fff 100%); text-align: right; } + .nav-head .item { padding: 5px 10px; font-size: 0.9rem; @@ -331,6 +367,7 @@ a.btn { margin: 0; padding: 0; } + .horizontal-list .item { vertical-align: middle; } @@ -339,32 +376,32 @@ a.btn { .dropdown-menu { margin: 5px 0 0; padding: 5px 0; + background: #222; + font-size: 0.8rem; border: 1px solid #171717; border-radius: 4px; box-shadow: 0 0 3px #000; - font-size: 0.8rem; text-align: left; - background: #222; } + .dropdown-menu::after { - content: ""; - position: absolute; - top: -6px; - right: 13px; + background: #222; width: 10px; height: 10px; - background: #222; border-top: 1px solid #171717; border-left: 1px solid #171717; + content: ""; + position: absolute; + top: -6px; + right: 13px; z-index: -10; transform: rotate(45deg); - -moz-transform: rotate(45deg); - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); } + .dropdown-header { - display:none; + display: none; } + .dropdown-menu > .item > a, .dropdown-menu > .item > span, .dropdown-menu > .item > .as-link { @@ -373,27 +410,25 @@ a.btn { color: #ccc; font-size: 0.8rem; } + .dropdown-menu > .item > label { color: #ccc; } + .dropdown-menu > .item:hover { background: #171717; color: #fff; } + .dropdown-menu > .item[aria-checked="true"] > a::before { font-weight: bold; margin: 0 0 0 -14px; } + .dropdown-menu > .item:hover > a { color: #fff; text-decoration: none; } -.dropdown-menu .input select, -.dropdown-menu .input input { - margin: 0 auto 5px; - padding: 2px 5px; - border-radius: 3px; -} .separator { margin: 5px 0; @@ -406,35 +441,40 @@ a.btn { margin: 15px auto; padding: 10px 15px; background: #f4f4f4; + color: #aaa; + font-size: 0.9em; border: 1px solid #ccc; border-right: 1px solid #aaa; border-bottom: 1px solid #aaa; border-radius: 5px; - color: #aaa; text-shadow: 0 0 1px #eee; - font-size: 0.9em; } + .alert-head { font-size: 1.15em; } + .alert > a { color: inherit; text-decoration: underline; } + .alert-warn { background: #ffe; - border: 1px solid #eeb; color: #c95; + border: 1px solid #eeb; } + .alert-success { background: #dfd; - border: 1px solid #cec; color: #484; + border: 1px solid #cec; } + .alert-error { background: #fdd; - border: 1px solid #ecc; color: #844; + border: 1px solid #ecc; } /*=== Pagination */ @@ -444,14 +484,17 @@ a.btn { color: #333; font-size: 0.8em; } + .content .pagination { margin: 0; padding: 0; } + .pagination .item.pager-current { font-weight: bold; font-size: 1.5em; } + .pagination .item a { display: block; color: #333; @@ -459,12 +502,15 @@ a.btn { line-height: 3em; text-decoration: none; } + .pagination .item a:hover { background: #ddd; } + .pagination:first-child .item { border-bottom: 1px solid #aaa; } + .pagination:last-child .item { border-top: 1px solid #ddd; } @@ -477,22 +523,24 @@ a.btn { /*=== Boxes */ .box { - background: #EDE7DE; + background: #ede7de; border-radius: 4px; box-shadow: 0 1px #fff; } + .box .box-title { margin: 0; padding: 5px 10px; - background: linear-gradient(0deg, #EDE7DE 0%, #fff 100%) #171717; - background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #fff 100%); - box-shadow: 0px -1px #fff inset,0 -2px #ccc inset; + background: linear-gradient(0deg, #ede7de 0%, #fff 100%) #171717; + background: -webkit-linear-gradient(bottom, #ede7de 0%, #fff 100%); color: #888; - text-shadow: 0 1px #ccc; - border-radius: 4px 4px 0 0; font-size: 1.1rem; + border-radius: 4px 4px 0 0; + box-shadow: 0px -1px #fff inset,0 -2px #ccc inset; + text-shadow: 0 1px #ccc; font-weight: normal; } + .box .box-content { max-height: 260px; } @@ -507,6 +555,7 @@ a.btn { .box .box-content .item .configure { visibility: hidden; } + .box .box-title:hover .configure, .box .box-content .item:hover .configure { visibility: visible; @@ -516,64 +565,74 @@ a.btn { .tree { margin: 10px 0; } + .tree-folder-title { position: relative; padding: 0 10px; line-height: 2.5rem; font-size: 0.9rem; } + .tree-folder-title .title { background: inherit; color: #fff; } + .tree-folder-title .title:hover { text-decoration: none; } + .tree-folder.active .tree-folder-title { background: linear-gradient(180deg, #222 0%, #171717 100%) #171717; background: -webkit-linear-gradient(top, #222 0%, #171717 100%); + color: #fff; box-shadow: 0px 1px #171717, 0px 1px rgba(255, 255, 255, 0.08) inset; text-shadow: 0 0 2px rgba(255,255,255,0.28); - color: #fff; } + .tree-folder-items { - background: #171717; - padding: 8px 0; + padding: 8px 0; + background: #171717; box-shadow: 0 4px 4px #171717 inset, 0 1px rgba(255,255,255,0.08),0 -1px rgba(255,255,255,0.08); } + .tree-folder-items > .item { padding: 0 10px; line-height: 2.5rem; font-size: 0.8rem; } + .tree-folder-items > .item.active { + margin: 0px 8px; background: linear-gradient(180deg, #222 0%, #171717 100%) #171717; background: -webkit-linear-gradient(top, #222 0%, #171717 100%); border-radius: 4px; - margin: 0px 8px; box-shadow: 0px 1px #171717, 0px 1px rgba(255, 255, 255, 0.08) inset, 0 2px 2px #111; } + .tree-folder-items > .item > a { text-decoration: none; color: #fff; font-size: 0.92em; } -.tree-folder-items > .item.active > a { -} /*=== Scrollbar */ + @supports (scrollbar-width: thin) { #sidebar { scrollbar-color: rgba(255, 255, 255, 0.05) rgba(0, 0, 0, 0.0); } + #sidebar:hover { scrollbar-color: rgba(255, 255, 255, 0.3) rgba(0, 0, 0, 0.0); } } + @supports not (scrollbar-width: thin) { #sidebar::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.1); } + #sidebar:hover::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.3); } @@ -583,54 +642,64 @@ a.btn { /*===============*/ /*=== Header */ .header { + background: linear-gradient(0deg, #ede7de 0%, #fff 100%) #ede7de; + background: -webkit-linear-gradient(bottom, #ede7de 0%, #fff 100%); height: 55px; - background: linear-gradient(0deg, #EDE7DE 0%, #FFF 100%) #EDE7DE; - background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #FFF 100%); } + .header > .item { padding: 0; vertical-align: middle; text-align: center; } + .header > .item.title .logo { - height: 60px; width: 60px; + height: 60px; } -.header > .item.title{ + +.header > .item.title { width: 250px; } + .header > .item.title h1 { margin: 0.5em 0; } + .header > .item.title h1 a { - text-decoration: none; + color: #222; font-size: 28px; - color:#222; + text-decoration: none; text-shadow: 0 1px #fff; } + .header > .item.search input { width: 230px; } + .header .item.search input:focus { width: 350px; } /*=== Body */ #global { - background:#EDE7DE; + background: #ede7de; height: calc(100% - 60px); } + .aside { - border-radius: 0px 12px 0px 0px; - box-shadow: 0px -1px #FFF, 0 2px 2px #171717 inset; - border-top: 1px solid #CCC; background: #222; width: 235px; + border-top: 1px solid #ccc; + border-radius: 0px 12px 0px 0px; + box-shadow: 0px -1px #fff, 0 2px 2px #171717 inset; } + .aside.aside_feed { padding: 10px 0; text-align: center; } + .aside.aside_feed .tree { margin: 10px 0 50px; } @@ -643,6 +712,7 @@ a.btn { color: #fff; text-shadow: 0 1px rgba(255,255,255,0.08); } + .aside_feed .btn-important { border: none; } @@ -652,13 +722,16 @@ a.btn { .feed.item.empty > a { color: #e67e22; } + .feed.item.error, .feed.item.error > a { - color: #BD362F; + color: #bd362f; } + .aside_feed .tree-folder-items .dropdown-menu::after { left: 2px; } + .aside_feed .tree-folder-items .item .dropdown-target:target ~ .dropdown-toggle > .icon, .aside_feed .tree-folder-items .item:hover .dropdown-toggle > .icon, .aside_feed .tree-folder-items .item.active .dropdown-toggle > .icon { @@ -670,9 +743,11 @@ a.btn { padding: 10px 50px; font-size: 0.9em; } + .post form { margin: 10px 0; } + .post.content { max-width: 550px; } @@ -683,44 +758,53 @@ a.btn { padding: 14px 0px; text-shadow: 0 1px rgba(255,255,255,0.08); } + .prompt label { text-align: left; } + .prompt form { margin: 10px auto 20px auto; width: 180px; } + .prompt input { margin: 5px auto; width: 100%; } + .prompt p { margin: 20px 0; } -.prompt input#username,.prompt input#passwordPlain{ - border:solid 1px #ccc; + +.prompt input#username,.prompt input#passwordPlain { + background: #fff; + border: solid 1px #ccc; box-shadow: 0 4px -4px #ccc inset,0px 1px rgba(255, 255, 255, 0.08); - background:#fff; } -.prompt input#username:focus,.prompt input#passwordPlain:focus{ - border: solid 1px #E7AB34; - box-shadow: 0 0 3px #E7AB34; + +.prompt input#username:focus,.prompt input#passwordPlain:focus { + border: solid 1px #e7ab34; + box-shadow: 0 0 3px #e7ab34; } /*=== New article notification */ #new-article { - background: #0084CC; + background: #0084cc; text-align: center; font-size: 0.9em; } + #new-article:hover { - background: #0066CC; + background: #06c; } + #new-article > a { line-height: 3em; color: #fff; font-weight: bold; } + #new-article > a:hover { text-decoration: none; } @@ -728,112 +812,133 @@ a.btn { /*=== Day indication */ .day { padding: 0 10px; - font-style:italic; - line-height: 3em; - box-shadow: 0 1px #BDB7AE inset, 0 -1px rgba(255,255,255,0.28) inset; - background: linear-gradient(0deg, #EDE7DE 0%, #C2BCB3 100%) #EDE7DE; - background: -webkit-linear-gradient(bottom, #C2BCB3 0%, #FFF 100%); + background: linear-gradient(0deg, #ede7de 0%, #c2bcb3 100%) #ede7de; + background: -webkit-linear-gradient(bottom, #c2bcb3 0%, #fff 100%); color: #666; + box-shadow: 0 1px #bdb7ae inset, 0 -1px rgba(255,255,255,0.28) inset; + font-style: italic; + line-height: 3em; text-shadow: 0 1px rgba(255,255,255,0.28); text-align: center; } + #new-article + .day { border-top: none; } + .day .name { display: none; } /*=== Index menu */ .nav_menu { - background: #EDE7DE; + padding: 5px 0; + background: #ede7de; border-bottom: 1px solid #ccc; - box-shadow:0 -1px rgba(255, 255, 255, 0.28) inset; + box-shadow: 0 -1px rgba(255, 255, 255, 0.28) inset; text-align: center; - padding: 5px 0; } -#panel >.nav_menu{ - background: linear-gradient(0deg, #EDE7DE 0%, #FFF 100%) #EDE7DE; - background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #FFF 100%); + +#panel >.nav_menu { + background: linear-gradient(0deg, #ede7de 0%, #fff 100%) #ede7de; + background: -webkit-linear-gradient(bottom, #ede7de 0%, #fff 100%); } -#panel > .nav_menu > #nav_menu_read_all{ - background: linear-gradient(0deg, #EDE7DE 0%, #FFF 100%) #EDE7DE; - background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #FFF 100%); + +#panel > .nav_menu > #nav_menu_read_all { + background: linear-gradient(0deg, #ede7de 0%, #fff 100%) #ede7de; + background: -webkit-linear-gradient(bottom, #ede7de 0%, #fff 100%); + border: 1px solid #ccc; border-radius: 4px; - border: 1px solid #CCC; - box-shadow: 0px 1px #FFF; + box-shadow: 0px 1px #fff; } + #panel > .nav_menu > #nav_menu_read_all .dropdown > .btn.dropdown-toggle { - border-radius: 0 4px 4px 0; - border:none; + border: none; border-left: solid 1px #ccc; + border-radius: 0 4px 4px 0; } /*=== Feed articles */ .flux_content { - background: #FFF; - border-radius: 10px; + background: #fff; + border-radius: 10px; } + .flux { - background: #EDE7DE; + background: #ede7de; } + .flux:hover { - background: #F9F7F4; + background: #f9f7f4; } + .flux:not(.current):hover .item.title { - background: #F9F7F4; + background: #f9f7f4; } + .flux.current .flux .item.title a { - text-shadow:0 0 2px #ccc; + text-shadow: 0 0 2px #ccc; } + .flux.not_read:not(.current):hover .item.title { - opacity:0.85; + opacity: 0.85; } + .flux.favorite { - background: #FFF6DA; + background: #fff6da; } -.flux.favorite:not(.current):hover{ - background: #F9F7F4; + +.flux.favorite:not(.current):hover { + background: #f9f7f4; } + .flux.favorite:not(.current):hover .item.title { - background: #F9F7F4; + background: #f9f7f4; } + .flux.current { - background: linear-gradient(0deg, #EDE7DE 0%, #FFF 100%) #EDE7DE; - background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #FFF 100%); - box-shadow: 0 -1px #fff inset, 0 2px #ccc; - border-radius: 10px; margin: 3px 6px; + background: linear-gradient(0deg, #ede7de 0%, #fff 100%) #ede7de; + background: -webkit-linear-gradient(bottom, #ede7de 0%, #fff 100%); + border-radius: 10px; + box-shadow: 0 -1px #fff inset, 0 2px #ccc; } .flux .item.title { -opacity: 0.35; + opacity: 0.35; } + .flux.favorite .item.title { -opacity: 1; + opacity: 1; } + .flux.not_read .item.title { -opacity: 1; + opacity: 1; } + .flux.current .item.title a { color: #0f0f0f; } + .flux .item.title a { color: #333; } .flux_header { - border-top: 1px solid #ddd; font-size: 0.8rem; - cursor: pointer; + border-top: 1px solid #ddd; box-shadow: 0 -1px rgba(255,255,255,0.28) inset; + cursor: pointer; } + .flux_header .title { font-size: 0.9rem; } + .flux .website .favicon { padding: 5px; } + .flux .date { color: #666; font-size: 0.7rem; @@ -848,14 +953,15 @@ opacity: 1; .content { padding: 20px 10px; } + .content > h1.title > a { color: #000; } .content hr { margin: 30px 10px; - height: 1px; background: #ddd; + height: 1px; border: 0; box-shadow: 0 2px 5px #ccc; } @@ -869,13 +975,15 @@ opacity: 1; font-size: 0.9rem; border-radius: 3px; } + .content code { padding: 2px 5px; - color: #dd1144; background: #fafafa; + color: #d14; border: 1px solid #eee; border-radius: 3px; } + .content pre code { background: transparent; color: #fff; @@ -883,14 +991,15 @@ opacity: 1; } .content blockquote { - display: block; margin: 0; padding: 5px 20px; - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; background: #fafafa; + display: block; color: #333; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; } + .content blockquote p { margin: 0; } @@ -898,49 +1007,55 @@ opacity: 1; /*=== Notification and actualize notification */ .notification { padding: 0 0 0 5px; - text-align: center; - background:#222; + background: #222; + color: #fff; + font-size: 0.9em; border: none; border-radius: 0 0 12px 12px; box-shadow: 0px 0px 4px rgba(0,0,0,0.45), 0 -1px rgba(255,255,255,0.08) inset, 0 2px 2px #171717 inset; - color:#fff; + text-align: center; font-weight: bold; - font-size: 0.9em; line-height: 3em; - position:absolute; - top:0; + position: absolute; + top: 0; z-index: 10; vertical-align: middle; } + .notification.good { color: #c95; } + .notification.bad { background: #fdd; color: #844; } + .notification a.close { padding: 0 15px; line-height: 3em; } + .notification#actualizeProgress { line-height: 2em; } /*=== "Load more" part */ #bigMarkAsRead { + background: #ede7de; + color: #666; + box-shadow: 0 1px rgba(255,255,255,0.28)inset; text-align: center; text-decoration: none; text-shadow: 0 -1px 0 #aaa; - color: #666; - background: #EDE7DE; - box-shadow: 0 1px rgba(255,255,255,0.28)inset; } + #bigMarkAsRead:hover { + background: #ede7de; + background: radial-gradient(circle at 50% -25% , #ccc 0%, #ede7de 50%); color: #000; - background: #EDE7DE; - background: radial-gradient(circle at 50% -25% , #ccc 0%, #EDE7DE 50%); } + #bigMarkAsRead:hover .bigTick { text-shadow: 0 0 10px #666; } @@ -949,69 +1064,71 @@ opacity: 1; #nav_entries { background: linear-gradient(180deg, #222 0%, #171717 100%) #222; background: -webkit-linear-gradient(top, #222 0%, #171717 100%); + width: 235px; border-top: 1px solid #171717; + box-shadow: 0 1px rgba(255,255,255,0.08) inset, 0 -2px 2px #171717; text-align: center; line-height: 3em; table-layout: fixed; - box-shadow: 0 1px rgba(255,255,255,0.08) inset, 0 -2px 2px #171717; - width:235px; } /*=== READER VIEW */ /*================*/ #stream.reader .flux { padding: 0 0 50px; - border: none; background: #f0f0f0; color: #333; + border: none; } + #stream.reader .flux .author { margin: 0 0 10px; - font-size: 90%; color: #666; + font-size: 90%; } /*=== GLOBAL VIEW */ /*================*/ #stream.global { padding: 24px 0; - box-shadow: 0px 8px 8px #C2BCB3 inset; + box-shadow: 0px 8px 8px #c2bcb3 inset; } .box.category .box-title { - background: linear-gradient(0deg, #EDE7DE 0%, #fff 100%) #171717; - background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #fff 100%); - box-shadow: 0px -1px #fff inset,0 -2px #ccc inset; + background: linear-gradient(0deg, #ede7de 0%, #fff 100%) #171717; + background: -webkit-linear-gradient(bottom, #ede7de 0%, #fff 100%); + font-size: 1.2rem; border-radius: none; + box-shadow: 0px -1px #fff inset,0 -2px #ccc inset; line-height: 2em; - font-size: 1.2rem; - text-shadow:0 1px #ccc; + text-shadow: 0 1px #ccc; } + .box.category .box-title .title { font-weight: normal; text-decoration: none; text-align: left; color: #888; } -.box.category:not([data-unread="0"]) .box-title { -} -.box.category:not([data-unread="0"]) .box-title:active { -} + .box.category:not([data-unread="0"]) .box-title .title { color: #222; font-weight: bold; } + .box.category .title:not([data-unread="0"])::after { + background: none; + border: 0; position: absolute; top: 5px; right: 10px; - border: 0; - background: none; font-weight: bold; } + .box.category .item.feed { padding: 2px 10px; font-size: 0.8rem; } + .box.category .item.feed:not(.empty):not(.error) .item-title { color: #222; } @@ -1019,18 +1136,20 @@ opacity: 1; /*=== PANEL */ /*===========*/ #panel { - box-shadow: 0px 0px 4px #000; + background: #ede7de; border-radius: 8px; - background:#EDE7DE; + box-shadow: 0px 0px 4px #000; } /*=== DIVERS */ /*===========*/ .aside.aside_feed .nav-form input,.aside.aside_feed .nav-form select { width: 130px; } + .aside.aside_feed .nav-form .dropdown .dropdown-menu { right: -20px; } + .aside.aside_feed .nav-form .dropdown .dropdown-menu::after { right: 33px; } @@ -1046,21 +1165,24 @@ opacity: 1; .stat tr { border: none; } + .stat > table td, .stat > table th { - border-bottom: 1px solid #ccc; background: rgba(255,255,255,0.38); + border-bottom: 1px solid #ccc; box-shadow: 0 1px #fff; } .stat > .horizontal-list { margin: 0 0 5px; } + .stat > .horizontal-list .item { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } + .stat > .horizontal-list .item:first-child { width: 250px; } @@ -1072,50 +1194,60 @@ opacity: 1; border-radius: 5px; overflow: hidden; } + .log { padding: 5px 10px; background: #fafafa; color: #333; font-size: 0.8rem; } + .log+.log { border-top: 1px solid #aaa; } + .log .date { display: block; font-weight: bold; } + .log.error { background: #fdd; color: #844; } + .log.warning { background: #ffe; color: #c95; } + .log.notice { background: #f4f4f4; color: #aaa; } + .log.debug { background: #333; color: #eee; } #slider.active { - box-shadow: -4px 0 4px rgba(15, 15, 15, 0.55); - background: #F8F8F8; + background: #f8f8f8; + box-shadow: -4px 0 4px rgba(15, 15, 15, 0.55); } + #close-slider.active { - background: rgba(15, 15, 15, 0.35); + background: rgba(15, 15, 15, 0.35); } /*=== MOBILE */ /*===========*/ + @media screen and (max-width: 840px) { .header { display: table; } + .nav-login { display: none; } @@ -1125,28 +1257,27 @@ opacity: 1; border-top: none; box-shadow: 3px 0 3px #000; transition: width 200ms linear; - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; } + .aside:target { width: 235px; } + .aside .toggle_aside, #panel .close { + background: #171717; display: block; width: 100%; height: 40px; + border-radius: 0 8px 0 8px; + box-shadow: 0 1px rgba(255,255,255,0.08); line-height: 40px; text-align: center; - background: #171717; - box-shadow: 0 1px rgba(255,255,255,0.08); - border-radius: 0 8px 0 8px; } + .aside .btn-important { - display: inline-block; margin: 20px 0 0; + display: inline-block; } .aside.aside_feed { @@ -1156,20 +1287,24 @@ opacity: 1; .nav_menu .btn { margin: 5px 10px; } + .nav_menu .stick { margin: 0 10px; } + .nav_menu .stick .btn { margin: 5px 0; } + .nav_menu .search { - display: inline-block; - max-width: 97%; + display: none; } + .nav_menu .search input { max-width: 97%; width: 90px; } + .nav_menu .search input:focus { width: 400px; } @@ -1183,19 +1318,18 @@ opacity: 1; } .notification a.close { + background: transparent; display: block; left: 0; - background: transparent; } + .notification a.close:hover { opacity: 0.5; } + .notification a.close .icon { display: none; } - .nav_menu .search { - display: none; - } #nav_entries { width: 100%; @@ -1203,16 +1337,19 @@ opacity: 1; } @media (max-width: 700px) { - .header{ + .header { display: none; } + .nav-login { display: inline-block; width: 100%; } + .nav_menu .search { display: inline-block; } + .aside .btn-important { display: none; } diff --git a/p/themes/Swage/icons/read.svg b/p/themes/Swage/icons/read.svg index 86f3e60f3..4ab272b39 100644 --- a/p/themes/Swage/icons/read.svg +++ b/p/themes/Swage/icons/read.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/p/themes/Swage/icons/view-reader.svg b/p/themes/Swage/icons/view-reader.svg index f8f9e7af5..afb82adf7 100644 --- a/p/themes/Swage/icons/view-reader.svg +++ b/p/themes/Swage/icons/view-reader.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/p/themes/Swage/swage.css b/p/themes/Swage/swage.css index a861bca5c..6528d2305 100644 --- a/p/themes/Swage/swage.css +++ b/p/themes/Swage/swage.css @@ -1,1129 +1,909 @@ -textarea, input, select { -min-height: 25px; -margin-top: 4px; -line-height: 25px; -vertical-align: middle; -background: #FCFCFC; -border: none; -padding-left: 5px; -} - -input:invalid, select:invalid { -color: #B0425B; -border-color: #B0425B; -box-shadow: none; -} +textarea, input, +select { + min-height: 25px; + margin-top: 4px; + line-height: 25px; + vertical-align: middle; + background: #fcfcfc; + border: none; + padding-left: 5px; } + +input:invalid, +select:invalid { + color: #b0425b; + border-color: #b0425b; + box-shadow: none; } .nav-list .nav-header, .nav-list .item { -height: 2.5em; -line-height: 2.5em; -font-size: 0.9rem; -} - -.dropdown-menu > .item, .dropdown-menu > .item > a, .dropdown-menu > .item > span, .dropdown-menu > .item > as-link, .dropdown-menu > .item button { -padding: 0 22px; -line-height: 2.5em; -font-size: 0.8rem; -color: #FCFCFC; -} + height: 2.5em; + line-height: 2.5em; + font-size: 0.9rem; } + +.dropdown-menu > .item, .dropdown-menu > .item > a, +.dropdown-menu > .item > span, +.dropdown-menu > .item > .as-link, +.dropdown-menu > .item button { + padding: 0 22px; + color: #fcfcfc; + font-size: 0.8rem; + line-height: 2.5em; } .form-group::after, .flux::after { -content: ""; -display: block; -clear: both; -} + content: ""; + display: block; + clear: both; } .stick.configure-feeds, .header > .item.title, .aside, #new-article, .notification, #nav_entries { -width: 231px; -} + width: 231px; } -html, body { -height: 100%; -font-family: Helvetica, Arial, sans-serif; -} +html, +body { + height: 100%; + font-family: Helvetica, Arial, sans-serif; } a { -color: #00488b; -outline: none; -} -a.btn { -min-height: 25px; -line-height: 25px; -text-decoration: none; -} -a.btn:hover { -background: #00488b; -} -a#btn-subscription { -width: 76%; -} -a#btn-importExport { -width: 5%; -} + color: #00488b; + outline: none; } + a.btn { + min-height: 25px; + line-height: 25px; + text-decoration: none; } + a.btn:hover { + background: #00488b; } + a#btn-subscription { + width: 76%; } + a#btn-importExport { + width: 5%; } img.icon:hover { -background: none; -} + background: none; } div#stream { -margin-top: 35px; -} + margin-top: 35px; } sup { -top: -0.3em; -} + top: -0.3em; } legend { -display: inline-block; -width: auto; -margin: 20px 0 5px; -padding: 5px 20px; -font-size: 1.4em; -clear: both; -background: #e3e3e3; -} + margin: 20px 0 5px; + padding: 5px 20px; + background: #e3e3e3; + display: inline-block; + width: auto; + font-size: 1.4em; + clear: both; } label { -min-height: 25px; -} + min-height: 25px; } textarea { -width: 360px; -height: 100px; -background: #e3e3e3; -} -textarea:focus { -border-color: #00488b; -} - -input:focus, select:focus { -border-color: #00488b; -} -input:disabled, select:disabled { -background: #FCFCFC; -} + background: #e3e3e3; + width: 360px; + height: 100px; } + textarea:focus { + border-color: #00488b; } + +input:focus, +select:focus { + border-color: #00488b; } +input:disabled, +select:disabled { + background: #fcfcfc; } select { -background: #e3e3e3; -} + background: #e3e3e3; } input.extend { -transition: width 200ms linear; -} + transition: width 200ms linear; } option { -padding: 0 .5em; -} + padding: 0 .5em; } table { -border-collapse: collapse; -} + border-collapse: collapse; } -tr, td, th { -padding: 0.5em; -border: 1px solid #e3e3e3; -} +tr, +td, +th { + padding: 0.5em; + border: 1px solid #e3e3e3; } th { -background: #FCFCFC; -} + background: #fcfcfc; } -form td, form th { -font-weight: normal; -text-align: center; -} +form td, +form th { + font-weight: normal; + text-align: center; } .category .title.error::before { -display: inline-block; -padding-right: 7px; -width: 16px; -content: url(../Swage/icons/error.svg); -} + display: inline-block; + padding-right: 7px; + width: 16px; + content: url(../Swage/icons/error.svg); } .form-group { -padding: 5px; -border: 1px solid transparent; -} -.form-group:hover { -background: #FCFCFC; -border: 1px solid #FCFCFC; -} -.form-group.form-actions { -margin: 15px 0 25px; -padding: 5px 0; -background: #e3e3e3; -border-top: 3px solid #e3e3e3; -} -.form-group.form-actions .btn { -margin: 0 10px; -} -.form-group .group-name { -padding: 10px 0; -text-align: right; -} -.form-group .group-controls { -min-height: 25px; -padding: 5px 0; -} -.form-group .group-controls .control { -line-height: 2.0em; -} -.form-group table { -margin: 10px 0 0 220px; -} + padding: 5px; + border: 1px solid transparent; } + .form-group:hover { + background: #fcfcfc; + border: 1px solid #fcfcfc; } + .form-group.form-actions { + margin: 15px 0 25px; + padding: 5px 0; + background: #e3e3e3; + border-top: 3px solid #e3e3e3; } + .form-group.form-actions .btn { + margin: 0 10px; } + .form-group .group-name { + padding: 10px 0; + text-align: right; } + .form-group .group-controls { + min-height: 25px; + padding: 5px 0; } + .form-group .group-controls .control { + line-height: 2.0em; } + .form-group table { + margin: 10px 0 0 220px; } .stick { -vertical-align: middle; -font-size: 0; -} + vertical-align: middle; + font-size: 0; } .btn { -display: inline-block; -min-height: 35px; -min-width: 15px; -margin: 0; -padding: 5px 10px; -font-size: 0.9rem; -vertical-align: middle; -cursor: pointer; -overflow: hidden; -background: #0062be; -border: none; -color: #FCFCFC; -} -.btn.active, .btn :active, .btn :hover { -background: #00488b; -text-decoration: none; -} + margin: 0; + padding: 5px 10px; + background: #0062be; + display: inline-block; + color: #fcfcfc; + font-size: 0.9rem; + border: none; + min-height: 35px; + min-width: 15px; + vertical-align: middle; + cursor: pointer; + overflow: hidden; } + .btn.active, + .btn :active, + .btn :hover { + background: #00488b; + text-decoration: none; } .btn-important, .btn-attention { -font-weight: normal; -background: #FA8052; -color: #FCFCFC; -} -.btn-important:hover, .btn-important :active, .btn-attention:hover, .btn-attention :active { -background: #f95c20 !important; -} + font-weight: normal; + background: #fa8052; + color: #fcfcfc; } + .btn-important:hover, + .btn-important :active, .btn-attention:hover, + .btn-attention :active { + background: #f95c20 !important; } .nav-list .nav-header { -padding: 0 10px; -font-weight: bold; -background: #22303d; -color: #FCFCFC; -cursor: default; -} + padding: 0 10px; + font-weight: bold; + background: #22303d; + color: #fcfcfc; + cursor: default; } .nav-list .item:hover, .nav-list .item.active { -background: #00488b; -color: #FCFCFC; -} -.nav-list .item:hover a, .nav-list .item.active a { -color: #FCFCFC; -} -.nav-list .item:hover.empty a, .nav-list .item:hover .error a, .nav-list .item.active.empty a, .nav-list .item.active .error a { -color: #FCFCFC; -} -.nav-list .item:hover.empty a, .nav-list .item.active.empty a { -background: #FA8052; -} -.nav-list .item:hover.error a, .nav-list .item.active.error a { -background: #c46178; -} + background: #00488b; + color: #fcfcfc; } + .nav-list .item:hover a, .nav-list .item.active a { + color: #fcfcfc; } + .nav-list .item:hover.empty a, + .nav-list .item:hover .error a, .nav-list .item.active.empty a, + .nav-list .item.active .error a { + color: #fcfcfc; } + .nav-list .item:hover.empty a, .nav-list .item.active.empty a { + background: #fa8052; } + .nav-list .item:hover.error a, .nav-list .item.active.error a { + background: #c46178; } .nav-list .item > a { -padding: 0 10px; -} + padding: 0 10px; } .nav-list .item.empty a { -color: #FA8052; -} + color: #fa8052; } .nav-list .item.error a { -color: #c46178; -} + color: #c46178; } .nav-list .disable { -text-align: center; -background: #FCFCFC; -color: #969696; -} + text-align: center; + background: #fcfcfc; + color: #969696; } .nav-list .nav-form { -padding: 3px; -text-align: center; -} + padding: 3px; + text-align: center; } .nav-list a:hover { -text-decoration: none; -} + text-decoration: none; } .nav-head { -margin: 0; -text-align: right; -background: #22303d; -color: #FCFCFC; -} -.nav-head a { -color: #FCFCFC; -} -.nav-head .item { -padding: 5px 10px; -font-size: 0.9rem; -line-height: 1.5rem; -} + margin: 0; + text-align: right; + background: #22303d; + color: #fcfcfc; } + .nav-head a { + color: #fcfcfc; } + .nav-head .item { + padding: 5px 10px; + font-size: 0.9rem; + line-height: 1.5rem; } .horizontal-list { -margin: 0; -padding: 0; -} -.horizontal-list .item { -vertical-align: middle; -} + margin: 0; + padding: 0; } + .horizontal-list .item { + vertical-align: middle; } .dropdown-menu { -padding: 5px 0; -font-size: 0.8rem; -text-align: left; -border: none; -background-color: #00488b; -} -.dropdown-menu .dropdown-header { -cursor: default; -} -.dropdown-menu > .item { -padding: 0; -margin-left: 10px; -} -.dropdown-menu > .item > a { -min-width: initial; -white-space: nowrap; -} -.dropdown-menu > .item:hover { -background: #0062be; -color: #FCFCFC; -} -.dropdown-menu > .item:hover > a { -text-decoration: none; -color: #FCFCFC; -} -.dropdown-menu > .item[aria-checked="true"] > a::before { -font-weight: bold; -margin: 0 0 0 -14px; -} -.dropdown-menu .input select, .dropdown-menu .input input { -margin: 0 auto 5px; -padding: 2px 5px; -} + padding: 5px 0; + font-size: 0.8rem; + text-align: left; + border: none; + background-color: #00488b; } + .dropdown-menu .dropdown-header { + cursor: default; } + .dropdown-menu > .item { + padding: 0; + margin-left: 10px; } + .dropdown-menu > .item > a { + min-width: initial; + white-space: nowrap; } + .dropdown-menu > .item:hover { + background: #0062be; + color: #fcfcfc; } + .dropdown-menu > .item:hover > a { + text-decoration: none; + color: #fcfcfc; } + .dropdown-menu > .item[aria-checked="true"] > a::before { + font-weight: bold; + margin: 0 0 0 -14px; } + .dropdown-menu .input select, + .dropdown-menu .input input { + margin: 0 auto 5px; + padding: 2px 5px; } .dropdown-header { -padding: 0 5px 5px; -font-weight: bold; -text-align: left; -color: #FCFCFC; -} + padding: 0 5px 5px; + font-weight: bold; + text-align: left; + color: #fcfcfc; } .separator { -margin: 5px 0; -border-bottom: 1px solid #e3e3e3; -cursor: default; -} + margin: 5px 0; + border-bottom: 1px solid #e3e3e3; + cursor: default; } .alert { -margin: 5px auto; -padding: 10px 15px; -font-size: 0.9em; -background: #FCFCFC; -border: none; -color: #969696; -text-shadow: 0 0 1px #FCFCFC; -} -.alert > a { -text-decoration: underline; -color: inherit; -} + margin: 5px auto; + padding: 10px 15px; + background: #fcfcfc; + color: #969696; + font-size: 0.9em; + border: none; + text-shadow: 0 0 1px #fcfcfc; } + .alert > a { + color: inherit; + text-decoration: underline; } .alert-head { -font-size: 1.15em; -} + font-size: 1.15em; } -.alert-warn, .alert-success, .alert-error { -border: none; -} +.alert-warn, +.alert-success, +.alert-error { + border: none; } .alert-warn { -background: #FCFCFC; -color: #FA8052; -} + background: #fcfcfc; + color: #fa8052; } .alert-success { -background: #FCFCFC; -color: #5EAABF; -} + background: #fcfcfc; + color: #5eaabf; } .alert-error { -background: #FCFCFC; -color: #B0425B; -} + background: #fcfcfc; + color: #b0425b; } .pagination { -text-align: center; -font-size: 0.8em; -background: #e3e3e3; -color: #181621; -} -.pagination .item.pager-current { -font-weight: bold; -font-size: 1.5em; -background: #22303d; -color: #e3e3e3; -} -.pagination .item a { -display: block; -font-style: italic; -line-height: 3em; -text-decoration: none; -color: #181621; -} -.pagination .item a:hover { -background: #22303d; -color: #e3e3e3; -} -.pagination .loading, .pagination a:hover.loading { -font-size: 0; -background: url(loader.gif) center center no-repeat #22303d; -} + background: #e3e3e3; + color: #181621; + font-size: 0.8em; + text-align: center; } + .pagination .item.pager-current { + background: #22303d; + color: #e3e3e3; + font-size: 1.5em; + font-weight: bold; } + .pagination .item a { + display: block; + color: #181621; + font-style: italic; + line-height: 3em; + text-decoration: none; } + .pagination .item a:hover { + background: #22303d; + color: #e3e3e3; } + .pagination .loading, + .pagination a:hover.loading { + background: url(loader.gif) center center no-repeat #22303d; + font-size: 0; } .content { -padding: 20px 10px; -} -.content .pagination { -margin: 0; -padding: 0; -} -.content hr { -margin: 30px 10px; -height: 1px; -background: #e3e3e3; -border: 0; -box-shadow: 0 2px 5px #e3e3e3; -} -.content pre { -margin: 10px auto; -padding: 10px 20px; -overflow: auto; -background: #181621; -color: #FCFCFC; -font-size: 0.9rem; -} -.content pre code { -background: transparent; -color: #FCFCFC; -border: none; -} -.content code { -padding: 2px 5px; -color: #B0425B; -background: #FCFCFC; -border: 1px solid #FCFCFC; -} -.content blockquote { -display: block; -margin: 0; -padding: 5px 20px; -border-top: 1px solid #e3e3e3; -border-bottom: 1px solid #e3e3e3; -background: #FCFCFC; -color: #969696; -} -.content blockquote p { -margin: 0; -} -.content > h1.title > a { -color: #181621; -} + padding: 20px 10px; } + .content .pagination { + margin: 0; + padding: 0; } + .content hr { + margin: 30px 10px; + background: #e3e3e3; + height: 1px; + border: 0; + box-shadow: 0 2px 5px #e3e3e3; } + .content pre { + margin: 10px auto; + padding: 10px 20px; + overflow: auto; + background: #181621; + color: #fcfcfc; + font-size: 0.9rem; } + .content pre code { + background: transparent; + color: #fcfcfc; + border: none; } + .content code { + padding: 2px 5px; + background: #fcfcfc; + color: #b0425b; + border: 1px solid #fcfcfc; } + .content blockquote { + margin: 0; + padding: 5px 20px; + background: #fcfcfc; + display: block; + color: #969696; + border-top: 1px solid #e3e3e3; + border-bottom: 1px solid #e3e3e3; } + .content blockquote p { + margin: 0; } + .content > h1.title > a { + color: #181621; } .box { -border: 1px solid #e3e3e3; -} -.box .box-title { -margin: 0; -padding: 5px 10px; -background: #e3e3e3; -color: #969696; -border-bottom: 1px solid #e3e3e3; -} -.box .box-content { -max-height: 260px; -} -.box .box-content .item { -padding: 0 10px; -font-size: 0.9rem; -line-height: 2.5em; -} -.box .box-content .item .configure { -visibility: hidden; -} -.box .box-content .item .configure .icon { -vertical-align: middle; -background-color: #e3e3e3; -} -.box .box-content .item:hover .configure { -visibility: visible; -} -.box.category .box-title .title { -font-weight: normal; -text-decoration: none; -text-align: left; -} -.box.category:not([data-unread="0"]) .box-title { -background: #0062be; -} -.box.category:not([data-unread="0"]) .box-title:active { -background: #00488b; -} -.box.category:not([data-unread="0"]) .box-title .title { -font-weight: bold; -color: #FCFCFC; -} -.box.category .title:not([data-unread="0"])::after { -position: absolute; -top: 5px; -right: 10px; -border: 0; -background: none; -font-weight: bold; -box-shadow: none; -text-shadow: none; -} -.box.category .item.feed { -padding: 2px 10px; -font-size: 0.8rem; -} + border: 1px solid #e3e3e3; } + .box .box-title { + margin: 0; + padding: 5px 10px; + background: #e3e3e3; + color: #969696; + border-bottom: 1px solid #e3e3e3; } + .box .box-content { + max-height: 260px; } + .box .box-content .item { + padding: 0 10px; + font-size: 0.9rem; + line-height: 2.5em; } + .box .box-content .item .configure { + visibility: hidden; } + .box .box-content .item .configure .icon { + vertical-align: middle; + background-color: #e3e3e3; } + .box .box-content .item:hover .configure { + visibility: visible; } + .box.category .box-title .title { + font-weight: normal; + text-decoration: none; + text-align: left; } + .box.category:not([data-unread="0"]) .box-title { + background: #0062be; } + .box.category:not([data-unread="0"]) .box-title:active { + background: #00488b; } + .box.category:not([data-unread="0"]) .box-title .title { + font-weight: bold; + color: #fcfcfc; } + .box.category .title:not([data-unread="0"])::after { + background: none; + border: 0; + box-shadow: none; + position: absolute; + top: 5px; + right: 10px; + font-weight: bold; + text-shadow: none; } + .box.category .item.feed { + padding: 2px 10px; + font-size: 0.8rem; } .tree { -margin: 10px 0; -} + margin: 10px 0; } .tree-folder-title { -position: relative; -padding: 0 10px; -background: #22303d; -line-height: 2.3rem; -font-size: 1rem; -height: 35px; -} -.tree-folder-title .title { -background: inherit; -color: #FCFCFC; -} -.tree-folder-title .title:hover { -text-decoration: none; -} + padding: 0 10px; + background: #22303d; + height: 35px; + font-size: 1rem; + position: relative; + line-height: 2.3rem; } + .tree-folder-title .title { + background: inherit; + color: #fcfcfc; } + .tree-folder-title .title:hover { + text-decoration: none; } .tree-folder-items { -background: #22303d; -} -.tree-folder-items > .item { -padding: 0 10px; -line-height: 2.5rem; -font-size: 0.8rem; -} -.tree-folder-items > .item.active { -background: #00488b; -} -.tree-folder-items > .item > a { -text-decoration: none; -color: #FCFCFC; -} + background: #22303d; } + .tree-folder-items > .item { + padding: 0 10px; + line-height: 2.5rem; + font-size: 0.8rem; } + .tree-folder-items > .item.active { + background: #00488b; } + .tree-folder-items > .item > a { + text-decoration: none; + color: #fcfcfc; } @supports (scrollbar-width: thin) { - #sidebar { -scrollbar-color: rgba(255, 255, 255, 0.05) rgba(0, 0, 0, 0.0); -} -#sidebar:hover { -scrollbar-color: rgba(255, 255, 255, 0.3) rgba(0, 0, 0, 0.0); -} -} + #sidebar { + scrollbar-color: rgba(255, 255, 255, 0.05) rgba(0, 0, 0, 0); } + #sidebar:hover { + scrollbar-color: rgba(255, 255, 255, 0.3) rgba(0, 0, 0, 0); } } @supports not (scrollbar-width: thin) { -#sidebar::-webkit-scrollbar-thumb { -background: rgba(255, 255, 255, 0.1); -} -#sidebar:hover::-webkit-scrollbar-thumb { -background: rgba(255, 255, 255, 0.3); -} -} + #sidebar::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.1); } + #sidebar:hover::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.3); } } .header > .item { -vertical-align: middle; -} -.header > .item.title { -position: absolute; -} -.header > .item.title h1 { -margin: 0; -display: block; -} -.header > .item.title h1 a { -text-decoration: none; -color: #FCFCFC; -} -.header > .item.title .logo { -display: inline-block; -height: 26px; -vertical-align: top; -position: relative; -top: 5px; -} -.header > .item.search input { -width: 230px; -} + vertical-align: middle; } + .header > .item.title { + position: absolute; } + .header > .item.title h1 { + margin: 0; + display: block; } + .header > .item.title h1 a { + text-decoration: none; + color: #fcfcfc; } + .header > .item.title .logo { + display: inline-block; + height: 26px; + vertical-align: top; + position: relative; + top: 5px; } + .header > .item.search input { + width: 230px; } .header .item.search input:focus { -width: 350px; -} + width: 350px; } .header .item.search { -display: none; -} + display: none; } .header .item.configure { -position: fixed; -right: 0px; -z-index: 1000; -width: 35px; -} + position: fixed; + right: 0px; + z-index: 1000; + width: 35px; } .header h1 { -text-align: center; -font-size: 1.5em; -} + text-align: center; + font-size: 1.5em; } .aside { -background: #22303d; -padding: 35px 0; -} -.aside.aside_feed .tree { -margin: 0 0 50px; -} -.aside.aside_feed .nav-form input, .aside.aside_feed .nav-form select { -width: 140px; -} -.aside.aside_feed .nav-form .dropdown .dropdown-menu { -right: -20px; -} -.aside.aside_feed .nav-form .dropdown .dropdown-menu::after { -right: 33px; -} + padding: 35px 0; + background: #22303d; } + .aside.aside_feed .tree { + margin: 0 0 50px; } + .aside.aside_feed .nav-form input, + .aside.aside_feed .nav-form select { + width: 140px; } + .aside.aside_feed .nav-form .dropdown .dropdown-menu { + right: -20px; } + .aside.aside_feed .nav-form .dropdown .dropdown-menu::after { + right: 33px; } .aside_feed .tree-folder-title > .title:not([data-unread="0"])::after { -position: absolute; -right: 0; -margin: 6px 0; -padding: 0 10px; -font-size: 0.9rem; -line-height: 1.5rem; -background: inherit; -} + margin: 6px 0; + padding: 0 10px; + background: inherit; + font-size: 0.9rem; + position: absolute; + right: 0; + line-height: 1.5rem; } .aside_feed .tree-folder-items .dropdown-menu::after { -left: 2px; -} + left: 2px; } .post { -padding: 10px 50px; -font-size: 0.9em; -} -.post input { -background: #e3e3e3; -} -.post input.long { -height: 33px; -margin-top: 0px; -} -.post form { -margin: 10px 0; -} -.post.content { -max-width: 550px; -} + padding: 10px 50px; + font-size: 0.9em; } + .post input { + background: #e3e3e3; } + .post input.long { + height: 33px; + margin-top: 0px; } + .post form { + margin: 10px 0; } + .post.content { + max-width: 550px; } .prompt { -text-align: center; -} -.prompt label { -text-align: left; -} -.prompt form { -margin: 10px auto 20px auto; -width: 200px; -} -.prompt input { -margin: 5px auto; -width: 100%; -} -.prompt p { -margin: 20px 0; -} + text-align: center; } + .prompt label { + text-align: left; } + .prompt form { + margin: 10px auto 20px auto; + width: 200px; } + .prompt input { + margin: 5px auto; + width: 100%; } + .prompt p { + margin: 20px 0; } #new-article { -text-align: center; -font-size: 1em; -background: #0062be; -position: fixed; -bottom: 48px; -z-index: 900; -left: 0; -line-height: 1.5em; -} -#new-article:hover { -background: #00488b; -} -#new-article > a { -line-height: 1.5em; -font-weight: bold; -color: #FCFCFC; -} -#new-article > a:hover { -text-decoration: none; -} + background: #0062be; + font-size: 1em; + text-align: center; + position: fixed; + bottom: 48px; + z-index: 900; + left: 0; + line-height: 1.5em; } + #new-article:hover { + background: #00488b; } + #new-article > a { + line-height: 1.5em; + font-weight: bold; + color: #fcfcfc; } + #new-article > a:hover { + text-decoration: none; } .day { -padding: 0 10px; -font-weight: bold; -line-height: 3em; -text-align: center; -} -.day .name { -display: none; -} + padding: 0 10px; + font-weight: bold; + line-height: 3em; + text-align: center; } + .day .name { + display: none; } .nav a { -color: #FCFCFC; -} + color: #fcfcfc; } .nav_menu { -font-size: 0; -background-color: #0062be; -position: fixed; -width: 100%; -z-index: 900; -} -.nav_menu .item.search { -display: inline-block; -position: fixed; -right: 40px; -} + width: 100%; + font-size: 0; + background-color: #0062be; + position: fixed; + z-index: 900; } + .nav_menu .item.search { + display: inline-block; + position: fixed; + right: 40px; } .flux { -padding-right: 10px; -background: #FCFCFC; -} -.flux::after { -margin: 0 auto; -width: 90%; -border-top: 1px solid #e3e3e3; -} -.flux:hover, .flux .current { -background: #FFFFFF; -} -.flux:hover:not(.current):hover .item.title, .flux .current:not(.current):hover .item.title { -background: #FFFFFF; -} -.flux.not_read { -background: #FFF3ED; -} -.flux.not_read:not(.current):hover .item.title { -background: #FFF3ED; -} -.flux.favorite { -background: #FFF6DA; -} -.flux.favorite:not(.current):hover .item.title { -background: #FFF6DA; -} -.flux .date { -font-size: 0.7rem; -color: #969696; -} -.flux .bottom { -font-size: 0.8rem; -text-align: center; -} -.flux .website .favicon { -padding: 5px; -} -.flux label { -color: #FCFCFC; -cursor: pointer; -} + padding-right: 10px; + background: #fcfcfc; } + .flux::after { + margin: 0 auto; + width: 90%; + border-top: 1px solid #e3e3e3; } + .flux:hover, + .flux .current { + background: #fff; } + .flux:hover:not(.current):hover .item.title, + .flux .current:not(.current):hover .item.title { + background: #fff; } + .flux.not_read:not(.current) { + background: #fff3ed; } + .flux.not_read:not(.current):hover .item.title { + background: #fff3ed; } + .flux.favorite { + background: #fff6da; } + .flux.favorite:not(.current):hover .item.title { + background: #fff6da; } + .flux .date { + color: #969696; + font-size: 0.7rem; } + .flux .bottom { + font-size: 0.8rem; + text-align: center; } + .flux .website .favicon { + padding: 5px; } + .flux label { + color: #fcfcfc; + cursor: pointer; } .flux_header { -font-size: 0.8rem; -cursor: pointer; -} -.flux_header .title { -font-size: 0.9rem; -} + font-size: 0.8rem; + cursor: pointer; } + .flux_header .title { + font-size: 0.9rem; } .notification { -text-align: center; -font-weight: bold; -font-size: 1em; -padding: 10px 0; -z-index: 10; -vertical-align: middle; -background: #e3e3e3; -color: #969696; -border: none; -position: fixed; -bottom: 48px; -left: 0; -top: auto; -height: auto; -} -.notification.good, .notification .bad { -color: #FCFCFC; -} -.notification.good { -background: #5EAABF; -} -.notification.good a.close:hover { -background: #5EAABF; -} -.notification.bad { -background: #c46178; -} -.notification.bad a.close:hover { -background: #c46178; -} -.notification#actualizeProgress { -line-height: 2em; -} -.notification a.close { -display: none; -} + padding: 10px 0; + background: #e3e3e3; + height: auto; + color: #969696; + font-size: 1em; + border: none; + text-align: center; + font-weight: bold; + z-index: 10; + vertical-align: middle; + position: fixed; + bottom: 48px; + left: 0; + top: auto; } + .notification.good, + .notification .bad { + color: #fcfcfc; } + .notification.good { + background: #5eaabf; } + .notification.good a.close:hover { + background: #5eaabf; } + .notification.bad { + background: #c46178; } + .notification.bad a.close:hover { + background: #c46178; } + .notification#actualizeProgress { + line-height: 2em; } + .notification a.close { + display: none; } #bigMarkAsRead { -text-align: center; -text-decoration: none; -background: #e3e3e3; -} -#bigMarkAsRead:hover { -background: #22303d; -color: #FCFCFC; -} + text-align: center; + text-decoration: none; + background: #e3e3e3; } + #bigMarkAsRead:hover { + background: #22303d; + color: #fcfcfc; } #nav_entries { -margin: 0; -text-align: center; -line-height: 3em; -table-layout: fixed; -background: #22303d; -} + margin: 0; + text-align: center; + line-height: 3em; + table-layout: fixed; + background: #22303d; } .stat { -margin: 10px 0 20px; -} -.stat th, .stat td, .stat tr { -border: none; -} -.stat > table td, .stat > table th { -border-bottom: 1px solid #e3e3e3; -} -.stat > .horizontal-list { -margin: 0 0 5px; -} -.stat > .horizontal-list .item { -overflow: hidden; -white-space: nowrap; -text-overflow: ellipsis; -} -.stat > .horizontal-list .item:first-child { -width: 270px; -} + margin: 10px 0 20px; } + .stat th, + .stat td, + .stat tr { + border: none; } + .stat > table td, + .stat > table th { + border-bottom: 1px solid #e3e3e3; } + .stat > .horizontal-list { + margin: 0 0 5px; } + .stat > .horizontal-list .item { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } + .stat > .horizontal-list .item:first-child { + width: 270px; } .formLogin #global { -height: 0; -} - + height: 0; } .formLogin .header { -height: 55px; -background: #22303d; -} - -.formLogin .header > .item.configure { -width: 200px; -position: unset; -} - + background: #22303d; + height: 55px; } + .formLogin .header > .item.configure { + width: 200px; + position: unset; } + .formLogin .header > .item.title h1 { + display: unset; } .formLogin a.signin { - color: #FCFCFC; - padding-left: 5px; -} - -.formLogin .header > .item.title h1 { - display: unset; -} - + color: #fcfcfc; + padding-left: 5px; } .formLogin input { - border-left: 5px solid; - border-right: 1px solid #e3e3e3; - border-top: 1px solid #e3e3e3; - border-bottom: 1px solid #e3e3e3; -} + border-top: 1px #e3e3e3; + border-right: 1px #e3e3e3; + border-bottom: 1px #e3e3e3; + border-left: 5px solid; } .loglist { -overflow: hidden; -border: 1px solid #969696; -} + overflow: hidden; + border: 1px solid #969696; } .log { -padding: 5px 2%; -overflow: auto; -font-size: 0.8rem; -background: #FCFCFC; -} -.log > .date { -margin: 0 10px 0 0; -padding: 5px 10px; -} -.log.error > .date { -background: #c46178; -color: #FCFCFC; -} -.log.warning > .date { -background: #FA8052; -color: #FCFCFC; -} -.log.notice > .date { -background: #e3e3e3; -color: #FCFCFC; -} -.log.debug > .date { -background: #181621; -color: #FCFCFC; -} + padding: 5px 2%; + background: #fcfcfc; + font-size: 0.8rem; + overflow: auto; } + .log > .date { + margin: 0 10px 0 0; + padding: 5px 10px; } + .log.error > .date { + background: #c46178; + color: #fcfcfc; } + .log.warning > .date { + background: #fa8052; + color: #fcfcfc; } + .log.notice > .date { + background: #e3e3e3; + color: #fcfcfc; } + .log.debug > .date { + background: #181621; + color: #fcfcfc; } @media (max-width: 840px) { -.formLogin .header { -display: none; -} - -.dropdown-header, .dropdown-menu > .item { -padding: 12px; -} - -#new-article { -width: 100%; -position: sticky; -top: 0; -} - -.header { -display: table; -} -.header .item.title .logo { -display: none; -} - -.header > .item.title h1 a { -display: block; -position: absolute; -top: -35px; -left: 10px; -font-size: 0.6em; -} - -.header .item.configure, button.read_all.btn { -display: none; -} - -.flux .item.manage, .flux_header .item.website { -width: 35px; -text-align: center; -} - -.aside { -width: 0; -transition: width 200ms linear; -} -.aside .toggle_aside { -display: block; -height: 50px; -line-height: 50px; -text-align: right; -padding-right: 10px; -background: #22303d; -} -.aside.aside_feed { -padding: 0; -} -.aside:target { -width: 78%; -z-index: 1000; -} - -.nav_menu { -position: initial; -height: 71px; -} -.nav_menu .btn { -margin: 5px 10px; -} -.nav_menu .stick { -margin: 0 10px; -} -.nav_menu .stick .btn { -margin: 5px 0; -} -.nav_menu .search { -position: absolute !important; -top: 35px; -left: 55px; -} -.nav_menu .search input { -width: 85%; -} - -.pagination { -margin: 0 0 3.5em; -} - -#panel .close { -display: block; -height: 50px; -line-height: 50px; -text-align: right; -padding-right: 10px; -background: #22303d; -} - -.day .name { -font-size: 1.1rem; -} - -.notification { -width: 100%; -} -.notification a.close { -display: block; -left: 0; -background: transparent; -} -.notification a.close:hover { -opacity: 0.5; -} -.notification a.close .icon { -display: none; -} - -#nav_entries { -width: 100% !important; -} - -div#stream { -margin-top: 0px; -} - -a.btn.toggle_aside { -position: absolute; -top: 29px; -} - -form#mark-read-menu, a#actualize, a#toggle-order, div#nav_menu_actions, div#nav_menu_views { -position: absolute; -} - -form#mark-read-menu { -right: 46px; -top: 30px; -z-index: 1100; -} - -a#actualize, a#toggle-order { -right: 0px; -} - -a#actualize { -top: 29px; -} - -a#toggle-order, div#nav_menu_actions, div#nav_menu_views { -top: 65px; -} - -div#nav_menu_actions { -left: 0px; -} - -div#nav_menu_views { -right: 50px; -} -} + .formLogin .header { + display: none; } + + .dropdown-header, .dropdown-menu > .item { + padding: 12px; } + + #new-article { + width: 100%; + position: sticky; + top: 0; } + + .header { + display: table; } + .header .item.title .logo { + display: none; } + + .header > .item.title h1 a { + display: block; + position: absolute; + top: -35px; + left: 10px; + font-size: 0.6em; } + + .header .item.configure, + button.read_all.btn { + display: none; } + + .flux .item.manage, + .flux_header .item.website { + width: 35px; + text-align: center; } + + .aside { + width: 0; + transition: width 200ms linear; } + .aside .toggle_aside { + background: #22303d; + display: block; + height: 50px; + line-height: 50px; + text-align: right; + padding-right: 10px; } + .aside.aside_feed { + padding: 0; } + .aside:target { + width: 78%; + z-index: 1000; } + + .nav_menu { + position: initial; + height: 71px; } + .nav_menu .btn { + margin: 5px 10px; } + .nav_menu .stick { + margin: 0 10px; } + .nav_menu .stick .btn { + margin: 5px 0; } + .nav_menu .search { + position: absolute !important; + top: 35px; + left: 55px; } + .nav_menu .search input { + width: 85%; } + + .pagination { + margin: 0 0 3.5em; } + + #panel .close { + background: #22303d; + display: block; + height: 50px; + line-height: 50px; + text-align: right; + padding-right: 10px; } + + .day .name { + font-size: 1.1rem; } + + .notification { + width: 100%; } + .notification a.close { + background: transparent; + display: block; + left: 0; } + .notification a.close:hover { + opacity: 0.5; } + .notification a.close .icon { + display: none; } + + #nav_entries { + width: 100% !important; } + + div#stream { + margin-top: 0px; } + + a.btn.toggle_aside { + position: absolute; + top: 29px; } + + form#mark-read-menu, + a#actualize, + a#toggle-order, + div#nav_menu_actions, + div#nav_menu_views { + position: absolute; } + + form#mark-read-menu { + right: 46px; + top: 30px; + z-index: 1100; } + + a#actualize, + a#toggle-order { + right: 0px; } + + a#actualize { + top: 29px; } + + a#toggle-order, + div#nav_menu_actions, + div#nav_menu_views { + top: 65px; } + + div#nav_menu_actions { + left: 0px; } + + div#nav_menu_views { + right: 50px; } } @media (max-width: 410px) { -.nav_menu .stick { -margin: 0; -} -} + .nav_menu .stick { + margin: 0; } } @media (max-width: 374px) { -#nav_menu_views { -display: none; -} -} + #nav_menu_views { + display: none; } } button.as-link { -color: #FCFCFC; -outline: none; -} + color: #fcfcfc; + outline: none; } .dropdown-target:target ~ .btn.dropdown-toggle { -background: #00488b; -} + background: #00488b; } .tree-folder.active .tree-folder-title { -background: #00488b; -font-weight: bold; -} + background: #00488b; + font-weight: bold; } .feed.item.empty { -color: #FA8052; -} -.feed.item.empty.active { -background: #FA8052; -color: #FCFCFC; -} -.feed.item.empty.active > a { -color: #FCFCFC; -} -.feed.item.empty > a { -color: #FA8052; -} + color: #fa8052; } + .feed.item.empty.active { + background: #fa8052; + color: #fcfcfc; } + .feed.item.empty.active > a { + color: #fcfcfc; } + .feed.item.empty > a { + color: #fa8052; } .feed.item.error { -color: #c46178; -} -.feed.item.error.active { -background: #c46178; -color: #FCFCFC; -} -.feed.item.error.active > a { -color: #FCFCFC; -} -.feed.item.error > a { -color: #c46178; -} + color: #c46178; } + .feed.item.error.active { + background: #c46178; + color: #fcfcfc; } + .feed.item.error.active > a { + color: #fcfcfc; } + .feed.item.error > a { + color: #c46178; } #dropdown-query ~ .dropdown-menu .dropdown-header .icon { -vertical-align: middle; -float: right; -} + vertical-align: middle; + float: right; } #stream.reader .flux { -padding: 0 0 50px; -background: #FCFCFC; -color: #22303d; -border: none; -} -#stream.reader .flux .author { -margin: 0 0 10px; -font-size: 90%; -color: #969696; -} + padding: 0 0 50px; + background: #fcfcfc; + color: #22303d; + border: none; } + #stream.reader .flux .author { + margin: 0 0 10px; + color: #969696; + font-size: 90%; } #nav_menu_actions ul.dropdown-menu, #nav_menu_read_all ul.dropdown-menu { -left: 0px; -} + left: 0px; } #slider label { -min-height: initial; -} + min-height: initial; } #slider .form-group:hover { -background: inital; -} + background: inital; } + +/*# sourceMappingURL=swage.css.map */ diff --git a/p/themes/Swage/swage.scss b/p/themes/Swage/swage.scss index eb1dbc1ab..9c2702fc0 100644 --- a/p/themes/Swage/swage.scss +++ b/p/themes/Swage/swage.scss @@ -2,15 +2,15 @@ //colors $color_text: #181621; -$color_light: #FCFCFC; +$color_light: #fcfcfc; $color_nav: #0062be; $color_aside: #22303d; -$color_alert: #FA8052; -$color_good: #5EAABF; -$color_bad: #B0425B; -$color_stared: #FFF6DA; -$color_unread: #FFF3ED; -$color_hover: #FFFFFF; +$color_alert: #fa8052; +$color_good: #5eaabf; +$color_bad: #b0425b; +$color_stared: #fff6da; +$color_unread: #fff3ed; +$color_hover: #fff; // @extend-elements @@ -38,9 +38,9 @@ $color_hover: #FFFFFF; %dropdown { padding: 0 22px; - line-height: 2.5em; - font-size: 0.8rem; color: $color_light; + font-size: 0.8rem; + line-height: 2.5em; } %after { @@ -63,19 +63,23 @@ body { a { color: darken( $color_nav, 10%); outline: none; + &.btn { min-height: 25px; line-height: 25px; text-decoration: none; + &:hover { background: darken( $color_nav, 10%); } } + &#btn-subscription { width: 76%; } + &#btn-importExport { - width: 5%; + width: 5%; } } @@ -94,13 +98,13 @@ sup { } legend { - display: inline-block; - width: auto; margin: 20px 0 5px; padding: 5px 20px; + background: darken( $color_light, 10%); + display: inline-block; + width: auto; font-size: 1.4em; clear: both; - background: darken( $color_light, 10%); } label { @@ -108,10 +112,12 @@ label { } textarea { + background: darken( $color_light, 10% ); width: 360px; height: 100px; + @extend %input; - background: darken( $color_light, 10% ); + &:focus { border-color: darken( $color_nav, 10%); } @@ -119,13 +125,18 @@ textarea { input, select { + @extend %input; + &:focus { border-color: darken( $color_nav, 10%); } + &:invalid { + @extend %invalid; } + &:disabled { background: $color_light; } @@ -167,6 +178,7 @@ form { text-align: center; } } + .category { .title.error::before { display: inline-block; @@ -180,60 +192,71 @@ form { .form-group { padding: 5px; border: 1px solid transparent; + &:hover { background: $color_light; border: 1px solid $color_light; } + &.form-actions { margin: 15px 0 25px; padding: 5px 0; background: darken( $color_light, 10%); border-top: 3px solid darken( $color_light, 10%); + .btn { margin: 0 10px; } } + .group-name { padding: 10px 0; text-align: right; } + .group-controls { min-height: 25px; padding: 5px 0; + .control { line-height: 2.0em; } } + table { margin: 10px 0 0 220px; } } .form-group::after { + @extend %after; } .stick { vertical-align: middle; font-size: 0; + &.configure-feeds { + @extend %aside-width; } } .btn { - display: inline-block; - min-height: 35px; - min-width: 15px; margin: 0; padding: 5px 10px; + background: $color_nav; + display: inline-block; + color: $color_light; font-size: 0.9rem; + border: none; + min-height: 35px; + min-width: 15px; vertical-align: middle; cursor: pointer; overflow: hidden; - background: $color_nav; - border: none; - color: $color_light; + &.active, :active, :hover { @@ -246,6 +269,7 @@ form { font-weight: normal; background: $color_alert; color: $color_light; + &:hover, :active { background: darken( $color_alert, 10%) !important; @@ -254,6 +278,7 @@ form { .nav-list { .nav-header { + @extend %nav-list; padding: 0 10px; font-weight: bold; @@ -261,45 +286,58 @@ form { color: $color_light; cursor: default; } + .item { + @extend %nav-list; + &:hover, &.active { background: darken( $color_nav, 10%); color: $color_light; + a { color: $color_light; } + &.empty a, .error a { color: $color_light; } + &.empty a { background: $color_alert; } + &.error a { background: lighten( $color_bad, 10%); } } + > a { padding: 0 10px; } + &.empty a { color: $color_alert; } + &.error a { color: lighten( $color_bad, 10%); } } + .disable { text-align: center; background: $color_light; color: darken( $color_light, 40% ); } + .nav-form { padding: 3px; text-align: center; } + a:hover { text-decoration: none; } @@ -310,9 +348,11 @@ form { text-align: right; background: $color_aside; color: $color_light; + a { color: $color_light; } + .item { padding: 5px 10px; font-size: 0.9rem; @@ -323,6 +363,7 @@ form { .horizontal-list { margin: 0; padding: 0; + .item { vertical-align: middle; } @@ -334,38 +375,48 @@ form { text-align: left; border: none; background-color: darken( $color_nav, 10%); + .dropdown-header { cursor: default; } + > { .item { + @extend %dropdown; padding: 0; margin-left: 10px; + > a, > span, - > as-link, + > .as-link, button { + @extend %dropdown; } + > a { min-width: initial; white-space: nowrap; } + &:hover { background: $color_nav; color: $color_light; + > a { text-decoration: none; color: $color_light; } } } + .item[aria-checked="true"] > a::before { font-weight: bold; margin: 0 0 0 -14px; } } + .input { select, input { @@ -391,14 +442,15 @@ form { .alert { margin: 5px auto; padding: 10px 15px; - font-size: 0.9em; background: $color_light; - border: none; color: darken( $color_light, 40% ); + font-size: 0.9em; + border: none; text-shadow: 0 0 1px $color_light; + > a { - text-decoration: underline; color: inherit; + text-decoration: underline; } } @@ -428,49 +480,56 @@ form { } .pagination { - text-align: center; - font-size: 0.8em; background: darken( $color_light, 10%); color: $color_text; + font-size: 0.8em; + text-align: center; + .item { &.pager-current { - font-weight: bold; - font-size: 1.5em; background: $color_aside; color: darken( $color_light, 10%); + font-size: 1.5em; + font-weight: bold; } + a { display: block; + color: $color_text; font-style: italic; line-height: 3em; text-decoration: none; - color: $color_text; + &:hover { background: $color_aside; color: darken( $color_light, 10%); } } } + .loading, a:hover.loading { - font-size: 0; background: url(loader.gif) center center no-repeat $color_aside; + font-size: 0; } } .content { padding: 20px 10px; + .pagination { margin: 0; padding: 0; } + hr { margin: 30px 10px; - height: 1px; background: darken( $color_light, 10%); + height: 1px; border: 0; box-shadow: 0 2px 5px darken( $color_light, 10%); } + pre { margin: 10px auto; padding: 10px 20px; @@ -478,30 +537,35 @@ form { background: $color_text; color: $color_light; font-size: 0.9rem; + code { background: transparent; color: $color_light; border: none; } } + code { padding: 2px 5px; - color: $color_bad; background: $color_light; + color: $color_bad; border: 1px solid $color_light; } + blockquote { - display: block; margin: 0; padding: 5px 20px; - border-top: 1px solid darken( $color_light, 10%); - border-bottom: 1px solid darken( $color_light, 10%); background: $color_light; + display: block; color: darken( $color_light, 40% ); + border-top: 1px solid darken( $color_light, 10%); + border-bottom: 1px solid darken( $color_light, 10%); + p { margin: 0; } } + > h1.title > a { color: $color_text; } @@ -509,6 +573,7 @@ form { .box { border: 1px solid darken( $color_light, 10%); + .box-title { margin: 0; padding: 5px 10px; @@ -516,50 +581,61 @@ form { color: darken( $color_light, 40% ); border-bottom: 1px solid darken( $color_light, 10%); } + .box-content { max-height: 260px; + .item { padding: 0 10px; font-size: 0.9rem; line-height: 2.5em; + .configure { visibility: hidden; + .icon { vertical-align: middle; background-color: darken( $color_light, 10%); } } + &:hover .configure { visibility: visible; } } } + &.category { .box-title .title { font-weight: normal; text-decoration: none; text-align: left; } + &:not([data-unread="0"]) .box-title { background: $color_nav; + &:active { background: darken( $color_nav, 10%); } + .title { font-weight: bold; color: $color_light; } } + .title:not([data-unread="0"])::after { + background: none; + border: 0; + box-shadow: none; position: absolute; top: 5px; right: 10px; - border: 0; - background: none; font-weight: bold; - box-shadow: none; text-shadow: none; } + .item.feed { padding: 2px 10px; font-size: 0.8rem; @@ -572,15 +648,17 @@ form { } .tree-folder-title { - position: relative; padding: 0 10px; background: $color_aside; - line-height: 2.3rem; - font-size: 1rem; height: 35px; + font-size: 1rem; + position: relative; + line-height: 2.3rem; + .title { background: inherit; color: $color_light; + &:hover { text-decoration: none; } @@ -589,13 +667,16 @@ form { .tree-folder-items { background: $color_aside; + > .item { padding: 0 10px; line-height: 2.5rem; font-size: 0.8rem; + &.active { background: darken( $color_nav, 10%); } + > a { text-decoration: none; color: $color_light; @@ -607,6 +688,7 @@ form { #sidebar { scrollbar-color: rgba(255, 255, 255, 0.05) rgba(0, 0, 0, 0.0); } + #sidebar:hover { scrollbar-color: rgba(255, 255, 255, 0.3) rgba(0, 0, 0, 0.0); } @@ -616,6 +698,7 @@ form { #sidebar::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.1); } + #sidebar:hover::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.3); } @@ -624,17 +707,22 @@ form { .header { > .item { vertical-align: middle; + &.title { + @extend %aside-width; position: absolute; + h1 { margin: 0; display: block; + a { text-decoration: none; color: $color_light; } } + .logo { display: inline-block; height: 26px; @@ -643,22 +731,27 @@ form { top: 5px; } } + &.search input { width: 230px; } } + .item.search input:focus { width: 350px; } + .item.search { display: none; } + .item.configure { position: fixed; right: 0px; z-index: 1000; width: 35px; } + h1 { text-align: center; font-size: 1.5em; @@ -666,22 +759,27 @@ form { } .aside { - background: $color_aside; padding: 35px 0; + background: $color_aside; + @extend %aside-width; + &.aside_feed { .tree { margin: 0 0 50px; } + .nav-form { input, select { width: 140px; } + .dropdown { .dropdown-menu { right: -20px; } + .dropdown-menu::after { right: 33px; } @@ -692,14 +790,15 @@ form { .aside_feed { .tree-folder-title > .title:not([data-unread="0"])::after { - position: absolute; - right: 0; margin: 6px 0; padding: 0 10px; + background: inherit; font-size: 0.9rem; + position: absolute; + right: 0; line-height: 1.5rem; - background: inherit; } + .tree-folder-items .dropdown-menu::after { left: 2px; } @@ -708,16 +807,20 @@ form { .post { padding: 10px 50px; font-size: 0.9em; + input { background: darken( $color_light, 10% ); - &.long{ + + &.long { height: 33px; - margin-top: 0px; + margin-top: 0px; } } + form { margin: 10px 0; } + &.content { max-width: 550px; } @@ -725,39 +828,47 @@ form { .prompt { text-align: center; + label { text-align: left; } + form { margin: 10px auto 20px auto; width: 200px; } + input { margin: 5px auto; width: 100%; } + p { margin: 20px 0; } } #new-article { - text-align: center; - font-size: 1em; background: $color_nav; + font-size: 1em; + text-align: center; position: fixed; bottom: 48px; z-index: 900; left: 0; - @extend %aside-width; line-height: 1.5em; + + @extend %aside-width; + &:hover { background: darken( $color_nav, 10%); } + > a { line-height: 1.5em; font-weight: bold; color: $color_light; + &:hover { text-decoration: none; } @@ -769,6 +880,7 @@ form { font-weight: bold; line-height: 3em; text-align: center; + .name { display: none; } @@ -782,11 +894,12 @@ form { .nav_menu { + width: 100%; font-size: 0; background-color: $color_nav; position: fixed; - width: 100%; z-index: 900; + .item.search { display: inline-block; position: fixed; @@ -797,42 +910,56 @@ form { .flux { padding-right: 10px; background: $color_light; + &::after { + @extend %after; margin: 0 auto; width: 90%; border-top: 1px solid darken( $color_light, 10%); } + &:hover, .current { background: $color_hover; + &:not(.current):hover .item.title { background: $color_hover; } } + &.not_read { - background: $color_unread; + &:not(.current) { + background: $color_unread; + } + &:not(.current):hover .item.title { background: $color_unread; } } - &.favorite,{ + + &.favorite, { background: $color_stared; + &:not(.current):hover .item.title { background: $color_stared; } } + .date { - font-size: 0.7rem; color: darken( $color_light, 40% ); + font-size: 0.7rem; } + .bottom { font-size: 0.8rem; text-align: center; } + .website .favicon { padding: 5px; } + label { color: $color_light; cursor: pointer; @@ -842,46 +969,55 @@ form { .flux_header { font-size: 0.8rem; cursor: pointer; + .title { font-size: 0.9rem; } } .notification { - text-align: center; - font-weight: bold; - font-size: 1em; padding: 10px 0; - z-index: 10; - vertical-align: middle; background: darken( $color_light, 10%); + height: auto; color: darken( $color_light, 40% ); + font-size: 1em; border: none; + text-align: center; + font-weight: bold; + z-index: 10; + vertical-align: middle; position: fixed; bottom: 48px; left: 0; top: auto; + @extend %aside-width; - height: auto; + &.good, .bad { color: $color_light; } + &.good { background: $color_good; + a.close:hover { background: $color_good; } } + &.bad { background: lighten( $color_bad, 10%); + a.close:hover { background: lighten( $color_bad, 10%); } } + &#actualizeProgress { line-height: 2em; } + a.close { display: none; } @@ -891,6 +1027,7 @@ form { text-align: center; text-decoration: none; background: darken( $color_light, 10%); + &:hover { background: $color_aside; color: $color_light; @@ -902,29 +1039,35 @@ form { text-align: center; line-height: 3em; table-layout: fixed; + @extend %aside-width; background: $color_aside; } .stat { margin: 10px 0 20px; + th, td, tr { border: none; } + > table { td, th { border-bottom: 1px solid darken( $color_light, 10%); } } + > .horizontal-list { margin: 0 0 5px; + .item { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; + &:first-child { width: 270px; } @@ -938,28 +1081,31 @@ form { } .header { - height: 55px; background: $color_aside; + height: 55px; + > .item { &.configure { width: 200px; position: unset; } + &.title h1 { display: unset; } } } + a.signin { color: $color_light; padding-left: 5px; } input { - border-left: 5px solid; - border-right: 1px darken( $color_light, 10%); border-top: 1px darken( $color_light, 10%); + border-right: 1px darken( $color_light, 10%); border-bottom: 1px darken( $color_light, 10%); + border-left: 5px solid; } } @@ -970,49 +1116,59 @@ form { .log { padding: 5px 2%; - overflow: auto; - font-size: 0.8rem; background: $color_light; + font-size: 0.8rem; + overflow: auto; + > .date { margin: 0 10px 0 0; padding: 5px 10px; } + &.error > .date { background: lighten( $color_bad, 10%); color: $color_light; } + &.warning > .date { background: $color_alert; color: $color_light; } + &.notice > .date { background: darken( $color_light, 10%); color: $color_light; } + &.debug > .date { background: $color_text; color: $color_light; } } -@media(max-width: 840px) { +@media (max-width: 840px) { .formLogin .header { display: none; } + .dropdown-header, .dropdown-menu > .item { padding: 12px; } + #new-article { width: 100%; position: sticky; top: 0; } + .header { display: table; + .item.title .logo { display: none; } } + .header > .item.title h1 a { display: block; position: absolute; @@ -1020,93 +1176,116 @@ form { left: 10px; font-size: 0.6em; } + .header .item.configure, button.read_all.btn { display: none; } + .flux .item.manage, .flux_header .item.website { width: 35px; text-align: center; } + .aside { width: 0; transition: width 200ms linear; + .toggle_aside { + background: $color_aside; display: block; height: 50px; line-height: 50px; text-align: right; padding-right: 10px; - background: $color_aside; } + &.aside_feed { padding: 0; } + &:target { width: 78%; z-index: 1000; } } + .nav_menu { position: initial; height: 71px; + .btn { margin: 5px 10px; } + .stick { margin: 0 10px; + .btn { margin: 5px 0; } } + .search { position: absolute !important; top: 35px; left: 55px; + input { width: 85%; } } } + .pagination { margin: 0 0 3.5em; } + #panel .close { + background: $color_aside; display: block; height: 50px; line-height: 50px; text-align: right; padding-right: 10px; - background: $color_aside; } + .day .name { font-size: 1.1rem; } + .notification { width: 100%; + a.close { + background: transparent; display: block; left: 0; - background: transparent; + &:hover { opacity: 0.5; } + .icon { display: none; } } } + #nav_entries { width: 100% !important; } + div#stream { margin-top: 0px; } + a.btn.toggle_aside { position: absolute; top: 29px; } + form#mark-read-menu, a#actualize, a#toggle-order, @@ -1114,38 +1293,44 @@ form { div#nav_menu_views { position: absolute; } + form#mark-read-menu { right: 46px; top: 30px; z-index: 1100; } + a#actualize, a#toggle-order { right: 0px; } + a#actualize { top: 29px; } + a#toggle-order, div#nav_menu_actions, div#nav_menu_views { top: 65px; } + div#nav_menu_actions { left: 0px; } + div#nav_menu_views { right: 50px; } } -@media(max-width: 410px) { +@media (max-width: 410px) { .nav_menu .stick { margin: 0; } } -@media(max-width: 374px) { +@media (max-width: 374px) { #nav_menu_views { display: none; } @@ -1168,26 +1353,33 @@ button.as-link { .feed.item { &.empty { color: $color_alert; + &.active { background: $color_alert; color: $color_light; + > a { color: $color_light; } } + > a { color: $color_alert; } } + &.error { color: lighten( $color_bad, 10%); + &.active { background: lighten( $color_bad, 10%); color: $color_light; + > a { color: $color_light; } } + > a { color: lighten( $color_bad, 10%); } @@ -1204,16 +1396,17 @@ button.as-link { background: $color_light; color: $color_aside; border: none; + .author { margin: 0 0 10px; - font-size: 90%; color: darken( $color_light, 40% ); + font-size: 90%; } } #nav_menu_actions, #nav_menu_read_all { ul.dropdown-menu { - left: 0px; + left: 0px; } } @@ -1221,6 +1414,7 @@ button.as-link { label { min-height: initial; } + .form-group { &:hover { background: inital; diff --git a/p/themes/base-theme/base.css b/p/themes/base-theme/base.css index e265cd7ff..72020e8e6 100644 --- a/p/themes/base-theme/base.css +++ b/p/themes/base-theme/base.css @@ -1,3 +1,5 @@ +/* stylelint-disable block-no-empty */ + @charset "UTF-8"; /*=== GENERAL */ @@ -18,36 +20,40 @@ legend { padding: 5px 0; font-size: 1.4em; } + label { min-height: 25px; padding: 5px 0; cursor: pointer; } + textarea { width: 360px; height: 100px; } + input, select, textarea { min-height: 25px; padding: 5px; line-height: 25px; vertical-align: middle; } + option { padding: 0 .5em; } + input:focus, select:focus, textarea:focus { } + input:invalid, select:invalid { } + input:disabled, select:disabled { } + input.extend { transition: width 200ms linear; - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; } /*=== Tables */ @@ -58,8 +64,10 @@ table { tr, th, td { padding: 0.5em; } + th { } + form td, form th { font-weight: normal; @@ -72,17 +80,21 @@ form th { .form-group.form-actions { padding: 5px 0; } + .form-group.form-actions .btn { margin: 0 10px; } + .form-group .group-name { padding: 10px 0; text-align: right; } + .form-group .group-controls { min-height: 25px; padding: 5px 0; } + .form-group table { margin: 10px 0 0 220px; } @@ -92,17 +104,22 @@ form th { vertical-align: middle; font-size: 0; } + .stick input, .stick .btn { } + .stick .btn:first-child, .stick input:first-child { } + .stick .btn-important:first-child { } + .stick .btn:last-child, .stick input:last-child { } + .stick .btn + .btn, .stick .btn + input, .stick .btn + .dropdown > .btn, @@ -113,29 +130,34 @@ form th { .stick .dropdown + input, .stick .dropdown + .dropdown > .btn { } + .stick input + .btn { } + .stick .btn + .dropdown > .btn { } .btn { + margin: 0; + padding: 5px 10px; display: inline-block; min-height: 37px; min-width: 15px; - margin: 0; - padding: 5px 10px; font-size: 0.9rem; vertical-align: middle; cursor: pointer; overflow: hidden; } + a.btn { min-height: 25px; line-height: 25px; } + .btn:hover { text-decoration: none; } + .btn.active, .btn:active, .dropdown-target:target ~ .btn.dropdown-toggle { @@ -144,15 +166,19 @@ a.btn { .btn-important { font-weight: normal; } + .btn-important:hover { } + .btn-important:active { } .btn-attention { } + .btn-attention:hover { } + .btn-attention:active { } @@ -163,29 +189,40 @@ a.btn { line-height: 2.5em; font-size: 0.9rem; } + .nav-list .item:hover { } + .nav-list .item:hover a { } + .nav-list .item.active { } + .nav-list .item.active a { } + .nav-list .disable { text-align: center; } + .nav-list .item > a { padding: 0 10px; } + .nav-list a:hover { text-decoration: none; } + .nav-list .item.empty a { } + .nav-list .item.active.empty a { } + .nav-list .item.error a { } + .nav-list .item.active.error a { } @@ -203,6 +240,7 @@ a.btn { margin: 0; text-align: right; } + .nav-head .item { padding: 5px 10px; font-size: 0.9rem; @@ -214,6 +252,7 @@ a.btn { margin: 0; padding: 0; } + .horizontal-list .item { vertical-align: middle; } @@ -225,6 +264,7 @@ a.btn { font-size: 0.8rem; text-align: left; } + .dropdown-menu::after { content: ""; position: absolute; @@ -234,32 +274,36 @@ a.btn { height: 10px; z-index: -10; transform: rotate(45deg); - -moz-transform: rotate(45deg); - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); } + .dropdown-header { padding: 0 5px 5px; font-weight: bold; text-align: left; } + .dropdown-menu > .item { } + .dropdown-menu > .item > a, .dropdown-menu > .item > span, .dropdown-menu > .item > .as-link { padding: 0 22px; line-height: 2.5em; } + .dropdown-menu > .item:hover { } + .dropdown-menu > .item[aria-checked="true"] > a::before { font-weight: bold; margin: 0 0 0 -14px; } + .dropdown-menu > .item:hover > a { text-decoration: none; } + .dropdown-menu .input select, .dropdown-menu .input input { margin: 0 auto 5px; @@ -276,16 +320,21 @@ a.btn { padding: 10px 15px; font-size: 0.9em; } + .alert-head { font-size: 1.15em; } + .alert > a { text-decoration: underline; } + .alert-warn { } + .alert-success { } + .alert-error { } @@ -294,24 +343,30 @@ a.btn { text-align: center; font-size: 0.8em; } + .content .pagination { margin: 0; padding: 0; } + .pagination .item.pager-current { font-weight: bold; font-size: 1.5em; } + .pagination .item a { display: block; font-style: italic; line-height: 3em; text-decoration: none; } + .pagination .item a:hover { } + .pagination:first-child .item { } + .pagination:last-child .item { } @@ -323,10 +378,12 @@ a.btn { /*=== Boxes */ .box { } + .box .box-title { margin: 0; padding: 5px 10px; } + .box .box-content { max-height: 260px; } @@ -340,6 +397,7 @@ a.btn { .box .box-content .item .configure { visibility: hidden; } + .box .box-content .item:hover .configure { visibility: visible; } @@ -348,35 +406,45 @@ a.btn { .tree { margin: 10px 0; } + .tree-folder-title { position: relative; padding: 0 10px; line-height: 2.5rem; font-size: 1rem; } + .tree-folder-title .title { background: inherit; } + .tree-folder-title .title:hover { text-decoration: none; } + .tree-folder.active .tree-folder-title { font-weight: bold; } + .tree-folder.active .tree-folder-title .title { } + .tree-folder-items { } + .tree-folder-items > .item { padding: 0 10px; line-height: 2.5rem; font-size: 0.8rem; } + .tree-folder-items > .item.active { } + .tree-folder-items > .item > a { text-decoration: none; } + .tree-folder-items > .item.active > a { } @@ -386,23 +454,29 @@ a.btn { .header { height: 85px; } + .header > .item { padding: 10px; vertical-align: middle; text-align: center; } -.header > .item.title{ + +.header > .item.title { width: 230px; } + .header > .item.title h1 { margin: 0.5em 0; } + .header > .item.title h1 a { text-decoration: none; } + .header > .item.search input { width: 230px; } + .header .item.search input:focus { width: 350px; } @@ -411,12 +485,15 @@ a.btn { #global { height: calc(100% - 85px); } + .aside { } + .aside.aside_feed { padding: 10px 0; text-align: center; } + .aside.aside_feed .tree { margin: 10px 0 50px; } @@ -434,22 +511,28 @@ a.btn { /*=== Aside main page (feeds) */ .feed.item.empty.active { } + .feed.item.error.active { } + .feed.item.empty, .feed.item.empty > a { } + .feed.item.error, .feed.item.error > a { } + .feed.item.empty.active, .feed.item.error.active, .feed.item.empty.active > a, .feed.item.error.active > a { } + .aside_feed .tree-folder-items .dropdown-menu::after { left: 2px; } + .aside_feed .tree-folder-items .item .dropdown-target:target ~ .dropdown-toggle > .icon, .aside_feed .tree-folder-items .item:hover .dropdown-toggle > .icon, .aside_feed .tree-folder-items .item.active .dropdown-toggle > .icon { @@ -461,9 +544,11 @@ a.btn { padding: 10px 50px; font-size: 0.9em; } + .post form { margin: 10px 0; } + .post.content { max-width: 550px; } @@ -472,17 +557,21 @@ a.btn { .prompt { text-align: center; } + .prompt label { text-align: left; } + .prompt form { margin: 10px auto 20px auto; width: 180px; } + .prompt input { margin: 5px auto; width: 100%; } + .prompt p { margin: 20px 0; } @@ -492,12 +581,15 @@ a.btn { text-align: center; font-size: 0.9em; } + #new-article:hover { } + #new-article > a { line-height: 3em; font-weight: bold; } + #new-article > a:hover { text-decoration: none; } @@ -508,8 +600,10 @@ a.btn { font-weight: bold; line-height: 3em; } + #new-article + .day { } + .day .name { padding: 0 10px 0 0; font-size: 1.8em; @@ -527,35 +621,42 @@ a.btn { /*=== Feed articles */ .flux { } + .flux:hover { } + .flux.current { } + .flux.not_read { } + .flux.not_read:not(.current):hover .item.title { } + .flux.favorite { } + .flux.favorite:not(.current):hover .item.title { } -.flux.current { -} - .flux_header { font-size: 0.8rem; cursor: pointer; } + .flux_header .title { font-size: 0.9rem; } + .flux .website .favicon { padding: 5px; } + .flux .date { font-size: 0.7rem; } + .flux:not(.current):hover .item.title { } @@ -568,6 +669,7 @@ a.btn { .content { padding: 20px 10px; } + .content > h1.title > a { } @@ -582,17 +684,20 @@ a.btn { overflow: auto; font-size: 0.9rem; } + .content code { padding: 2px 5px; } + .content pre code { } .content blockquote { - display: block; margin: 0; padding: 5px 20px; + display: block; } + .content blockquote p { margin: 0; } @@ -607,16 +712,21 @@ a.btn { z-index: 10; vertical-align: middle; } + .notification.good { } + .notification.bad { } + .notification a.close { padding: 0 15px; line-height: 3em; } + .notification.good a.close:hover { } + .notification.bad a.close:hover { } @@ -629,8 +739,10 @@ a.btn { text-align: center; text-decoration: none; } + #bigMarkAsRead:hover { } + #bigMarkAsRead:hover .bigTick { } @@ -647,6 +759,7 @@ a.btn { #stream.reader .flux { padding: 0 0 50px; } + #stream.reader .flux .author { margin: 0 0 10px; font-size: 90%; @@ -659,22 +772,27 @@ a.btn { text-decoration: none; text-align: left; } + .box.category:not([data-unread="0"]) .box-title { } + .box.category:not([data-unread="0"]) .box-title:active { } + .box.category:not([data-unread="0"]) .box-title .title { font-weight: bold; } + .box.category .title:not([data-unread="0"])::after { + background: none; + border: 0; position: absolute; top: 5px; right: 10px; - border: 0; - background: none; font-weight: bold; box-shadow: none; text-shadow: none; } + .box.category .item.feed { padding: 2px 10px; font-size: 0.8rem; @@ -686,9 +804,11 @@ a.btn { .aside.aside_feed .nav-form select { width: 140px; } + .aside.aside_feed .nav-form .dropdown .dropdown-menu { right: -20px; } + .aside.aside_feed .nav-form .dropdown .dropdown-menu::after { right: 33px; } @@ -703,6 +823,7 @@ a.btn { .stat td, .stat tr { } + .stat > table td, .stat > table th { text-align: center; @@ -711,11 +832,13 @@ a.btn { .stat > .horizontal-list { margin: 0 0 5px; } + .stat > .horizontal-list .item { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } + .stat > .horizontal-list .item:first-child { width: 250px; } @@ -725,35 +848,40 @@ a.btn { .loglist { overflow: hidden; } + .log { padding: 5px 10px; font-size: 0.8rem; } + .log+.log { } + .log .date { display: block; font-weight: bold; } + .log.error { } + .log.warning { } + .log.notice { } + .log.debug { } /*=== MOBILE */ /*===========*/ -@media(max-width: 840px) { + +@media (max-width: 840px) { .aside { transition: width 200ms linear; - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; } + .aside .toggle_aside, #panel .close { display: block; @@ -770,20 +898,25 @@ a.btn { .nav_menu .btn { margin: 5px 10px; } + .nav_menu .stick { margin: 0 10px; } + .nav_menu .stick .btn { margin: 5px 0; } + .nav_menu .search { display: inline-block; max-width: 97%; } + .nav_menu .search input { max-width: 97%; width: 90px; } + .nav_menu .search input:focus { width: 400px; } @@ -800,9 +933,11 @@ a.btn { display: block; left: 0; } + .notification a.close:hover { opacity: 0.5; } + .notification a.close .icon { display: none; } diff --git a/p/themes/base-theme/template.css b/p/themes/base-theme/template.css index 226d06f59..ebceea526 100644 --- a/p/themes/base-theme/template.css +++ b/p/themes/base-theme/template.css @@ -2,6 +2,7 @@ /*=== GENERAL */ /*============*/ + @font-face { font-family: 'OpenSans'; font-style: normal; @@ -22,6 +23,7 @@ html, body { a { text-decoration: none; } + a:hover { text-decoration: underline; } @@ -38,11 +40,13 @@ h1 { font-size: 1.5em; line-height: 1.6em; } + h2 { margin: 0.5em 0 0.25em; font-size: 1.3em; line-height: 2em; } + h3 { margin: 0.5em 0 0.25em; font-size: 1.1em; @@ -54,6 +58,7 @@ p { margin: 1em 0 0.5em; font-size: 1em; } + sup { line-height: 25px; position: relative; @@ -63,14 +68,16 @@ sup { /*=== Images */ img { - height: auto; max-width: 100%; + height: auto; } + img.favicon { - height: 16px; width: 16px; + height: 16px; vertical-align: middle; } + .feed.mute::before { content: '🔇'; } @@ -86,54 +93,68 @@ legend { width: 100%; clear: both; } + label { display: block; } + input { width: 180px; } + +input[type=number] { + width: 6em; +} + textarea, input[type="file"], input.long, input.extend:focus { width: 300px; } + input, select, textarea { display: inline-block; max-width: 100%; font-size: 0.8rem; } + input[type="radio"], input[type="checkbox"] { width: 15px !important; min-height: 15px !important; } + .dropdown-menu label > input[type="text"] { width: 150px; width: calc(99% - 5em); } + .dropdown-menu input[type="checkbox"] { margin-left: 1em; margin-right: .5em; } + button.as-link, button.as-link:hover, button.as-link:active { background: transparent; - border: none; color: inherit; - cursor: pointer; font-size: 1.1em; + border: none; + cursor: pointer; text-align: left; } + button.as-link[disabled] { - color:#DDD !important; + color: #ddd !important; } /*=== Tables */ table { max-width: 100%; } + th.numeric, td.numeric { text-align: center; @@ -141,7 +162,6 @@ td.numeric { /*=== COMPONENTS */ /*===============*/ - [aria-hidden="true"] { display: none !important; } @@ -152,18 +172,22 @@ td.numeric { display: block; clear: both; } + .form-group.form-actions { min-width: 250px; } + .form-group .group-name { display: block; float: left; width: 200px; } + .form-group .group-controls { min-width: 250px; margin: 0 0 0 220px; } + .form-group .group-controls .control { display: block; } @@ -180,12 +204,14 @@ td.numeric { display: inline-block; white-space: nowrap; } + .btn, a.btn { display: inline-block; cursor: pointer; overflow: hidden; } + .btn-important { font-weight: bold; } @@ -195,6 +221,7 @@ a.btn { .nav-list .item { display: block; } + .nav-list .item, .nav-list .item > a { display: block; @@ -202,9 +229,11 @@ a.btn { white-space: nowrap; text-overflow: ellipsis; } + .nav-head { display: block; } + .nav-head .item { display: inline-block; } @@ -215,6 +244,7 @@ a.btn { table-layout: fixed; width: 100%; } + .horizontal-list .item { display: table-cell; } @@ -225,29 +255,35 @@ a.btn { display: inline-block; vertical-align: middle; } + .dropdown-target { display: none; } + .dropdown-menu { + margin: 0; + background: #fff; display: none; + border: 1px solid #aaa; min-width: 200px; - margin: 0; position: absolute; right: 0; - background: #fff; - border: 1px solid #aaa; } + .dropdown-menu-scrollable { max-height: 75vh; overflow-x: hidden; overflow-y: auto; } + .dropdown-header { display: block; } + .dropdown-menu > .item { display: block; } + .dropdown-menu > .item > a, .dropdown-menu > .item > .as-link, .dropdown-menu > .item > span { @@ -255,33 +291,40 @@ a.btn { min-width: 200px; white-space: nowrap; } + .dropdown-menu > .item[aria-checked="true"] > a::before { content: '✓'; } + .dropdown-menu .input { display: block; } + .dropdown-menu .input select, .dropdown-menu .input input { display: block; max-width: 95%; } + .dropdown-target:target ~ .dropdown-menu { display: block; z-index: 1000; } + .dropdown-close { display: inline; } + .dropdown-close a { + display: block; font-size: 0; position: fixed; top: 0; bottom: 0; left: 0; right: 0; - display: block; z-index: -10; cursor: default; } + .separator { display: block; height: 0; @@ -293,13 +336,16 @@ a.btn { display: block; width: 90%; } + .group-controls .alert { width: 100% } + .alert-head { margin: 0; font-weight: bold; } + .alert ul { margin: 5px 20px; } @@ -315,15 +361,17 @@ a.btn { /*=== Pagination */ .pagination { - display: table; - width: 100%; margin: 0; padding: 0; + display: table; + width: 100%; table-layout: fixed; } + .pagination .item { display: table-cell; } + .pagination .pager-first, .pagination .pager-previous, .pagination .pager-next, @@ -333,28 +381,33 @@ a.btn { /*=== Boxes */ .box { + margin: 20px 10px; display: inline-block; - width: 20rem; max-width: 95%; - margin: 20px 10px; + width: 20rem; border: 1px solid #ccc; vertical-align: top; } + .box .box-title { position: relative; font-size: 1.2rem; font-weight: bold; } + .box .box-title form { margin: 0; } + .box .box-content { display: block; overflow: auto; } + .box .box-content .item { display: block; } + .box .box-content .item.disabled { text-align: center; font-style: italic; @@ -364,6 +417,7 @@ a.btn { padding: 30px 5px; text-align: center; } + .box .box-content-centered .btn { margin: 20px 0 0; } @@ -373,17 +427,20 @@ a.btn { margin: 0 0 5px; border-bottom: 2px solid #ccc; } + [draggable=true] { cursor: grab; } /*=== Scrollbar */ + @supports (scrollbar-width: thin) { #sidebar { overflow-y: auto; scrollbar-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.05); scrollbar-width: thin; } + #sidebar:hover { scrollbar-color: rgba(0, 0, 0, 0.3) rgba(0, 0, 0, 0.05); } @@ -394,11 +451,13 @@ a.btn { background: rgba(0, 0, 0, 0.05); width: 8px; } + #sidebar::-webkit-scrollbar-thumb { background: rgba(0, 0, 0, 0.1); - border-radius: 5px; display: unset; + border-radius: 5px; } + #sidebar:hover::-webkit-scrollbar-thumb { background: rgba(0, 0, 0, 0.3); } @@ -414,26 +473,30 @@ a.btn { } .tree-folder-items { - list-style: none; - max-height: 200em; padding: 0; + max-height: 200em; + list-style: none; transition: max-height .3s linear; } + .tree-folder-title { display: block; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } + .tree-folder-title .title { display: inline-block; width: 100%; vertical-align: middle; } + .tree-folder-items > .item { display: block; white-space: nowrap; } + .tree-folder-items > .item > a { display: inline-block; vertical-align: middle; @@ -442,6 +505,7 @@ a.btn { white-space: nowrap; text-overflow: ellipsis; } + .tree-bottom { visibility: hidden; margin-bottom: 18em; @@ -455,22 +519,27 @@ a.btn { width: 100%; table-layout: fixed; } + .header > .item { display: table-cell; } + .header > .item.title { width: 250px; white-space: nowrap; } + .header > .item.title h1 { display: inline-block; } + .header > .item.title .logo { display: inline-block; - height: 32px; width: 32px; + height: 32px; vertical-align: middle; } + .header > .item.configure { width: 100px; } @@ -482,6 +551,7 @@ a.btn { height: 100%; table-layout: fixed; } + .aside { display: table-cell; width: 300px; @@ -496,26 +566,32 @@ a.btn { .aside_feed .tree-folder-title .icon { padding: 5px; } + .aside_feed .tree-folder-items .item.feed { padding: 0px 15px; } + .aside_feed .tree-folder-items:not(.active) { - border: none; margin: 0; - max-height: 0; padding: 0; + max-height: 0; + border: none; overflow: hidden; } + .aside_feed .tree-folder-items .dropdown { vertical-align: top; } + .aside_feed .tree-folder-items .dropdown-menu { left: 0; } + .aside_feed .tree-folder-items .item .dropdown-toggle > .icon { visibility: hidden; cursor: pointer; } + .aside_feed .tree-folder-items .item .dropdown-target:target ~ .dropdown-toggle > .icon, .aside_feed .tree-folder-items .item:hover .dropdown-toggle > .icon, .aside_feed .tree-folder-items .item.active .dropdown-toggle > .icon { @@ -526,6 +602,7 @@ a.btn { #new-article { display: none; } + #new-article > a { display: block; } @@ -544,39 +621,51 @@ a.btn { .flux_header { position: relative; } + .flux .item { line-height: 40px; white-space: nowrap; } + .flux .item.manage, .flux .item.link { width: 40px; text-align: center; } + .flux .item.website { width: 200px; } + .flux.not_read .item.title, .flux.current .item.title { font-weight: bold; } + .flux:not(.current):hover .item.title { - position: absolute; - max-width: calc(100% - 320px); background: #fff; + max-width: calc(100% - 320px); + position: absolute; } + .flux .item.title a { color: #000; text-decoration: none; } + +.flux .item.author { + color: #555; + font-size: .7rem; + font-weight: normal; + white-space: normal; +} + .flux .item.date { width: 155px; text-align: right; overflow: hidden; } -.flux .item > a { - display: block; -} + .flux .item > a { display: block; text-decoration: none; @@ -584,6 +673,7 @@ a.btn { text-overflow: ellipsis; overflow: hidden; } + .flux .item.share > a, .item.query > a { display: list-item; @@ -595,30 +685,37 @@ a.btn { .hide_posts > .flux:not(.active) > .flux_content { display: none; } + .content { min-height: 20em; margin: auto; line-height: 1.7em; word-wrap: break-word; } + .content.large { max-width: 1000px; } + .content.medium { max-width: 800px; } + .content.thin { max-width: 550px; } + .content ul, .content ol, .content dd { margin: 0 0 0 15px; padding: 0 0 5px 15px; } + .content pre { overflow: auto; } + br { line-height: 1em; } @@ -635,10 +732,12 @@ br { visibility: visible; transition: visibility 0s, opacity .3s linear; } + .notification.closed { opacity: 0; visibility: hidden; } + .notification a.close { position: absolute; top: 0; bottom: 0; @@ -649,27 +748,31 @@ br { #actualizeProgress { position: fixed; } + #actualizeProgress progress { max-width: 100%; vertical-align: middle; } + #actualizeProgress .progress { vertical-align: middle; } /*=== Navigation menu (for articles) */ #nav_entries { + background: #fff; + display: table; position: fixed; bottom: 0; left: 0; - display: table; width: 300px; - background: #fff; table-layout: fixed; } + #nav_entries .item { display: table-cell; width: 30%; } + #nav_entries a { display: block; } @@ -678,19 +781,22 @@ br { #load_more { min-height: 40px; } + .loading { background: url("loader.gif") center center no-repeat; font-size: 0; } + #bigMarkAsRead { + margin: 0 0 100% 0; + margin: 0 0 100vh 0; + padding: 1em 0 50px 0; display: block; width: 100%; text-align: center; font-size: 1.4em; - padding: 1em 0 50px 0; - margin: 0 0 100% 0; - margin: 0 0 100vh 0; } + .bigTick { font-size: 4em; } @@ -699,14 +805,17 @@ br { .stat { margin: 15px 0; } + .stat.half { + padding: 0 2%; display: inline-block; width: 46%; - padding: 0 2%; } + .stat > table { width: 100%; } + .statGraph { height: 300px; } @@ -735,12 +844,14 @@ br { #stream.global .box { text-align: left; } + #global > #panel { bottom: 99vh; display: block; transition: visibility .3s, bottom .3s; visibility: hidden; } + #global > #panel.visible { bottom: 1em; visibility: visible; @@ -755,24 +866,28 @@ br { transition: visibility .3s, opacity .3s; visibility: hidden; } + #overlay.visible { opacity: 1; visibility: visible; } + #panel { + background: #fff; display: none; position: fixed; top: 1em; bottom: 1em; left: 2em; right: 2em; overflow: auto; - background: #fff; } + #overlay .close { position: fixed; top: 0; bottom: 0; left: 0; right: 0; display: block; } + #overlay .close img { display: none; } @@ -786,20 +901,19 @@ br { background: #fff; border-left: 1px solid #aaa; transition: left 200ms linear; - -moz-transition: left 200ms linear; - -webkit-transition: left 200ms linear; - -o-transition: left 200ms linear; - -ms-transition: left 200ms linear; } + #slider.active { left: 40%; } + #close-slider { position: fixed; top: 0; bottom: 0; left: 100%; right: 0; cursor: pointer; } + #close-slider.active { left: 0; } @@ -808,85 +922,98 @@ br { /*==============*/ .slides { padding: 0; - height: 320px; display: block; + max-width: 640px; + height: 320px; + border: 1px solid #aaa; position: relative; min-width: 260px; - max-width: 640px; margin-bottom: 30px; - border: 1px solid #aaa; } + .slides input { display: none; } + .slide-container { display: block; } + .slide { - top: 0; - opacity: 0; + display: block; width: 100%; height: 100%; - display: block; + top: 0; + opacity: 0; position: absolute; transform: scale(0); transition: all .7s ease-in-out; } + .slide img { width: 100%; height: 100%; } + .nav label { + padding: 0; + display: none; width: 10%; height: 100%; - display: none; + color: #fff; + font-family: "Varela Round", sans-serif; + font-size: 1000%; position: absolute; opacity: 0; z-index: 9; cursor: pointer; transition: opacity .2s; - color: #FFF; - font-size: 1000%; text-align: center; line-height: 225%; - font-family: "Varela Round", sans-serif; background-color: rgba(255, 255, 255, .3); text-shadow: 0px 0px 15px rgb(119, 119, 119); - padding: 0; } + .properties { - display: none; - bottom: 0; - left: 0; right: 0; - position: absolute; padding: 5px; background: rgba(255, 255, 255, 0.7); + display: none; color: #000; border-top: 1px solid #aaa; + bottom: 0; + left: 0; right: 0; + position: absolute; z-index: 10; } + .properties .page-number { right: 5px; top: 0; position: absolute; } + .slide:hover + .nav label { opacity: 0.5; } + .nav label:hover { opacity: 1; } + .nav .next { right: 0; } + input:checked + .slide-container .slide { opacity: 1; transform: scale(1); transition: opacity 1s ease-in-out; } + input:checked + .slide-container .nav label { display: block; } + input:checked + .slide-container .properties { display: block; } @@ -896,13 +1023,16 @@ input:checked + .slide-container .properties { .category .title:not([data-unread="0"])::after { content: attr(data-unread); } + .category .title.error::before { content: "⚠ "; color: #bd362f; } + .feed .item-title:not([data-unread="0"])::before { content: "(" attr(data-unread) ") "; } + .feed .item-title:not([data-unread="0"]) { font-weight: bold; } @@ -953,7 +1083,8 @@ pre.enclosure-description { /*=== MOBILE */ /*===========*/ -@media(max-width: 840px) { + +@media (max-width: 840px) { .header, .aside .btn-important, .flux_header .item.website span, @@ -962,19 +1093,23 @@ pre.enclosure-description { .no-mobile { display: none; } + .dropdown .dropdown-menu { + width: 100%; border-radius: 0; bottom: 0; position: fixed; - width: 100%; } + .dropdown-menu::after { display: none; } + .aside .toggle_aside, .nav-login { display: block; } + .nav_menu .toggle_aside, .nav_menu .search, #panel .close img { @@ -989,6 +1124,7 @@ pre.enclosure-description { overflow: hidden; z-index: 100; } + .aside:target { width: 90%; } @@ -1017,6 +1153,7 @@ pre.enclosure-description { top: 25px; bottom: 30px; left: 0; right: 0; } + #panel .close { top: 0; right: 0; left: auto; bottom: auto; @@ -1028,6 +1165,7 @@ pre.enclosure-description { /*=== PRINTER */ /*============*/ + @media print { .header, .aside, .nav_menu, .day, @@ -1037,21 +1175,26 @@ pre.enclosure-description { #nav_entries { display: none; } + html, body { background: #fff; color: #000; font-family: Serif; } + #global, .flux_content { display: block !important; } + .flux_content .content { width: 100% !important; } + .flux_content .content a { color: #000; } + .flux_content .content a::after { content: " [" attr(href) "] "; font-style: italic; diff --git a/p/themes/p.css b/p/themes/p.css index 171b2078b..34b2ea72f 100644 --- a/p/themes/p.css +++ b/p/themes/p.css @@ -4,14 +4,17 @@ body { font-family: sans-serif; text-align: center; } + h1 { font-size: xx-large; - text-shadow: 1px -1px 0 #CCCCCC; + text-shadow: 1px -1px 0 #ccc; } + h1 a { - color: #0062BE; + color: #0062be; text-decoration: none; } + img { border: 0; } diff --git a/phpcs.xml b/phpcs.xml index c6cc44e80..9bea1f3ad 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -3,13 +3,10 @@ Created with the PHP Coding Standard Generator. https://edorian.github.com/php-coding-standard-generator/ - ./static - ./vendor ./lib/SimplePie/ + ./lib/PHPMailer/ ./lib/http-conditional.php - ./lib/JSON.php ./lib/lib_phpQuery.php - ./lib/password_compat.php @@ -29,9 +26,6 @@ ./app/install.php ./tests/app/ - - ./app/SQL/install.sql.mysql.php - ./app/SQL/install.sql.pgsql.php @@ -45,7 +39,9 @@ - + + +
     '+(e.label||String.fromCharCode(65+t))+"