diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..35b742117 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +# Forces Git to handle line endings, preventing any changes (like from editors or tools) being commited. +# Also improves reliability of tools like Git Lens. +* text=auto + +# NPM always rewrites package.json and package-lock.json to lf. +# This stops the annoying tendancy for false positive changes to appear under Windows. +package.json text=lf +package-lock.json text=lf \ No newline at end of file diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 99b92e005..46a180c97 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -44,28 +44,76 @@ When it's time to integrate changes, our git flow more or less follows http://nv ### Branches -- `master`: The current release or release candidate. Always numbered as `major.minor.revision`, possibly with an `-alpha` or `-beta` extension as well. -- `develop`: During alpha/beta, contains major changes to a release candidate. After beta, contains breaking changes that will need to wait for the next version to be integrated. Always numbered as `major.minor.x`, possibly with an `-alpha` or `-beta` extension as well. +#### `master` +The current release or release candidate. Always numbered as `major.minor.revision`, possibly with an `-alpha`, `-beta` or `-RC` extension as well. Commits should **never** be send directly on this branch. -### Changes +#### `hotfix` +Contains the next bug fix release, typically matching the next `revision` version. Any changes not introducing a breaking change can be committed to this branch. Always numbered as `major.minor.revision`. -#### Hotfixes +When ready, changes should be merged into both **master** and **develop**. -Hotfixes should be created in a separate branch, and then merged into both **master** and **develop**. +#### `develop` +Contains breaking changes that will need to wait for the next version to be integrated. Typically matched the next `minor` version. Always numbered as `major.minor.x`. -#### Features +When ready, changes should be merged into both **master** and **hotfix**. -New features that introduce some breaking changes should be created in a separate branch. When they are ready, they can be merged into `develop`. +#### `feature-*` +New features that introduce some breaking changes or incomplete code should be committed in a separate `feature-{name}` branch. + +When ready, the branch should be **[squashed-merged](https://github.com/blog/2141-squash-your-commits)** ([guide](https://stackoverflow.com/a/5309051/445757)) into `develop` (or `hotfix` if it doesn't introduce a breaking change). ### Releases -After every release, the `master` branch (and possibly `develop`, for minor/major releases) should immediately be version-bumped. That way, new changes can be accumulated until the next release. +After every release, the `hotfix` branch (and possibly `develop`, for minor/major releases) should immediately be version-bumped. That way, new changes can be accumulated until the next release. When a new version is created, the version number need to be changed in `app/define.php`. `CHANGELOG.md` should also be updated and the associated tag should be created on Github. -#### Alpha/beta releases +#### Alpha/beta/RC releases + +During alpha/beta/RC, a release candidate always sits on the `master` branch. During the alpha/beta phase, major changes can still be integrated into `master` from `develop`. However, this should bump the revision number instead of the minor/major number. During RC, only _hotfixes_ can be merged into `master`. + +## Working together + +### Issues + +Issues are used as a todo list. Each issue represent something that needs to be fixed, added or improved. Be sure to assign issues to yourself when working on something so everyones knows this issue is taken care of. + +Issues are tagged to represent the feature or category it refers to. We also have some special tags to help organize issues. These includes: + + - [`good first issue`](https://github.com/userfrosting/UserFrosting/labels/good%20first%20issue): If this is your first time contributing to UserFrosting, look for the `good first issue` tag. It's associated with easier issues anyone can tackle. + + - [`up-for-grabs`](https://github.com/userfrosting/UserFrosting/labels/up-for-grabs): Theses issues have not yet been assigned to anybody. Look for theses when you want to start working on a new issue. + + - [`needs discussion`](https://github.com/userfrosting/UserFrosting/labels/needs%20discussion) : This issue needs to be discussed with the dev team before being implemented as more information is required, questions remain or a higher level decision needs to be made. + + - [`needs more info`](https://github.com/userfrosting/UserFrosting/labels/needs%20more%20info): More information is required from the author of the issue. + +### Milestones + +In order to keep a clear roadmap, milestones are used to track what is happening and what needs to be done. Milestones are used to classify problems by: +- Things that need to be done ASAP +- Things we are doing right now +- Things we will probably do soon +- Things we probably will not do soon + +**Things that need to be done ASAP**: this is the highest priority and this milestone should always be empty. Issues related to important bug fixes should be set on this milestone immediately. The milestone always refers to the next version of _revision_, also known as the next bugfix version. + +**Things we are doing right now**: this is the "main" milestone we are currently working on. Usually represents the next `minor` version, but may also represent the next major version when the focus is on the next major release. -During alpha/beta, a release candidate sits on the `master` branch. Minor improvements should be treated as hotfixes, while major changes should be treated as features. In alpha/beta, major changes can still be integrated into `master` from `develop`. However, this should bump the revision number instead of the minor/major number. +**Things we’ll probably do soon**: It's a "Next Tasks" milestone. These tasks will be addressed in the near future, but not close enough for the next version. Usually represents the second minor revision **and** the next major release. + +**Things we probably won’t do soon**: We refer to these issues and sometimes look through them, but they are easy to ignore and sometimes intentionally ignored. Represent issues without milestones that do not have a defined timeframe. + + +To maintain a clear history of progress on each milestone, milestones must be closed when completed and the corresponding version released. A new milestone must then be created for the next release. In addition, the milestone version must be updated when new versions are released. + +## Learn documentation + +The [Learn Documentation](https://learn.userfrosting.com) should always be updated along side code changes. + +Changes to the [learn repository](https://github.com/userfrosting/learn) should follow the same logic as the main repository, ie. any changes applied to the `hotfix` branch should be documented in the learn `hotfix` branch. This also apply to `feature-*` branches. + +Additionally, the `learn` repository can have `dev-*` for learn specific features and fixes. ## Building the API documentation @@ -74,3 +122,19 @@ To build the API documentation, install [ApiGen](http://www.apigen.org/) globall `apigen generate --source UserFrosting/app,userfrosting-assets/src,userfrosting-config/Config,userfrosting-fortress/Fortress,userfrosting-i18n/I18n,userfrosting-session/Session,userfrosting-support/Support --destination userfrosting-api --exclude *vendor*,*_meta* --template-theme "bootstrap"` from inside your dev directory. + +## Automatically fixing coding style with PHP-CS-Fixer + +[PHP-CS-Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) can be used to automatically fix PHP code styling. UserFrosting provides a project specific configuration file ([`.php_cs`](.php_cs)) with a set of rules reflecting our [style guidelines](../STYLE-GUIDE.md). This tool should be used before submitting any code change to assure the style guidelines are met. Every sprinkles will also be parsed by the fixer. + +PHP-CS-Fixer is automatically loaded by Composer and can be used from the UserFrosting root directory : + +``` +app/vendor/bin/php-cs-fixer fix +``` + +## Useful tools + +If you are using **Atom**, be sure to checkout theses useful packages : + - [Docblockr](https://atom.io/packages/docblockr) : Used to generate [documentation block](https://github.com/userfrosting/UserFrosting/blob/master/STYLE-GUIDE.md#documentation). + - [php-ide-serenata](https://atom.io/packages/php-ide-serenata) : Integrates [Serenata](https://gitlab.com/Serenata/Serenata) as PHP IDE, providing autocompletion, code navigation, refactoring, signature help, linting and annotations. diff --git a/.gitignore b/.gitignore index ee30f1eee..6a6137dbc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ -# Ignores my text editor metadata +# Ignores text editor metadata *.komodoproject +.php_cs.cache # Ignores Mac metadata. You can configure this globally if you use a Mac: http://islegend.com/development/setting-global-gitignore-mac-windows/ .DS_Store @@ -14,15 +15,17 @@ app/.env composer.lock composer.phar -# Ignore log, cache, and sessions directories +# Ignore log, cache, sessions and storage directories app/cache/* app/logs/* app/sessions/* +app/storage/* -# Unignore log, cache, and sessions .gitkeeps +# Unignore log, cache, sessions and storage .gitkeeps !app/cache/.gitkeep !app/logs/.gitkeep !app/sessions/.gitkeep +!app/storage/.gitkeep # Ignore bower vendor assets /app/sprinkles/*/assets/vendor/ @@ -59,4 +62,16 @@ public/css/min/* # Ignore personal config files (v0.3.x) app/config-userfrosting.php +# Physical database storage for containers (?) app/database/userfrosting.db + +# Ignore vendor assets +app/assets/* + +# Ignore Vagrant & Homestead VM +vagrant/Homestead/ +.vagrant/* + +# Igore npm lockfile +build/package-lock.json +build/package.lock diff --git a/.php_cs b/.php_cs new file mode 100755 index 000000000..1dc05a1a2 --- /dev/null +++ b/.php_cs @@ -0,0 +1,115 @@ + true, + 'array_syntax' => ['syntax' => 'short'], + 'binary_operator_spaces' => ['align_double_arrow' => true, 'align_equals' => false], + 'blank_line_after_namespace' => true, + 'blank_line_after_opening_tag' => true, + 'blank_line_before_return' => true, + 'braces' => true, + 'cast_spaces' => true, + 'class_definition' => true, + 'declare_equal_normalize' => true, + 'elseif' => true, + 'encoding' => true, + 'full_opening_tag' => true, + 'function_declaration' => true, + 'function_typehint_space' => true, + 'hash_to_slash_comment' => true, + 'heredoc_to_nowdoc' => true, + 'include' => true, + 'indentation_type' => true, + 'line_ending' => true, + 'lowercase_cast' => true, + 'lowercase_constants' => true, + 'lowercase_keywords' => true, + 'method_argument_space' => true, + 'method_separation' => true, + 'multiline_whitespace_before_semicolons' => true, + 'native_function_casing' => true, + 'new_with_braces' => true, + 'no_blank_lines_after_class_opening' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_break_comment' => true, + 'no_closing_tag' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_extra_blank_lines' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_mixed_echo_print' => ['use' => 'echo'], + 'no_multiline_whitespace_around_double_arrow' => true, + 'no_short_bool_cast' => true, + 'no_singleline_whitespace_before_semicolons' => true, + 'no_spaces_after_function_name' => true, + 'no_spaces_around_offset' => true, + 'no_spaces_inside_parenthesis' => true, + 'no_trailing_comma_in_list_call' => true, + 'no_trailing_comma_in_singleline_array' => true, + 'no_trailing_whitespace' => true, + 'no_trailing_whitespace_in_comment' => true, + 'no_unneeded_control_parentheses' => true, + 'no_unreachable_default_argument_value' => true, + 'no_unused_imports' => true, + 'no_useless_return' => true, + 'no_whitespace_before_comma_in_array' => true, + 'no_whitespace_in_blank_line' => true, + 'normalize_index_brace' => true, + 'object_operator_without_whitespace' => true, + 'phpdoc_align' => true, + 'phpdoc_indent' => true, + 'phpdoc_inline_tag' => true, + 'phpdoc_no_empty_return' => true, + 'phpdoc_no_access' => true, + 'phpdoc_no_alias_tag' => ['type' => 'var'], + 'phpdoc_no_package' => true, + 'phpdoc_order' => true, + 'phpdoc_scalar' => true, + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_trim' => true, + 'phpdoc_types' => true, + 'psr4' => true, + 'short_scalar_cast' => true, + 'simplified_null_return' => true, + 'single_blank_line_at_eof' => true, + 'single_blank_line_before_namespace' => true, + 'single_class_element_per_statement' => true, + 'single_import_per_statement' => true, + 'single_line_after_imports' => true, + 'single_quote' => true, + 'space_after_semicolon' => true, + 'standardize_not_equals' => true, + 'switch_case_semicolon_to_colon' => true, + 'switch_case_space' => true, + 'ternary_operator_spaces' => true, + 'trim_array_spaces' => true, + 'unary_operator_spaces' => true, + 'visibility_required' => true, + 'whitespace_after_comma_in_array' => true, + + 'header_comment' => [ + 'header' => $header, + 'separate' => 'bottom', + 'comment_type' => 'PHPDoc', + ] +]; + +$finder = PhpCsFixer\Finder::create() + ->exclude([ + 'vendor', + ]) + ->in([__DIR__ . '/app', __DIR__ . '/public']); + +return PhpCsFixer\Config::create() + ->setRules($rules) + ->setFinder($finder) + ->setUsingCache(true) + ->setCacheFile(__DIR__.'/.php_cs.cache') + ->setRiskyAllowed(true); diff --git a/.travis.yml b/.travis.yml index d011db724..637fe2650 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ php: - 7 - 7.1 - 7.2 + - 7.3 env: matrix: @@ -18,20 +19,31 @@ env: - DB=sqlite - DB=pgsql +cache: + directories: + - $HOME/.composer/cache + before_install: # copy sprinkles.json - cp app/sprinkles.example.json app/sprinkles.json # set up db - bash build/before_install.sh $DB + # update node + - nvm install 10.12.0 before_script: # install deps and UF - composer install + - php bakery debug + - php bakery build-assets - php bakery migrate script: # run unit tests - - composer test + - app/vendor/bin/phpunit --coverage-clover=coverage.xml + +after_success: + - bash <(curl -s https://codecov.io/bash) after_failure: - cat app/log/userfrosting.log diff --git a/CHANGELOG.md b/CHANGELOG.md index dea42a4c4..4c6854d9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,108 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [v4.2.0] +### Changed Requirements +- Changed minimum Node.js version to **v10.12.0** +- Changed minimum NPM version to **6.0.0** + +### Added +- Use locale requested by browser when possible for guests ([#718]) +- Add locale drop down to registration page, with the currently applied locale selected ([#718]) +- Added new `filesystem` service ([#869]) +- Greek locale (Thanks @lenasterg!; [#940]) +- Add cache facade (Ref [#838]) +- Added new `Seeder` +- `NoCache` middleware to prevent caching of routes with dynamic content +- Bakery : + - Added `sprinkle:list` Bakery Command + - Added `migrate:status` Bakery Command + - Added `test:mail` Bakery Command + - Added `seed` Bakery command + - New `isProduction` method for Bakery command to test if app is in production mode + - Added `database` option for `migrate` and `migrate:*` Bakery commands + - Added arguments to the `create-admin` and `setup` Bakery commands so it can be used in a non-interactive way ([#808]) + - Extended `bakery test` to add Test Scope and sprinkle selection argument ([#919], Thanks @ssnukala !) +- Testing : + - Added `RefreshDatabase` test Trait to use a fresh database for a test + - Added `TestDatabase` test Trait to use the in memory database for a test + - Added tests for migrator and it's components + - Added tests for `migrate` Bakery command and sub-commands + - Added `withTestUser` trait for helper methods when running tests requiring a user + - Added `ControllerTestCase` special test case to help testing controllers + - Improved overall test coverage and added coverage config to `phpunit.xml` +- Assets : + - Added support for npm dependencies on the frontend with auditting for known vulnerabilities +- Database : + - Implement `withRaw`, `withSum`, `withAvg`, `withMin`, `withMax` (see https://github.com/laravel/framework/pull/16815) +- Vagrant / Docker : + - Include Vagrant integration directly inside UF ([#829]) + - Sample test environment for Docker +- Misc : + - Integrated improvements from [v4.0.25-Alpha](#v4025-alpha) + - Added code style config (`.php_cs`) and instructions for PHP-CS-Fixer in Readme + - Add support for other config['mailer'] options ([#872]; Thanks @apple314159 !) + +### Changed +- Move User registration out of the `AccountController` ([#793]) +- Rewritten the `locator` service so it's better suited for sprinkle system ([#853]) +- Bakery : + - Moved Bakery commands from `app/System/Bakery` to the `Core` sprinkle and `UserFrosting\Sprinkle\Core\Bakery` namespace. + - Improved `route:list` Bakery command from [v4.1.20](#v4.1.20) + - Sprinkle list in the bakery `debug` command to uses the new `sprinkle:list` table +- Migrations & Database : + - `migrate` and `migrate:*` Bakery command now require confirmation before execution when in production mode. + - Re-written the migrator. It is now detached from the console and Bakery and is now included in the Core Sprinkle ServicesProvider ([#795]) + - Makes the `semantic versioning` part of a migration class optional. Migrations classes can now have the `UserFrosting\Sprinkle\{sprinkleName}\Database\Migrations` namespace, or any other sub-namespace + - Uncomment foreign keys in core migrations ([#833]) + - Move default groups, roles & permissions creation to seeds +- Assets + - Rewrote asset processing to minimise file sizes, drastically reduce IO, and improve maintainability + - Rewrote frontend dependency installation to prevent duplication and detect incompatibilities + - Rewrite `AssetLoader` to act as a wrapper for `Assets` +- Misc : + - Updated Docker integration + - Moved some constants from `app/defines.php` to `app/sprinkles/core/defines.php` + - Move route initialization from system to core sprinkle as router service is located in the core sprinkle + - `dev` environment changed to `debug` ([#653]) + - Changed deprecations to `warning`, and suppressed them in tests + - `routerCacheFile` config now only contains filename. Locator is used to find the full path + +### Fixed +- Sprinkle without a `template/` folder won't cause error anymore +- Fixed routes not available in Tests and Bakery ([#854]) +- redirect failing in UserController::pageInfo when user not found ([#888]) +- Fix WhoopsRenderer integration, resolving a temp fix in [v4.1.21](#v4.1.21). +- Fix Travis not running tests with the env database +- Ignore existing `package-lock.json` which caused incorrect dependencies to be installed when upgrading from older versions of UserFrosting. +- Testing : + - Added `coverage-format` and `coverage-path` options to `test` Bakery command + - Sprinkle Testscope is now case insensitive + - **Class testscope now relative to `/` instead of `/UserFrosting/Sprinkle/` for more intuitive usage and to enable testing of non sprinkle tests** + - Detect and use the sprinkle `phpunit.xml` config when testing a specific sprinkle +- SprinkleManager Improvements : + - Added public `getSprinklePath` method to get path to the sprinkle directory + - Added public `getSprinkleClassNamespace` method to get sprinkle base namespace + - Added public `getSprinkle` method. Returns the sprinkle name as formatted in `sprinkles.json` file, independent of the case of the search argument. + - Public `isAvailable` method now case insensitive. + - Added public `getSprinklesPath` & `setSprinklesPath` to return or set the path to the sprinkle dir (`app/sprinkles/`) + - Added `JsonException` if `sprinkles.json` doesn't contain valid json. + - Added specific tests for sprinkleManager with 100% test coverage + +### Deprecated +- Migrations should now extends `UserFrosting\Sprinkle\Core\Database\Migration` instead of `UserFrosting\System\Bakery\Migration` +- Migrations dependencies property should now be a static property +- Deprecated migration `seed` method. Database seeding should now be done using the new Seeder +- Trait `\UserFrosting\Tests\DatabaseTransactions` has been deprecated. Tests should now use the `\UserFrosting\Sprinkle\Core\Tests\DatabaseTransactions` trait instead. ([#826]) + +### Removed +- The console IO instance is not available anymore in migrations. Removed the `io` property from migration classes +- Removed Bakery `projectRoot` property. Use the `\UserFrosting\ROOT_DIR` constant instead +- Removed `pretend` option from Bakery `migrate:refresh` and `migrate:reset` commands +- Removed `UserFrosting\System\Bakery\DatabaseTest` trait, use `UserFrosting\Sprinkle\Core\Bakery\Helper\DatabaseTest` instead. +- Removed `UserFrosting\System\Bakery\ConfirmableTrait` trait, use `UserFrosting\Sprinkle\Core\Bakery\Helper\ConfirmableTrait` instead. + + ## v4.1.22 - Updated Docker `README.md`. - Replaced `libpng12-dev` which has been dropped since Ubuntu 16.04 with `libpng-dev` in PHP `Dockerfile`. @@ -203,6 +305,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. See [http://learn.userfrosting.com/upgrading/40-to-41](Upgrading 4.0.x to 4.1.x documentation) for complete list of changes and breaking changes. +## v4.0.25-Alpha +- Support npm for frontend vendor assets, and deprecation of bower (#737) +- Duplicate frontend vendor assets are no longer downloaded (#727) +- Detect incompatibilites between frontend vendor assets (related to #727) +- Improved reliability of generated base URL, especially when using docker +- Fixed syntax error in Portugese translations +- Minimise verbosity of assets build scripts when not in 'dev' mode +- Fix to stop bower complaining about sudo when using docker +- The `assetLoader` service has been deprecated, and may be removed in the future. +- **Potential breaking change:** Some packages like `Handlebars` are organised differently at npm. If referencing vendor assets introduced by UF, make sure they are still correct. + ## v4.0.24-alpha - Fixes to nginx config file, and add location block for LE acme challenge - Fix JS errors when `#alerts-page` is not present on a page @@ -574,3 +687,22 @@ See [http://learn.userfrosting.com/upgrading/40-to-41](Upgrading 4.0.x to 4.1.x - Added 404 error page - Standardized JSON interface for backend scripts - Front-end should now be able to catch virtually any backend error and take an appropriate action (instead of white screen of death) + +[#653]: https://github.com/userfrosting/UserFrosting/issues/653 +[#718]: https://github.com/userfrosting/UserFrosting/issues/718 +[#793]: https://github.com/userfrosting/UserFrosting/issues/793 +[#795]: https://github.com/userfrosting/UserFrosting/issues/795 +[#808]: https://github.com/userfrosting/UserFrosting/issues/808 +[#826]: https://github.com/userfrosting/UserFrosting/issues/826 +[#829]: https://github.com/userfrosting/UserFrosting/issues/829 +[#833]: https://github.com/userfrosting/UserFrosting/issues/833 +[#838]: https://github.com/userfrosting/UserFrosting/issues/838 +[#853]: https://github.com/userfrosting/UserFrosting/issues/853 +[#854]: https://github.com/userfrosting/UserFrosting/issues/854 +[#869]: https://github.com/userfrosting/UserFrosting/issues/869 +[#872]: https://github.com/userfrosting/UserFrosting/issues/872 +[#888]: https://github.com/userfrosting/UserFrosting/issues/888 +[#919]: https://github.com/userfrosting/UserFrosting/issues/919 +[#940]: https://github.com/userfrosting/UserFrosting/issues/940 + +[v4.2.0]: https://github.com/userfrosting/UserFrosting/compare/v4.1.22...v4.2.0 diff --git a/LICENSE.md b/LICENSE.md index cf1bc8ed6..8df8d0f52 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (c) 2018 by Alexander Weissman (https://alexanderweissman.com) +Copyright (c) 2019 by Alexander Weissman (https://alexanderweissman.com) UserFrosting is 100% free and open-source. diff --git a/README.md b/README.md index bbd4e3015..35d5c7161 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,24 @@ -# UserFrosting 4.1 +# UserFrosting 4.2 -[https://www.userfrosting.com](https://www.userfrosting.com) - -[![Build Status](https://travis-ci.org/userfrosting/UserFrosting.svg?branch=master)](https://travis-ci.org/userfrosting/UserFrosting) +[![Latest Version](https://img.shields.io/github/release/userfrosting/UserFrosting.svg)](https://github.com/userfrosting/UserFrosting/releases) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.md) [![Join the chat at https://chat.userfrosting.com/channel/support](https://demo.rocket.chat/images/join-chat.svg)](https://chat.userfrosting.com/channel/support) -[![Backers on Open Collective](https://opencollective.com/userfrosting/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/userfrosting/sponsors/badge.svg)](#sponsors) +[![Backers on Open Collective](https://opencollective.com/userfrosting/backers/badge.svg)](#backers) +[![Sponsors on Open Collective](https://opencollective.com/userfrosting/sponsors/badge.svg)](#sponsors) +[![Donate](https://img.shields.io/badge/Open%20Collective-Donate-blue.svg)](https://opencollective.com/userfrosting#backer) - - - +| Branch | Status | +| ------ | ------ | +| master | [![Build Status](https://travis-ci.org/userfrosting/UserFrosting.svg?branch=master)](https://travis-ci.org/userfrosting/UserFrosting) [![codecov](https://codecov.io/gh/userfrosting/userfrosting/branch/master/graph/badge.svg)](https://codecov.io/gh/userfrosting/userfrosting/branch/master) | +| develop | [![Build Status](https://travis-ci.org/userfrosting/UserFrosting.svg?branch=develop)](https://travis-ci.org/userfrosting/UserFrosting) [![codecov](https://codecov.io/gh/userfrosting/userfrosting/branch/develop/graph/badge.svg)](https://codecov.io/gh/userfrosting/userfrosting/branch/develop) | + +[https://www.userfrosting.com](https://www.userfrosting.com) If you simply want to show that you like this project, or want to remember it for later, you should **star**, not **fork**, this repository. Forking is only for when you are ready to create your own copy of the code to work on. ## By [Alex Weissman](https://alexanderweissman.com) -Copyright (c) 2018, free to use in personal and commercial software as per the [license](LICENSE.md). +Copyright (c) 2019, free to use in personal and commercial software as per the [license](LICENSE.md). UserFrosting is a secure, modern user management system written in PHP and built on top of the [Slim Microframework](http://www.slimframework.com/), [Twig](http://twig.sensiolabs.org/) templating engine, and [Eloquent](https://laravel.com/docs/5.4/eloquent#introduction) ORM. @@ -76,7 +80,7 @@ Louis's a civil engineer in Montréal, Québec who also has a passion for coding ### Jordan Mele -Jordan's a developer at Mayvin Training and a student studying Computer Science at the University of Wollongong. His passion is creating software-based solutions to overcomplicated problems, without taking control away from the user. He's also Australian. +Jordan's an Australian PHP Developer at [4mation](https://www.4mation.com.au) in Surry Hills, NSW. His passion is creating simple yet intuitive software-based solutions for problems that would otherwise be tedious and overcomplicated to address, while keeping the user in control. ### Sarah Baghdadi @@ -108,6 +112,7 @@ This project exists thanks to all the people who contribute. If you're intereste - Dmitriy (@rendername) - Russian - Amin Akbari (@aminakbari) - Farsi - Dumblledore - Turkish +- Lena Stergatou (@lenasterg) - Greek ## Supporting UserFrosting diff --git a/STYLE-GUIDE.md b/STYLE-GUIDE.md index 94c10b211..ecec2b29a 100644 --- a/STYLE-GUIDE.md +++ b/STYLE-GUIDE.md @@ -16,10 +16,11 @@ In addition: * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ ``` - + ### Classes - All classes MUST be prefaced with a documentation block containing a description and the author(s) of that class. You SHOULD add other descriptive properties as well. @@ -32,7 +33,7 @@ In addition: - Front controller (Slim) routes should be alphabetized, first by route type and then by route URL. If you have route groups, those should come first and be alphabetized as well. ### Variables - + - All class member variables and local variables MUST be declared in `camelCase`. ### Arrays @@ -40,5 +41,11 @@ In addition: - Array keys MUST be defined using `snake_case`. This is so they can be referenced in Twig and other templating languages. - Array keys MUST NOT contain `.`. This is because `.` is a reserved operator in Laravel and Twig's [dot syntax](https://medium.com/@assertchris/dot-notation-3fd3e42edc61). - Multidimensional arrays SHOULD be referenced using dot syntax whenever possible. So, instead of doing `$myArray['person1']['email']`, you should use `$myArray['person1.email']` if your array structure supports it. - + +### Tools + +[php-cs-fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) can be used to automatically fix styling. See [Contributing](.github/CONTRIBUTING.md) for more info. + ### Twig Templates + +[TODO] \ No newline at end of file diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 000000000..f86db1266 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,32 @@ +require 'json' +require 'yaml' + +VAGRANTFILE_API_VERSION ||= "2" +confDir = $confDir ||= File.expand_path("vagrant/homestead", File.dirname(__FILE__)) + +homesteadYamlPath = "vagrant/bootstrap.yaml" +afterScriptPath = "vagrant/after.sh" +aliasesPath = "vagrant/aliases" + +require File.expand_path(confDir + '/scripts/homestead.rb') + +Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| + if File.exists? aliasesPath then + config.vm.provision "file", source: aliasesPath, destination: "~/.bash_aliases" + end + + if File.exists? homesteadYamlPath then + settings = YAML::load(File.read(homesteadYamlPath)) + Homestead.configure(config, settings) + end + + if File.exists? afterScriptPath then + config.vm.provision "shell", path: afterScriptPath + end + + config.vm.provider :virtualbox do |vb| + vb_name = settings["name"] ||= "homestead-7" + vb_name = vb_name + "_" + File.basename(Dir.pwd) + vb.name = vb_name + end +end \ No newline at end of file diff --git a/app/defines.php b/app/defines.php index d7160776b..28fb5ba03 100755 --- a/app/defines.php +++ b/app/defines.php @@ -1,12 +1,21 @@ [ + 'auth' => true + ], +]; diff --git a/app/sprinkles/account/config/default.php b/app/sprinkles/account/config/default.php index 367e14012..c38b50006 100644 --- a/app/sprinkles/account/config/default.php +++ b/app/sprinkles/account/config/default.php @@ -1,79 +1,144 @@ [ - 'auth' => false - ], - // configuration for the 'password reset' feature - 'password_reset' => [ - 'algorithm' => 'sha512', - 'timeouts' => [ - 'create' => 86400, - 'reset' => 10800 - ] - ], - // See https://github.com/gbirke/rememberme for an explanation of these settings - 'remember_me' => [ - 'cookie' => [ - 'name' => 'rememberme' - ], - 'expire_time' => 604800, - 'session' => [ - 'path' => '/' - ], - 'table' => [ - 'tableName' => 'persistences', - 'credentialColumn' => 'user_id', - 'tokenColumn' => 'token', - 'persistentTokenColumn' => 'persistent_token', - 'expiresColumn' => 'expires_at' - ] +/* + * Account configuration file for UserFrosting. + */ +return [ + /* + * ---------------------------------------------------------------------- + * User Cache Config + * ---------------------------------------------------------------------- + * Cache current user info for a given time to speed up process. + * Set to zero to disable. + */ + 'cache' => [ + 'user' => [ + 'delay' => 120, // In minutes + 'key' => '_user', ], - 'reserved_user_ids' => [ - 'guest' => -1, - 'master' => 1 + ], + + /* + * ---------------------------------------------------------------------- + * AuthorizationManager Debug + * ---------------------------------------------------------------------- + * Turn this on to send AuthorizationManager::checkAccess process details + * to log. This can help debugging your permissions and roles + */ + 'debug' => [ + 'auth' => false + ], + + /* + * ---------------------------------------------------------------------- + * Configuration for the 'password reset' feature + * ---------------------------------------------------------------------- + */ + 'password_reset' => [ + 'algorithm' => 'sha512', + 'timeouts' => [ + 'create' => 86400, + 'reset' => 10800 + ] + ], + + /* + * ---------------------------------------------------------------------- + * RememberMe Package Settings + * ---------------------------------------------------------------------- + * See https://github.com/gbirke/rememberme for an explanation of these settings + */ + 'remember_me' => [ + 'cookie' => [ + 'name' => 'rememberme' ], - 'session' => [ - // The keys used in the session to store info about authenticated users - 'keys' => [ - 'current_user_id' => 'account.current_user_id', // the key to use for storing the authenticated user's id - 'captcha' => 'account.captcha' // Key used to store a captcha hash during captcha verification - ] + 'expire_time' => 604800, + 'session' => [ + 'path' => '/' + ] + ], + + /* + * ---------------------------------------------------------------------- + * Reserved user IDs + * ---------------------------------------------------------------------- + * Master (root) user will be the one with this user id. Same goes for + * guest users + */ + 'reserved_user_ids' => [ + 'guest' => -1, + 'master' => 1 + ], + + /* + * ---------------------------------------------------------------------- + * Account Session config + * ---------------------------------------------------------------------- + * The keys used in the session to store info about authenticated users + */ + 'session' => [ + 'keys' => [ + 'current_user_id' => 'account.current_user_id', // the key to use for storing the authenticated user's id + 'captcha' => 'account.captcha' // Key used to store a captcha hash during captcha verification + ] + ], + + /* + * ---------------------------------------------------------------------- + * Account Site Settings + * ---------------------------------------------------------------------- + * "Site" settings that are automatically passed to Twig. Use theses + * settings to control the login and registration process + */ + 'site' => [ + 'login' => [ + 'enable_email' => true // Set to false to allow login by username only ], - // "Site" settings that are automatically passed to Twig - 'site' => [ - 'login' => [ - 'enable_email' => true - ], - 'registration' => [ - 'enabled' => true, //if this set to false, you probably want to also set require_email_verification to false as well to disable the link on the signup page - 'captcha' => true, - 'require_email_verification' => true, - 'user_defaults' => [ - 'locale' => 'en_US', - 'group' => 'terran', - // Default roles for newly registered users - 'roles' => [ - 'user' => true - ] + 'registration' => [ + 'enabled' => true, //if this set to false, you probably want to also set require_email_verification to false as well to disable the link on the signup page + 'captcha' => true, + 'require_email_verification' => true, + // Default roles and other settings for newly registered users + 'user_defaults' => [ + 'locale' => 'en_US', + 'group' => 'terran', + 'roles' => [ + 'user' => true ] ] - ], - 'throttles' => [ - 'check_username_request' => null, - 'password_reset_request' => null, - 'registration_attempt' => null, - 'sign_in_attempt' => null, - 'verification_request' => null - ], - // configuration for the 'email verification' feature - 'verification' => [ - 'algorithm' => 'sha512', - 'timeout' => 10800 ] - ]; + ], + + /* + * ---------------------------------------------------------------------- + * Throttles Configuration + * ---------------------------------------------------------------------- + * No throttling is enforced by default. Everything is setup in + * production mode. See http://security.stackexchange.com/a/59550/74909 + * for the inspiration for our throttling system + */ + 'throttles' => [ + 'check_username_request' => null, + 'password_reset_request' => null, + 'registration_attempt' => null, + 'sign_in_attempt' => null, + 'verification_request' => null + ], + + /* + * ---------------------------------------------------------------------- + * Configuration for the 'email verification' feature + * ---------------------------------------------------------------------- + */ + 'verification' => [ + 'algorithm' => 'sha512', + 'timeout' => 10800 + ] +]; diff --git a/app/sprinkles/account/config/production.php b/app/sprinkles/account/config/production.php index b7c3288ed..b7ce4111f 100644 --- a/app/sprinkles/account/config/production.php +++ b/app/sprinkles/account/config/production.php @@ -1,67 +1,79 @@ [ - 'check_username_request' => [ - 'method' => 'ip', - 'interval' => 3600, - 'delays' => [ - 40 => 1000 - ] - ], - 'password_reset_request' => [ - 'method' => 'ip', - 'interval' => 3600, - 'delays' => [ - 2 => 5, - 3 => 10, - 4 => 20, - 5 => 40, - 6 => 80, - 7 => 600 - ] - ], - 'registration_attempt' => [ - 'method' => 'ip', - 'interval' => 3600, - 'delays' => [ - 2 => 5, - 3 => 10, - 4 => 20, - 5 => 40, - 6 => 80, - 7 => 600 - ] - ], - 'sign_in_attempt' => [ - 'method' => 'ip', - 'interval' => 3600, - 'delays' => [ - 4 => 5, - 5 => 10, - 6 => 20, - 7 => 40, - 8 => 80, - 9 => 600 - ] - ], - 'verification_request' => [ - 'method' => 'ip', - 'interval' => 3600, - 'delays' => [ - 2 => 5, - 3 => 10, - 4 => 20, - 5 => 40, - 6 => 80, - 7 => 600 - ] +/* + * Account production config file for UserFrosting. You may override/extend this in your site's configuration file to customize deploy settings. + */ +return [ + /* + * ---------------------------------------------------------------------- + * Throttles Configuration + * ---------------------------------------------------------------------- + * Enable throttling in production mode. + * See http://security.stackexchange.com/a/59550/74909 for the + * inspiration for our throttling system + */ + 'throttles' => [ + 'check_username_request' => [ + 'method' => 'ip', + 'interval' => 3600, + 'delays' => [ + 40 => 1000 + ] + ], + 'password_reset_request' => [ + 'method' => 'ip', + 'interval' => 3600, + 'delays' => [ + 2 => 5, + 3 => 10, + 4 => 20, + 5 => 40, + 6 => 80, + 7 => 600 + ] + ], + 'registration_attempt' => [ + 'method' => 'ip', + 'interval' => 3600, + 'delays' => [ + 2 => 5, + 3 => 10, + 4 => 20, + 5 => 40, + 6 => 80, + 7 => 600 + ] + ], + 'sign_in_attempt' => [ + 'method' => 'ip', + 'interval' => 3600, + 'delays' => [ + 4 => 5, + 5 => 10, + 6 => 20, + 7 => 40, + 8 => 80, + 9 => 600 + ] + ], + 'verification_request' => [ + 'method' => 'ip', + 'interval' => 3600, + 'delays' => [ + 2 => 5, + 3 => 10, + 4 => 20, + 5 => 40, + 6 => 80, + 7 => 600 ] ] - ]; + ] +]; diff --git a/app/sprinkles/account/factories/Permissions.php b/app/sprinkles/account/factories/Permissions.php index 591f5fdf4..be7b9fcf5 100644 --- a/app/sprinkles/account/factories/Permissions.php +++ b/app/sprinkles/account/factories/Permissions.php @@ -3,17 +3,18 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ use League\FactoryMuffin\Faker\Facade as Faker; -/** +/* * General factory for the Permission Model */ $fm->define('UserFrosting\Sprinkle\Account\Database\Models\Permission')->setDefinitions([ - 'slug' => Faker::word(), - 'name' => Faker::word(), + 'slug' => Faker::word(), + 'name' => Faker::word(), 'description' => Faker::paragraph(), - 'conditions' => Faker::word() + 'conditions' => Faker::word() ]); diff --git a/app/sprinkles/account/factories/Roles.php b/app/sprinkles/account/factories/Roles.php index cdbb5a308..34704f3f2 100644 --- a/app/sprinkles/account/factories/Roles.php +++ b/app/sprinkles/account/factories/Roles.php @@ -3,16 +3,17 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ use League\FactoryMuffin\Faker\Facade as Faker; -/** +/* * General factory for the Role Model */ $fm->define('UserFrosting\Sprinkle\Account\Database\Models\Role')->setDefinitions([ - 'slug' => Faker::unique()->word(), - 'name' => Faker::word(), + 'slug' => Faker::unique()->word(), + 'name' => Faker::word(), 'description' => Faker::paragraph() ]); diff --git a/app/sprinkles/account/factories/Users.php b/app/sprinkles/account/factories/Users.php index 7390c4448..abd34c4fd 100644 --- a/app/sprinkles/account/factories/Users.php +++ b/app/sprinkles/account/factories/Users.php @@ -3,21 +3,22 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ use League\FactoryMuffin\Faker\Facade as Faker; -/** +/* * General factory for the User Model */ $fm->define('UserFrosting\Sprinkle\Account\Database\Models\User')->setDefinitions([ - 'user_name' => Faker::unique()->firstNameMale(), - 'first_name' => Faker::firstNameMale(), - 'last_name' => Faker::firstNameMale(), - 'email' => Faker::unique()->email(), - 'locale' => 'en_US', + 'user_name' => Faker::unique()->firstNameMale(), + 'first_name' => Faker::firstNameMale(), + 'last_name' => Faker::firstNameMale(), + 'email' => Faker::unique()->email(), + 'locale' => 'en_US', 'flag_verified' => 1, - 'flag_enabled' => 1, - 'password' => Faker::password() + 'flag_enabled' => 1, + 'password' => Faker::password() ]); diff --git a/app/sprinkles/account/locale/ar/messages.php b/app/sprinkles/account/locale/ar/messages.php index 72039047b..5cec36485 100644 --- a/app/sprinkles/account/locale/ar/messages.php +++ b/app/sprinkles/account/locale/ar/messages.php @@ -3,174 +3,176 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * Modern Standard Arabic message token translations for the 'account' sprinkle. * - * @package userfrosting\i18n\ar - * @author Alexander Weissman and Abdullah Seba + * @author Alexander Weissman + * @author Abdullah Seba */ - return [ - "ACCOUNT" => [ - "@TRANSLATION" => "الحساب", + 'ACCOUNT' => [ + '@TRANSLATION' => 'الحساب', - "ACCESS_DENIED" => "يبدو أنك لا تملك صلاحية للقيام بذلك", + 'ACCESS_DENIED' => 'يبدو أنك لا تملك صلاحية للقيام بذلك', - "DISABLED" => "هذا الحساب معطل يمكنك الاتصال بنا للحصول على مزيد من المعلومات", + 'DISABLED' => 'هذا الحساب معطل يمكنك الاتصال بنا للحصول على مزيد من المعلومات', - "EMAIL_UPDATED" => "تم تجديد البريد الإلكتروني بالحساب", + 'EMAIL_UPDATED' => 'تم تجديد البريد الإلكتروني بالحساب', - "INVALID" => "هذا الحساب غير موجود قد تم حذفه يمكنك الاتصا بنا للحصول على مزيد من المعلومات", + 'INVALID' => 'هذا الحساب غير موجود قد تم حذفه يمكنك الاتصا بنا للحصول على مزيد من المعلومات', - "MASTER_NOT_EXISTS" => "لا يمكنك تسجيل حساب جديد حتى تم إنشاء الحساب الرئيسي", - "MY" => "حسابي", + 'MASTER_NOT_EXISTS' => 'لا يمكنك تسجيل حساب جديد حتى تم إنشاء الحساب الرئيسي', + 'MY' => 'حسابي', - "SESSION_COMPROMISED" => [ - "@TRANSLATION" => "تم اختراق جلسنك يجب عليك الخروج على كافة الأجهزة، ثم تسجيل الدخول مرة أخرى والتأكد من أن المعلومات الخاصة بك لم يعبث بها", - "TITLE" => "من الممكن أن حسابك قد اخترق", - "TEXT" => "ربما استخدم شخص معلومات التسجيل الدخول للدخول إلى هذه الصفحة. لسلامتك، تم انتهاء جميع الجلسات يرجا التسجيل مرة اخرى وتحقق من حسابك بسبب النشاط الغريب قد ترغب في تغيير كلمة المرور" + 'SESSION_COMPROMISED' => [ + '@TRANSLATION' => 'تم اختراق جلسنك يجب عليك الخروج على كافة الأجهزة، ثم تسجيل الدخول مرة أخرى والتأكد من أن المعلومات الخاصة بك لم يعبث بها', + 'TITLE' => 'من الممكن أن حسابك قد اخترق', + 'TEXT' => 'ربما استخدم شخص معلومات التسجيل الدخول للدخول إلى هذه الصفحة. لسلامتك، تم انتهاء جميع الجلسات يرجا التسجيل مرة اخرى وتحقق من حسابك بسبب النشاط الغريب قد ترغب في تغيير كلمة المرور' ], - "SESSION_EXPIRED" => "انتهت جلستك تستطيع تسجيل الدخول مرة أخرى", + 'SESSION_EXPIRED' => 'انتهت جلستك تستطيع تسجيل الدخول مرة أخرى', - "SETTINGS" => [ - "@TRANSLATION" => "إعدادات الحساب", - "DESCRIPTION" => "غير إعدادات حسابك، بما في ذلك البريد الإلكتروني، واسم وكلمة المرور -", - "UPDATED" => "تم تجديد إعدادات الحساب" + 'SETTINGS' => [ + '@TRANSLATION' => 'إعدادات الحساب', + 'DESCRIPTION' => 'غير إعدادات حسابك، بما في ذلك البريد الإلكتروني، واسم وكلمة المرور +', + 'UPDATED' => 'تم تجديد إعدادات الحساب' ], - "TOOLS" => "أدوات الحساب", + 'TOOLS' => 'أدوات الحساب', - "UNVERIFIED" => "لم يتم التحقق من حسابك بعد افحص في رسائل البريد الإلكتروني و ملف البريد المزعج للحصول على تعليمات تفعيل الحساب", + 'UNVERIFIED' => 'لم يتم التحقق من حسابك بعد افحص في رسائل البريد الإلكتروني و ملف البريد المزعج للحصول على تعليمات تفعيل الحساب', - "VERIFICATION" => [ - "NEW_LINK_SENT" => "لقد أرسلنا رابط جديدا لتحقق عبر البريد الإلكتروني إلى {{email}} افحص في رسائل البريد الإلكتروني و ملف البريد المزعج", - "RESEND" => "إعادة ارسال بريد التحقق", - "COMPLETE" => "لقد تم التحقق من حسابك بنجاح يمكنك الآن تسجيل الدخول", - "EMAIL" => "ادخل عنوان البريد الإلكتروني الذي استخدمته للتسجيل، و سوف نرسل البريد الإلكتروني لتحقق مرة أخرى", - "PAGE" => "إعادة إرسال البريد الإلكتروني التحقق من حسابك الجديد", - "SEND" => "ارسل رابط للتحقق عبر البريد الالكتروني", - "TOKEN_NOT_FOUND" => "رمز التحقق غير موجود أو تم تحقق الحساب من قبل", + 'VERIFICATION' => [ + 'NEW_LINK_SENT' => 'لقد أرسلنا رابط جديدا لتحقق عبر البريد الإلكتروني إلى {{email}} افحص في رسائل البريد الإلكتروني و ملف البريد المزعج', + 'RESEND' => 'إعادة ارسال بريد التحقق', + 'COMPLETE' => 'لقد تم التحقق من حسابك بنجاح يمكنك الآن تسجيل الدخول', + 'EMAIL' => 'ادخل عنوان البريد الإلكتروني الذي استخدمته للتسجيل، و سوف نرسل البريد الإلكتروني لتحقق مرة أخرى', + 'PAGE' => 'إعادة إرسال البريد الإلكتروني التحقق من حسابك الجديد', + 'SEND' => 'ارسل رابط للتحقق عبر البريد الالكتروني', + 'TOKEN_NOT_FOUND' => 'رمز التحقق غير موجود أو تم تحقق الحساب من قبل', ] ], - "EMAIL" => [ - "INVALID" => "لا يوجد حساب ل {{email}}", - "IN_USE" => "البريد الإلكتروني {{email}} قيد الاستخدام" + 'EMAIL' => [ + 'INVALID' => 'لا يوجد حساب ل {{email}}', + 'IN_USE' => 'البريد الإلكتروني {{email}} قيد الاستخدام' ], - "FIRST_NAME" => "الاسم الاول", + 'FIRST_NAME' => 'الاسم الاول', - "HEADER_MESSAGE_ROOT" => "تسجيل الدخول باسم المستخدم ROOT", + 'HEADER_MESSAGE_ROOT' => 'تسجيل الدخول باسم المستخدم ROOT', - "LAST_NAME" => "اسم العائلة", + 'LAST_NAME' => 'اسم العائلة', - "LOCALEACCOUNT" => "اللغة التي تستخدم لحسابك", + 'LOCALEACCOUNT' => 'اللغة التي تستخدم لحسابك', - "LOGIN" => [ - "@TRANSLATION" => "تسجيل الدخول", + 'LOGIN' => [ + '@TRANSLATION' => 'تسجيل الدخول', - "ALREADY_COMPLETE" => "انت بالفعل داخل", - "SOCIAL" => "أو الدخول مع", - "REQUIRED" => "عذرا، يجب عليك تسجيل الدخول للوصول إلى هذا المكان" + 'ALREADY_COMPLETE' => 'انت بالفعل داخل', + 'SOCIAL' => 'أو الدخول مع', + 'REQUIRED' => 'عذرا، يجب عليك تسجيل الدخول للوصول إلى هذا المكان' ], - "LOGOUT" => "تسجيل الخروج", + 'LOGOUT' => 'تسجيل الخروج', - "NAME" => "اسم", + 'NAME' => 'اسم', - "PAGE" => [ - "LOGIN" => [ - "DESCRIPTION" => "سجل الدخول إلى حسابك في {{site_name}} أو سجيل للحصول على حساب جديد", - "SUBTITLE" => "التسجيل مجانا أو قم بتسجيل الدخول باستخدام حساب موجود", - "TITLE" => "هيا نبدأ", + 'PAGE' => [ + 'LOGIN' => [ + 'DESCRIPTION' => 'سجل الدخول إلى حسابك في {{site_name}} أو سجيل للحصول على حساب جديد', + 'SUBTITLE' => 'التسجيل مجانا أو قم بتسجيل الدخول باستخدام حساب موجود', + 'TITLE' => 'هيا نبدأ', ] ], - "PASSWORD" => [ - "@TRANSLATION" => "كلمه المرور", - - "BETWEEN" => "ما بين {{min}}-{{max}} حروف", - - "CONFIRM" => "تأكيد كلمة المرور", - "CONFIRM_CURRENT" => "تأكيد كلمه المرور الحالي", - "CONFIRM_NEW" => "تأكيد كلمة المرور الجديدة", - "CONFIRM_NEW_EXPLAIN" => "إعادة إدخال كلمة المرور الجديدة", - "CONFIRM_NEW_HELP" => "لازم إذا كان المطلوب اختيار كلمة مرور جديدة", - "CURRENT" => "كلمة المرور الحالية", - "CURRENT_EXPLAIN" => "يجب عليك تأكيد كلمة المرور الحالية لإجراء التغييرات", - - "FORGOTTEN" => "كلمه المرور منسية", - "FORGET" => [ - "@TRANSLATION" => "لقد نسيت كلمة المرور", - - "COULD_NOT_UPDATE" => "لا يمكن تحديث كلمة المرور", - "EMAIL" => "ادخل عنوان البريد الإلكتروني الذي استخدمته للتسجيل وسوف نرسل تعليمات لإعادة تعيين كلمة المرور", - "EMAIL_SEND" => "أرسل رابط تعيين كلمة المرور عبر البريد الالكتروني", - "INVALID" => "لم يتم العثور على إعادة تعيين كلمة المرور، أو انتهت صلاحية رابط حاول إعادة تقديم طلبك", - "PAGE" => "الحصول على رابط لإعادة تعيين كلمة المرور", - "REQUEST_CANNED" => "إلغاء طلب كلمة المرور", - "REQUEST_SENT" => "إذا تطابق البريد الإلكتروني {{email}} حسابا في نظامنا، فسيتم إرسال رابط إعادة تعيين كلمة المرور إلى {{email}}." + 'PASSWORD' => [ + '@TRANSLATION' => 'كلمه المرور', + + 'BETWEEN' => 'ما بين {{min}}-{{max}} حروف', + + 'CONFIRM' => 'تأكيد كلمة المرور', + 'CONFIRM_CURRENT' => 'تأكيد كلمه المرور الحالي', + 'CONFIRM_NEW' => 'تأكيد كلمة المرور الجديدة', + 'CONFIRM_NEW_EXPLAIN' => 'إعادة إدخال كلمة المرور الجديدة', + 'CONFIRM_NEW_HELP' => 'لازم إذا كان المطلوب اختيار كلمة مرور جديدة', + 'CURRENT' => 'كلمة المرور الحالية', + 'CURRENT_EXPLAIN' => 'يجب عليك تأكيد كلمة المرور الحالية لإجراء التغييرات', + + 'FORGOTTEN' => 'كلمه المرور منسية', + 'FORGET' => [ + '@TRANSLATION' => 'لقد نسيت كلمة المرور', + + 'COULD_NOT_UPDATE' => 'لا يمكن تحديث كلمة المرور', + 'EMAIL' => 'ادخل عنوان البريد الإلكتروني الذي استخدمته للتسجيل وسوف نرسل تعليمات لإعادة تعيين كلمة المرور', + 'EMAIL_SEND' => 'أرسل رابط تعيين كلمة المرور عبر البريد الالكتروني', + 'INVALID' => 'لم يتم العثور على إعادة تعيين كلمة المرور، أو انتهت صلاحية رابط حاول إعادة تقديم طلبك', + 'PAGE' => 'الحصول على رابط لإعادة تعيين كلمة المرور', + 'REQUEST_CANNED' => 'إلغاء طلب كلمة المرور', + 'REQUEST_SENT' => 'إذا تطابق البريد الإلكتروني {{email}} حسابا في نظامنا، فسيتم إرسال رابط إعادة تعيين كلمة المرور إلى {{email}}.' ], - "RESET" => [ - "@TRANSLATION" => "إعادة تعيين كلمة المرور", - "CHOOSE" => "اختيار كلمة مرور جديدة للتواصل", - "PAGE" => "اختيار كلمة مرور جديدة لحسابك", - "SEND" => "تعيين كلمة المرور الجديدة وتسجيل الدخول" + 'RESET' => [ + '@TRANSLATION' => 'إعادة تعيين كلمة المرور', + 'CHOOSE' => 'اختيار كلمة مرور جديدة للتواصل', + 'PAGE' => 'اختيار كلمة مرور جديدة لحسابك', + 'SEND' => 'تعيين كلمة المرور الجديدة وتسجيل الدخول' ], - "HASH_FAILED" => "فشلت التجزئة كلمة المرور يرجى الاتصال بمسؤول الموقع", - "INVALID" => "كلمة مرور الحالية لا تتطابق مع ما لدينا", - "NEW" => "كلمة مرور الجديدة", - "NOTHING_TO_UPDATE" => "لا يمكنك تحديث مع نفس كلمة مرور", - "UPDATED" => "جدد كلمة مرور", - - "CREATE" => [ - "@TRANSLATION" => "إنشاء كلمة مرور", - "PAGE" => "اختر كلمة مرور لحسابك الجديد", - "SET" => "تعيين كلمة المرور وتسجيل الدخول" + 'HASH_FAILED' => 'فشلت التجزئة كلمة المرور يرجى الاتصال بمسؤول الموقع', + 'INVALID' => 'كلمة مرور الحالية لا تتطابق مع ما لدينا', + 'NEW' => 'كلمة مرور الجديدة', + 'NOTHING_TO_UPDATE' => 'لا يمكنك تحديث مع نفس كلمة مرور', + 'UPDATED' => 'جدد كلمة مرور', + + 'CREATE' => [ + '@TRANSLATION' => 'إنشاء كلمة مرور', + 'PAGE' => 'اختر كلمة مرور لحسابك الجديد', + 'SET' => 'تعيين كلمة المرور وتسجيل الدخول' ] ], - "REGISTER" => "تسجيل", - "REGISTER_ME" => "سجلني", - "SIGN_IN_HERE" => "هل لديك حساب؟ تسجيل الدخول هنا", - - "REGISTRATION" => [ - "BROKEN" => "نحن آسفون، هناك مشكلة مع عملية تسجيل الحساب يرجى الاتصال بنا مباشرة للحصول على المساعدة", - "COMPLETE_TYPE1" => "لقد سجلت بنجاح يمكنك الآن تسجيل الدخول", - "COMPLETE_TYPE2" => "لقد سجلت بنجاح سوف تتلقى قريبا رسالة التحقق تحتوي على رابط لتفعيل حسابك لن تكون قادرا على تسجيل الدخول حتى الانتهاء من هذه الخطوة", - "DISABLED" => "عذرا، لقد تم تعطيل تسجيل اي حساب", - "LOGOUT" => "لا يمكنك التسجيل للحصول على حساب أثناء تسجيل الدخول", - "WELCOME" => "التسجيل سريع وبسيط" + 'REGISTER' => 'تسجيل', + 'REGISTER_ME' => 'سجلني', + 'SIGN_IN_HERE' => 'هل لديك حساب؟ تسجيل الدخول هنا', + + 'REGISTRATION' => [ + 'BROKEN' => 'نحن آسفون، هناك مشكلة مع عملية تسجيل الحساب يرجى الاتصال بنا مباشرة للحصول على المساعدة', + 'COMPLETE_TYPE1' => 'لقد سجلت بنجاح يمكنك الآن تسجيل الدخول', + 'COMPLETE_TYPE2' => 'لقد سجلت بنجاح سوف تتلقى قريبا رسالة التحقق تحتوي على رابط لتفعيل حسابك لن تكون قادرا على تسجيل الدخول حتى الانتهاء من هذه الخطوة', + 'DISABLED' => 'عذرا، لقد تم تعطيل تسجيل اي حساب', + 'LOGOUT' => 'لا يمكنك التسجيل للحصول على حساب أثناء تسجيل الدخول', + 'WELCOME' => 'التسجيل سريع وبسيط' ], - "RATE_LIMIT_EXCEEDED" => "تم تجاوز الحد عددا لهذا الإجراء يجب الانتظار {{delay}} ثواني قبل القيام بمحاولة أخرى", - "REMEMBER_ME" => "تذكرنى", - "REMEMBER_ME_ON_COMPUTER" => "تذكرني على هذا الحاسوب (غير مستحسن للحواسب العامة)", + 'RATE_LIMIT_EXCEEDED' => 'تم تجاوز الحد عددا لهذا الإجراء يجب الانتظار {{delay}} ثواني قبل القيام بمحاولة أخرى', + 'REMEMBER_ME' => 'تذكرنى', + 'REMEMBER_ME_ON_COMPUTER' => 'تذكرني على هذا الحاسوب (غير مستحسن للحواسب العامة)', - "SIGNIN" => "تسجيل الدخول", - "SIGNIN_OR_REGISTER" => "تسجيل الدخول أو التسجيل", - "SIGNUP" => "تسجيل", + 'SIGNIN' => 'تسجيل الدخول', + 'SIGNIN_OR_REGISTER' => 'تسجيل الدخول أو التسجيل', + 'SIGNUP' => 'تسجيل', - "TOS" => "الأحكام والشروط", - "TOS_AGREEMENT" => "من خلال تسجيل حساب جديد في {{site_title}}, انت تقبل الأحكام والشروط", - "TOS_FOR" => "الأحكام والشروط ل {{title}}", + 'TOS' => 'الأحكام والشروط', + 'TOS_AGREEMENT' => 'من خلال تسجيل حساب جديد في {{site_title}}, انت تقبل الأحكام والشروط', + 'TOS_FOR' => 'الأحكام والشروط ل {{title}}', - "USERNAME" => [ - "@TRANSLATION" => "اسم المستخدم", + 'USERNAME' => [ + '@TRANSLATION' => 'اسم المستخدم', - "CHOOSE" => "اختيار اسم مستخدم فريد", - "INVALID" => "اسم المستخدم غير صالح", - "IN_USE" => "اسم المستخدم {{user_name}} قيد الاستخدام" + 'CHOOSE' => 'اختيار اسم مستخدم فريد', + 'INVALID' => 'اسم المستخدم غير صالح', + 'IN_USE' => 'اسم المستخدم {{user_name}} قيد الاستخدام' ], - "USER_ID_INVALID" => "عدم وجود هوية المستخدم المطلوب", - "USER_OR_EMAIL_INVALID" => "اسم المستخدم أو عنوان البريد الإلكتروني غير صالح", - "USER_OR_PASS_INVALID" => "اسم المستخدم أو كلمة المرور غير صالحة", + 'USER_ID_INVALID' => 'عدم وجود هوية المستخدم المطلوب', + 'USER_OR_EMAIL_INVALID' => 'اسم المستخدم أو عنوان البريد الإلكتروني غير صالح', + 'USER_OR_PASS_INVALID' => 'اسم المستخدم أو كلمة المرور غير صالحة', - "WELCOME" => "مرحبا بعودتك, {{first_name}}" + 'WELCOME' => 'مرحبا بعودتك, {{first_name}}' ]; diff --git a/app/sprinkles/account/locale/ar/validate.php b/app/sprinkles/account/locale/ar/validate.php index 37693fb4c..5bdb39469 100644 --- a/app/sprinkles/account/locale/ar/validate.php +++ b/app/sprinkles/account/locale/ar/validate.php @@ -1,18 +1,20 @@ - [ - "PASSWORD_MISMATCH" => "يجب أن تكون كلمة المرور وكلمة المرور التأكيدية نفس" - ] -]; + [ + 'PASSWORD_MISMATCH' => 'يجب أن تكون كلمة المرور وكلمة المرور التأكيدية نفس' + ] +]; diff --git a/app/sprinkles/account/locale/de_DE/messages.php b/app/sprinkles/account/locale/de_DE/messages.php index b33155274..0b2a7b436 100644 --- a/app/sprinkles/account/locale/de_DE/messages.php +++ b/app/sprinkles/account/locale/de_DE/messages.php @@ -3,186 +3,187 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * German message token translations for the 'account' sprinkle. * - * @package userfrosting\i18n\de * @author X-Anonymous-Y * @author kevinrombach * @author splitt3r */ - return [ - "ACCOUNT" => [ - "@TRANSLATION" => "Konto", + 'ACCOUNT' => [ + '@TRANSLATION' => 'Konto', - "ACCESS_DENIED" => "Hmm, sieht aus als hätten Sie keine Berechtigung, um dies zu tun.", + 'ACCESS_DENIED' => 'Hmm, sieht aus als hätten Sie keine Berechtigung, um dies zu tun.', - "DISABLED" => "Dieses Konto wurde deaktiviert. Bitte Kontaktieren Sie uns für weitere Informationen.", + 'DISABLED' => 'Dieses Konto wurde deaktiviert. Bitte Kontaktieren Sie uns für weitere Informationen.', - "EMAIL_UPDATED" => "E-Mail-Adresse aktualisiert.", + 'EMAIL_UPDATED' => 'E-Mail-Adresse aktualisiert.', - "INVALID" => "Dieses Konto existiert nicht. Es wurde möglicherweise gelöscht. Bitte kontaktieren Sie uns für weitere Informationen.", + 'INVALID' => 'Dieses Konto existiert nicht. Es wurde möglicherweise gelöscht. Bitte kontaktieren Sie uns für weitere Informationen.', - "MASTER_NOT_EXISTS" => "Sie können kein neues Konto anlegen solange kein Root-Konto angelegt wurde!", - "MY" => "Mein Konto", + 'MASTER_NOT_EXISTS' => 'Sie können kein neues Konto anlegen solange kein Root-Konto angelegt wurde!', + 'MY' => 'Mein Konto', - "SESSION_COMPROMISED" => [ - "@TRANSLATION" => "Ihre Sitzung wurde beeinträchtigt. Sie sollten sich auf allen Geräten abmelden, sich dann wieder anmelden und sicherstellen, dass Ihre Daten nicht manipuliert wurden.", - "TITLE" => "Ihr Konto wurde möglicherweise beeinträchtigt", - "TEXT" => "Möglicherweise ist es jemandem gelungen, Ihren Zugang zu dieser Seite zu übernehmen. Aus Sicherheitsgründen wurden Sie überall abgemeldet. Bitte melden Sie sich neu an und untersuchen Sie das Konto nach verdächtigen Aktivitäten. Außerdem sollten Sie Ihr Passwort ändern." + 'SESSION_COMPROMISED' => [ + '@TRANSLATION' => 'Ihre Sitzung wurde beeinträchtigt. Sie sollten sich auf allen Geräten abmelden, sich dann wieder anmelden und sicherstellen, dass Ihre Daten nicht manipuliert wurden.', + 'TITLE' => 'Ihr Konto wurde möglicherweise beeinträchtigt', + 'TEXT' => 'Möglicherweise ist es jemandem gelungen, Ihren Zugang zu dieser Seite zu übernehmen. Aus Sicherheitsgründen wurden Sie überall abgemeldet. Bitte melden Sie sich neu an und untersuchen Sie das Konto nach verdächtigen Aktivitäten. Außerdem sollten Sie Ihr Passwort ändern.' ], - "SESSION_EXPIRED" => "Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an.", + 'SESSION_EXPIRED' => 'Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an.', - "SETTINGS" => [ - "@TRANSLATION" => "Kontoeinstellungen", - "DESCRIPTION" => "Aktualisieren Sie Ihre Kontoeinstellungen, einschließlich E-Mail, Name und Passwort.", - "UPDATED" => "Kontoeinstellungen aktualisiert" + 'SETTINGS' => [ + '@TRANSLATION' => 'Kontoeinstellungen', + 'DESCRIPTION' => 'Aktualisieren Sie Ihre Kontoeinstellungen, einschließlich E-Mail, Name und Passwort.', + 'UPDATED' => 'Kontoeinstellungen aktualisiert' ], - "TOOLS" => "Konto-Werkzeuge", + 'TOOLS' => 'Konto-Werkzeuge', - "UNVERIFIED" => "Ihr Konto wurde noch nicht bestätigt. Überprüfen Sie Ihr E-Mails/Spam-Ordner für die Konto-Aktivierungsanleitung.", + 'UNVERIFIED' => 'Ihr Konto wurde noch nicht bestätigt. Überprüfen Sie Ihr E-Mails/Spam-Ordner für die Konto-Aktivierungsanleitung.', - "VERIFICATION" => [ - "NEW_LINK_SENT" => "Wir haben einen neuen Bestätigungslink an {{email}} gesendet. Überprüfen Sie Ihr E-Mail/Spam-Ordner oder versuchen Sie es später noch einmal.", - "RESEND" => "Bestätigungsmail erneut senden", - "COMPLETE" => "Sie haben Ihr Konto erfolgreich Verifiziert. Sie können sich jetzt anmelden.", - "EMAIL" => "Bitte geben Sie die E-Mail-Adresse ein, mit der Sie sich registriert haben, Überprüfen Sie Ihr E-Mails/Spam-Ordner für die Bestätigungs-E-Mail.", - "PAGE" => "Senden Sie die Bestätigungs-E-Mail erneut für Ihr neues Konto.", - "SEND" => "Bestätigungslink erneut per E-Mail zusenden", - "TOKEN_NOT_FOUND" => "Verifizierungstoken existiert nicht / Konto wurde bereits verifiziert" + 'VERIFICATION' => [ + 'NEW_LINK_SENT' => 'Wir haben einen neuen Bestätigungslink an {{email}} gesendet. Überprüfen Sie Ihr E-Mail/Spam-Ordner oder versuchen Sie es später noch einmal.', + 'RESEND' => 'Bestätigungsmail erneut senden', + 'COMPLETE' => 'Sie haben Ihr Konto erfolgreich Verifiziert. Sie können sich jetzt anmelden.', + 'EMAIL' => 'Bitte geben Sie die E-Mail-Adresse ein, mit der Sie sich registriert haben, Überprüfen Sie Ihr E-Mails/Spam-Ordner für die Bestätigungs-E-Mail.', + 'PAGE' => 'Senden Sie die Bestätigungs-E-Mail erneut für Ihr neues Konto.', + 'SEND' => 'Bestätigungslink erneut per E-Mail zusenden', + 'TOKEN_NOT_FOUND' => 'Verifizierungstoken existiert nicht / Konto wurde bereits verifiziert' ] ], - "EMAIL" => [ - "INVALID" => "Es gibt kein Konto für {{email}}.", - "IN_USE" => "Die E-Mail Adresse {{email}} wird bereits verwendet.", - "VERIFICATION_REQUIRED" => "E-Mail (Bestätigung benötigt - Benutzen Sie eine echte E-Mail Adresse!)" + 'EMAIL' => [ + 'INVALID' => 'Es gibt kein Konto für {{email}}.', + 'IN_USE' => 'Die E-Mail Adresse {{email}} wird bereits verwendet.', + 'VERIFICATION_REQUIRED' => 'E-Mail (Bestätigung benötigt - Benutzen Sie eine echte E-Mail Adresse!)' ], - "EMAIL_OR_USERNAME" => "Benutzername oder E-mail Adresse", + 'EMAIL_OR_USERNAME' => 'Benutzername oder E-mail Adresse', - "FIRST_NAME" => "Vorname", + 'FIRST_NAME' => 'Vorname', - "HEADER_MESSAGE_ROOT" => "Sie sind als Root-Benutzer angemeldet.", + 'HEADER_MESSAGE_ROOT' => 'Sie sind als Root-Benutzer angemeldet.', - "LAST_NAME" => "Nachname", + 'LAST_NAME' => 'Nachname', - "LOCALE" => [ - "ACCOUNT" => "Die Sprache und das Gebietsschema für Ihr Konto", - "INVALID" => "{{locale}} ist kein gültiges Gebietsschema." + 'LOCALE' => [ + 'ACCOUNT' => 'Die Sprache und das Gebietsschema für Ihr Konto', + 'INVALID' => '{{locale}} ist kein gültiges Gebietsschema.' ], - "LOGIN" => [ - "@TRANSLATION" => "Anmelden", - "ALREADY_COMPLETE" => "Sie sind bereits eingeloggt!", - "SOCIAL" => "Oder loggen Sie sich ein mit", - "REQUIRED" => "Sorry, Sie müssen angemeldet sein. Um auf diese Ressource zugreifen zu können." + 'LOGIN' => [ + '@TRANSLATION' => 'Anmelden', + 'ALREADY_COMPLETE' => 'Sie sind bereits eingeloggt!', + 'SOCIAL' => 'Oder loggen Sie sich ein mit', + 'REQUIRED' => 'Sorry, Sie müssen angemeldet sein. Um auf diese Ressource zugreifen zu können.' ], - "LOGOUT" => "Ausloggen", + 'LOGOUT' => 'Ausloggen', - "NAME" => "Name", + 'NAME' => 'Name', - "NAME_AND_EMAIL" => "Name und E-Mail", + 'NAME_AND_EMAIL' => 'Name und E-Mail', - "PAGE" => [ - "LOGIN" => [ - "DESCRIPTION" => "Melden Sie sich in Ihr {{site_name}} Konto an oder registrieren Sie sich für ein neues Konto.", - "SUBTITLE" => "Registrieren Sie sich kostenlos oder melden Sie sich mit einem bestehenden Konto an.", - "TITLE" => "Lass uns anfangen!" + 'PAGE' => [ + 'LOGIN' => [ + 'DESCRIPTION' => 'Melden Sie sich in Ihr {{site_name}} Konto an oder registrieren Sie sich für ein neues Konto.', + 'SUBTITLE' => 'Registrieren Sie sich kostenlos oder melden Sie sich mit einem bestehenden Konto an.', + 'TITLE' => 'Lass uns anfangen!' ] ], - "PASSWORD" => [ - "@TRANSLATION" => "Passwort", + 'PASSWORD' => [ + '@TRANSLATION' => 'Passwort', - "BETWEEN" => "Zwischen {{min}}-{{max}} Zeichen", + 'BETWEEN' => 'Zwischen {{min}}-{{max}} Zeichen', - "CONFIRM" => "Bestätige das Passwort", - "CONFIRM_CURRENT" => "Bitte bestätige dein jetziges Passwort", - "CONFIRM_NEW" => "Neues Passwort bestätigen", - "CONFIRM_NEW_EXPLAIN" => "Geben Sie Ihr neues Passwort erneut ein", - "CONFIRM_NEW_HELP" => "Erforderlich, wenn Sie ein neues Passwort wählen", - "CREATE" => [ - "@TRANSLATION" => "Passwort setzen", - "PAGE" => "Setzen Sie ein Passwort für den Account.", - "SET" => "Passwort setzen und anmelden" + 'CONFIRM' => 'Bestätige das Passwort', + 'CONFIRM_CURRENT' => 'Bitte bestätige dein jetziges Passwort', + 'CONFIRM_NEW' => 'Neues Passwort bestätigen', + 'CONFIRM_NEW_EXPLAIN' => 'Geben Sie Ihr neues Passwort erneut ein', + 'CONFIRM_NEW_HELP' => 'Erforderlich, wenn Sie ein neues Passwort wählen', + 'CREATE' => [ + '@TRANSLATION' => 'Passwort setzen', + 'PAGE' => 'Setzen Sie ein Passwort für den Account.', + 'SET' => 'Passwort setzen und anmelden' ], - "CURRENT" => "Aktuelles Passwort", - "CURRENT_EXPLAIN" => "Sie müssen Ihr aktuelles Passwort bestätigen, um Änderungen vorzunehmen", - - "FORGOTTEN" => "Passwort vergessen", - "FORGET" => [ - "@TRANSLATION" => "Ich habe mein Passwort vergessen", - - "COULD_NOT_UPDATE" => "Das Passwort konnte nicht aktualisiert werden.", - "EMAIL" => "Bitte geben Sie die E-Mail-Adresse ein, mit der Sie sich registriert haben. Ein Link mit der Anweisungen zum Zurücksetzen Ihres Passworts wird Ihnen per E-Mail zugeschickt.", - "EMAIL_SEND" => "Neue Passwort zurücksetzen E-Mail senden", - "INVALID" => "Diese Anforderung zum Zurücksetzen des Passworts wurde nicht gefunden oder ist abgelaufen.Bitte versuchen Sie Ihre Anfrage erneut einzureichen.", - "PAGE" => "Holen Sie sich einen Link, um Ihr Passwort zurückzusetzen.", - "REQUEST_CANNED" => "Verlorene Passwortanforderung abgebrochen.", - "REQUEST_SENT" => "Wenn die E-Mail {{email}} mit einem Account in unserem System übereinstimmt, wird ein Passwort-Reset-Link an {{email}} gesendet." + 'CURRENT' => 'Aktuelles Passwort', + 'CURRENT_EXPLAIN' => 'Sie müssen Ihr aktuelles Passwort bestätigen, um Änderungen vorzunehmen', + + 'FORGOTTEN' => 'Passwort vergessen', + 'FORGET' => [ + '@TRANSLATION' => 'Ich habe mein Passwort vergessen', + + 'COULD_NOT_UPDATE' => 'Das Passwort konnte nicht aktualisiert werden.', + 'EMAIL' => 'Bitte geben Sie die E-Mail-Adresse ein, mit der Sie sich registriert haben. Ein Link mit der Anweisungen zum Zurücksetzen Ihres Passworts wird Ihnen per E-Mail zugeschickt.', + 'EMAIL_SEND' => 'Neue Passwort zurücksetzen E-Mail senden', + 'INVALID' => "Diese Anforderung zum Zurücksetzen des Passworts wurde nicht gefunden oder ist abgelaufen.Bitte versuchen Sie Ihre Anfrage erneut einzureichen.", + 'PAGE' => 'Holen Sie sich einen Link, um Ihr Passwort zurückzusetzen.', + 'REQUEST_CANNED' => 'Verlorene Passwortanforderung abgebrochen.', + 'REQUEST_SENT' => 'Wenn die E-Mail {{email}} mit einem Account in unserem System übereinstimmt, wird ein Passwort-Reset-Link an {{email}} gesendet.' ], - "HASH_FAILED" => "Passwort Hashing fehlgeschlagen. Bitte kontaktieren Sie einen Administrator.", - "INVALID" => "Das aktuelle Passwort stimmt nicht mit dem Datensatz überein", - "NEW" => "Neues Passwort", - "NOTHING_TO_UPDATE" => "Sie können nicht das gleiche Passwort zum Aktualisieren verwenden", + 'HASH_FAILED' => 'Passwort Hashing fehlgeschlagen. Bitte kontaktieren Sie einen Administrator.', + 'INVALID' => 'Das aktuelle Passwort stimmt nicht mit dem Datensatz überein', + 'NEW' => 'Neues Passwort', + 'NOTHING_TO_UPDATE' => 'Sie können nicht das gleiche Passwort zum Aktualisieren verwenden', - "RESET" => [ - "@TRANSLATION" => "Passwort zurücksetzen", - "CHOOSE" => "Bitte wählen Sie ein neues Passwort, um fortzufahren.", - "PAGE" => "Wählen Sie ein neues Passwort für Ihr Konto.", - "SEND" => "Neues Passwort festlegen und anmelden" + 'RESET' => [ + '@TRANSLATION' => 'Passwort zurücksetzen', + 'CHOOSE' => 'Bitte wählen Sie ein neues Passwort, um fortzufahren.', + 'PAGE' => 'Wählen Sie ein neues Passwort für Ihr Konto.', + 'SEND' => 'Neues Passwort festlegen und anmelden' ], - "UPDATED" => "Konto Passwort aktualisiert" + 'UPDATED' => 'Konto Passwort aktualisiert' ], - "PROFILE" => [ - "SETTINGS" => "Profileinstellungen", - "UPDATED" => "Profileinstellungen aktualisiert" + 'PROFILE' => [ + 'SETTINGS' => 'Profileinstellungen', + 'UPDATED' => 'Profileinstellungen aktualisiert' ], - "RATE_LIMIT_EXCEEDED" => "Die grenze für diese Maßnahme wurde überschritten. Sie müssen weitere {{delay}} Sekunden warten, bevor Sie einen weiteren Versuch machen dürfen.", - - "REGISTER" => "Registrieren", - "REGISTER_ME" => "Melden Sie mich an", - "REGISTRATION" => [ - "BROKEN" => "Es tut uns leid, es gibt ein Problem mit unserer Registrierung. Bitte kontaktieren Sie uns direkt für Hilfe.", - "COMPLETE_TYPE1" => "Sie haben sich erfolgreich registriert. Sie können sich jetzt anmelden.", - "COMPLETE_TYPE2" => "Sie haben sich erfolgreich registriert. Sie erhalten in Kürze eine Bestätigungs-E-Mail mit einem Link zur Aktivierung Ihres Kontos. Sie können sich nicht anmelden, bis Sie diesen Schritt abgeschlossen haben.", - "DISABLED" => "Es tut uns leid, Die Registrierung des Kontos ist deaktiviert.", - "LOGOUT" => "Es tut uns leid, Sie können kein neues Konto registrieren, während Sie angemeldet sind. Bitte melden Sie sich zuerst ab.", - "WELCOME" => "Die Registrierung ist schnell und einfach." + 'RATE_LIMIT_EXCEEDED' => 'Die grenze für diese Maßnahme wurde überschritten. Sie müssen weitere {{delay}} Sekunden warten, bevor Sie einen weiteren Versuch machen dürfen.', + + 'REGISTER' => 'Registrieren', + 'REGISTER_ME' => 'Melden Sie mich an', + 'REGISTRATION' => [ + 'BROKEN' => 'Es tut uns leid, es gibt ein Problem mit unserer Registrierung. Bitte kontaktieren Sie uns direkt für Hilfe.', + 'COMPLETE_TYPE1' => 'Sie haben sich erfolgreich registriert. Sie können sich jetzt anmelden.', + 'COMPLETE_TYPE2' => 'Sie haben sich erfolgreich registriert. Sie erhalten in Kürze eine Bestätigungs-E-Mail mit einem Link zur Aktivierung Ihres Kontos. Sie können sich nicht anmelden, bis Sie diesen Schritt abgeschlossen haben.', + 'DISABLED' => 'Es tut uns leid, Die Registrierung des Kontos ist deaktiviert.', + 'LOGOUT' => 'Es tut uns leid, Sie können kein neues Konto registrieren, während Sie angemeldet sind. Bitte melden Sie sich zuerst ab.', + 'WELCOME' => 'Die Registrierung ist schnell und einfach.' ], - "REMEMBER_ME" => "Erinnere dich an mich!", - "REMEMBER_ME_ON_COMPUTER" => "Erinnere dich an mich auf diesem Computer (nicht für öffentliche Computer empfohlen)", + 'REMEMBER_ME' => 'Erinnere dich an mich!', + 'REMEMBER_ME_ON_COMPUTER' => 'Erinnere dich an mich auf diesem Computer (nicht für öffentliche Computer empfohlen)', - "SIGN_IN_HERE" => "Sie haben bereits einen Account? Melden Sie sich hier an.", - "SIGNIN" => "Anmelden", - "SIGNIN_OR_REGISTER" => "Anmelden oder registrieren", - "SIGNUP" => "Anmelden", + 'SIGN_IN_HERE' => 'Sie haben bereits einen Account? Melden Sie sich hier an.', + 'SIGNIN' => 'Anmelden', + 'SIGNIN_OR_REGISTER' => 'Anmelden oder registrieren', + 'SIGNUP' => 'Anmelden', - "TOS" => "Geschäftsbedingungen", - "TOS_AGREEMENT" => "Durch die Registrierung eines Kontos auf {{site_title}} akzeptieren Sie die Bedingungen .", - "TOS_FOR" => "Allgemeine Geschäftsbedingungen für {{title}}", + 'TOS' => 'Geschäftsbedingungen', + 'TOS_AGREEMENT' => 'Durch die Registrierung eines Kontos auf {{site_title}} akzeptieren Sie die Bedingungen .', + 'TOS_FOR' => 'Allgemeine Geschäftsbedingungen für {{title}}', - "USERNAME" => [ - "@TRANSLATION" => "Benutzername", + 'USERNAME' => [ + '@TRANSLATION' => 'Benutzername', - "CHOOSE" => "Wählen Sie einen eindeutigen Benutzernamen", - "INVALID" => "Ungültiger Benutzername", - "IN_USE" => "Benutzername {{user_name}} wird bereits verwendet.", - "NOT_AVAILABLE" => "Benutzername {{user_name}} ist nicht verfügbar. Wähle einen anderen Namen, der klicken Sie auf 'vorschlagen'." + 'CHOOSE' => 'Wählen Sie einen eindeutigen Benutzernamen', + 'INVALID' => 'Ungültiger Benutzername', + 'IN_USE' => 'Benutzername {{user_name}} wird bereits verwendet.', + 'NOT_AVAILABLE' => "Benutzername {{user_name}} ist nicht verfügbar. Wähle einen anderen Namen, der klicken Sie auf 'vorschlagen'." ], - "USER_ID_INVALID" => "Die angeforderte Benutzer-ID existiert nicht.", - "USER_OR_EMAIL_INVALID" => "Benutzername oder E-Mail-Adresse ist ungültig.", - "USER_OR_PASS_INVALID" => "Benutzername oder Passwort ist ungültig.", + 'USER_ID_INVALID' => 'Die angeforderte Benutzer-ID existiert nicht.', + 'USER_OR_EMAIL_INVALID' => 'Benutzername oder E-Mail-Adresse ist ungültig.', + 'USER_OR_PASS_INVALID' => 'Benutzername oder Passwort ist ungültig.', - "WELCOME" => "Willkommen zurück, {{first_name}}" + 'WELCOME' => 'Willkommen zurück, {{first_name}}' ]; diff --git a/app/sprinkles/account/locale/de_DE/validate.php b/app/sprinkles/account/locale/de_DE/validate.php index 30cf98ba1..c81a7334d 100644 --- a/app/sprinkles/account/locale/de_DE/validate.php +++ b/app/sprinkles/account/locale/de_DE/validate.php @@ -3,19 +3,20 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * German message token translations for the 'account' sprinkle. * - * @package userfrosting\i18n\de * @author X-Anonymous-Y * @author kevinrombach * @author splitt3r */ - return [ - "VALIDATE" => [ - "PASSWORD_MISMATCH" => "Ihr Passwort und das Bestätigungspasswort müssen übereinstimmen.", - "USERNAME" => "Benutzernamen dürfen nur aus Kleinbuchstaben, Zahlen, '.', '-' und '_' bestehen." + 'VALIDATE' => [ + 'PASSWORD_MISMATCH' => 'Ihr Passwort und das Bestätigungspasswort müssen übereinstimmen.', + 'USERNAME' => "Benutzernamen dürfen nur aus Kleinbuchstaben, Zahlen, '.', '-' und '_' bestehen." ] ]; diff --git a/app/sprinkles/account/locale/el/messages.php b/app/sprinkles/account/locale/el/messages.php new file mode 100644 index 000000000..06fc51f4d --- /dev/null +++ b/app/sprinkles/account/locale/el/messages.php @@ -0,0 +1,176 @@ + [ + "@TRANSLATION" => "Λογαριασμός", + + "ACCESS_DENIED" => "Χμμ, φαίνεται ότι δεν έχετε άδεια να το κάνετε αυτό", + + "DISABLED" => "Αυτός ο λογαριασμός έχει απενεργοποιηθεί. Επικοινωνήστε μαζί μας για περισσότερες πληροφορίες.", + + "EMAIL_UPDATED" => "Ενημερώθηκε η διεύθυνση ηλεκτρονικού ταχυδρομείου του λογαριασμού", + + "INVALID" => "Αυτός ο λογαριασμός δεν υπάρχει, μπορεί να έχει διαγραφεί. Παρακαλούμε επικοινωνήστε μαζί μας για περισσότερες πληροφορίες.", + + "MASTER_NOT_EXISTS" => "Δεν μπορείτε να καταχωρίσετε έναν λογαριασμό μέχρι να δημιουργηθεί ο κύριος λογαριασμός!", + + "MY" => "Ο λογαριασμός μου", + + "SESSION_COMPROMISED" => [ + "@TRANSLATION" => "Η συνεδρία σας έχει παραβιαστεί, θα πρέπει να αποσυνδεθείτε σε όλες τις συσκευές και στη συνέχεια, να συνδεθείτε ξανά και να βεβαιωθείτε ότι τα δεδομένα σας δεν έχουν αλλοιωθεί.", + "TITLE" => "Ο λογαριασμός σας ενδέχεται να έχει παραβιαστεί", + "TEXT" => "Κάποιος μπορεί να έχει χρησιμοποιήσει τα στοιχεία σύνδεσής σας για να συνδεθεί στον ιστόποτο. Για την ασφάλειά σας, όλες οι σύνδεσεις σας αποσυνδέθηκαν. Ελέγξτε το λογαριασμό σας για ύποπτη δραστηριότητα. Καλό είναι να αλλάξετε τον κωδικό πρόσβασής σας." + ], + "SESSION_EXPIRED" => "Η περίοδος σύνδεσης σας έχει λήξει. Συνδεθείτε ξανά.", + + "SETTINGS" => [ + "@TRANSLATION" => "Ρυθμίσεις λογαριασμού", + "DESCRIPTION" => "Ενημερώστε τις ρυθμίσεις του λογαριασμού σας, συμπεριλαμβανομένου του email, του ονόματος και του κωδικού πρόσβασης.", + "UPDATED" => "Οι ρυθμίσεις λογαριασμού ενημερώθηκαν" + ], + + "TOOLS" => "Εργαλεία λογαριασμού", + + "UNVERIFIED" => "Ο λογαριασμός σας δεν έχει ακόμη επαληθευτεί. Ελέγξτε τα μηνύματα ηλεκτρονικού ταχυδρομείου / (και τον φάκελο ανεπιθύμητης αλληλογραφίας) για τις οδηγίες ενεργοποίησης λογαριασμού.", + + "VERIFICATION" => [ + "NEW_LINK_SENT" => "Έχουμε στείλει ένα νέο σύνδεσμο επιβεβαίωσης με μήνυμα ηλεκτρονικού ταχυδρομείου στη διεύθυνση {{email}}. Ελέγξτε τους φακέλους εισερχομένων και ανεπιθύμητων μηνυμάτων για αυτό το μήνυμα ηλεκτρονικού ταχυδρομείου.", + "RESEND" => "Επανααποστολή του ηλ. μηνύματος επιβεβαίωσης", + "COMPLETE" => "Έχετε επαληθεύσει με επιτυχία το λογαριασμό σας. Μπορείτε τώρα να συνδεθείτε.", + "EMAIL" => "Παρακαλώ εισάγετε τη διεύθυνση ηλεκτρονικού ταχυδρομείου που χρησιμοποιήσατε για να εγγραφείτε και το e-mail επιβεβαίωσης θα σας αποσταλεί.", + "PAGE" => "Επανααποστολή του μήνυματος ηλ. ταχυδρομείου επιβεβαίωσης για το νέο σας λογαριασμό.", + "SEND" => "Αποστολή μηνύματος ηλ. ταχυδρομείου με τον σύνδεσμο επιβεβαίωσης για τον λογαριασμό μου", + "TOKEN_NOT_FOUND" => "Το διακριτικό ελέγχου επιβεβαίωσης δεν υπάρχει / ο λογαριασμός έχει ήδη επαληθευτεί", + ] + ], + + "EMAIL" => [ + "INVALID" => "Δεν υπάρχει λογαριασμό για το {{email}}.", + "IN_USE" => "Το email {{email}} χρησιμοποιείται ήδη.", + "VERIFICATION_REQUIRED" => "Email (απαιτείται επιβεβαίωση - χρησιμοποιήστε μια πραγματική διεύθυνση!)" + ], + + "EMAIL_OR_USERNAME" => "Όνομα χρήστη ή διεύθυνση ηλεκτρονικού ταχυδρομείου", + + "FIRST_NAME" => "Όνομα", + + "HEADER_MESSAGE_ROOT" => "YOU ARE SIGNED IN AS THE ROOT USER", + + "LAST_NAME" => "Επώνυμο", + "LOCALE" => [ + "ACCOUNT" => "Η γλώσσα και η τοπική ρύθμιση που πρέπει να χρησιμοποιήσετε για το λογαριασμό σας", + "INVALID" => "{{locale}} δεν είναι έγκυρη τοπική ρύθμιση." + ], + "LOGIN" => [ + "@TRANSLATION" => "Σύνδεση", + "ALREADY_COMPLETE" => "Έχετε ήδη συνδεθεί!", + "SOCIAL" => "Ή συνδεθείτε με", + "REQUIRED" => "Λυπούμαστε, πρέπει να είστε συνδεδεμένοι για να αποκτήσετε πρόσβαση σε αυτόν τον πόρο." + ], + "LOGOUT" => "Αποσύνδεση", + + "NAME" => "Όνομα", + "NAME_AND_EMAIL" => "Όνομα και ηλεκτρονικό ταχυδρομείο", + "PAGE" => [ + "LOGIN" => [ + "DESCRIPTION" => "Συνδεθείτε στον λογαριασμό σας {{site_name}} ή εγγραφείτε για ένα νέο λογαριασμό.", + "SUBTITLE" => "Εγγραφείτε δωρεάν ή συνδεθείτε με έναν υπάρχοντα λογαριασμό.", + "TITLE" => "Ας ξεκινήσουμε!", + ] + ], + "PASSWORD" => [ + "@TRANSLATION" => "Κωδικός πρόσβασης", + "BETWEEN" => "Μεταξύ {{min}} - {{max}} χαρακτήρων", + "CONFIRM" => "Επιβεβαίωση κωδικού πρόσβασης", + "CONFIRM_CURRENT" => "Επιβεβαιώστε τον τρέχοντα κωδικό πρόσβασης", + "CONFIRM_NEW" => "Επιβεβαίωση νέου κωδικού πρόσβασης", + "CONFIRM_NEW_EXPLAIN" => "Πληκτρολογήστε ξανά τον νέο κωδικό πρόσβασής σας", + "CONFIRM_NEW_HELP" => "Απαιτείται μόνο αν επιλέξετε νέο κωδικό πρόσβασης", + "CREATE" => [ + "@TRANSLATION" => "Δημιουργία κωδικού πρόσβασης", + "PAGE" => "Επιλέξτε έναν κωδικό πρόσβασης για το νέο σας λογαριασμό.", + "SET" => "Ορισμός κωδικού πρόσβασης και σύνδεση" + ], + "CURRENT" => "Τρέχων κωδικός πρόσβασης", + "CURRENT_EXPLAIN" => "Πρέπει να επιβεβαιώσετε τον τρέχοντα κωδικό πρόσβασης για να κάνετε αλλαγές", + "FORGOTTEN" => "Ξέχασα τον κωδικό μου", + "FORGET" => [ + "@TRANSLATION" => "Ξέχασα τον κωδικό μου", + "COULD_NOT_UPDATE" => "Δεν ήταν δυνατή η αλλαγή του κωδικού πρόσβασης", + "EMAIL" => "Παρακαλώ εισάγετε τη διεύθυνση ηλεκτρονικού ταχυδρομείου που χρησιμοποιήσατε για να εγγραφείτε. Θα σας αποσταλεί ένας σύνδεσμος με οδηγίες για την επαναφορά του κωδικού πρόσβασής σας.", + "EMAIL_SEND" => "Σύνδεσμος επαναφοράς κωδικού πρόσβασης ηλεκτρονικού ταχυδρομείου", + "INVALID" => "Αυτό το αίτημα επαναφοράς κωδικού πρόσβασης δεν βρέθηκε ή έχει λήξει.", + "PAGE" => "Λήψη συνδέσμου για επαναφορά του κωδικού πρόσβασής σας", + "REQUEST_CANNED" => "Ακυρώθηκε το αίτημα επαναφοράς κωδικού πρόσβασης.", + "REQUEST_SENT" => "Αν το μήνυμα ηλεκτρονικού ταχυδρομείου {{email}} αντιστοιχεί σε ένα λογαριασμό στο σύστημά μας, ένας σύνδεσμος επαναφοράς κωδικού πρόσβασης θα αποσταλεί στο {{email}} ." + ], + + "HASH_FAILED" => "Η ενημέρωση του κωδικού πρόσβασης απέτυχε. Επικοινωνήστε με έναν διαχειριστή ιστότοπου.", + "INVALID" => "Ο τρέχων κωδικός πρόσβασης δεν ταιριάζει με αυτόν που έχουμε στην εγγραφή", + "NEW" => "Νέος κωδικός πρόσβασης", + "NOTHING_TO_UPDATE" => "Δεν μπορείτε να ενημερώσετε με τον ίδιο κωδικό πρόσβασης", + + "RESET" => [ + "@TRANSLATION" => "Επαναφορά κωδικού πρόσβασης", + "CHOOSE" => "Παρακαλώ επιλέξτε νέο κωδικό πρόσβασης για να συνεχίσετε.", + "PAGE" => "Επιλέξτε έναν νέο κωδικό πρόσβασης για το λογαριασμό σας.", + "SEND" => "Ορισμός νέου κωδικού πρόσβασης και σύνδεσης" + ], + + "UPDATED" => "Ο κωδικός πρόσβασης λογαριασμού ενημερώθηκε" + ], + + "PROFILE" => [ + "SETTINGS" => "Ρυθμίσεις προφίλ", + "UPDATED" => "Οι ρυθμίσεις του προφίλ ενημερώθηκαν" + ], + "RATE_LIMIT_EXCEEDED" => "Το όριο προσπαθειών για αυτήν τη ενέργεια έχει ξεπεραστεί. Θα πρέπει να περιμένετε {{delay}} δευτερόλεπτα πριν να σας επιτραπεί να κάνετε μια άλλη προσπάθεια.", + + "REGISTER" => "Εγγραφή", + "REGISTER_ME" => "Θέλω να εγγραφώ", + "REGISTRATION" => [ + "BROKEN" => "Λυπούμαστε, υπάρχει πρόβλημα στη διαδικασία εγγραφής στο λογαριασμό μας. Επικοινωνήστε απευθείας μαζί μας για βοήθεια.", + "COMPLETE_TYPE1" => "Έχετε εγγραφεί επιτυχώς, μπορείτε τώρα να συνδεθείτε.", + "COMPLETE_TYPE2" => "Έχετε εγγραφεί επιτυχώς. Ένας σύνδεσμος για την ενεργοποίηση του λογαριασμού σας έχει αποσταλεί στο {{email}} και δεν θα μπορείτε να συνδεθείτε μέχρι να ολοκληρώσετε αυτό το βήμα.", + "DISABLED" => "Λυπούμαστε, η εγγραφή λογαριασμών έχει απενεργοποιηθεί.", + "LOGOUT" => "Λυπούμαστε, δεν μπορείτε να εγγραφείτε για λογαριασμό ενώ είστε συνδεδεμένοι. Παρακαλώ αποσυνδεθείτε πρώτα.", + "WELCOME" => "Η εγγραφή είναι γρήγορη και απλή." + ], + "REMEMBER_ME" => "Να με θυμάσαι", + "REMEMBER_ME_ON_COMPUTER" => "Να με θυμάσαι σε αυτόν τον υπολογιστή (δεν συνιστάται για δημόσιους υπολογιστές)", + + "SIGN_IN_HERE" => "Έχετε ήδη λογαριασμό; Συνδεθείτε εδώ. ", + "SIGNIN" => "Σύνδεση", + "SIGNIN_OR_REGISTER" => "Σύνδεση ή εγγραφή", + "SIGNUP" => "Εγγραφή", + + "TOS" => "Όροι και Προϋποθέσεις", + "TOS_AGREEMENT" => "Με την εγγραφή ενός λογαριασμού στο {{site_title}}, αποδέχεστε τους όρους και προϋποθέσεις .", + "TOS_FOR" => "Όροι και προϋποθέσεις για {{title}}", + + "USERNAME" => [ + "@TRANSLATION" => "Όνομα χρήστη", + + "CHOOSE" => "Επιλέξτε ένα μοναδικό όνομα χρήστη", + "INVALID" => "Μη έγκυρο όνομα χρήστη", + "IN_USE" => "Το όνομα χρήστη {{user_name}} χρησιμοποιείται ήδη.", + "NOT_AVAILABLE" => "Το όνομα χρήστη {{user_name}} δεν είναι διαθέσιμο. Επιλέξτε ένα διαφορετικό όνομα ή κάντε κλικ στο κουμπί 'Πρόταση'." + ], + + "USER_ID_INVALID" => "Το id χρήστη που ζητήθηκε δεν υπάρχει.", + "USER_OR_EMAIL_INVALID" => "Το όνομα χρήστη ή η διεύθυνση ηλεκτρονικού ταχυδρομείου δεν είναι έγκυρη.", + "USER_OR_PASS_INVALID" => "Ο χρήστης δεν βρέθηκε ή ο κωδικός πρόσβασης δεν είναι έγκυρος.", + + "WELCOME" => "Καλωσορίσατε ξανά, {{first_name}}" +]; diff --git a/app/sprinkles/account/locale/el/validate.php b/app/sprinkles/account/locale/el/validate.php new file mode 100644 index 000000000..2a8a79778 --- /dev/null +++ b/app/sprinkles/account/locale/el/validate.php @@ -0,0 +1,20 @@ + [ + "PASSWORD_MISMATCH" => "Ο κωδικός πρόσβασής σας και ο κωδικός επιβεβαίωσης πρέπει να ταιριάζουν", + "USERNAME" => "Το όνομα χρήστη μπορεί να αποτελείται μόνο από πεζά γράμματα, αριθμούς, '.', '-', and '_'." + ] +]; diff --git a/app/sprinkles/account/locale/en_US/messages.php b/app/sprinkles/account/locale/en_US/messages.php index 17d7582be..998856f32 100644 --- a/app/sprinkles/account/locale/en_US/messages.php +++ b/app/sprinkles/account/locale/en_US/messages.php @@ -3,181 +3,182 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * US English message token translations for the 'account' sprinkle. * - * @package userfrosting\i18n\en_US * @author Alexander Weissman */ - return [ - "ACCOUNT" => [ - "@TRANSLATION" => "Account", + 'ACCOUNT' => [ + '@TRANSLATION' => 'Account', - "ACCESS_DENIED" => "Hmm, looks like you don't have permission to do that.", + 'ACCESS_DENIED' => "Hmm, looks like you don't have permission to do that.", - "DISABLED" => "This account has been disabled. Please contact us for more information.", + 'DISABLED' => 'This account has been disabled. Please contact us for more information.', - "EMAIL_UPDATED" => "Account email updated", + 'EMAIL_UPDATED' => 'Account email updated', - "INVALID" => "This account does not exist. It may have been deleted. Please contact us for more information.", + 'INVALID' => 'This account does not exist. It may have been deleted. Please contact us for more information.', - "MASTER_NOT_EXISTS" => "You cannot register an account until the master account has been created!", - "MY" => "My Account", + 'MASTER_NOT_EXISTS' => 'You cannot register an account until the master account has been created!', + 'MY' => 'My Account', - "SESSION_COMPROMISED" => [ - "@TRANSLATION" => "Your session has been compromised. You should log out on all devices, then log back in and make sure that your data has not been tampered with.", - "TITLE" => "Your account may have been compromised", - "TEXT" => "Someone may have used your login information to acccess this page. For your safety, all sessions were logged out. Please log in and check your account for suspicious activity. You may also wish to change your password." + 'SESSION_COMPROMISED' => [ + '@TRANSLATION' => 'Your session has been compromised. You should log out on all devices, then log back in and make sure that your data has not been tampered with.', + 'TITLE' => 'Your account may have been compromised', + 'TEXT' => 'Someone may have used your login information to acccess this page. For your safety, all sessions were logged out. Please log in and check your account for suspicious activity. You may also wish to change your password.' ], - "SESSION_EXPIRED" => "Your session has expired. Please sign in again.", + 'SESSION_EXPIRED' => 'Your session has expired. Please sign in again.', - "SETTINGS" => [ - "@TRANSLATION" => "Account settings", - "DESCRIPTION" => "Update your account settings, including email, name, and password.", - "UPDATED" => "Account settings updated" + 'SETTINGS' => [ + '@TRANSLATION' => 'Account settings', + 'DESCRIPTION' => 'Update your account settings, including email, name, and password.', + 'UPDATED' => 'Account settings updated' ], - "TOOLS" => "Account tools", + 'TOOLS' => 'Account tools', - "UNVERIFIED" => "Your account has not yet been verified. Check your emails / spam folder for account activation instructions.", + 'UNVERIFIED' => 'Your account has not yet been verified. Check your emails / spam folder for account activation instructions.', - "VERIFICATION" => [ - "NEW_LINK_SENT" => "We have emailed a new verification link to {{email}}. Please check your inbox and spam folders for this email.", - "RESEND" => "Resend verification email", - "COMPLETE" => "You have successfully verified your account. You can now login.", - "EMAIL" => "Please enter the email address you used to sign up, and your verification email will be resent.", - "PAGE" => "Resend the verification email for your new account.", - "SEND" => "Email the verification link for my account", - "TOKEN_NOT_FOUND" => "Verification token does not exist / Account is already verified", + 'VERIFICATION' => [ + 'NEW_LINK_SENT' => 'We have emailed a new verification link to {{email}}. Please check your inbox and spam folders for this email.', + 'RESEND' => 'Resend verification email', + 'COMPLETE' => 'You have successfully verified your account. You can now login.', + 'EMAIL' => 'Please enter the email address you used to sign up, and your verification email will be resent.', + 'PAGE' => 'Resend the verification email for your new account.', + 'SEND' => 'Email the verification link for my account', + 'TOKEN_NOT_FOUND' => 'Verification token does not exist / Account is already verified', ] ], - "EMAIL" => [ - "INVALID" => "There is no account for {{email}}.", - "IN_USE" => "Email {{email}} is already in use.", - "VERIFICATION_REQUIRED" => "Email (verification required - use a real address!)" + 'EMAIL' => [ + 'INVALID' => 'There is no account for {{email}}.', + 'IN_USE' => 'Email {{email}} is already in use.', + 'VERIFICATION_REQUIRED' => 'Email (verification required - use a real address!)' ], - "EMAIL_OR_USERNAME" => "Username or email address", + 'EMAIL_OR_USERNAME' => 'Username or email address', - "FIRST_NAME" => "First name", + 'FIRST_NAME' => 'First name', - "HEADER_MESSAGE_ROOT" => "YOU ARE SIGNED IN AS THE ROOT USER", + 'HEADER_MESSAGE_ROOT' => 'YOU ARE SIGNED IN AS THE ROOT USER', - "LAST_NAME" => "Last name", - "LOCALE" => [ - "ACCOUNT" => "The language and locale to use for your account", - "INVALID" => "{{locale}} is not a valid locale." + 'LAST_NAME' => 'Last name', + 'LOCALE' => [ + 'ACCOUNT' => 'The language and locale to use for your account', + 'INVALID' => '{{locale}} is not a valid locale.' ], - "LOGIN" => [ - "@TRANSLATION" => "Login", - "ALREADY_COMPLETE" => "You are already logged in!", - "SOCIAL" => "Or login with", - "REQUIRED" => "Sorry, you must be logged in to access this resource." + 'LOGIN' => [ + '@TRANSLATION' => 'Login', + 'ALREADY_COMPLETE' => 'You are already logged in!', + 'SOCIAL' => 'Or login with', + 'REQUIRED' => 'Sorry, you must be logged in to access this resource.' ], - "LOGOUT" => "Logout", + 'LOGOUT' => 'Logout', - "NAME" => "Name", + 'NAME' => 'Name', - "NAME_AND_EMAIL" => "Name and email", + 'NAME_AND_EMAIL' => 'Name and email', - "PAGE" => [ - "LOGIN" => [ - "DESCRIPTION" => "Sign in to your {{site_name}} account, or register for a new account.", - "SUBTITLE" => "Register for free, or sign in with an existing account.", - "TITLE" => "Let's get started!", + 'PAGE' => [ + 'LOGIN' => [ + 'DESCRIPTION' => 'Sign in to your {{site_name}} account, or register for a new account.', + 'SUBTITLE' => 'Register for free, or sign in with an existing account.', + 'TITLE' => "Let's get started!", ] ], - "PASSWORD" => [ - "@TRANSLATION" => "Password", + 'PASSWORD' => [ + '@TRANSLATION' => 'Password', - "BETWEEN" => "Between {{min}}-{{max}} characters", + 'BETWEEN' => 'Between {{min}}-{{max}} characters', - "CONFIRM" => "Confirm password", - "CONFIRM_CURRENT" => "Please confirm your current password", - "CONFIRM_NEW" => "Confirm New Password", - "CONFIRM_NEW_EXPLAIN" => "Re-enter your new password", - "CONFIRM_NEW_HELP" => "Required only if selecting a new password", - "CREATE" => [ - "@TRANSLATION" => "Create Password", - "PAGE" => "Choose a password for your new account.", - "SET" => "Set Password and Sign In" + 'CONFIRM' => 'Confirm password', + 'CONFIRM_CURRENT' => 'Please confirm your current password', + 'CONFIRM_NEW' => 'Confirm New Password', + 'CONFIRM_NEW_EXPLAIN' => 'Re-enter your new password', + 'CONFIRM_NEW_HELP' => 'Required only if selecting a new password', + 'CREATE' => [ + '@TRANSLATION' => 'Create Password', + 'PAGE' => 'Choose a password for your new account.', + 'SET' => 'Set Password and Sign In' ], - "CURRENT" => "Current Password", - "CURRENT_EXPLAIN" => "You must confirm your current password to make changes", - - "FORGOTTEN" => "Forgotten Password", - "FORGET" => [ - "@TRANSLATION" => "I forgot my password", - - "COULD_NOT_UPDATE" => "Couldn't update password.", - "EMAIL" => "Please enter the email address you used to sign up. A link with instructions to reset your password will be emailed to you.", - "EMAIL_SEND" => "Email Password Reset Link", - "INVALID" => "This password reset request could not be found, or has expired. Please try resubmitting your request.", - "PAGE" => "Get a link to reset your password.", - "REQUEST_CANNED" => "Lost password request cancelled.", - "REQUEST_SENT" => "If the email {{email}} matches an account in our system, a password reset link will be sent to {{email}}." + 'CURRENT' => 'Current Password', + 'CURRENT_EXPLAIN' => 'You must confirm your current password to make changes', + + 'FORGOTTEN' => 'Forgotten Password', + 'FORGET' => [ + '@TRANSLATION' => 'I forgot my password', + + 'COULD_NOT_UPDATE' => "Couldn't update password.", + 'EMAIL' => 'Please enter the email address you used to sign up. A link with instructions to reset your password will be emailed to you.', + 'EMAIL_SEND' => 'Email Password Reset Link', + 'INVALID' => 'This password reset request could not be found, or has expired. Please try resubmitting your request.', + 'PAGE' => 'Get a link to reset your password.', + 'REQUEST_CANNED' => 'Lost password request cancelled.', + 'REQUEST_SENT' => 'If the email {{email}} matches an account in our system, a password reset link will be sent to {{email}}.' ], - "HASH_FAILED" => "Password hashing failed. Please contact a site administrator.", - "INVALID" => "Current password doesn't match the one we have on record", - "NEW" => "New Password", - "NOTHING_TO_UPDATE" => "You cannot update with the same password", + 'HASH_FAILED' => 'Password hashing failed. Please contact a site administrator.', + 'INVALID' => "Current password doesn't match the one we have on record", + 'NEW' => 'New Password', + 'NOTHING_TO_UPDATE' => 'You cannot update with the same password', - "RESET" => [ - "@TRANSLATION" => "Reset Password", - "CHOOSE" => "Please choose a new password to continue.", - "PAGE" => "Choose a new password for your account.", - "SEND" => "Set New Password and Sign In" + 'RESET' => [ + '@TRANSLATION' => 'Reset Password', + 'CHOOSE' => 'Please choose a new password to continue.', + 'PAGE' => 'Choose a new password for your account.', + 'SEND' => 'Set New Password and Sign In' ], - "UPDATED" => "Account password updated" + 'UPDATED' => 'Account password updated' ], - "PROFILE" => [ - "SETTINGS" => "Profile settings", - "UPDATED" => "Profile settings updated" + 'PROFILE' => [ + 'SETTINGS' => 'Profile settings', + 'UPDATED' => 'Profile settings updated' ], - "RATE_LIMIT_EXCEEDED" => "The rate limit for this action has been exceeded. You must wait another {{delay}} seconds before you will be allowed to make another attempt.", - - "REGISTER" => "Register", - "REGISTER_ME" => "Sign me up", - "REGISTRATION" => [ - "BROKEN" => "We're sorry, there is a problem with our account registration process. Please contact us directly for assistance.", - "COMPLETE_TYPE1" => "You have successfully registered. You can now sign in.", - "COMPLETE_TYPE2" => "You have successfully registered. A link to activate your account has been sent to {{email}}. You will not be able to sign in until you complete this step.", - "DISABLED" => "We're sorry, account registration has been disabled.", - "LOGOUT" => "I'm sorry, you cannot register for an account while logged in. Please log out first.", - "WELCOME" => "Registration is fast and simple." + 'RATE_LIMIT_EXCEEDED' => 'The rate limit for this action has been exceeded. You must wait another {{delay}} seconds before you will be allowed to make another attempt.', + + 'REGISTER' => 'Register', + 'REGISTER_ME' => 'Sign me up', + 'REGISTRATION' => [ + 'BROKEN' => "We're sorry, there is a problem with our account registration process. Please contact us directly for assistance.", + 'COMPLETE_TYPE1' => 'You have successfully registered. You can now sign in.', + 'COMPLETE_TYPE2' => 'You have successfully registered. A link to activate your account has been sent to {{email}}. You will not be able to sign in until you complete this step.', + 'DISABLED' => "We're sorry, account registration has been disabled.", + 'LOGOUT' => "I'm sorry, you cannot register for an account while logged in. Please log out first.", + 'WELCOME' => 'Registration is fast and simple.' ], - "REMEMBER_ME" => "Keep me signed in", - "REMEMBER_ME_ON_COMPUTER" => "Remember me on this computer (not recommended for public computers)", + 'REMEMBER_ME' => 'Keep me signed in', + 'REMEMBER_ME_ON_COMPUTER' => 'Remember me on this computer (not recommended for public computers)', - "SIGN_IN_HERE" => "Already have an account? Sign in here.", - "SIGNIN" => "Sign in", - "SIGNIN_OR_REGISTER" => "Sign in or register", - "SIGNUP" => "Sign Up", + 'SIGN_IN_HERE' => 'Already have an account? Sign in here.', + 'SIGNIN' => 'Sign in', + 'SIGNIN_OR_REGISTER' => 'Sign in or register', + 'SIGNUP' => 'Sign Up', - "TOS" => "Terms and Conditions", - "TOS_AGREEMENT" => "By registering an account with {{site_title}}, you accept the terms and conditions.", - "TOS_FOR" => "Terms and Conditions for {{title}}", + 'TOS' => 'Terms and Conditions', + 'TOS_AGREEMENT' => 'By registering an account with {{site_title}}, you accept the terms and conditions.', + 'TOS_FOR' => 'Terms and Conditions for {{title}}', - "USERNAME" => [ - "@TRANSLATION" => "Username", + 'USERNAME' => [ + '@TRANSLATION' => 'Username', - "CHOOSE" => "Choose a unique username", - "INVALID" => "Invalid username", - "IN_USE" => "Username {{user_name}} is already in use.", - "NOT_AVAILABLE" => "Username {{user_name}} is not available. Choose a different name, or click 'suggest'." + 'CHOOSE' => 'Choose a unique username', + 'INVALID' => 'Invalid username', + 'IN_USE' => 'Username {{user_name}} is already in use.', + 'NOT_AVAILABLE' => "Username {{user_name}} is not available. Choose a different name, or click 'suggest'." ], - "USER_ID_INVALID" => "The requested user id does not exist.", - "USER_OR_EMAIL_INVALID" => "Username or email address is invalid.", - "USER_OR_PASS_INVALID" => "User not found or password is invalid.", + 'USER_ID_INVALID' => 'The requested user id does not exist.', + 'USER_OR_EMAIL_INVALID' => 'Username or email address is invalid.', + 'USER_OR_PASS_INVALID' => 'User not found or password is invalid.', - "WELCOME" => "Welcome back, {{first_name}}" + 'WELCOME' => 'Welcome back, {{first_name}}' ]; diff --git a/app/sprinkles/account/locale/en_US/validate.php b/app/sprinkles/account/locale/en_US/validate.php index 00c0aef6c..d91ef2a9f 100644 --- a/app/sprinkles/account/locale/en_US/validate.php +++ b/app/sprinkles/account/locale/en_US/validate.php @@ -3,17 +3,18 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * US English message token translations for the 'account' sprinkle. * - * @package userfrosting\i18n\en_US * @author Alexander Weissman */ - return [ - "VALIDATE" => [ - "PASSWORD_MISMATCH" => "Your password and confirmation password must match.", - "USERNAME" => "Username may consist only of lowercase letters, numbers, '.', '-', and '_'." + 'VALIDATE' => [ + 'PASSWORD_MISMATCH' => 'Your password and confirmation password must match.', + 'USERNAME' => "Username may consist only of lowercase letters, numbers, '.', '-', and '_'." ] ]; diff --git a/app/sprinkles/account/locale/es_ES/messages.php b/app/sprinkles/account/locale/es_ES/messages.php index e60c7d172..01503acc8 100755 --- a/app/sprinkles/account/locale/es_ES/messages.php +++ b/app/sprinkles/account/locale/es_ES/messages.php @@ -3,187 +3,187 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * Spanish message token translations for the 'account' sprinkle. * - * @package userfrosting\i18n\es_ES * @author rafa31gz */ - return [ - "ACCOUNT" => [ - "@TRANSLATION" => "Perfil", + 'ACCOUNT' => [ + '@TRANSLATION' => 'Perfil', - "ACCESS_DENIED" => "Hmm, parece que no tienes permiso para hacer eso.", + 'ACCESS_DENIED' => 'Hmm, parece que no tienes permiso para hacer eso.', - "DISABLED" => "Esta cuenta se ha inhabilitado. Por favor contáctanos para más información.", + 'DISABLED' => 'Esta cuenta se ha inhabilitado. Por favor contáctanos para más información.', - "EMAIL_UPDATED" => "Correo electrónico de la cuenta actualizado", + 'EMAIL_UPDATED' => 'Correo electrónico de la cuenta actualizado', - "INVALID" => "Esta cuenta no existe. Puede haber sido eliminado. Por favor contáctanos para más información.", + 'INVALID' => 'Esta cuenta no existe. Puede haber sido eliminado. Por favor contáctanos para más información.', - "MASTER_NOT_EXISTS" => "No puedes registrar una cuenta hasta que se haya creado la cuenta principal.", - "MY" => "Mi Perfil", + 'MASTER_NOT_EXISTS' => 'No puedes registrar una cuenta hasta que se haya creado la cuenta principal.', + 'MY' => 'Mi Perfil', - "SESSION_COMPROMISED" => [ - "@TRANSLATION" => "Tu sesión ha sido comprometida. Debes desconectarse de todos los dispositivos y, a continuación, volver a iniciar sesión y asegurarte de que sus datos no han sido manipulados.", - "TITLE" => "Es posible que tu cuenta se haya visto comprometida.", - "TEXT" => "Alguien puede haber utilizado tu información de acceso para acceder a esta página. Para tu seguridad, todas las sesiones se cerraron. ingrese y comprueba si tu actividad es sospechosa en tu cuenta. También puedes cambiar su contraseña." + 'SESSION_COMPROMISED' => [ + '@TRANSLATION' => 'Tu sesión ha sido comprometida. Debes desconectarse de todos los dispositivos y, a continuación, volver a iniciar sesión y asegurarte de que sus datos no han sido manipulados.', + 'TITLE' => 'Es posible que tu cuenta se haya visto comprometida.', + 'TEXT' => 'Alguien puede haber utilizado tu información de acceso para acceder a esta página. Para tu seguridad, todas las sesiones se cerraron. ingrese y comprueba si tu actividad es sospechosa en tu cuenta. También puedes cambiar su contraseña.' ], - "SESSION_EXPIRED" => "Tu sesión ha caducado. Inicie sesión nuevamente.", + 'SESSION_EXPIRED' => 'Tu sesión ha caducado. Inicie sesión nuevamente.', - "SETTINGS" => [ - "@TRANSLATION" => "Configuraciones de la cuenta", - "DESCRIPTION" => "Actualiza la configuración de su cuenta, incluido el correo electrónico, el nombre y la contraseña.", - "UPDATED" => "Configuración de la cuenta actualizada" + 'SETTINGS' => [ + '@TRANSLATION' => 'Configuraciones de la cuenta', + 'DESCRIPTION' => 'Actualiza la configuración de su cuenta, incluido el correo electrónico, el nombre y la contraseña.', + 'UPDATED' => 'Configuración de la cuenta actualizada' ], - "TOOLS" => "Herramientas de la cuenta", + 'TOOLS' => 'Herramientas de la cuenta', - "UNVERIFIED" => "Tu cuenta aún no se ha verificado. Revisa sus correos electrónicos / carpeta de spam para obtener instrucciones sobre la activación de la cuenta.", + 'UNVERIFIED' => 'Tu cuenta aún no se ha verificado. Revisa sus correos electrónicos / carpeta de spam para obtener instrucciones sobre la activación de la cuenta.', - "VERIFICATION" => [ - "NEW_LINK_SENT" => "Hemos enviado por correo electrónico un nuevo enlace de verificación a {{email}}. Comprueba tu bandeja de entrada y las carpetas de spam para este correo electrónico.", - "RESEND" => "Reenviar correo electrónico de verificación", - "COMPLETE" => "Has verificado correctamente su cuenta. Ahora puedes iniciar sesión.", - "EMAIL" => "Ingresa la dirección de correo electrónico que utilizaste para registrarte y tu correo electrónico de verificación será enviado de nuevo.", - "PAGE" => "Vuelve a enviar el correo electrónico de verificación de tu nueva cuenta.", - "SEND" => "Reenviar correo de verificación", - "TOKEN_NOT_FOUND" => "El token de verificación no existe / La cuenta ya está verificada", + 'VERIFICATION' => [ + 'NEW_LINK_SENT' => 'Hemos enviado por correo electrónico un nuevo enlace de verificación a {{email}}. Comprueba tu bandeja de entrada y las carpetas de spam para este correo electrónico.', + 'RESEND' => 'Reenviar correo electrónico de verificación', + 'COMPLETE' => 'Has verificado correctamente su cuenta. Ahora puedes iniciar sesión.', + 'EMAIL' => 'Ingresa la dirección de correo electrónico que utilizaste para registrarte y tu correo electrónico de verificación será enviado de nuevo.', + 'PAGE' => 'Vuelve a enviar el correo electrónico de verificación de tu nueva cuenta.', + 'SEND' => 'Reenviar correo de verificación', + 'TOKEN_NOT_FOUND' => 'El token de verificación no existe / La cuenta ya está verificada', ] ], - "EMAIL" => [ - "INVALID" => "No hay cuenta para {{email}} .", - "IN_USE" => "El correo electrónico {{email}} ya está en uso.", - "VERIFICATION_REQUIRED" => "Correo electrónico (se requiere verificación - ¡usa una dirección real!)" + 'EMAIL' => [ + 'INVALID' => 'No hay cuenta para {{email}} .', + 'IN_USE' => 'El correo electrónico {{email}} ya está en uso.', + 'VERIFICATION_REQUIRED' => 'Correo electrónico (se requiere verificación - ¡usa una dirección real!)' ], - "EMAIL_OR_USERNAME" => "Nombre de usuario o dirección de correo electrónico", + 'EMAIL_OR_USERNAME' => 'Nombre de usuario o dirección de correo electrónico', - "FIRST_NAME" => "Nombre", + 'FIRST_NAME' => 'Nombre', - "HEADER_MESSAGE_ROOT" => "USTED HA INGRESADO COMO USUARIO ROOT", + 'HEADER_MESSAGE_ROOT' => 'USTED HA INGRESADO COMO USUARIO ROOT', - "LAST_NAME" => "Apellidos", + 'LAST_NAME' => 'Apellidos', - "LOCALE" => [ - "ACCOUNT" => "El idioma y la configuración regional para utilizar en tu cuenta", - "INVALID" => "{{locale}} no es un idioma válido." + 'LOCALE' => [ + 'ACCOUNT' => 'El idioma y la configuración regional para utilizar en tu cuenta', + 'INVALID' => '{{locale}} no es un idioma válido.' ], - "LOGIN" => [ - "@TRANSLATION" => "Acceder", - "ALREADY_COMPLETE" => "¡Ya te has autentificado!", - "SOCIAL" => "O ingresa con", - "REQUIRED" => "Lo sentimos, debes iniciar sesión para acceder a este recurso." + 'LOGIN' => [ + '@TRANSLATION' => 'Acceder', + 'ALREADY_COMPLETE' => '¡Ya te has autentificado!', + 'SOCIAL' => 'O ingresa con', + 'REQUIRED' => 'Lo sentimos, debes iniciar sesión para acceder a este recurso.' ], - "LOGOUT" => "Cerrar sesión", + 'LOGOUT' => 'Cerrar sesión', - "NAME" => "Nombre", + 'NAME' => 'Nombre', - "NAME_AND_EMAIL" => "Nombre y correo electrónico", + 'NAME_AND_EMAIL' => 'Nombre y correo electrónico', - "PAGE" => [ - "LOGIN" => [ - "DESCRIPTION" => "Inicia sesión en tu cuenta de {{site_name}} o regístrate para obtener una nueva cuenta.", - "SUBTITLE" => "Regístrate gratis o inicia sesión con una cuenta existente.", - "TITLE" => "¡Empecemos!", + 'PAGE' => [ + 'LOGIN' => [ + 'DESCRIPTION' => 'Inicia sesión en tu cuenta de {{site_name}} o regístrate para obtener una nueva cuenta.', + 'SUBTITLE' => 'Regístrate gratis o inicia sesión con una cuenta existente.', + 'TITLE' => '¡Empecemos!', ] ], - "PASSWORD" => [ - "@TRANSLATION" => "Contraseña", + 'PASSWORD' => [ + '@TRANSLATION' => 'Contraseña', - "BETWEEN" => "Entre {{min}} - {{max}} (recomendado 12)", + 'BETWEEN' => 'Entre {{min}} - {{max}} (recomendado 12)', - "CONFIRM" => "Confirmar contraseña", - "CONFIRM_CURRENT" => "Por favor, confirma tu contraseña actual", - "CONFIRM_NEW" => "Confirmar nueva contraseña", - "CONFIRM_NEW_EXPLAIN" => "Vuelve a ingresar tu nueva contraseña", - "CONFIRM_NEW_HELP" => "Sólo se requiere si se selecciona una nueva contraseña", - "CREATE" => [ - "@TRANSLATION" => "Crear contraseña", - "PAGE" => "Elije una contraseña para su nueva cuenta.", - "SET" => "Establecer contraseña e iniciar sesión" + 'CONFIRM' => 'Confirmar contraseña', + 'CONFIRM_CURRENT' => 'Por favor, confirma tu contraseña actual', + 'CONFIRM_NEW' => 'Confirmar nueva contraseña', + 'CONFIRM_NEW_EXPLAIN' => 'Vuelve a ingresar tu nueva contraseña', + 'CONFIRM_NEW_HELP' => 'Sólo se requiere si se selecciona una nueva contraseña', + 'CREATE' => [ + '@TRANSLATION' => 'Crear contraseña', + 'PAGE' => 'Elije una contraseña para su nueva cuenta.', + 'SET' => 'Establecer contraseña e iniciar sesión' ], - "CURRENT" => "Contraseña actual", - "CURRENT_EXPLAIN" => "Debes confirmar tu contraseña actual para realizar cambios", - - "FORGOTTEN" => "Contraseña olvidada", - "FORGET" => [ - "@TRANSLATION" => "Olvidé mi contraseña", - - "COULD_NOT_UPDATE" => "No se pudo actualizar la contraseña.", - "EMAIL" => "Introduce la dirección de correo electrónico que utilizaste para registrarte. Se te enviará por correo electrónico un enlace con las instrucciones para restablecer tu contraseña.", - "EMAIL_SEND" => "Contraseña de correo electrónico Restablecer enlace", - "INVALID" => "No se pudo encontrar esta solicitud de restablecimiento de contraseña o ha caducado. Intenta volver a enviar tu solicitud .", - "PAGE" => "Obtén un enlace para restablecer tu contraseña.", - "REQUEST_CANNED" => "Se ha cancelado la solicitud de contraseña perdida.", - "REQUEST_SENT" => "Se ha enviado un enlace de restablecimiento de contraseña a {{email}} ." + 'CURRENT' => 'Contraseña actual', + 'CURRENT_EXPLAIN' => 'Debes confirmar tu contraseña actual para realizar cambios', + + 'FORGOTTEN' => 'Contraseña olvidada', + 'FORGET' => [ + '@TRANSLATION' => 'Olvidé mi contraseña', + + 'COULD_NOT_UPDATE' => 'No se pudo actualizar la contraseña.', + 'EMAIL' => 'Introduce la dirección de correo electrónico que utilizaste para registrarte. Se te enviará por correo electrónico un enlace con las instrucciones para restablecer tu contraseña.', + 'EMAIL_SEND' => 'Contraseña de correo electrónico Restablecer enlace', + 'INVALID' => 'No se pudo encontrar esta solicitud de restablecimiento de contraseña o ha caducado. Intenta volver a enviar tu solicitud .', + 'PAGE' => 'Obtén un enlace para restablecer tu contraseña.', + 'REQUEST_CANNED' => 'Se ha cancelado la solicitud de contraseña perdida.', + 'REQUEST_SENT' => 'Se ha enviado un enlace de restablecimiento de contraseña a {{email}} .' ], - "RESET" => [ - "@TRANSLATION" => "Restablecer la contraseña", - "CHOOSE" => "Por favor, elije una nueva contraseña para continuar.", - "PAGE" => "Elige una nueva contraseña para tu cuenta.", - "SEND" => "Establecer nueva contraseña e iniciar sesión" + 'RESET' => [ + '@TRANSLATION' => 'Restablecer la contraseña', + 'CHOOSE' => 'Por favor, elije una nueva contraseña para continuar.', + 'PAGE' => 'Elige una nueva contraseña para tu cuenta.', + 'SEND' => 'Establecer nueva contraseña e iniciar sesión' ], - "HASH_FAILED" => "El hash de la contraseña ha fallado. Ponte en contacto con un administrador del sitio.", - "INVALID" => "La contraseña actual no coincide con la que tenemos registrada", - "NEW" => "Nueva contraseña", - "NOTHING_TO_UPDATE" => "No se puede actualizar con la misma contraseña", - "UPDATED" => "Contraseña de la cuenta actualizada" + 'HASH_FAILED' => 'El hash de la contraseña ha fallado. Ponte en contacto con un administrador del sitio.', + 'INVALID' => 'La contraseña actual no coincide con la que tenemos registrada', + 'NEW' => 'Nueva contraseña', + 'NOTHING_TO_UPDATE' => 'No se puede actualizar con la misma contraseña', + 'UPDATED' => 'Contraseña de la cuenta actualizada' ], - "PROFILE" => [ - "SETTINGS" => "Configuración de perfil", - "UPDATED" => "Configuración del perfil actualizada" + 'PROFILE' => [ + 'SETTINGS' => 'Configuración de perfil', + 'UPDATED' => 'Configuración del perfil actualizada' ], - "RATE_LIMIT_EXCEEDED" => "Se ha superado el límite de velocidad para esta acción. Debe esperar otro {{delay}} segundos antes de que se le permita hacer otro intento.", - - "REGISTER" => "Registro", - "REGISTER_ME" => "Inscríbeme", - "REGISTRATION" => [ - "BROKEN" => "Lo sentimos, hay un problema con nuestro proceso de registro de cuenta. Ponte en contacto con nosotros directamente para obtener ayuda.", - "COMPLETE_TYPE1" => "Te has registrado exitosamente. Ahora puedes iniciar sesión.", - "COMPLETE_TYPE2" => "Te has registrado exitosamente. Se ha enviado un enlace para activar tu cuenta a {{email}} . No podrás iniciar sesión hasta que complete este paso.", - "DISABLED" => "Lo sentimos, el registro de cuenta se ha deshabilitado.", - "LOGOUT" => "Lo siento, no puedes registrarte para una cuenta mientras está conectado. Por favor, cierra la sesión primero.", - "WELCOME" => "El registro es rápido y sencillo." + 'RATE_LIMIT_EXCEEDED' => 'Se ha superado el límite de velocidad para esta acción. Debe esperar otro {{delay}} segundos antes de que se le permita hacer otro intento.', + + 'REGISTER' => 'Registro', + 'REGISTER_ME' => 'Inscríbeme', + 'REGISTRATION' => [ + 'BROKEN' => 'Lo sentimos, hay un problema con nuestro proceso de registro de cuenta. Ponte en contacto con nosotros directamente para obtener ayuda.', + 'COMPLETE_TYPE1' => 'Te has registrado exitosamente. Ahora puedes iniciar sesión.', + 'COMPLETE_TYPE2' => 'Te has registrado exitosamente. Se ha enviado un enlace para activar tu cuenta a {{email}} . No podrás iniciar sesión hasta que complete este paso.', + 'DISABLED' => 'Lo sentimos, el registro de cuenta se ha deshabilitado.', + 'LOGOUT' => 'Lo siento, no puedes registrarte para una cuenta mientras está conectado. Por favor, cierra la sesión primero.', + 'WELCOME' => 'El registro es rápido y sencillo.' ], - - "REMEMBER_ME" => "¡Recuérdame!", - "REMEMBER_ME_ON_COMPUTER" => "Recuérdame en este ordenador (no se recomienda para ordenadores públicos)", - "SIGNIN" => "Iniciar sesión", - "SIGNIN_OR_REGISTER" => "Ingresa o Registro", - "SIGNUP" => "Regístrate", - "SUGGEST" => "Sugerencia", - "HAVE_ACCOUNT" => "¿Ya tienes una cuenta?", - "SIGN_IN_HERE"=> "¿Ya tienes una cuenta? Acceda aquí. ", + 'REMEMBER_ME' => '¡Recuérdame!', + 'REMEMBER_ME_ON_COMPUTER' => 'Recuérdame en este ordenador (no se recomienda para ordenadores públicos)', + 'SIGNIN' => 'Iniciar sesión', + 'SIGNIN_OR_REGISTER' => 'Ingresa o Registro', + 'SIGNUP' => 'Regístrate', + 'SUGGEST' => 'Sugerencia', + 'HAVE_ACCOUNT' => '¿Ya tienes una cuenta?', + 'SIGN_IN_HERE' => '¿Ya tienes una cuenta? Acceda aquí. ', - "TOS" => "Términos y Condiciones", - "TOS_AGREEMENT" => "Al registrar una cuenta con {{site_title}}, acepta los términos y condiciones .", - "TOS_FOR" => "Términos y condiciones para {{title}}", + 'TOS' => 'Términos y Condiciones', + 'TOS_AGREEMENT' => 'Al registrar una cuenta con {{site_title}}, acepta los términos y condiciones .', + 'TOS_FOR' => 'Términos y condiciones para {{title}}', - "USERNAME" => [ - "@TRANSLATION" => "Nombre de usuario", + 'USERNAME' => [ + '@TRANSLATION' => 'Nombre de usuario', - "CHOOSE" => "Elige un nombre de usuario único", - "INVALID" => "Nombre de usuario no válido", - "IN_USE" => "El nombre de usuario {{user_name}} ya está en uso.", - "NOT_AVAILABLE" => "El nombre de usuario {{user_name}} no está disponible. Elija otro nombre o haga clic en \"sugerir\"." + 'CHOOSE' => 'Elige un nombre de usuario único', + 'INVALID' => 'Nombre de usuario no válido', + 'IN_USE' => 'El nombre de usuario {{user_name}} ya está en uso.', + 'NOT_AVAILABLE' => 'El nombre de usuario {{user_name}} no está disponible. Elija otro nombre o haga clic en "sugerir".' ], - "USER_ID_INVALID" => "El ID de usuario solicitado no existe.", - "USER_OR_EMAIL_INVALID" => "El nombre de usuario o la dirección de correo electrónico no son válidos.", - "USER_OR_PASS_INVALID" => "Usuario no encontrado o la contraseña no es válida.", + 'USER_ID_INVALID' => 'El ID de usuario solicitado no existe.', + 'USER_OR_EMAIL_INVALID' => 'El nombre de usuario o la dirección de correo electrónico no son válidos.', + 'USER_OR_PASS_INVALID' => 'Usuario no encontrado o la contraseña no es válida.', - "WELCOME" => "Bienvenido de nuevo, {{first_name}}" + 'WELCOME' => 'Bienvenido de nuevo, {{first_name}}' ]; diff --git a/app/sprinkles/account/locale/es_ES/validate.php b/app/sprinkles/account/locale/es_ES/validate.php index bf4f27e0f..246034eff 100755 --- a/app/sprinkles/account/locale/es_ES/validate.php +++ b/app/sprinkles/account/locale/es_ES/validate.php @@ -3,17 +3,18 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * Spanish message token translations for the 'account' sprinkle. * - * @package userfrosting\i18n\es_ES * @author rafa31gz */ - return [ - "VALIDATE" => [ - "PASSWORD_MISMATCH" => "Tu contraseña y contraseña de confirmación deben coincidir.", - "USERNAME" => "El nombre de usuario puede consistir sólo en letras minúsculas, números, '.', '-' y '_'." + 'VALIDATE' => [ + 'PASSWORD_MISMATCH' => 'Tu contraseña y contraseña de confirmación deben coincidir.', + 'USERNAME' => "El nombre de usuario puede consistir sólo en letras minúsculas, números, '.', '-' y '_'." ] ]; diff --git a/app/sprinkles/account/locale/fa/messages.php b/app/sprinkles/account/locale/fa/messages.php index 22623babf..0c2d8c789 100644 --- a/app/sprinkles/account/locale/fa/messages.php +++ b/app/sprinkles/account/locale/fa/messages.php @@ -1,178 +1,178 @@ [ - "@TRANSLATION" => "حساب", + 'ACCOUNT' => [ + '@TRANSLATION' => 'حساب', - "ACCESS_DENIED" => "به نظر می آید که شما اجازه انجام این کار را ندارید", + 'ACCESS_DENIED' => 'به نظر می آید که شما اجازه انجام این کار را ندارید', - "DISABLED" => "این حساب کاربری غیر فعال شده است. برای اطلاعات بیشتر، لطفا با ما تماس برقرار کنید.", + 'DISABLED' => 'این حساب کاربری غیر فعال شده است. برای اطلاعات بیشتر، لطفا با ما تماس برقرار کنید.', - "EMAIL_UPDATED" => "آدرس پست الکترونیکی حساب، به روز رسانی شد", + 'EMAIL_UPDATED' => 'آدرس پست الکترونیکی حساب، به روز رسانی شد', - "INVALID" => "این اکانت موجود نیست. ممکن است که حذف شده باشد. برای اطلاعات بیشتر، لطفا با ما تماس برقرار کنید.", + 'INVALID' => 'این اکانت موجود نیست. ممکن است که حذف شده باشد. برای اطلاعات بیشتر، لطفا با ما تماس برقرار کنید.', - "MASTER_NOT_EXISTS" => "تا زمانی که حساب اصلی ساخته نشده است نمیتوانید حساب کاربری جدیدی بسازید.", - "MY" => "حساب من", + 'MASTER_NOT_EXISTS' => 'تا زمانی که حساب اصلی ساخته نشده است نمیتوانید حساب کاربری جدیدی بسازید.', + 'MY' => 'حساب من', - "SESSION_COMPROMISED" => "ممکن است سژن شما مورد حمله واقع شده باشد. بهتر است با همه دستگاه های خود از وب سایت خارج شوید و دوباره وارد شوید. همچنین توجه بفرمایید که اطلاعات حسابتان، مورد حمله واقع نشده باشد. ", - "SESSION_COMPROMISED_TITLE" => "ممکن است که اکانت شما مورد حمله واقع شده باشد", - "SESSION_EXPIRED" => "سژن شما به پایان رسیده است. لطفا دوباره وارد شوید.", + 'SESSION_COMPROMISED' => 'ممکن است سژن شما مورد حمله واقع شده باشد. بهتر است با همه دستگاه های خود از وب سایت خارج شوید و دوباره وارد شوید. همچنین توجه بفرمایید که اطلاعات حسابتان، مورد حمله واقع نشده باشد. ', + 'SESSION_COMPROMISED_TITLE' => 'ممکن است که اکانت شما مورد حمله واقع شده باشد', + 'SESSION_EXPIRED' => 'سژن شما به پایان رسیده است. لطفا دوباره وارد شوید.', - "SETTINGS" => [ - "@TRANSLATION" => "تنظیمات حساب", - "DESCRIPTION" => "اطلاعات حسابتان یعنی پست الکترونیکی،نام و گذرواژه خود را به روز رسانی کنید", - "UPDATED" => "تنظیمات حساب به روز رسانی شد" + 'SETTINGS' => [ + '@TRANSLATION' => 'تنظیمات حساب', + 'DESCRIPTION' => 'اطلاعات حسابتان یعنی پست الکترونیکی،نام و گذرواژه خود را به روز رسانی کنید', + 'UPDATED' => 'تنظیمات حساب به روز رسانی شد' ], - "TOOLS" => "ابزار حساب", + 'TOOLS' => 'ابزار حساب', - "UNVERIFIED" => "شما هنوز آدرس پست الکترونیکی خود را فعال نکرده اید. برای فعال سازی لطفا ایمیل خود را چک کنید.", + 'UNVERIFIED' => 'شما هنوز آدرس پست الکترونیکی خود را فعال نکرده اید. برای فعال سازی لطفا ایمیل خود را چک کنید.', - "VERIFICATION" => [ - "NEW_LINK_SENT" => "لینک فعال سازی برای ایمیل {{email}} ارسال شد. لطفا ایمیل خود را چک کنید.", - "RESEND" => "ارسال دوباره ایمیل فعال سازی", - "COMPLETE" => "شما پست الکترونیکی خود را با موفقیت فعال سازی کردید. حالا می توانید وارد شوید.", - "EMAIL" => "لطفا آدرس پست الکترونیکی که با آن ثبت نام کردید وارد کنید تا ایمیل فعال سازی دوباره برایتان ارسال شود.", - "PAGE" => "ارسال دوباره ایمیل فعال سازی برای حساب جدید شما", - "SEND" => "ارسال ایمیل فعال سازی برای حساب کاربری", - "TOKEN_NOT_FOUND" => "این حساب کاربری یا قبلا فعال شده است و یا کد فعال سازی موجود نیست.", + 'VERIFICATION' => [ + 'NEW_LINK_SENT' => 'لینک فعال سازی برای ایمیل {{email}} ارسال شد. لطفا ایمیل خود را چک کنید.', + 'RESEND' => 'ارسال دوباره ایمیل فعال سازی', + 'COMPLETE' => 'شما پست الکترونیکی خود را با موفقیت فعال سازی کردید. حالا می توانید وارد شوید.', + 'EMAIL' => 'لطفا آدرس پست الکترونیکی که با آن ثبت نام کردید وارد کنید تا ایمیل فعال سازی دوباره برایتان ارسال شود.', + 'PAGE' => 'ارسال دوباره ایمیل فعال سازی برای حساب جدید شما', + 'SEND' => 'ارسال ایمیل فعال سازی برای حساب کاربری', + 'TOKEN_NOT_FOUND' => 'این حساب کاربری یا قبلا فعال شده است و یا کد فعال سازی موجود نیست.', ] ], - "EMAIL" => [ - "INVALID" => "حساب کاربری با {{email}} ثبت نشده است.", - "IN_USE" => "ایمیل {{email}} قبلا استفاده شده است", - "VERIFICATION_REQUIRED" => "آدرس پست الکترونیکی را بصورت صحیح وارد کنید" + 'EMAIL' => [ + 'INVALID' => 'حساب کاربری با {{email}} ثبت نشده است.', + 'IN_USE' => 'ایمیل {{email}} قبلا استفاده شده است', + 'VERIFICATION_REQUIRED' => 'آدرس پست الکترونیکی را بصورت صحیح وارد کنید' ], - "EMAIL_OR_USERNAME" => "نام کاربری یا آدرس پست الکترونیکی", + 'EMAIL_OR_USERNAME' => 'نام کاربری یا آدرس پست الکترونیکی', - "FIRST_NAME" => "نام", + 'FIRST_NAME' => 'نام', - "HEADER_MESSAGE_ROOT" => "شما بعنوان کاربر اصلی وارد شده اید", + 'HEADER_MESSAGE_ROOT' => 'شما بعنوان کاربر اصلی وارد شده اید', - "LAST_NAME" => "نام خانوادگی", + 'LAST_NAME' => 'نام خانوادگی', - "LOCALE" => [ - "ACCOUNT" => "زبان انتخابی برای حساب شما", - "INVALID" => "{{locale}} زبان صحیحی نیست" + 'LOCALE' => [ + 'ACCOUNT' => 'زبان انتخابی برای حساب شما', + 'INVALID' => '{{locale}} زبان صحیحی نیست' ], - "LOGIN" => [ - "@TRANSLATION" => "ورود", - "ALREADY_COMPLETE" => "شما قبلا وارد شده اید.", - "SOCIAL" => "یا با روش های زیر وارد شوید", - "REQUIRED" => "برای دیدن این صفحه لازم است که وارد شوید" + 'LOGIN' => [ + '@TRANSLATION' => 'ورود', + 'ALREADY_COMPLETE' => 'شما قبلا وارد شده اید.', + 'SOCIAL' => 'یا با روش های زیر وارد شوید', + 'REQUIRED' => 'برای دیدن این صفحه لازم است که وارد شوید' ], - "LOGOUT" => "خروج", + 'LOGOUT' => 'خروج', - "NAME" => "نام", + 'NAME' => 'نام', - "NAME_AND_EMAIL" => "نام و پست الکترونیکی", + 'NAME_AND_EMAIL' => 'نام و پست الکترونیکی', - "PAGE" => [ - "LOGIN" => [ - "DESCRIPTION" => "به حساب کاربری خود در {{site_name}} وارد شوید و یا حساب کاربری جدیدی بسازید", - "SUBTITLE" => "ثبت نام کنید و یا با حساب کاربری خود وارد شوید", - "TITLE" => "بیایید شروع کنیم!", + 'PAGE' => [ + 'LOGIN' => [ + 'DESCRIPTION' => 'به حساب کاربری خود در {{site_name}} وارد شوید و یا حساب کاربری جدیدی بسازید', + 'SUBTITLE' => 'ثبت نام کنید و یا با حساب کاربری خود وارد شوید', + 'TITLE' => 'بیایید شروع کنیم!', ] ], - "PASSWORD" => [ - "@TRANSLATION" => "گذرواژه", - - "BETWEEN" => "بین {{min}}-{{max}} حرف", - - "CONFIRM" => "رمز عبور را وارد کنید", - "CONFIRM_CURRENT" => "لطفا رمز عبور فعلی را تایید کنید", - "CONFIRM_NEW" => "رمز عبور جدید را وارد کنید", - "CONFIRM_NEW_EXPLAIN" => "رمز عبور جدید را تکرار کنید", - "CONFIRM_NEW_HELP" => "فقط زمانی لازم است که می خواهید گذرواژه جدیدی انتخاب کنید", - "CURRENT" => "گذرواژه فعلی", - "CURRENT_EXPLAIN" => "شما باید گذرواژه فعلی خود را وارد کنید تا بتوانید اطلاعات را به روز رسانی کنید", - - "FORGOTTEN" => "فراموشی گذرواژه", - "FORGET" => [ - "@TRANSLATION" => "گذرواژه خود را فراموش کرده ام", - - "COULD_NOT_UPDATE" => "گذرواژه به روز رسانی نشد", - "EMAIL" => "لطفا آدرس پست الکترونیکی که در زمان ثبت نام استفاده کردید، وارد کنید. لینک بازیابی گذرواژه برای شما ایمیل خواهد شد.", - "EMAIL_SEND" => "لینک بازیابی گذرواژه ایمیل شود", - "INVALID" => "درخواست بازیابی کذرواژه پیدا نشد و یا منقضی شده است. لطفا درخواست را دوباره ارسال کنید", - "PAGE" => "دریافت لینک بازیابی گذرواژه", - "REQUEST_CANNED" => "درخواست فراموشی گذرواژه، حذف شد.", - "REQUEST_SENT" => "ایمیل بازیابی گذرواژه به {{email}} ارسال شد." + 'PASSWORD' => [ + '@TRANSLATION' => 'گذرواژه', + + 'BETWEEN' => 'بین {{min}}-{{max}} حرف', + + 'CONFIRM' => 'رمز عبور را وارد کنید', + 'CONFIRM_CURRENT' => 'لطفا رمز عبور فعلی را تایید کنید', + 'CONFIRM_NEW' => 'رمز عبور جدید را وارد کنید', + 'CONFIRM_NEW_EXPLAIN' => 'رمز عبور جدید را تکرار کنید', + 'CONFIRM_NEW_HELP' => 'فقط زمانی لازم است که می خواهید گذرواژه جدیدی انتخاب کنید', + 'CURRENT' => 'گذرواژه فعلی', + 'CURRENT_EXPLAIN' => 'شما باید گذرواژه فعلی خود را وارد کنید تا بتوانید اطلاعات را به روز رسانی کنید', + + 'FORGOTTEN' => 'فراموشی گذرواژه', + 'FORGET' => [ + '@TRANSLATION' => 'گذرواژه خود را فراموش کرده ام', + + 'COULD_NOT_UPDATE' => 'گذرواژه به روز رسانی نشد', + 'EMAIL' => 'لطفا آدرس پست الکترونیکی که در زمان ثبت نام استفاده کردید، وارد کنید. لینک بازیابی گذرواژه برای شما ایمیل خواهد شد.', + 'EMAIL_SEND' => 'لینک بازیابی گذرواژه ایمیل شود', + 'INVALID' => 'درخواست بازیابی کذرواژه پیدا نشد و یا منقضی شده است. لطفا درخواست را دوباره ارسال کنید', + 'PAGE' => 'دریافت لینک بازیابی گذرواژه', + 'REQUEST_CANNED' => 'درخواست فراموشی گذرواژه، حذف شد.', + 'REQUEST_SENT' => 'ایمیل بازیابی گذرواژه به {{email}} ارسال شد.' ], - "RESET" => [ - "@TRANSLATION" => "تغییر گذرواژه", - "CHOOSE" => "لطفا گذرواژه جدید را انتخاب کنید", - "PAGE" => "برای حساب خود، گذرواژه جدیدی انتخاب کنید.", - "SEND" => "گذرواژه جدید را انتخاب کرده و وارد شوید" + 'RESET' => [ + '@TRANSLATION' => 'تغییر گذرواژه', + 'CHOOSE' => 'لطفا گذرواژه جدید را انتخاب کنید', + 'PAGE' => 'برای حساب خود، گذرواژه جدیدی انتخاب کنید.', + 'SEND' => 'گذرواژه جدید را انتخاب کرده و وارد شوید' ], - "HASH_FAILED" => "هشینگ گذرواژه با مشکل روبرو شد. لطفا با مسولین وب سایت تماس برقرار کنید", - "INVALID" => "گذرواژه فعلی درست وارد نشده است", - "NEW" => "گذرواژه جدید", - "NOTHING_TO_UPDATE" => "شما نمیتوانید همان گذرواژه را دوباره وارد کنید", - "UPDATED" => "گذرواژه به روز رسانی شد" + 'HASH_FAILED' => 'هشینگ گذرواژه با مشکل روبرو شد. لطفا با مسولین وب سایت تماس برقرار کنید', + 'INVALID' => 'گذرواژه فعلی درست وارد نشده است', + 'NEW' => 'گذرواژه جدید', + 'NOTHING_TO_UPDATE' => 'شما نمیتوانید همان گذرواژه را دوباره وارد کنید', + 'UPDATED' => 'گذرواژه به روز رسانی شد' ], - "PROFILE" => [ - "SETTINGS" => "تنظیمات شخصی حساب", - "UPDATED" => "تنظیمات شخصی حساب به روز رسانی شد" + 'PROFILE' => [ + 'SETTINGS' => 'تنظیمات شخصی حساب', + 'UPDATED' => 'تنظیمات شخصی حساب به روز رسانی شد' ], - "REGISTER" => "ثبت نام", - "REGISTER_ME" => "ثبت نام کن", + 'REGISTER' => 'ثبت نام', + 'REGISTER_ME' => 'ثبت نام کن', - "REGISTRATION" => [ - "BROKEN" => "متاسفانه پروسه ثبت نام با مشکلی روبرو شد. برای دریافت کمک لطفا با ما تماس بگیرید.", - "COMPLETE_TYPE1" => "شما با موفقیت ثبت نام کردید. حالا میتوانید وارد شوید.", - "COMPLETE_TYPE2" => "شما با موفقیت ثبت نام کردید. لینک فعال سازی حساب به آدرس پست الکترونیکیتان {{email}} ارسال شد. بدون فعال سازی نمیتوانید وارد شوید.", - "DISABLED" => "با عرض تاسف، امکان ثبت در وب سایت، غیر فعال شده است.", - "LOGOUT" => "شما همزمان این که وارد شده اید نمیتوانید حساب کاربری جدیدی بسازید. لطفا ابتدا خارج شوید.", - "WELCOME" => "سریع و ساده ثبت نام کنید" + 'REGISTRATION' => [ + 'BROKEN' => 'متاسفانه پروسه ثبت نام با مشکلی روبرو شد. برای دریافت کمک لطفا با ما تماس بگیرید.', + 'COMPLETE_TYPE1' => 'شما با موفقیت ثبت نام کردید. حالا میتوانید وارد شوید.', + 'COMPLETE_TYPE2' => 'شما با موفقیت ثبت نام کردید. لینک فعال سازی حساب به آدرس پست الکترونیکیتان {{email}} ارسال شد. بدون فعال سازی نمیتوانید وارد شوید.', + 'DISABLED' => 'با عرض تاسف، امکان ثبت در وب سایت، غیر فعال شده است.', + 'LOGOUT' => 'شما همزمان این که وارد شده اید نمیتوانید حساب کاربری جدیدی بسازید. لطفا ابتدا خارج شوید.', + 'WELCOME' => 'سریع و ساده ثبت نام کنید' ], - "RATE_LIMIT_EXCEEDED" => "شما محدودیت تعداد انجام این کار را پشت سر گذاشتید. لطفا {{delay}} ثانیه دیگر صبر کرده و دوباره تلاش کنید.", - "REMEMBER_ME" => "من را به خاطر بسپار!", - "REMEMBER_ME_ON_COMPUTER" => "من را در این دستگاه به خاطر بسپار (برای دستگاه های عمومی پیشنهاد نمی شود)", + 'RATE_LIMIT_EXCEEDED' => 'شما محدودیت تعداد انجام این کار را پشت سر گذاشتید. لطفا {{delay}} ثانیه دیگر صبر کرده و دوباره تلاش کنید.', + 'REMEMBER_ME' => 'من را به خاطر بسپار!', + 'REMEMBER_ME_ON_COMPUTER' => 'من را در این دستگاه به خاطر بسپار (برای دستگاه های عمومی پیشنهاد نمی شود)', - "SIGNIN" => "ورود", - "SIGNIN_OR_REGISTER" => "ثبت نام کنید و یا وارد شوید", - "SIGNUP" => "ثبت نام", + 'SIGNIN' => 'ورود', + 'SIGNIN_OR_REGISTER' => 'ثبت نام کنید و یا وارد شوید', + 'SIGNUP' => 'ثبت نام', - "TOS" => "شرایط و مقررات", - "TOS_AGREEMENT" => "با ثبت نام در {{site_title}} موافقت خود با شرایط و مقررات را نشان میدهید.", - "TOS_FOR" => "شرایط و مقررات {{title}}", + 'TOS' => 'شرایط و مقررات', + 'TOS_AGREEMENT' => 'با ثبت نام در {{site_title}} موافقت خود با شرایط و مقررات را نشان میدهید.', + 'TOS_FOR' => 'شرایط و مقررات {{title}}', - "USERNAME" => [ - "@TRANSLATION" => "نام کاربری", + 'USERNAME' => [ + '@TRANSLATION' => 'نام کاربری', - "CHOOSE" => "یک نام کاربری منحصر به فرد انتخاب کنید", - "INVALID" => "نام کاربری معتبر نیست", - "IN_USE" => "نام کاربری {{user_name}} قبلا استفاده شده است", - "NOT_AVAILABLE" => "نام کاربری {{user_name}} موجود نیست. لطفا نام کاربری دیگری انتخاب کنید" + 'CHOOSE' => 'یک نام کاربری منحصر به فرد انتخاب کنید', + 'INVALID' => 'نام کاربری معتبر نیست', + 'IN_USE' => 'نام کاربری {{user_name}} قبلا استفاده شده است', + 'NOT_AVAILABLE' => 'نام کاربری {{user_name}} موجود نیست. لطفا نام کاربری دیگری انتخاب کنید' ], - "USER_ID_INVALID" => "آی دی کاربری مد نظر شما موجود نیست", - "USER_OR_EMAIL_INVALID" => "نام کاربری و یا آدرس پست الکترونیکی معتبر نیست", - "USER_OR_PASS_INVALID" => "کاربری یافت نشد و یا گذرواژه صحیح نیست", + 'USER_ID_INVALID' => 'آی دی کاربری مد نظر شما موجود نیست', + 'USER_OR_EMAIL_INVALID' => 'نام کاربری و یا آدرس پست الکترونیکی معتبر نیست', + 'USER_OR_PASS_INVALID' => 'کاربری یافت نشد و یا گذرواژه صحیح نیست', - "WELCOME" => "خوش آمدید {{first_name}}" + 'WELCOME' => 'خوش آمدید {{first_name}}' ]; diff --git a/app/sprinkles/account/locale/fa/validate.php b/app/sprinkles/account/locale/fa/validate.php index a63cae124..84afbec53 100644 --- a/app/sprinkles/account/locale/fa/validate.php +++ b/app/sprinkles/account/locale/fa/validate.php @@ -1,20 +1,20 @@ [ - "PASSWORD_MISMATCH" => "گذرواژه و تکرار آن باید با یکدیگر تطبیق پیدا کنند", - "USERNAME" => "نام کاربری فقط میتواند از حروف کوچک، اعداد، '.'، '-' و '_' متشکل شوند." + 'VALIDATE' => [ + 'PASSWORD_MISMATCH' => 'گذرواژه و تکرار آن باید با یکدیگر تطبیق پیدا کنند', + 'USERNAME' => "نام کاربری فقط میتواند از حروف کوچک، اعداد، '.'، '-' و '_' متشکل شوند." ] ]; diff --git a/app/sprinkles/account/locale/fr_FR/messages.php b/app/sprinkles/account/locale/fr_FR/messages.php index 6e5a032f1..5af63230f 100644 --- a/app/sprinkles/account/locale/fr_FR/messages.php +++ b/app/sprinkles/account/locale/fr_FR/messages.php @@ -3,177 +3,178 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * French message token translations for the 'account' sprinkle. * - * @package userfrosting\i18n\fr * @author Louis Charette */ - return [ - "ACCOUNT" => [ - "@TRANSLATION" => "Compte d'utilisateur", + 'ACCOUNT' => [ + '@TRANSLATION' => "Compte d'utilisateur", - "ACCESS_DENIED" => "Hmm, on dirait que vous n'avez pas la permission de faire ceci.", + 'ACCESS_DENIED' => "Hmm, on dirait que vous n'avez pas la permission de faire ceci.", - "DISABLED" => "Ce compte a été désactivé. Veuillez nous contacter pour plus d'informations.", + 'DISABLED' => "Ce compte a été désactivé. Veuillez nous contacter pour plus d'informations.", - "EMAIL_UPDATED" => "Adresse email mise à jour", + 'EMAIL_UPDATED' => 'Adresse email mise à jour', - "INVALID" => "Ce compte n'existe pas. Il a peut-être été supprimé. Veuillez nous contacter pour plus d'informations.", + 'INVALID' => "Ce compte n'existe pas. Il a peut-être été supprimé. Veuillez nous contacter pour plus d'informations.", - "MASTER_NOT_EXISTS" => "Vous ne pouvez pas enregistrer un compte tant que le compte principal n'a pas été créé!", - "MY" => "Mon compte", + 'MASTER_NOT_EXISTS' => "Vous ne pouvez pas enregistrer un compte tant que le compte principal n'a pas été créé!", + 'MY' => 'Mon compte', - "SESSION_COMPROMISED" => [ - "@TRANSLATION" => "Votre session a été compromise. Vous devez vous déconnecter de tous les périphériques, puis vous reconnecter et vous assurer que vos données n'ont pas été altérées.", - "TITLE" => "Votre compte peut avoir été compromis" + 'SESSION_COMPROMISED' => [ + '@TRANSLATION' => "Votre session a été compromise. Vous devez vous déconnecter de tous les périphériques, puis vous reconnecter et vous assurer que vos données n'ont pas été altérées.", + 'TITLE' => 'Votre compte peut avoir été compromis' ], - "SESSION_EXPIRED" => "Votre session a expiré. Veuillez vous connecter à nouveau.", + 'SESSION_EXPIRED' => 'Votre session a expiré. Veuillez vous connecter à nouveau.', - "SETTINGS" => [ - "@TRANSLATION" => "Paramètres du compte", - "DESCRIPTION" => "Mettez à jour les paramètres de votre compte, y compris votre adresse e-mail, votre nom et votre mot de passe.", - "UPDATED" => "Paramètres du compte mis à jour" + 'SETTINGS' => [ + '@TRANSLATION' => 'Paramètres du compte', + 'DESCRIPTION' => 'Mettez à jour les paramètres de votre compte, y compris votre adresse e-mail, votre nom et votre mot de passe.', + 'UPDATED' => 'Paramètres du compte mis à jour' ], - "TOOLS" => "Outils du compte", + 'TOOLS' => 'Outils du compte', - "UNVERIFIED" => "Votre compte n'a pas encore été vérifié. Vérifiez vos emails / dossier spam pour les instructions d'activation du compte.", + 'UNVERIFIED' => "Votre compte n'a pas encore été vérifié. Vérifiez vos emails / dossier spam pour les instructions d'activation du compte.", - "VERIFICATION" => [ - "NEW_LINK_SENT" => "Nous avons envoyé un nouveau lien de vérification à {{email}}. Veuillez vérifier vos dossiers de boîte de réception et de spam pour ce courriel.", - "RESEND" => "Renvoyer le courriel de validation", - "COMPLETE" => "Votre compte a été validé. Vous pouvez maintenant vous connecter.", - "EMAIL" => "Veuillez saisir l'adresse email que vous avez utilisée pour vous inscrire et votre courriel de vérification sera renvoyé.", - "PAGE" => "Renvoyer l'email de validation de votre nouveau compte.", - "SEND" => "Envoyer le lien de validation de mon compte", - "TOKEN_NOT_FOUND" => "Le jeton de vérification n'existe pas / Le compte est déjà vérifié", + 'VERIFICATION' => [ + 'NEW_LINK_SENT' => 'Nous avons envoyé un nouveau lien de vérification à {{email}}. Veuillez vérifier vos dossiers de boîte de réception et de spam pour ce courriel.', + 'RESEND' => 'Renvoyer le courriel de validation', + 'COMPLETE' => 'Votre compte a été validé. Vous pouvez maintenant vous connecter.', + 'EMAIL' => "Veuillez saisir l'adresse email que vous avez utilisée pour vous inscrire et votre courriel de vérification sera renvoyé.", + 'PAGE' => "Renvoyer l'email de validation de votre nouveau compte.", + 'SEND' => 'Envoyer le lien de validation de mon compte', + 'TOKEN_NOT_FOUND' => "Le jeton de vérification n'existe pas / Le compte est déjà vérifié", ] ], - "EMAIL" => [ - "INVALID" => "Il n'y a aucun compte pour {{email}}.", - "IN_USE" => "Le email {{email}} est déjà utilisé.", - "VERIFICATION_REQUIRED" => "Email (vérification requise - utiliser une adresse réelle!)" + 'EMAIL' => [ + 'INVALID' => "Il n'y a aucun compte pour {{email}}.", + 'IN_USE' => 'Le email {{email}} est déjà utilisé.', + 'VERIFICATION_REQUIRED' => 'Email (vérification requise - utiliser une adresse réelle!)' ], - "EMAIL_OR_USERNAME" => "Nom d'utilisateur ou adresse email", + 'EMAIL_OR_USERNAME' => "Nom d'utilisateur ou adresse email", - "FIRST_NAME" => "Prénom", + 'FIRST_NAME' => 'Prénom', - "HEADER_MESSAGE_ROOT" => "VOUS ÊTES CONNECTÉ EN TANT QUE L'UTILISATEUR ROOT", + 'HEADER_MESSAGE_ROOT' => "VOUS ÊTES CONNECTÉ EN TANT QUE L'UTILISATEUR ROOT", - "LAST_NAME" => "Nom de famille", + 'LAST_NAME' => 'Nom de famille', - "LOCALE" => [ - "ACCOUNT" => "La langue utilisé pour votre compte d'utilisateur", - "INVALID" => "{{locale}} n'est pas une langue valide." + 'LOCALE' => [ + 'ACCOUNT' => "La langue utilisé pour votre compte d'utilisateur", + 'INVALID' => "{{locale}} n'est pas une langue valide." ], - "LOGIN" => [ - "@TRANSLATION" => "Connexion", - "ALREADY_COMPLETE" => "Vous êtes déjà connecté!", - "SOCIAL" => "Ou se connecter avec", - "REQUIRED" => "Désolé, vous devez être connecté pour accéder à cette ressource." + 'LOGIN' => [ + '@TRANSLATION' => 'Connexion', + 'ALREADY_COMPLETE' => 'Vous êtes déjà connecté!', + 'SOCIAL' => 'Ou se connecter avec', + 'REQUIRED' => 'Désolé, vous devez être connecté pour accéder à cette ressource.' ], - "LOGOUT" => "Déconnexion", + 'LOGOUT' => 'Déconnexion', - "NAME" => "Nom", + 'NAME' => 'Nom', - "NAME_AND_EMAIL" => "Nom et email", + 'NAME_AND_EMAIL' => 'Nom et email', - "PAGE" => [ - "LOGIN" => [ - "DESCRIPTION" => "Connectez-vous à votre compte {{site_name}} ou enregistrez-vous pour un nouveau compte.", - "SUBTITLE" => "Inscrivez-vous gratuitement ou connectez-vous avec un compte existant.", - "TITLE" => "Commençons!", + 'PAGE' => [ + 'LOGIN' => [ + 'DESCRIPTION' => 'Connectez-vous à votre compte {{site_name}} ou enregistrez-vous pour un nouveau compte.', + 'SUBTITLE' => 'Inscrivez-vous gratuitement ou connectez-vous avec un compte existant.', + 'TITLE' => 'Commençons!', ] ], - "PASSWORD" => [ - "@TRANSLATION" => "Mot de passe", - - "BETWEEN" => "Entre {{min}} et {{max}} charactères", - - "CONFIRM" => "Confirmer le mot de passe", - "CONFIRM_CURRENT" => "Veuillez confirmer votre mot de passe actuel", - "CONFIRM_NEW" => "Confirmer le nouveau mot de passe", - "CONFIRM_NEW_EXPLAIN" => "Confirmer le mot de passe", - "CONFIRM_NEW_HELP" => "Obligatoire uniquement si vous sélectionnez un nouveau mot de passe", - "CURRENT" => "Mot de passe actuel", - "CURRENT_EXPLAIN" => "Vous devez confirmer votre mot de passe actuel pour apporter des modifications", - - "FORGOTTEN" => "Mot de passe oublié", - "FORGET" => [ - "@TRANSLATION" => "J'ai oublié mon mot de passe", - - "COULD_NOT_UPDATE" => "Impossible de mettre à jour le mot de passe.", - "EMAIL" => "Veuillez saisir l'adresse e-mail que vous avez utilisée pour vous inscrire. Un lien avec les instructions pour réinitialiser votre mot de passe vous sera envoyé par email.", - "EMAIL_SEND" => "Envoyer le lien de réinitialisation", - "INVALID" => "Cette requête de réinitialisation de mot de passe n'a pas pu être trouvée ou a expiré. Veuillez réessayer de soumettre votre demande .", - "PAGE" => "Obtenir un lien pour réinitialiser votre mot de passe.", - "REQUEST_CANNED" => "Demande de mot de passe perdu annulée.", - "REQUEST_SENT" => "Si l'adresse e-mail {{email}} correspond à un compte dans notre système, un lien de réinitialisation de mot de passe sera envoyé à {{email}}." + 'PASSWORD' => [ + '@TRANSLATION' => 'Mot de passe', + + 'BETWEEN' => 'Entre {{min}} et {{max}} charactères', + + 'CONFIRM' => 'Confirmer le mot de passe', + 'CONFIRM_CURRENT' => 'Veuillez confirmer votre mot de passe actuel', + 'CONFIRM_NEW' => 'Confirmer le nouveau mot de passe', + 'CONFIRM_NEW_EXPLAIN' => 'Confirmer le mot de passe', + 'CONFIRM_NEW_HELP' => 'Obligatoire uniquement si vous sélectionnez un nouveau mot de passe', + 'CURRENT' => 'Mot de passe actuel', + 'CURRENT_EXPLAIN' => 'Vous devez confirmer votre mot de passe actuel pour apporter des modifications', + + 'FORGOTTEN' => 'Mot de passe oublié', + 'FORGET' => [ + '@TRANSLATION' => "J'ai oublié mon mot de passe", + + 'COULD_NOT_UPDATE' => 'Impossible de mettre à jour le mot de passe.', + 'EMAIL' => "Veuillez saisir l'adresse e-mail que vous avez utilisée pour vous inscrire. Un lien avec les instructions pour réinitialiser votre mot de passe vous sera envoyé par email.", + 'EMAIL_SEND' => 'Envoyer le lien de réinitialisation', + 'INVALID' => "Cette requête de réinitialisation de mot de passe n'a pas pu être trouvée ou a expiré. Veuillez réessayer de soumettre votre demande .", + 'PAGE' => 'Obtenir un lien pour réinitialiser votre mot de passe.', + 'REQUEST_CANNED' => 'Demande de mot de passe perdu annulée.', + 'REQUEST_SENT' => "Si l'adresse e-mail {{email}} correspond à un compte dans notre système, un lien de réinitialisation de mot de passe sera envoyé à {{email}}." ], - "RESET" => [ - "@TRANSLATION" => "Réinitialiser le mot de passe", - "CHOOSE" => "Veuillez choisir un nouveau mot de passe pour continuer.", - "PAGE" => "Choisissez un nouveau mot de passe pour votre compte.", - "SEND" => "Définir un nouveau mot de passe" + 'RESET' => [ + '@TRANSLATION' => 'Réinitialiser le mot de passe', + 'CHOOSE' => 'Veuillez choisir un nouveau mot de passe pour continuer.', + 'PAGE' => 'Choisissez un nouveau mot de passe pour votre compte.', + 'SEND' => 'Définir un nouveau mot de passe' ], - "HASH_FAILED" => "Le hachage du mot de passe a échoué. Veuillez contacter un administrateur de site.", - "INVALID" => "Le mot de passe actuel ne correspond pas à celui que nous avons au dossier", - "NEW" => "Nouveau mot de passe", - "NOTHING_TO_UPDATE" => "Vous ne pouvez pas mettre à jour avec le même mot de passe", - "UPDATED" => "Mot de passe du compte mis à jour" + 'HASH_FAILED' => 'Le hachage du mot de passe a échoué. Veuillez contacter un administrateur de site.', + 'INVALID' => 'Le mot de passe actuel ne correspond pas à celui que nous avons au dossier', + 'NEW' => 'Nouveau mot de passe', + 'NOTHING_TO_UPDATE' => 'Vous ne pouvez pas mettre à jour avec le même mot de passe', + 'UPDATED' => 'Mot de passe du compte mis à jour' ], - "PROFILE" => [ - "SETTINGS" => "Paramètres du profil", - "UPDATED" => "Paramètres du profil mis à jour" + 'PROFILE' => [ + 'SETTINGS' => 'Paramètres du profil', + 'UPDATED' => 'Paramètres du profil mis à jour' ], - "REGISTER" => "S'inscrire", - "REGISTER_ME" => "S'inscrire", + 'REGISTER' => "S'inscrire", + 'REGISTER_ME' => "S'inscrire", - "REGISTRATION" => [ - "BROKEN" => "Nous sommes désolés, il ya un problème avec notre processus d'enregistrement de compte. Veuillez nous contacter directement pour obtenir de l'aide.", - "COMPLETE_TYPE1" => "Vous êtes inscrit avec succès. Vous pouvez maintenant vous connecter.", - "COMPLETE_TYPE2" => "Vous êtes inscrit avec succès. Vous recevrez bientôt un e-mail de validation contenant un lien pour activer votre compte. Vous ne pourrez pas vous connecter avant d'avoir terminé cette étape.", - "DISABLED" => "Désolé, l'enregistrement de compte a été désactivé.", - "LOGOUT" => "Désolé, vous ne pouvez pas vous inscrire tout en étant connecté. Veuillez vous déconnecter en premier.", - "WELCOME" => "L'inscription est rapide et simple." + 'REGISTRATION' => [ + 'BROKEN' => "Nous sommes désolés, il ya un problème avec notre processus d'enregistrement de compte. Veuillez nous contacter directement pour obtenir de l'aide.", + 'COMPLETE_TYPE1' => 'Vous êtes inscrit avec succès. Vous pouvez maintenant vous connecter.', + 'COMPLETE_TYPE2' => "Vous êtes inscrit avec succès. Vous recevrez bientôt un e-mail de validation contenant un lien pour activer votre compte. Vous ne pourrez pas vous connecter avant d'avoir terminé cette étape.", + 'DISABLED' => "Désolé, l'enregistrement de compte a été désactivé.", + 'LOGOUT' => 'Désolé, vous ne pouvez pas vous inscrire tout en étant connecté. Veuillez vous déconnecter en premier.', + 'WELCOME' => "L'inscription est rapide et simple." ], - "RATE_LIMIT_EXCEEDED" => "La limite de tentatives pour cette action a été dépassée. Vous devez attendre {{delay}} secondes avant de pouvoir effectuer une autre tentative.", - "REMEMBER_ME" => "Se souvenir de moi!", - "REMEMBER_ME_ON_COMPUTER" => "Se souvenir de moi sur cet ordinateur (non recommandé pour les ordinateurs publics)", + 'RATE_LIMIT_EXCEEDED' => 'La limite de tentatives pour cette action a été dépassée. Vous devez attendre {{delay}} secondes avant de pouvoir effectuer une autre tentative.', + 'REMEMBER_ME' => 'Se souvenir de moi!', + 'REMEMBER_ME_ON_COMPUTER' => 'Se souvenir de moi sur cet ordinateur (non recommandé pour les ordinateurs publics)', - "SIGNIN" => "Se connecter", - "SIGNIN_OR_REGISTER" => "Se connecter ou s'inscrire", - "SIGNUP" => "S'inscrire", + 'SIGNIN' => 'Se connecter', + 'SIGNIN_OR_REGISTER' => "Se connecter ou s'inscrire", + 'SIGNUP' => "S'inscrire", - "TOS" => "Termes et conditions", - "TOS_AGREEMENT" => "En créant un compte avec {{site_title}}, vous acceptez les termes et conditions.", - "TOS_FOR" => "Termes et conditions pour {{title}}", + 'TOS' => 'Termes et conditions', + 'TOS_AGREEMENT' => 'En créant un compte avec {{site_title}}, vous acceptez les termes et conditions.', + 'TOS_FOR' => 'Termes et conditions pour {{title}}', - "USERNAME" => [ - "@TRANSLATION" => "Nom d'utilisateur", + 'USERNAME' => [ + '@TRANSLATION' => "Nom d'utilisateur", - "CHOOSE" => "Choisissez un nom d'utilisateur unique", - "INVALID" => "Nom d'utilisateur invalide", - "IN_USE" => "Le nom d'utilisateur '{{username}}' est déjà utilisé.", - "NOT_AVAILABLE" => "Le nom d'utilisateur {{user_name}} n'est pas disponible. Choisissez un autre nom, ou cliquez sur « suggérer »." + 'CHOOSE' => "Choisissez un nom d'utilisateur unique", + 'INVALID' => "Nom d'utilisateur invalide", + 'IN_USE' => "Le nom d'utilisateur '{{username}}' est déjà utilisé.", + 'NOT_AVAILABLE' => "Le nom d'utilisateur {{user_name}} n'est pas disponible. Choisissez un autre nom, ou cliquez sur « suggérer »." ], - "USER_ID_INVALID" => "L'identifiant d'utilisateur demandé n'existe pas.", - "USER_OR_EMAIL_INVALID" => "Nom d'utilisateur ou adresse e-mail non valide.", - "USER_OR_PASS_INVALID" => "Nom d'utilisateur ou mot de passe incorrect.", + 'USER_ID_INVALID' => "L'identifiant d'utilisateur demandé n'existe pas.", + 'USER_OR_EMAIL_INVALID' => "Nom d'utilisateur ou adresse e-mail non valide.", + 'USER_OR_PASS_INVALID' => "Nom d'utilisateur ou mot de passe incorrect.", - "WELCOME" => "Bienvenue {{first_name}}" + 'WELCOME' => 'Bienvenue {{first_name}}' ]; diff --git a/app/sprinkles/account/locale/fr_FR/validate.php b/app/sprinkles/account/locale/fr_FR/validate.php index 44b1bc19f..395995a2b 100644 --- a/app/sprinkles/account/locale/fr_FR/validate.php +++ b/app/sprinkles/account/locale/fr_FR/validate.php @@ -3,16 +3,17 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * French message token translations for the 'account' sprinkle. * - * @package userfrosting\i18n\fr * @author Louis Charette */ - return [ - "VALIDATE" => [ - "PASSWORD_MISMATCH" => "Votre mot de passe et votre mot de passe de confirmation doivent correspondre." + 'VALIDATE' => [ + 'PASSWORD_MISMATCH' => 'Votre mot de passe et votre mot de passe de confirmation doivent correspondre.' ] ]; diff --git a/app/sprinkles/account/locale/it_IT/messages.php b/app/sprinkles/account/locale/it_IT/messages.php index 455a302cc..780e8ad82 100644 --- a/app/sprinkles/account/locale/it_IT/messages.php +++ b/app/sprinkles/account/locale/it_IT/messages.php @@ -3,184 +3,184 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * Italian message token translations for the 'account' sprinkle. * This translation was generated with Google translate. Please contribute if you are a native speaker. * - * @package userfrosting\i18n\it * @author Alexander Weissman * @author Pietro Marangon (@Pe46dro) */ - return [ - "ACCOUNT" => [ - "@TRANSLATION" => "Account", + 'ACCOUNT' => [ + '@TRANSLATION' => 'Account', - "ACCESS_DENIED" => "Sembra tu non abbia il permesso per effettuare questa azione.", + 'ACCESS_DENIED' => 'Sembra tu non abbia il permesso per effettuare questa azione.', - "DISABLED" => "Questo account è stato disattivato, contattaci per maggiori informazioni", + 'DISABLED' => 'Questo account è stato disattivato, contattaci per maggiori informazioni', - "EMAIL_UPDATED" => "Email aggiornata", + 'EMAIL_UPDATED' => 'Email aggiornata', - "INVALID" => "Questo account non esiste. Può essere stato eliminato. Contattaci per ulteriori informazioni.", + 'INVALID' => 'Questo account non esiste. Può essere stato eliminato. Contattaci per ulteriori informazioni.', - "MASTER_NOT_EXISTS" => "Non puoi registrare un account finché l'account primario non sarà creato!", - "MY" => "Il mio account", + 'MASTER_NOT_EXISTS' => "Non puoi registrare un account finché l'account primario non sarà creato!", + 'MY' => 'Il mio account', - "SESSION_COMPROMISED" => [ - "@TRANSLATION" => "La tua sessione è stata compromessa. Devi eseguire il logout su tutti i dispositivi, quindi accedere nuovamente e assicurarti che i tuoi dati non siano stati manomessi.", - "TITLE" => "Il tuo account potrebbe essere stato compromesso", - "TEXT" => "Qualcuno potrebbe aver utilizzato le tue informazioni di accesso per accedere a questa pagina. Per la tua sicurezza tutte le sessioni sono state disconnesse. Accedi e controlla l'account per attività sospette. Potresti anche voler cambiare la password." + 'SESSION_COMPROMISED' => [ + '@TRANSLATION' => 'La tua sessione è stata compromessa. Devi eseguire il logout su tutti i dispositivi, quindi accedere nuovamente e assicurarti che i tuoi dati non siano stati manomessi.', + 'TITLE' => 'Il tuo account potrebbe essere stato compromesso', + 'TEXT' => "Qualcuno potrebbe aver utilizzato le tue informazioni di accesso per accedere a questa pagina. Per la tua sicurezza tutte le sessioni sono state disconnesse. Accedi e controlla l'account per attività sospette. Potresti anche voler cambiare la password." ], - "SESSION_EXPIRED" => "La tua sessione è scaduta. Accedi nuovamente.", + 'SESSION_EXPIRED' => 'La tua sessione è scaduta. Accedi nuovamente.', - "SETTINGS" => [ - "@TRANSLATION" => "Impostazioni account", - "DESCRIPTION" => "Aggiorna le impostazioni del tuo account, tra cui email, nome e password.", - "UPDATED" => "Impostazioni account aggiornate" + 'SETTINGS' => [ + '@TRANSLATION' => 'Impostazioni account', + 'DESCRIPTION' => 'Aggiorna le impostazioni del tuo account, tra cui email, nome e password.', + 'UPDATED' => 'Impostazioni account aggiornate' ], - "TOOLS" => "Strumenti account", + 'TOOLS' => 'Strumenti account', - "UNVERIFIED" => "Il tuo account non è stato attivato. Controlla nella tua mail (anche nella cartella dello spam) per ricevere le instruzioni per attivare il tuo account", + 'UNVERIFIED' => 'Il tuo account non è stato attivato. Controlla nella tua mail (anche nella cartella dello spam) per ricevere le instruzioni per attivare il tuo account', - "VERIFICATION" => [ - "NEW_LINK_SENT" => "Ti è stato inviato un nuovo codice di attivazione, controlla la tua email ({{email}}).", - "RESEND" => "Invia nuovamente email di verifica.", - "COMPLETE" => "Hai verificato con successo il tuo account. Puoi ora accedere.", - "EMAIL" => "Inserisci l'indirizzo email che hai utilizzato per registrarti e la tua email di verifica sarà inviata nuovamente.", - "PAGE" => "Invia nuovamente l'email di verifica per il tuo nuovo account.", - "SEND" => "Invia il collegamento di verifica per il mio account", - "TOKEN_NOT_FOUND" => "Il token non esiste / l'account è già stato attivato" + 'VERIFICATION' => [ + 'NEW_LINK_SENT' => 'Ti è stato inviato un nuovo codice di attivazione, controlla la tua email ({{email}}).', + 'RESEND' => 'Invia nuovamente email di verifica.', + 'COMPLETE' => 'Hai verificato con successo il tuo account. Puoi ora accedere.', + 'EMAIL' => "Inserisci l'indirizzo email che hai utilizzato per registrarti e la tua email di verifica sarà inviata nuovamente.", + 'PAGE' => "Invia nuovamente l'email di verifica per il tuo nuovo account.", + 'SEND' => 'Invia il collegamento di verifica per il mio account', + 'TOKEN_NOT_FOUND' => "Il token non esiste / l'account è già stato attivato" ] ], - "EMAIL" => [ - "INVALID" => "Non esiste alcun account per {{email}}.", - "IN_USE" => "L'email '{{email}}' è già in uso", - "VERIFICATION_REQUIRED" => "Email (verifica richiesta - utilizza un indirizzo reale!)" + 'EMAIL' => [ + 'INVALID' => 'Non esiste alcun account per {{email}}.', + 'IN_USE' => "L'email '{{email}}' è già in uso", + 'VERIFICATION_REQUIRED' => 'Email (verifica richiesta - utilizza un indirizzo reale!)' ], - "EMAIL_OR_USERNAME" => "Nome utente o Indirizzo Email", + 'EMAIL_OR_USERNAME' => 'Nome utente o Indirizzo Email', + + 'FIRST_NAME' => 'Nome', - "FIRST_NAME" => "Nome", + 'HEADER_MESSAGE_ROOT' => 'ACCESSO ROOT', - "HEADER_MESSAGE_ROOT" => "ACCESSO ROOT", + 'LAST_NAME' => 'Cognome', + 'LOCALE' => [ + 'ACCOUNT' => 'La lingua da utilizzare per il tuo account', + 'INVALID' => '{{locale}} non è una lingua valida.', - "LAST_NAME" => "Cognome", - "LOCALE" => [ - "ACCOUNT" => "La lingua da utilizzare per il tuo account", - "INVALID" => "{{locale}} non è una lingua valida.", - - ], - "LOGIN" => [ - "@TRANSLATION" => "Accesso", - "ALREADY_COMPLETE" => "Hai già eseguito l'accesso!", - "SOCIAL" => "O accedi con", - "REQUIRED" => "Devi eseguire l'accesso per accedere a questa risorsa" + 'LOGIN' => [ + '@TRANSLATION' => 'Accesso', + 'ALREADY_COMPLETE' => "Hai già eseguito l'accesso!", + 'SOCIAL' => 'O accedi con', + 'REQUIRED' => "Devi eseguire l'accesso per accedere a questa risorsa" ], - "LOGOUT" => "Esci", + 'LOGOUT' => 'Esci', - "NAME" => "Nome", + 'NAME' => 'Nome', - "NAME_AND_EMAIL" => "Nome e email", + 'NAME_AND_EMAIL' => 'Nome e email', - "PAGE" => [ - "LOGIN" => [ - "DESCRIPTION" => "Accedi al tuo account {{site_name}} o iscriviti per un nuovo account.", - "SUBTITLE" => "Registrati gratuitamente o accedi con un account esistente.", - "TITLE" => "Iniziamo!", + 'PAGE' => [ + 'LOGIN' => [ + 'DESCRIPTION' => 'Accedi al tuo account {{site_name}} o iscriviti per un nuovo account.', + 'SUBTITLE' => 'Registrati gratuitamente o accedi con un account esistente.', + 'TITLE' => 'Iniziamo!', ] ], - "PASSWORD" => [ - "@TRANSLATION" => "Password", + 'PASSWORD' => [ + '@TRANSLATION' => 'Password', - "BETWEEN" => "La password deve essere tra {{min}} e i {{max}} caratteri", + 'BETWEEN' => 'La password deve essere tra {{min}} e i {{max}} caratteri', - "CONFIRM" => "Conferma la password", - "CONFIRM_CURRENT" => "Conferma la password attuale", - "CONFIRM_NEW" => "Conferma la tua nuova password", - "CONFIRM_NEW_EXPLAIN" => "Inserisci nuovamente la nuova password", - "CONFIRM_NEW_HELP" => "Richiesto solo se si seleziona una nuova password", - "CREATE" => [ - "@TRANSLATION" => "Crea password", - "PAGE" => "Scegli una password per il tuo nuovo account.", - "SET" => "Imposta password e accedi" + 'CONFIRM' => 'Conferma la password', + 'CONFIRM_CURRENT' => 'Conferma la password attuale', + 'CONFIRM_NEW' => 'Conferma la tua nuova password', + 'CONFIRM_NEW_EXPLAIN' => 'Inserisci nuovamente la nuova password', + 'CONFIRM_NEW_HELP' => 'Richiesto solo se si seleziona una nuova password', + 'CREATE' => [ + '@TRANSLATION' => 'Crea password', + 'PAGE' => 'Scegli una password per il tuo nuovo account.', + 'SET' => 'Imposta password e accedi' ], - "CURRENT" => "Password attuale", - "CURRENT_EXPLAIN" => "Devi confermare la tua password corrente per apportare modifiche", - - "FORGOTTEN" => "Password dimenticata", - "FORGET" => [ - "@TRANSLATION" => "Ho dimenticato la mia password", - - "COULD_NOT_UPDATE" => "Password non aggiornata", - "EMAIL" => "Inserisci l'indirizzo email che hai utilizzato per iscriverti. Un link con le istruzioni per reimpostare la tua password verrà inviata via email.", - "EMAIL_SEND" => "Invia email per il reset della password", - "INVALID" => "Questa richiesta di ripristino della password non è stata trovata o è scaduta. Prova a reinviare la tua richiesta.", - "PAGE" => "Ottieni un collegamento per reimpostare la tua password.", - "REQUEST_CANNED" => "Richiesta di recupero password annullata.", - "REQUEST_SENT" => "Se l'email {{email}} corrisponde a un account, verrà inviato un collegamento per la reimpostazione della password a {{email}}." + 'CURRENT' => 'Password attuale', + 'CURRENT_EXPLAIN' => 'Devi confermare la tua password corrente per apportare modifiche', + + 'FORGOTTEN' => 'Password dimenticata', + 'FORGET' => [ + '@TRANSLATION' => 'Ho dimenticato la mia password', + + 'COULD_NOT_UPDATE' => 'Password non aggiornata', + 'EMAIL' => "Inserisci l'indirizzo email che hai utilizzato per iscriverti. Un link con le istruzioni per reimpostare la tua password verrà inviata via email.", + 'EMAIL_SEND' => 'Invia email per il reset della password', + 'INVALID' => 'Questa richiesta di ripristino della password non è stata trovata o è scaduta. Prova a reinviare la tua richiesta.', + 'PAGE' => 'Ottieni un collegamento per reimpostare la tua password.', + 'REQUEST_CANNED' => 'Richiesta di recupero password annullata.', + 'REQUEST_SENT' => "Se l'email {{email}} corrisponde a un account, verrà inviato un collegamento per la reimpostazione della password a {{email}}." ], - "HASH_FAILED" => "Hash della password fallito. Contatta l'amministratore di sistema.", - "INVALID" => "La password corrente non corrisponde con quella attuale", - "NEW" => "Nuova Password", - "NOTHING_TO_UPDATE" => "Non puoi impostare la stessa password precedente", + 'HASH_FAILED' => "Hash della password fallito. Contatta l'amministratore di sistema.", + 'INVALID' => 'La password corrente non corrisponde con quella attuale', + 'NEW' => 'Nuova Password', + 'NOTHING_TO_UPDATE' => 'Non puoi impostare la stessa password precedente', - "RESET" => [ - "@TRANSLATION" => "Reimposta la Password", - "CHOOSE" => "Inserisci la tua nuova password", - "PAGE" => "Scegli una nuova password per il tuo account.", - "SEND" => "Imposta nuova password e accedi" + 'RESET' => [ + '@TRANSLATION' => 'Reimposta la Password', + 'CHOOSE' => 'Inserisci la tua nuova password', + 'PAGE' => 'Scegli una nuova password per il tuo account.', + 'SEND' => 'Imposta nuova password e accedi' ], - "UPDATED" => "Password aggiornata" + 'UPDATED' => 'Password aggiornata' ], - "PROFILE" => [ - "SETTINGS" => "Impostazioni profilo", - "UPDATED" => "Impostazioni profilo aggiornate" + 'PROFILE' => [ + 'SETTINGS' => 'Impostazioni profilo', + 'UPDATED' => 'Impostazioni profilo aggiornate' ], - "RATE_LIMIT_EXCEEDED" => "Il limite di esecuzioni per questa azione è stato superato. Devi aspettare altri {{delay}} secondi prima che tu possa fare un altro tentativo.", - "REGISTER" => "Registrati", - "REGISTER_ME" => "Iscrivimi", - "REGISTRATION" => [ - "BROKEN" => "Siamo spiacenti, c'è un problema con il nostro processo di registrazione dell'account. Vi preghiamo di contattarci direttamente per assistenza.", - "COMPLETE_TYPE1" => "Registrazione effettuata con successo. Ora puoi eseguire il login", - "COMPLETE_TYPE2" => "Registrazione effettuata con successo. Riceverai presto una mail a {{email}} per l'attivazione. Devi attivare il tuo account prima di eseguire il login.", - "DISABLED" => "La registrazione di nuovi account è limitata", - "LOGOUT" => "Non è possibile registrare un account mentre hai eseguito l'accesso ad un altro account", - "WELCOME" => "La registrazione è semplice e veloce" + 'RATE_LIMIT_EXCEEDED' => 'Il limite di esecuzioni per questa azione è stato superato. Devi aspettare altri {{delay}} secondi prima che tu possa fare un altro tentativo.', + 'REGISTER' => 'Registrati', + 'REGISTER_ME' => 'Iscrivimi', + 'REGISTRATION' => [ + 'BROKEN' => "Siamo spiacenti, c'è un problema con il nostro processo di registrazione dell'account. Vi preghiamo di contattarci direttamente per assistenza.", + 'COMPLETE_TYPE1' => 'Registrazione effettuata con successo. Ora puoi eseguire il login', + 'COMPLETE_TYPE2' => "Registrazione effettuata con successo. Riceverai presto una mail a {{email}} per l'attivazione. Devi attivare il tuo account prima di eseguire il login.", + 'DISABLED' => 'La registrazione di nuovi account è limitata', + 'LOGOUT' => "Non è possibile registrare un account mentre hai eseguito l'accesso ad un altro account", + 'WELCOME' => 'La registrazione è semplice e veloce' ], - "REMEMBER_ME" => "Ricordami", - "REMEMBER_ME_ON_COMPUTER" => "Ricordami su questo dispositivo (non consigliato per i computer pubblici)", + 'REMEMBER_ME' => 'Ricordami', + 'REMEMBER_ME_ON_COMPUTER' => 'Ricordami su questo dispositivo (non consigliato per i computer pubblici)', - "SIGN_IN_HERE" => "Hai già un account? Accedi qui", - "SIGNIN" => "Accedi", - "SIGNIN_OR_REGISTER" => "Accedi o iscriviti", - "SIGNUP" => "Registrazione", + 'SIGN_IN_HERE' => 'Hai già un account? Accedi qui', + 'SIGNIN' => 'Accedi', + 'SIGNIN_OR_REGISTER' => 'Accedi o iscriviti', + 'SIGNUP' => 'Registrazione', - "TOS" => "Termini e condizioni", - "TOS_AGREEMENT" => "Registrando un account su {{site_title}}, accetti i Termini e le Condizioni.", - "TOS_FOR" => "Termini e condizioni di {{title}}", + 'TOS' => 'Termini e condizioni', + 'TOS_AGREEMENT' => 'Registrando un account su {{site_title}}, accetti i Termini e le Condizioni.', + 'TOS_FOR' => 'Termini e condizioni di {{title}}', - "USERNAME" => [ - "@TRANSLATION" => "Nome utente", + 'USERNAME' => [ + '@TRANSLATION' => 'Nome utente', - "CHOOSE" => "Inserisci il tuo nome utente", - "INVALID" => "Nome utente non valido", - "IN_USE" => "Il nome utente '{{user_name}}' è già in uso", - "NOT_AVAILABLE" => "Il nome utente {{user_name}} non è disponibile. Scegli un nome diverso, oppure fai clic su \"suggerisci\"." + 'CHOOSE' => 'Inserisci il tuo nome utente', + 'INVALID' => 'Nome utente non valido', + 'IN_USE' => "Il nome utente '{{user_name}}' è già in uso", + 'NOT_AVAILABLE' => 'Il nome utente {{user_name}} non è disponibile. Scegli un nome diverso, oppure fai clic su "suggerisci".' ], - "USER_ID_INVALID" => "Questo ID utente non esiste", - "USER_OR_EMAIL_INVALID" => "L'indirizzo mail o il nome utente non sono validi", - "USER_OR_PASS_INVALID" => "Il nome utente o la password non sono validi", + 'USER_ID_INVALID' => 'Questo ID utente non esiste', + 'USER_OR_EMAIL_INVALID' => "L'indirizzo mail o il nome utente non sono validi", + 'USER_OR_PASS_INVALID' => 'Il nome utente o la password non sono validi', - "WELCOME" => "Bentornato, {{display_name}}" + 'WELCOME' => 'Bentornato, {{display_name}}' ]; diff --git a/app/sprinkles/account/locale/it_IT/validate.php b/app/sprinkles/account/locale/it_IT/validate.php index c9144a0ad..7ee338b2a 100644 --- a/app/sprinkles/account/locale/it_IT/validate.php +++ b/app/sprinkles/account/locale/it_IT/validate.php @@ -3,19 +3,20 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * Italian message token translations for the 'account' sprinkle. * This translation was generated with Google translate. Please contribute if you are a native speaker. * - * @package userfrosting\i18n\it * @author Alexander Weissman * @author Pietro Marangon (@Pe46dro) */ - return [ - "VALIDATE" => [ - "PASSWORD_MISMATCH" => "I due campi devono combaciare", - "USERNAME" => "Il nome utente può essere composto solo da caratteri alfanumerici, '.', '-', e '_'." + 'VALIDATE' => [ + 'PASSWORD_MISMATCH' => 'I due campi devono combaciare', + 'USERNAME' => "Il nome utente può essere composto solo da caratteri alfanumerici, '.', '-', e '_'." ] ]; diff --git a/app/sprinkles/account/locale/pt_PT/messages.php b/app/sprinkles/account/locale/pt_PT/messages.php index 3db4200b4..d3fa04f91 100644 --- a/app/sprinkles/account/locale/pt_PT/messages.php +++ b/app/sprinkles/account/locale/pt_PT/messages.php @@ -3,164 +3,165 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * Portuguese message token translations for the 'account' sprinkle. * - * @package userfrosting\i18n\pt * @author Bruno Silva (brunomnsilva@gmail.com) */ - return [ - "ACCOUNT" => [ - "@TRANSLATION" => "Conta", + 'ACCOUNT' => [ + '@TRANSLATION' => 'Conta', - "ACCESS_DENIED" => "Hmm, parece que não tem permissões para fazer isso.", + 'ACCESS_DENIED' => 'Hmm, parece que não tem permissões para fazer isso.', - "DISABLED" => "Esta conta foi desativada. Por favor contacte-nos para mais informações.", + 'DISABLED' => 'Esta conta foi desativada. Por favor contacte-nos para mais informações.', - "EMAIL_UPDATED" => "Email da conta atualizado", + 'EMAIL_UPDATED' => 'Email da conta atualizado', - "INVALID" => "Esta conta não existe. Pode ter sido removida. Por favor contacte-nos para mais informações.", + 'INVALID' => 'Esta conta não existe. Pode ter sido removida. Por favor contacte-nos para mais informações.', - "MASTER_NOT_EXISTS" => "Não pode registrar uma conta enquanto a conta principal não for criada!", - "MY" => "A minha conta", + 'MASTER_NOT_EXISTS' => 'Não pode registrar uma conta enquanto a conta principal não for criada!', + 'MY' => 'A minha conta', - "SESSION_COMPROMISED" => [ - "@TRANSLATION" => "A sua sessão foi comprometida. Deverá fechar todas as sessões, voltar a iniciar sessão e verificar que os seus dados não foram alterados por alheios.", - "TITLE" => "A sua sessão pode ter sido comprometida" + 'SESSION_COMPROMISED' => [ + '@TRANSLATION' => 'A sua sessão foi comprometida. Deverá fechar todas as sessões, voltar a iniciar sessão e verificar que os seus dados não foram alterados por alheios.', + 'TITLE' => 'A sua sessão pode ter sido comprometida' ], - "SESSION_EXPIRED" => "A sua sessão expirou. Por favor inicie nova sessão.", + 'SESSION_EXPIRED' => 'A sua sessão expirou. Por favor inicie nova sessão.', - "SETTINGS" => [ - "@TRANSLATION" => "Definições de conta", - "DESCRIPTION" => "Atualize as suas definições, incluindo email, nome e password.", - "UPDATED" => "Definições de conta atualizadas" + 'SETTINGS' => [ + '@TRANSLATION' => 'Definições de conta', + 'DESCRIPTION' => 'Atualize as suas definições, incluindo email, nome e password.', + 'UPDATED' => 'Definições de conta atualizadas' ], - "TOOLS" => "Ferramentas de conta", + 'TOOLS' => 'Ferramentas de conta', - "UNVERIFIED" => "A sua conta ainda não foi verificada. Consulte o seu email (incluindo a pasta de spam) para instruções de ativação.", + 'UNVERIFIED' => 'A sua conta ainda não foi verificada. Consulte o seu email (incluindo a pasta de spam) para instruções de ativação.', - "VERIFICATION" => [ - "NEW_LINK_SENT" => "Enviámos um link de verificação para o endereço {{email}}. Por favor consulte o seu email (incluindo a pasta de spam).", - "RESEND" => "Enviar novamente email de verificação", - "COMPLETE" => "Verificou com sucesso a sua conta. Pode iniciar sessão.", - "EMAIL" => "Por favor introduza o endereço de email que utilizou no registro e um email de verificação será enviado.", - "PAGE" => "Reenviar email de verificação para a sua nova conta.", - "SEND" => "Enviar email com link de verificação", - "TOKEN_NOT_FOUND" => "Token de verificação inexistente / Conta já verificada", + 'VERIFICATION' => [ + 'NEW_LINK_SENT' => 'Enviámos um link de verificação para o endereço {{email}}. Por favor consulte o seu email (incluindo a pasta de spam).', + 'RESEND' => 'Enviar novamente email de verificação', + 'COMPLETE' => 'Verificou com sucesso a sua conta. Pode iniciar sessão.', + 'EMAIL' => 'Por favor introduza o endereço de email que utilizou no registro e um email de verificação será enviado.', + 'PAGE' => 'Reenviar email de verificação para a sua nova conta.', + 'SEND' => 'Enviar email com link de verificação', + 'TOKEN_NOT_FOUND' => 'Token de verificação inexistente / Conta já verificada', ] ], - "EMAIL" => [ - "INVALID" => "Não existe nenhuma conta para {{email}}.", - "IN_USE" => "O email {{email}} já se encontra em uso." + 'EMAIL' => [ + 'INVALID' => 'Não existe nenhuma conta para {{email}}.', + 'IN_USE' => 'O email {{email}} já se encontra em uso.' ], - "FIRST_NAME" => "Primeiro nome", + 'FIRST_NAME' => 'Primeiro nome', - "HEADER_MESSAGE_ROOT" => "INICIOU SESSÃO COM A CONTA ROOT", + 'HEADER_MESSAGE_ROOT' => 'INICIOU SESSÃO COM A CONTA ROOT', - "LAST_NAME" => "Último nome", + 'LAST_NAME' => 'Último nome', - "LOCALE.ACCOUNT" => "Linguagem e localização a utilizar na sua conta", + 'LOCALE.ACCOUNT' => 'Linguagem e localização a utilizar na sua conta', - "LOGIN" => [ - "@TRANSLATION" => "Entrar", + 'LOGIN' => [ + '@TRANSLATION' => 'Entrar', - "ALREADY_COMPLETE" => "Sessão já iniciada!", - "SOCIAL" => "Ou inicie sessão com", - "REQUIRED" => "Lamentamos, tem de iniciar sessão para aceder a este recurso." + 'ALREADY_COMPLETE' => 'Sessão já iniciada!', + 'SOCIAL' => 'Ou inicie sessão com', + 'REQUIRED' => 'Lamentamos, tem de iniciar sessão para aceder a este recurso.' ], - "LOGOUT" => "Sair", + 'LOGOUT' => 'Sair', - "NAME" => "Nome", + 'NAME' => 'Nome', - "PAGE" => [ - "LOGIN" => [ - "DESCRIPTION" => "Inicie sessão na sua conta {{site_name}}, ou registre-se para uma nova conta.", - "SUBTITLE" => "Registre-se gratuitamente, ou inicie sessão com uma conta existente.", - "TITLE" => "Vamos começar!", + 'PAGE' => [ + 'LOGIN' => [ + 'DESCRIPTION' => 'Inicie sessão na sua conta {{site_name}}, ou registre-se para uma nova conta.', + 'SUBTITLE' => 'Registre-se gratuitamente, ou inicie sessão com uma conta existente.', + 'TITLE' => 'Vamos começar!', ] ], - "PASSWORD" => [ - "@TRANSLATION" => "Password", - - "BETWEEN" => "Entre {{min}}-{{max}} carateres", - - "CONFIRM" => "Confirme a password", - "CONFIRM_CURRENT" => "Por favor confirme a sua password atual", - "CONFIRM_NEW" => "Confirmar Nova Password", - "CONFIRM_NEW_EXPLAIN" => "Re-introduza a sua nova password", - "CONFIRM_NEW_HELP" => "Apenas necessário se escolher uma nova password", - "CURRENT" => "Password Atual", - "CURRENT_EXPLAIN" => "Tem de confirmar a sua password atual para efetuar alterações", - - "FORGOTTEN" => "Password Esquecida", - "FORGET" => [ - "@TRANSLATION" => "Esqueci a minha password", - - "COULD_NOT_UPDATE" => "Não foi possível atualizar a password.", - "EMAIL" => "Por favor introduza o endereço de email que utilizou no registro. Enviaremos um email com instruções para efetuar o reset à sua password.", - "EMAIL_SEND" => "Enviar email com link de reset da password", - "INVALID" => "This password reset request could not be found, or has expired. Please try resubmitting your request.", - "PAGE" => "Obtenha um link para fazer reset à sua password.", - "REQUEST_CANNED" => "Pedido de password esquecida foi cancelado.", - "REQUEST_SENT" => "Se o email {{email}} corresponder a uma conta em nosso sistema, um link de redefinição de senha será enviado para {{email}}." + 'PASSWORD' => [ + '@TRANSLATION' => 'Password', + + 'BETWEEN' => 'Entre {{min}}-{{max}} carateres', + + 'CONFIRM' => 'Confirme a password', + 'CONFIRM_CURRENT' => 'Por favor confirme a sua password atual', + 'CONFIRM_NEW' => 'Confirmar Nova Password', + 'CONFIRM_NEW_EXPLAIN' => 'Re-introduza a sua nova password', + 'CONFIRM_NEW_HELP' => 'Apenas necessário se escolher uma nova password', + 'CURRENT' => 'Password Atual', + 'CURRENT_EXPLAIN' => 'Tem de confirmar a sua password atual para efetuar alterações', + + 'FORGOTTEN' => 'Password Esquecida', + 'FORGET' => [ + '@TRANSLATION' => 'Esqueci a minha password', + + 'COULD_NOT_UPDATE' => 'Não foi possível atualizar a password.', + 'EMAIL' => 'Por favor introduza o endereço de email que utilizou no registro. Enviaremos um email com instruções para efetuar o reset à sua password.', + 'EMAIL_SEND' => 'Enviar email com link de reset da password', + 'INVALID' => 'This password reset request could not be found, or has expired. Please try resubmitting your request.', + 'PAGE' => 'Obtenha um link para fazer reset à sua password.', + 'REQUEST_CANNED' => 'Pedido de password esquecida foi cancelado.', + 'REQUEST_SENT' => 'Se o email {{email}} corresponder a uma conta em nosso sistema, um link de redefinição de senha será enviado para {{email}}.' ], - "RESET" => [ - "@TRANSLATION" => "Reset Password", - "CHOOSE" => "Por favor escolha uma nova password para continuar.", - "PAGE" => "Escolha uma nova password para a sua conta.", - "SEND" => "Definir nova password e registrar" + 'RESET' => [ + '@TRANSLATION' => 'Reset Password', + 'CHOOSE' => 'Por favor escolha uma nova password para continuar.', + 'PAGE' => 'Escolha uma nova password para a sua conta.', + 'SEND' => 'Definir nova password e registrar' ], - "HASH_FAILED" => "Falhou o hashing da password. Por favor contacte um administrador do site.", - "INVALID" => "A password atual não coincide com a que temos em sistema", - "NEW" => "Nova Password", - "NOTHING_TO_UPDATE" => "Não pode atualizar para a mesma password", - "UPDATED" => "Password da conta foi atualizada" + 'HASH_FAILED' => 'Falhou o hashing da password. Por favor contacte um administrador do site.', + 'INVALID' => 'A password atual não coincide com a que temos em sistema', + 'NEW' => 'Nova Password', + 'NOTHING_TO_UPDATE' => 'Não pode atualizar para a mesma password', + 'UPDATED' => 'Password da conta foi atualizada' ], - "REGISTER" => "Registrar", - "REGISTER_ME" => "Registrar-me", + 'REGISTER' => 'Registrar', + 'REGISTER_ME' => 'Registrar-me', - "REGISTRATION" => [ - "BROKEN" => "Lamentamos, existe um problema com o nosso processo de registro. Contacte-nos diretamente para assistência.", - "COMPLETE_TYPE1" => "Registrou-se com sucesso. Pode iniciar sessão.", - "COMPLETE_TYPE2" => "Registrou-se com sucesso. Receberá em breve um email de verificação contendo um link para verificar a sua conta. Não será possível iniciar sessão até completar este passo.", - "DISABLED" => "Lamentamos, o registro de novas contas foi desativado.", - "LOGOUT" => "Não pode registrar uma nova conta enquanto tiver sessão iniciada. Por favor feche a sua sessão primeiro.", - "WELCOME" => "O registro é rápido e simples." + 'REGISTRATION' => [ + 'BROKEN' => 'Lamentamos, existe um problema com o nosso processo de registro. Contacte-nos diretamente para assistência.', + 'COMPLETE_TYPE1' => 'Registrou-se com sucesso. Pode iniciar sessão.', + 'COMPLETE_TYPE2' => 'Registrou-se com sucesso. Receberá em breve um email de verificação contendo um link para verificar a sua conta. Não será possível iniciar sessão até completar este passo.', + 'DISABLED' => 'Lamentamos, o registro de novas contas foi desativado.', + 'LOGOUT' => 'Não pode registrar uma nova conta enquanto tiver sessão iniciada. Por favor feche a sua sessão primeiro.', + 'WELCOME' => 'O registro é rápido e simples.' ], - "RATE_LIMIT_EXCEEDED" => "Excedeu o número de tentativas para esta ação. Tem de aguardar {{delay}} segundos até lhe ser permitida nova tentativa.", - "REMEMBER_ME" => "Lembrar de mim!", - "REMEMBER_ME_ON_COMPUTER" => "Lembrar de mim neste computador (não recomendado em computadores públicos)", + 'RATE_LIMIT_EXCEEDED' => 'Excedeu o número de tentativas para esta ação. Tem de aguardar {{delay}} segundos até lhe ser permitida nova tentativa.', + 'REMEMBER_ME' => 'Lembrar de mim!', + 'REMEMBER_ME_ON_COMPUTER' => 'Lembrar de mim neste computador (não recomendado em computadores públicos)', - "SIGNIN" => "Iniciar Sessão", - "SIGNIN_OR_REGISTER" => "Iniciar sessão ou registrar", - "SIGNUP" => "Registrar", + 'SIGNIN' => 'Iniciar Sessão', + 'SIGNIN_OR_REGISTER' => 'Iniciar sessão ou registrar', + 'SIGNUP' => 'Registrar', - "TOS" => "Termos e Condições", - "TOS_AGREEMENT" => "Ao registrar uma conta em {{site_title}}, está a aceitar os termos e condições.", - "TOS_FOR" => "Termos e Condições para {{title}}", + 'TOS' => 'Termos e Condições', + 'TOS_AGREEMENT' => 'Ao registrar uma conta em {{site_title}}, está a aceitar os termos e condições.', + 'TOS_FOR' => 'Termos e Condições para {{title}}', - "USERNAME" => [ - "@TRANSLATION" => "Nome de utilizador", + 'USERNAME' => [ + '@TRANSLATION' => 'Nome de utilizador', - "CHOOSE" => "Escolha um nome de utilizador único", - "INVALID" => "Nome de utilizador inválido", - "IN_USE" => "O nome de utilizador {{user_name}} já se encontra em uso." + 'CHOOSE' => 'Escolha um nome de utilizador único', + 'INVALID' => 'Nome de utilizador inválido', + 'IN_USE' => 'O nome de utilizador {{user_name}} já se encontra em uso.' ], - "USER_ID_INVALID" => "O id de utilizador solicitado não existe.", - "USER_OR_EMAIL_INVALID" => "Nome de utilizador ou endereço de email inválidos.", - "USER_OR_PASS_INVALID" => "Nome de utilizador ou password inválidos.", + 'USER_ID_INVALID' => 'O id de utilizador solicitado não existe.', + 'USER_OR_EMAIL_INVALID' => 'Nome de utilizador ou endereço de email inválidos.', + 'USER_OR_PASS_INVALID' => 'Nome de utilizador ou password inválidos.', - "WELCOME" => "Bem-vindo, {{first_name}}" + 'WELCOME' => 'Bem-vindo, {{first_name}}' ]; diff --git a/app/sprinkles/account/locale/pt_PT/validate.php b/app/sprinkles/account/locale/pt_PT/validate.php index c05f14c6d..f0a3eb0a1 100644 --- a/app/sprinkles/account/locale/pt_PT/validate.php +++ b/app/sprinkles/account/locale/pt_PT/validate.php @@ -3,16 +3,17 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * Portuguese message token translations for the 'account' sprinkle. * - * @package userfrosting\i18n\pt * @author Bruno Silva (brunomnsilva@gmail.com) */ - return [ - "VALIDATE" => [ - "PASSWORD_MISMATCH" => "A password e respetiva confirmação têm de coincidir." + 'VALIDATE' => [ + 'PASSWORD_MISMATCH' => 'A password e respetiva confirmação têm de coincidir.' ] ]; diff --git a/app/sprinkles/account/locale/ru_RU/messages.php b/app/sprinkles/account/locale/ru_RU/messages.php index 328db13bb..1111bf249 100644 --- a/app/sprinkles/account/locale/ru_RU/messages.php +++ b/app/sprinkles/account/locale/ru_RU/messages.php @@ -3,181 +3,182 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * Russian message token translations for the 'account' sprinkle. * - * @package userfrosting\i18n\ru_RU * @author @rendername */ - return [ - "ACCOUNT" => [ - "@TRANSLATION" => "Аккаунт", + 'ACCOUNT' => [ + '@TRANSLATION' => 'Аккаунт', - "ACCESS_DENIED" => "Для получения доступа у вас недостаточно прав.", + 'ACCESS_DENIED' => 'Для получения доступа у вас недостаточно прав.', - "DISABLED" => "Аккаунт отключен. Пожалуйста, свяжитесь с нами для получения дополнительной информации.", + 'DISABLED' => 'Аккаунт отключен. Пожалуйста, свяжитесь с нами для получения дополнительной информации.', - "EMAIL_UPDATED" => "Email аккаунта обновлён", + 'EMAIL_UPDATED' => 'Email аккаунта обновлён', - "INVALID" => "Этот аккаунт не существует. Возможно, он удалён. Пожалуйста, свяжитесь с нами для получения дополнительной информации.", + 'INVALID' => 'Этот аккаунт не существует. Возможно, он удалён. Пожалуйста, свяжитесь с нами для получения дополнительной информации.', - "MASTER_NOT_EXISTS" => "Вы не можете зарегистрировать аккаунт до тех пор, пока основная учётная запись не будет создана!", - "MY" => "Мой профиль", + 'MASTER_NOT_EXISTS' => 'Вы не можете зарегистрировать аккаунт до тех пор, пока основная учётная запись не будет создана!', + 'MY' => 'Мой профиль', - "SESSION_COMPROMISED" => [ - "@TRANSLATION" => "Ваша сессия была скомпрометирована. Вы должны выйти на всех устройствах, а затем снова войти и убедиться, что ваши данные не были изменены.", - "TITLE" => "Возможно, ваш аккаунт был скомпрометированн", - "TEXT" => "Возможно, кто-то использовал ваши данные для входа на эту страницу. В целях безопасности все сеансы были завершены. Пожалуйста, повторно войдите и проверьте свой аккаунт на подозрительную активность. Рекомендуем сменить пароль." + 'SESSION_COMPROMISED' => [ + '@TRANSLATION' => 'Ваша сессия была скомпрометирована. Вы должны выйти на всех устройствах, а затем снова войти и убедиться, что ваши данные не были изменены.', + 'TITLE' => 'Возможно, ваш аккаунт был скомпрометированн', + 'TEXT' => 'Возможно, кто-то использовал ваши данные для входа на эту страницу. В целях безопасности все сеансы были завершены. Пожалуйста, повторно войдите и проверьте свой аккаунт на подозрительную активность. Рекомендуем сменить пароль.' ], - "SESSION_EXPIRED" => "Срок вашей сессии истек. Пожалуйста войдите еще раз.", + 'SESSION_EXPIRED' => 'Срок вашей сессии истек. Пожалуйста войдите еще раз.', - "SETTINGS" => [ - "@TRANSLATION" => "Настройки аккаунта", - "DESCRIPTION" => "Обновите настройки своего аккаунта, включая адрес электронной почты, имя и пароль.", - "UPDATED" => "Данные аккаунта обновлены" + 'SETTINGS' => [ + '@TRANSLATION' => 'Настройки аккаунта', + 'DESCRIPTION' => 'Обновите настройки своего аккаунта, включая адрес электронной почты, имя и пароль.', + 'UPDATED' => 'Данные аккаунта обновлены' ], - "TOOLS" => "Инструменты аккаунта", + 'TOOLS' => 'Инструменты аккаунта', - "UNVERIFIED" => "Ваш аккаунт ещё не подтверждён. Проверьте вашу email почту, в том числе папку спам и следуйте инструкциям.", + 'UNVERIFIED' => 'Ваш аккаунт ещё не подтверждён. Проверьте вашу email почту, в том числе папку спам и следуйте инструкциям.', - "VERIFICATION" => [ - "NEW_LINK_SENT" => "Мы отправили на ваш email новую ссылку для активации {{email}}. Пожалуйста, проверьте папку \"Входящие\" и \"Спам\".", - "RESEND" => "Повторно отправить письмо с подтверждением", - "COMPLETE" => "Вы успешно подтвердили свой аккаунт. Теперь вы можете войти.", - "EMAIL" => "Введите email, который вы использовали для регистрации, вам будет повторно отправлено письмо с подтверждением.", - "PAGE" => "Повторно оправить письмо подтверждения на email для нового аккаунта.", - "SEND" => "Проверка по электронной почте для аккаунта", - "TOKEN_NOT_FOUND" => "Код подтверждения не действителен либо аккаунт уже подтверждён", + 'VERIFICATION' => [ + 'NEW_LINK_SENT' => 'Мы отправили на ваш email новую ссылку для активации {{email}}. Пожалуйста, проверьте папку "Входящие" и "Спам".', + 'RESEND' => 'Повторно отправить письмо с подтверждением', + 'COMPLETE' => 'Вы успешно подтвердили свой аккаунт. Теперь вы можете войти.', + 'EMAIL' => 'Введите email, который вы использовали для регистрации, вам будет повторно отправлено письмо с подтверждением.', + 'PAGE' => 'Повторно оправить письмо подтверждения на email для нового аккаунта.', + 'SEND' => 'Проверка по электронной почте для аккаунта', + 'TOKEN_NOT_FOUND' => 'Код подтверждения не действителен либо аккаунт уже подтверждён', ] ], - "EMAIL" => [ - "INVALID" => "Нет не одного аккаунта с {{email}} .", - "IN_USE" => "Email {{email}} уже используется.", - "VERIFICATION_REQUIRED" => "Email (указывайте верный - необходим для активации!)" + 'EMAIL' => [ + 'INVALID' => 'Нет не одного аккаунта с {{email}} .', + 'IN_USE' => 'Email {{email}} уже используется.', + 'VERIFICATION_REQUIRED' => 'Email (указывайте верный - необходим для активации!)' ], - "EMAIL_OR_USERNAME" => "Имя пользователя или Email", + 'EMAIL_OR_USERNAME' => 'Имя пользователя или Email', - "FIRST_NAME" => "Имя", + 'FIRST_NAME' => 'Имя', - "HEADER_MESSAGE_ROOT" => "ВЫ АВТОРИЗОВАНЫ С СУПЕР-ПРАВАМИ", + 'HEADER_MESSAGE_ROOT' => 'ВЫ АВТОРИЗОВАНЫ С СУПЕР-ПРАВАМИ', - "LAST_NAME" => "Фамилия", - "LOCALE" => [ - "ACCOUNT" => "Основной язык для вашего аккаунта", - "INVALID" => "{{locale}} язык недопустим." + 'LAST_NAME' => 'Фамилия', + 'LOCALE' => [ + 'ACCOUNT' => 'Основной язык для вашего аккаунта', + 'INVALID' => '{{locale}} язык недопустим.' ], - "LOGIN" => [ - "@TRANSLATION" => "Вход", - "ALREADY_COMPLETE" => "Вы уже выполнили вход!", - "SOCIAL" => "Или войти через", - "REQUIRED" => "Извините, Вы должны авторизоваться для доступа к этому ресурсу." + 'LOGIN' => [ + '@TRANSLATION' => 'Вход', + 'ALREADY_COMPLETE' => 'Вы уже выполнили вход!', + 'SOCIAL' => 'Или войти через', + 'REQUIRED' => 'Извините, Вы должны авторизоваться для доступа к этому ресурсу.' ], - "LOGOUT" => "Выход", + 'LOGOUT' => 'Выход', - "NAME" => "Имя", + 'NAME' => 'Имя', - "NAME_AND_EMAIL" => "Имя и email", + 'NAME_AND_EMAIL' => 'Имя и email', - "PAGE" => [ - "LOGIN" => [ - "DESCRIPTION" => "Войдите в свой аккаунт {{site_name}}, или Зарегистрируйтесь.", - "SUBTITLE" => "Зарегистрироваться или войти в существующий аккаунт.", - "TITLE" => "Приступим!", + 'PAGE' => [ + 'LOGIN' => [ + 'DESCRIPTION' => 'Войдите в свой аккаунт {{site_name}}, или Зарегистрируйтесь.', + 'SUBTITLE' => 'Зарегистрироваться или войти в существующий аккаунт.', + 'TITLE' => 'Приступим!', ] ], - "PASSWORD" => [ - "@TRANSLATION" => "Пароль", + 'PASSWORD' => [ + '@TRANSLATION' => 'Пароль', - "BETWEEN" => "Кол-во {{min}}-{{max}} символов", + 'BETWEEN' => 'Кол-во {{min}}-{{max}} символов', - "CONFIRM" => "Подтверждение пароля", - "CONFIRM_CURRENT" => "Пожалуйста, введите ваш текущий пароль", - "CONFIRM_NEW" => "Подтвердите новый пароль", - "CONFIRM_NEW_EXPLAIN" => "Повторно введите Ваш новый пароль", - "CONFIRM_NEW_HELP" => "Требуется только при выборе нового пароля", - "CREATE" => [ - "@TRANSLATION" => "Создать пароль", - "PAGE" => "Выберите пароль для вашего аккаунта.", - "SET" => "Установить пароль и войти" + 'CONFIRM' => 'Подтверждение пароля', + 'CONFIRM_CURRENT' => 'Пожалуйста, введите ваш текущий пароль', + 'CONFIRM_NEW' => 'Подтвердите новый пароль', + 'CONFIRM_NEW_EXPLAIN' => 'Повторно введите Ваш новый пароль', + 'CONFIRM_NEW_HELP' => 'Требуется только при выборе нового пароля', + 'CREATE' => [ + '@TRANSLATION' => 'Создать пароль', + 'PAGE' => 'Выберите пароль для вашего аккаунта.', + 'SET' => 'Установить пароль и войти' ], - "CURRENT" => "Текущий пароль", - "CURRENT_EXPLAIN" => "Для продолжения вы должны ввести текущий пароль", - - "FORGOTTEN" => "Забытый пароль?", - "FORGET" => [ - "@TRANSLATION" => "Я забыл свой пароль", - - "COULD_NOT_UPDATE" => "Не удалось обновить пароль.", - "EMAIL" => "Пожалуйста, введите адрес электронной почты, который Вы использовали при регистрации. Ссылка с инструкцией по сбросу пароля будет отправлена вам по электронной почте.", - "EMAIL_SEND" => "Ссылка сброса пароля по Email", - "INVALID" => "Этот запрос сброса пароля не может быть найден, или истек. Пожалуйста, попробуйте повторно сбросить пароль.", - "PAGE" => "Получите ссылку для сброса пароля.", - "REQUEST_CANNED" => "Запрос на сброс пароля отменен.", - "REQUEST_SENT" => "Если email {{email}} существует в нашей системе у какого-либо аккаунта, ссылка на сброс пароля будет направлена на {{email}}." + 'CURRENT' => 'Текущий пароль', + 'CURRENT_EXPLAIN' => 'Для продолжения вы должны ввести текущий пароль', + + 'FORGOTTEN' => 'Забытый пароль?', + 'FORGET' => [ + '@TRANSLATION' => 'Я забыл свой пароль', + + 'COULD_NOT_UPDATE' => 'Не удалось обновить пароль.', + 'EMAIL' => 'Пожалуйста, введите адрес электронной почты, который Вы использовали при регистрации. Ссылка с инструкцией по сбросу пароля будет отправлена вам по электронной почте.', + 'EMAIL_SEND' => 'Ссылка сброса пароля по Email', + 'INVALID' => 'Этот запрос сброса пароля не может быть найден, или истек. Пожалуйста, попробуйте повторно сбросить пароль.', + 'PAGE' => 'Получите ссылку для сброса пароля.', + 'REQUEST_CANNED' => 'Запрос на сброс пароля отменен.', + 'REQUEST_SENT' => 'Если email {{email}} существует в нашей системе у какого-либо аккаунта, ссылка на сброс пароля будет направлена на {{email}}.' ], - "HASH_FAILED" => "Хэширование пароля не удалось. Пожалуйста, попробуйте другой пароль, либо свяжитесь с администратором сайта.", - "INVALID" => "Текущий пароль не соответствует тому, который задан в системе.", - "NEW" => "Новый пароль", - "NOTHING_TO_UPDATE" => "Невозможно обновить с тем же паролем", + 'HASH_FAILED' => 'Хэширование пароля не удалось. Пожалуйста, попробуйте другой пароль, либо свяжитесь с администратором сайта.', + 'INVALID' => 'Текущий пароль не соответствует тому, который задан в системе.', + 'NEW' => 'Новый пароль', + 'NOTHING_TO_UPDATE' => 'Невозможно обновить с тем же паролем', - "RESET" => [ - "@TRANSLATION" => "Сбросить пароль", - "CHOOSE" => "Пожалуйста, выберите новый пароль, чтобы продолжить.", - "PAGE" => "Выберите новый пароль для вашего аккаунта.", - "SEND" => "Задать новый пароль и войти" + 'RESET' => [ + '@TRANSLATION' => 'Сбросить пароль', + 'CHOOSE' => 'Пожалуйста, выберите новый пароль, чтобы продолжить.', + 'PAGE' => 'Выберите новый пароль для вашего аккаунта.', + 'SEND' => 'Задать новый пароль и войти' ], - "UPDATED" => "Пароль аккаунта обновлён" + 'UPDATED' => 'Пароль аккаунта обновлён' ], - "PROFILE" => [ - "SETTINGS" => "Настройки профиля", - "UPDATED" => "Настройки профиля обновлены" + 'PROFILE' => [ + 'SETTINGS' => 'Настройки профиля', + 'UPDATED' => 'Настройки профиля обновлены' ], - "RATE_LIMIT_EXCEEDED" => "Превышен лимит попыток для этого действия. Вы должны подождать еще {{delay}} секунд, прежде чем вам вам будет разрешено сделать ещё попытку.", - - "REGISTER" => "Регистрация", - "REGISTER_ME" => "Зарегистрируйте меня", - "REGISTRATION" => [ - "BROKEN" => "К сожалению, есть проблема с регистрации аккаунта. Свяжитесь с нами напрямую для получения помощи.", - "COMPLETE_TYPE1" => "Вы успешно зарегистрировались. Теперь вы можете войти.", - "COMPLETE_TYPE2" => "Вы успешно зарегистрировались. Ссылка для активации вашего аккаунта была отправлена на {{email}}. Вы сможете войти в систему только после активации аккаунта.", - "DISABLED" => "Извините, регистрация аккаунта была отключена.", - "LOGOUT" => "Извините, вы не можете зарегистрироваться когда уже авторизовались в системе. Сначала выйдите из системы.", - "WELCOME" => "Быстрая и простая регистрация." + 'RATE_LIMIT_EXCEEDED' => 'Превышен лимит попыток для этого действия. Вы должны подождать еще {{delay}} секунд, прежде чем вам вам будет разрешено сделать ещё попытку.', + + 'REGISTER' => 'Регистрация', + 'REGISTER_ME' => 'Зарегистрируйте меня', + 'REGISTRATION' => [ + 'BROKEN' => 'К сожалению, есть проблема с регистрации аккаунта. Свяжитесь с нами напрямую для получения помощи.', + 'COMPLETE_TYPE1' => 'Вы успешно зарегистрировались. Теперь вы можете войти.', + 'COMPLETE_TYPE2' => 'Вы успешно зарегистрировались. Ссылка для активации вашего аккаунта была отправлена на {{email}}. Вы сможете войти в систему только после активации аккаунта.', + 'DISABLED' => 'Извините, регистрация аккаунта была отключена.', + 'LOGOUT' => 'Извините, вы не можете зарегистрироваться когда уже авторизовались в системе. Сначала выйдите из системы.', + 'WELCOME' => 'Быстрая и простая регистрация.' ], - "REMEMBER_ME" => "Запомнить", - "REMEMBER_ME_ON_COMPUTER" => "Запомнить меня на этом компьютере (не рекомендуется для общедоступных компьютеров)", + 'REMEMBER_ME' => 'Запомнить', + 'REMEMBER_ME_ON_COMPUTER' => 'Запомнить меня на этом компьютере (не рекомендуется для общедоступных компьютеров)', - "SIGN_IN_HERE" => "Уже есть аккаунт? войти.", - "SIGNIN" => "Вход", - "SIGNIN_OR_REGISTER" => "Регистрация или вход", - "SIGNUP" => "Вход", + 'SIGN_IN_HERE' => 'Уже есть аккаунт? войти.', + 'SIGNIN' => 'Вход', + 'SIGNIN_OR_REGISTER' => 'Регистрация или вход', + 'SIGNUP' => 'Вход', - "TOS" => "Пользовательское соглашение", - "TOS_AGREEMENT" => "Регистрируя аккаунт на {{site_title}}, вы принимаете условия и положения.", - "TOS_FOR" => "Правила и условия для {{title}}", + 'TOS' => 'Пользовательское соглашение', + 'TOS_AGREEMENT' => 'Регистрируя аккаунт на {{site_title}}, вы принимаете условия и положения.', + 'TOS_FOR' => 'Правила и условия для {{title}}', - "USERNAME" => [ - "@TRANSLATION" => "Пользователь", + 'USERNAME' => [ + '@TRANSLATION' => 'Пользователь', - "CHOOSE" => "Выберите имя пользователя", - "INVALID" => "Недопустимое имя пользователя", - "IN_USE" => "{{user_name}} имя пользователя уже используется.", - "NOT_AVAILABLE" => "Имя пользователя {{user_name}} не доступно. Выберите другое имя или нажмите кнопку «предложить»." + 'CHOOSE' => 'Выберите имя пользователя', + 'INVALID' => 'Недопустимое имя пользователя', + 'IN_USE' => '{{user_name}} имя пользователя уже используется.', + 'NOT_AVAILABLE' => 'Имя пользователя {{user_name}} не доступно. Выберите другое имя или нажмите кнопку «предложить».' ], - "USER_ID_INVALID" => "ID запрашиваемого пользователя не существует.", - "USER_OR_EMAIL_INVALID" => "Имя пользователя или email не верный.", - "USER_OR_PASS_INVALID" => "Пользователь не найден или пароль является недействительным.", + 'USER_ID_INVALID' => 'ID запрашиваемого пользователя не существует.', + 'USER_OR_EMAIL_INVALID' => 'Имя пользователя или email не верный.', + 'USER_OR_PASS_INVALID' => 'Пользователь не найден или пароль является недействительным.', - "WELCOME" => "Добро пожаловать, {{first_name}}" + 'WELCOME' => 'Добро пожаловать, {{first_name}}' ]; diff --git a/app/sprinkles/account/locale/ru_RU/validate.php b/app/sprinkles/account/locale/ru_RU/validate.php index 8ede5d8ec..0d74fff6f 100644 --- a/app/sprinkles/account/locale/ru_RU/validate.php +++ b/app/sprinkles/account/locale/ru_RU/validate.php @@ -3,17 +3,18 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * Russian message token translations for the 'account' sprinkle. * - * @package userfrosting\i18n\ru_RU * @author @rendername */ - return [ - "VALIDATE" => [ - "PASSWORD_MISMATCH" => "Пароли не совпадают.", - "USERNAME" => "Имя может состоять только из строчных букв, цифр, '.', '-' и «_»." + 'VALIDATE' => [ + 'PASSWORD_MISMATCH' => 'Пароли не совпадают.', + 'USERNAME' => "Имя может состоять только из строчных букв, цифр, '.', '-' и «_»." ] ]; diff --git a/app/sprinkles/account/locale/th_TH/messages.php b/app/sprinkles/account/locale/th_TH/messages.php index 642a7c5dd..7a301c096 100644 --- a/app/sprinkles/account/locale/th_TH/messages.php +++ b/app/sprinkles/account/locale/th_TH/messages.php @@ -1,164 +1,165 @@ - [ - "@TRANSLATION" => "บัญชี", - - "ACCESS_DENIED" => "หืมม ดูเหมือนว่าคุณไม่ได้รับอนุญาตให้ทำเช่นนั้น", - - "DISABLED" => "บัญชีนี้ถูกปิดการใช้งานไปแล้ว กรุณาติดต่อเราสำหรับข้อมูลเพิ่มเติม", - - "EMAIL_UPDATED" => "ปรับปรุงบัญชีอีเมลแล้ว", - - "INVALID" => "ไม่พบบัญชีนี้ มันอาจถูกลบไปแล้ว กรุณาติดต่อเราสำหรับข้อมูลเพิ่มเติม", - - "MASTER_NOT_EXISTS" => "คุณไม่สามารถสมัครสมาชิกได้จนกว่าจะสร้างบัญชีหลัก!", - "MY" => "บัญชีของฉัน", - - "SESSION_COMPROMISED" => "เซสชันของคุณถูกลักลอบใช้ คุณควรจะออกจากระบบบนอุปกรณ์ทั้งหมดแล้วกลับเข้าสู่ระบบและตรวจสอบให้แน่ใจว่าไม่มีการแก้ไขข้อมูลของคุณ", - "SESSION_COMPROMISED_TITLE" => "บัญชีของคุณอาจถูกบุกรุก", - "SESSION_EXPIRED" => "เซสชันของคุณหมดอายุ กรุณาเข้าสู่ระบบอีกครั้ง", - - "SETTINGS" => [ - "@TRANSLATION" => "การตั้งค่าบัญชี", - "DESCRIPTION" => "ปรับปรุงการตั้งค่าบัญชีของคุณ รวมไปถึงอีเมล ชื่อ และรหัสผ่าน", - "UPDATED" => "ปรับปรุงการตั้งค่าบัญชีของคุณแล้ว" - ], - - "TOOLS" => "เครื่องมือบัญชี", - - "UNVERIFIED" => "บัญชีของคุณยังไม่ได้รับการยืนยัน กรุณาตรวจสอบกล่องอีเมลและจดหมายขยะของคุณสำหรับขั้นตอนการเปิดใช้งานบัญชี", - - "VERIFICATION" => [ - "NEW_LINK_SENT" => "เราได้ส่งลิงก์สำหรับการยืนยันใหม่ไปยังอีเมล {{email}} กรุณาตรวจสอบอีเมลนี้ในกล่องอีเมลและจดหมายขยะของคุณ", - "RESEND" => "ส่งอีเมลยืนยันอีกครั้ง", - "COMPLETE" => "คุณได้ยืนยันอีเมลของคุณเรียบร้อยแล้ว คุณสามารถเข้าสู่ระบบได้ทันที", - "EMAIL" => "กรุณากรอกอีเมลที่คุณได้ใช้สมัครไว้แล้วอีเมลยืนยันจะถูกส่งไปให้ใหม่", - "PAGE" => "ส่งอีเมลยืนยันสำหรับบัญชีของฉันใหม่", - "SEND" => "ส่งอีเมลยืนยันให้บัญชีของฉัน", - "TOKEN_NOT_FOUND" => "ไม่พบโทเคนยืนยันอีเมล / บัญชีนี้ได้ยืนยันแล้ว", - ] - ], - - "EMAIL" => [ - "INVALID" => "อีเมล {{email}} ไม่มีอยู่จริง", - "IN_USE" => "อีเมล {{email}} ได้ถูกใช้งานแล้ว" - ], - - "FIRST_NAME" => "ชื่อจริง", - - "HEADER_MESSAGE_ROOT" => "คุณได้เข้าสู่ระบบเป็นผู้ดูแลสูงสุด", - - "LAST_NAME" => "นามสกุล", - - "LOCALE.ACCOUNT" => "ภาษาและสถานที่ที่จะใช้สำหรับบัญชีของคุณ", - - "LOGIN" => [ - "@TRANSLATION" => "เข้าสู่ะระบบ", - - "ALREADY_COMPLETE" => "คุณได้เข้าสู่ระบบอยู่แล้ว!", - "SOCIAL" => "หรือเข้าสู่ระบบด้วย", - "REQUIRED" => "ขออภัย คุณจะต้องเข้าสู่ระบบเพื่อเข้าถึงส่วนนี้" - ], - - "LOGOUT" => "ออกจากระบบ", - - "NAME" => "ชื่อ", - - "PAGE" => [ - "LOGIN" => [ - "DESCRIPTION" => "เข้าสู่ระบบไปยังบัญชี {{site_name}} หรือสมัครสมาชิกสำหรับบัญชีใหม่", - "SUBTITLE" => "สมัครสมาชิกฟรี หรือเข้าสู่ระบบด้วยบัญชีที่มีอยู่", - "TITLE" => "มาเริ่มกันเลย!", - ] - ], - - "PASSWORD" => [ - "@TRANSLATION" => "รหัสผ่าน", - - "BETWEEN" => "ระหว่าง {{min}}-{{max}} ตัวอักษร", - - "CONFIRM" => "ยืนยันรหัสผ่าน", - "CONFIRM_CURRENT" => "กรุณายืนยันรหัสผ่านปัจจุบันของคุณ", - "CONFIRM_NEW" => "ยืนยันรหัสผ่านใหม่", - "CONFIRM_NEW_EXPLAIN" => "กรอกรหัสผ่านใหม่ของคุณอีกครั้ง", - "CONFIRM_NEW_HELP" => "กรอกเฉพาะเมื่อคุณต้องการตั้งรหัสผ่านใหม่", - "CURRENT" => "รหัสผ่านปัจจุบัน", - "CURRENT_EXPLAIN" => "คุณจะต้องยืนยันรหัสผ่านปัจจุบันเพื่อแก้ไขข้อมูล", - - "FORGOTTEN" => "ลืมรหัสผ่าน", - "FORGET" => [ - "@TRANSLATION" => "ฉันลืมรหัสผ่านของฉัน", - - "COULD_NOT_UPDATE" => "ไม่สามารถปรับปรุงรหัสผ่าน", - "EMAIL" => "กรุณากรอกที่อยู่อีเมลที่คุณเคยใช้เข้าสู่ระบบ ลิงก์ขั้นตอนการรีเซ็ตรหัสผ่านของคุณจะถูกส่งไปให้คุณ", - "EMAIL_SEND" => "ลิงก์รีเซ็ตรหัสผ่านจากอีเมล", - "INVALID" => "ขอรีเซ็ตรหัสผ่านนี้ไม่มีอยู่ หรือหมดอายุไปแล้ว กรุณาลอง ส่งคำขอของคุณอีกครั้ง", - "PAGE" => "รับลิงก์สำหรับการรีเซ็ตรหัสผ่านของคุณ", - "REQUEST_CANNED" => "คำขอลืมรหัสผ่านได้ถูกยกเลิก", - "REQUEST_SENT" => "หากอีเมล {{email}} ตรงกับบัญชีในระบบของเราลิงก์การรีเซ็ตรหัสผ่านจะถูกส่งไปที่ {{email}}" - ], - - "RESET" => [ - "@TRANSLATION" => "รีเซ็ตรหัสผ่าน", - "CHOOSE" => "กรุณาเลือกรหัสผ่านใหม่เพื่อดำเนินการต่อ", - "PAGE" => "เลือกรหัสผ่านใหม่สำหรับบัญชีของคุณ", - "SEND" => "ตั้งรหัสผ่านใหม่และเข้าสู่ระบบ" - ], - - "HASH_FAILED" => "เข้ารหัสรหัสผ่านล้มเหลว กรุณาติดต่อผู้ดูแลระบบของเว็บไซต์", - "INVALID" => "รหัสผ่านปัจจุบันไม่ตรงกับรหัสผ่านที่เราบันทึกไว้", - "NEW" => "รหัสผ่านใหม่", - "NOTHING_TO_UPDATE" => "คุณไม่สามารถปรังปรุงด้วยรหัสผ่านเดียวกัน", - "UPDATED" => "ปรังปรุงรหัสผ่านของบัญชีแล้ว" - ], - - "REGISTER" => "สมัครสมาชิก", - "REGISTER_ME" => "ให้ฉันสมัครสมาชิกด้วย", - - "REGISTRATION" => [ - "BROKEN" => "เราขออภัย มันมีปัญหาในการดำเนินการสมัครสมาชิกของเรา กรุณาติดต่อเราโดยตรงเพื่อขอความช่วยเหลือ", - "COMPLETE_TYPE1" => "คุณได้สมัครสมาชิกเรียบร้อยแล้ว คุณสามารถเข้าสู่ระบบได้ทันที", - "COMPLETE_TYPE2" => "คุณได้สมัครสมาชิกเรียบร้อยแล้ว คุณจะได้รับอีเมลยืนยันที่มีลิงก์สำหรับเปิดใช้งานบัญชีของคุณอยู่ คุณจะไม่สามารถเข้าสู่ระบบจนกว่าคุณจะยืนยันอีเมลแล้ว", - "DISABLED" => "เราขออภัย ระบบสมัครสมาชิกได้ถูกปิดไว้", - "LOGOUT" => "เราขออภัย คุณไม่สามารถสมัครสมาชิกขณะที่เข้าสู่ระบบอยู่ กรุณาออกจากระบบก่อน", - "WELCOME" => "การสมัครสมาชิกนั้นรวดเร็ว และง่ายดาย" - ], - - "RATE_LIMIT_EXCEEDED" => "ถึงขีดจำกัดสำหรับการกระทำนี้แล้ว คุณจะต้องรออีก {{delay}} วินาที ก่อนที่คุณจะได้รับอนุญาตให้ลองใหม่อีกครั้ง", - "REMEMBER_ME" => "จำฉันไว้ในระบบ!", - "REMEMBER_ME_ON_COMPUTER" => "จำฉันไว้ในระบบบนคอมพิวเตอร์นี้ (ไม่แนะนำสำหรับคอมพิวเตอร์สาธารณะ)", - - "SIGNIN" => "เข้าสู่ะระบบ", - "SIGNIN_OR_REGISTER" => "เข้าสู่ระบบหรือสมัครสมาชิก", - "SIGNUP" => "สมัครสมาชิก", - - "TOS" => "ข้อตกลงและเงื่อนไข", - "TOS_AGREEMENT" => "ในการสมัครสมาชิกกับ {{site_title}} หมายถึงคุณยอมรับ ข้อตกลงและเงื่อนไข แล้ว", - "TOS_FOR" => "ข้อตกลงและเงื่อนไขสำหรับ {{title}}", - - "USERNAME" => [ - "@TRANSLATION" => "ชื่อผู้ใช้", - - "CHOOSE" => "เลือกชื่อผู้ใช้ที่เป็นเป็นเอกลักษณ์", - "INVALID" => "ชื่อผู้ใช้ไม่ถูกต้อง", - "IN_USE" => "ชื่อผู้ใช้ {{user_name}} ถูกใช้งานแล้ว" - ], - - "USER_ID_INVALID" => "ไม่พบหมายเลขผู้ใช้ที่ร้องขอมา", - "USER_OR_EMAIL_INVALID" => "ชื่อผู้ใช้หรือที่อยู่อีเมลไม่ถูกต้อง", - "USER_OR_PASS_INVALID" => "ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง", - - "WELCOME" => "ยินดีต้อนรับ {{first_name}}" -]; + [ + '@TRANSLATION' => 'บัญชี', + + 'ACCESS_DENIED' => 'หืมม ดูเหมือนว่าคุณไม่ได้รับอนุญาตให้ทำเช่นนั้น', + + 'DISABLED' => 'บัญชีนี้ถูกปิดการใช้งานไปแล้ว กรุณาติดต่อเราสำหรับข้อมูลเพิ่มเติม', + + 'EMAIL_UPDATED' => 'ปรับปรุงบัญชีอีเมลแล้ว', + + 'INVALID' => 'ไม่พบบัญชีนี้ มันอาจถูกลบไปแล้ว กรุณาติดต่อเราสำหรับข้อมูลเพิ่มเติม', + + 'MASTER_NOT_EXISTS' => 'คุณไม่สามารถสมัครสมาชิกได้จนกว่าจะสร้างบัญชีหลัก!', + 'MY' => 'บัญชีของฉัน', + + 'SESSION_COMPROMISED' => 'เซสชันของคุณถูกลักลอบใช้ คุณควรจะออกจากระบบบนอุปกรณ์ทั้งหมดแล้วกลับเข้าสู่ระบบและตรวจสอบให้แน่ใจว่าไม่มีการแก้ไขข้อมูลของคุณ', + 'SESSION_COMPROMISED_TITLE' => 'บัญชีของคุณอาจถูกบุกรุก', + 'SESSION_EXPIRED' => 'เซสชันของคุณหมดอายุ กรุณาเข้าสู่ระบบอีกครั้ง', + + 'SETTINGS' => [ + '@TRANSLATION' => 'การตั้งค่าบัญชี', + 'DESCRIPTION' => 'ปรับปรุงการตั้งค่าบัญชีของคุณ รวมไปถึงอีเมล ชื่อ และรหัสผ่าน', + 'UPDATED' => 'ปรับปรุงการตั้งค่าบัญชีของคุณแล้ว' + ], + + 'TOOLS' => 'เครื่องมือบัญชี', + + 'UNVERIFIED' => 'บัญชีของคุณยังไม่ได้รับการยืนยัน กรุณาตรวจสอบกล่องอีเมลและจดหมายขยะของคุณสำหรับขั้นตอนการเปิดใช้งานบัญชี', + + 'VERIFICATION' => [ + 'NEW_LINK_SENT' => 'เราได้ส่งลิงก์สำหรับการยืนยันใหม่ไปยังอีเมล {{email}} กรุณาตรวจสอบอีเมลนี้ในกล่องอีเมลและจดหมายขยะของคุณ', + 'RESEND' => 'ส่งอีเมลยืนยันอีกครั้ง', + 'COMPLETE' => 'คุณได้ยืนยันอีเมลของคุณเรียบร้อยแล้ว คุณสามารถเข้าสู่ระบบได้ทันที', + 'EMAIL' => 'กรุณากรอกอีเมลที่คุณได้ใช้สมัครไว้แล้วอีเมลยืนยันจะถูกส่งไปให้ใหม่', + 'PAGE' => 'ส่งอีเมลยืนยันสำหรับบัญชีของฉันใหม่', + 'SEND' => 'ส่งอีเมลยืนยันให้บัญชีของฉัน', + 'TOKEN_NOT_FOUND' => 'ไม่พบโทเคนยืนยันอีเมล / บัญชีนี้ได้ยืนยันแล้ว', + ] + ], + + 'EMAIL' => [ + 'INVALID' => 'อีเมล {{email}} ไม่มีอยู่จริง', + 'IN_USE' => 'อีเมล {{email}} ได้ถูกใช้งานแล้ว' + ], + + 'FIRST_NAME' => 'ชื่อจริง', + + 'HEADER_MESSAGE_ROOT' => 'คุณได้เข้าสู่ระบบเป็นผู้ดูแลสูงสุด', + + 'LAST_NAME' => 'นามสกุล', + + 'LOCALE.ACCOUNT' => 'ภาษาและสถานที่ที่จะใช้สำหรับบัญชีของคุณ', + + 'LOGIN' => [ + '@TRANSLATION' => 'เข้าสู่ะระบบ', + + 'ALREADY_COMPLETE' => 'คุณได้เข้าสู่ระบบอยู่แล้ว!', + 'SOCIAL' => 'หรือเข้าสู่ระบบด้วย', + 'REQUIRED' => 'ขออภัย คุณจะต้องเข้าสู่ระบบเพื่อเข้าถึงส่วนนี้' + ], + + 'LOGOUT' => 'ออกจากระบบ', + + 'NAME' => 'ชื่อ', + + 'PAGE' => [ + 'LOGIN' => [ + 'DESCRIPTION' => 'เข้าสู่ระบบไปยังบัญชี {{site_name}} หรือสมัครสมาชิกสำหรับบัญชีใหม่', + 'SUBTITLE' => 'สมัครสมาชิกฟรี หรือเข้าสู่ระบบด้วยบัญชีที่มีอยู่', + 'TITLE' => 'มาเริ่มกันเลย!', + ] + ], + + 'PASSWORD' => [ + '@TRANSLATION' => 'รหัสผ่าน', + + 'BETWEEN' => 'ระหว่าง {{min}}-{{max}} ตัวอักษร', + + 'CONFIRM' => 'ยืนยันรหัสผ่าน', + 'CONFIRM_CURRENT' => 'กรุณายืนยันรหัสผ่านปัจจุบันของคุณ', + 'CONFIRM_NEW' => 'ยืนยันรหัสผ่านใหม่', + 'CONFIRM_NEW_EXPLAIN' => 'กรอกรหัสผ่านใหม่ของคุณอีกครั้ง', + 'CONFIRM_NEW_HELP' => 'กรอกเฉพาะเมื่อคุณต้องการตั้งรหัสผ่านใหม่', + 'CURRENT' => 'รหัสผ่านปัจจุบัน', + 'CURRENT_EXPLAIN' => 'คุณจะต้องยืนยันรหัสผ่านปัจจุบันเพื่อแก้ไขข้อมูล', + + 'FORGOTTEN' => 'ลืมรหัสผ่าน', + 'FORGET' => [ + '@TRANSLATION' => 'ฉันลืมรหัสผ่านของฉัน', + + 'COULD_NOT_UPDATE' => 'ไม่สามารถปรับปรุงรหัสผ่าน', + 'EMAIL' => 'กรุณากรอกที่อยู่อีเมลที่คุณเคยใช้เข้าสู่ระบบ ลิงก์ขั้นตอนการรีเซ็ตรหัสผ่านของคุณจะถูกส่งไปให้คุณ', + 'EMAIL_SEND' => 'ลิงก์รีเซ็ตรหัสผ่านจากอีเมล', + 'INVALID' => 'ขอรีเซ็ตรหัสผ่านนี้ไม่มีอยู่ หรือหมดอายุไปแล้ว กรุณาลอง ส่งคำขอของคุณอีกครั้ง', + 'PAGE' => 'รับลิงก์สำหรับการรีเซ็ตรหัสผ่านของคุณ', + 'REQUEST_CANNED' => 'คำขอลืมรหัสผ่านได้ถูกยกเลิก', + 'REQUEST_SENT' => 'หากอีเมล {{email}} ตรงกับบัญชีในระบบของเราลิงก์การรีเซ็ตรหัสผ่านจะถูกส่งไปที่ {{email}}' + ], + + 'RESET' => [ + '@TRANSLATION' => 'รีเซ็ตรหัสผ่าน', + 'CHOOSE' => 'กรุณาเลือกรหัสผ่านใหม่เพื่อดำเนินการต่อ', + 'PAGE' => 'เลือกรหัสผ่านใหม่สำหรับบัญชีของคุณ', + 'SEND' => 'ตั้งรหัสผ่านใหม่และเข้าสู่ระบบ' + ], + + 'HASH_FAILED' => 'เข้ารหัสรหัสผ่านล้มเหลว กรุณาติดต่อผู้ดูแลระบบของเว็บไซต์', + 'INVALID' => 'รหัสผ่านปัจจุบันไม่ตรงกับรหัสผ่านที่เราบันทึกไว้', + 'NEW' => 'รหัสผ่านใหม่', + 'NOTHING_TO_UPDATE' => 'คุณไม่สามารถปรังปรุงด้วยรหัสผ่านเดียวกัน', + 'UPDATED' => 'ปรังปรุงรหัสผ่านของบัญชีแล้ว' + ], + + 'REGISTER' => 'สมัครสมาชิก', + 'REGISTER_ME' => 'ให้ฉันสมัครสมาชิกด้วย', + + 'REGISTRATION' => [ + 'BROKEN' => 'เราขออภัย มันมีปัญหาในการดำเนินการสมัครสมาชิกของเรา กรุณาติดต่อเราโดยตรงเพื่อขอความช่วยเหลือ', + 'COMPLETE_TYPE1' => 'คุณได้สมัครสมาชิกเรียบร้อยแล้ว คุณสามารถเข้าสู่ระบบได้ทันที', + 'COMPLETE_TYPE2' => 'คุณได้สมัครสมาชิกเรียบร้อยแล้ว คุณจะได้รับอีเมลยืนยันที่มีลิงก์สำหรับเปิดใช้งานบัญชีของคุณอยู่ คุณจะไม่สามารถเข้าสู่ระบบจนกว่าคุณจะยืนยันอีเมลแล้ว', + 'DISABLED' => 'เราขออภัย ระบบสมัครสมาชิกได้ถูกปิดไว้', + 'LOGOUT' => 'เราขออภัย คุณไม่สามารถสมัครสมาชิกขณะที่เข้าสู่ระบบอยู่ กรุณาออกจากระบบก่อน', + 'WELCOME' => 'การสมัครสมาชิกนั้นรวดเร็ว และง่ายดาย' + ], + + 'RATE_LIMIT_EXCEEDED' => 'ถึงขีดจำกัดสำหรับการกระทำนี้แล้ว คุณจะต้องรออีก {{delay}} วินาที ก่อนที่คุณจะได้รับอนุญาตให้ลองใหม่อีกครั้ง', + 'REMEMBER_ME' => 'จำฉันไว้ในระบบ!', + 'REMEMBER_ME_ON_COMPUTER' => 'จำฉันไว้ในระบบบนคอมพิวเตอร์นี้ (ไม่แนะนำสำหรับคอมพิวเตอร์สาธารณะ)', + + 'SIGNIN' => 'เข้าสู่ะระบบ', + 'SIGNIN_OR_REGISTER' => 'เข้าสู่ระบบหรือสมัครสมาชิก', + 'SIGNUP' => 'สมัครสมาชิก', + + 'TOS' => 'ข้อตกลงและเงื่อนไข', + 'TOS_AGREEMENT' => 'ในการสมัครสมาชิกกับ {{site_title}} หมายถึงคุณยอมรับ ข้อตกลงและเงื่อนไข แล้ว', + 'TOS_FOR' => 'ข้อตกลงและเงื่อนไขสำหรับ {{title}}', + + 'USERNAME' => [ + '@TRANSLATION' => 'ชื่อผู้ใช้', + + 'CHOOSE' => 'เลือกชื่อผู้ใช้ที่เป็นเป็นเอกลักษณ์', + 'INVALID' => 'ชื่อผู้ใช้ไม่ถูกต้อง', + 'IN_USE' => 'ชื่อผู้ใช้ {{user_name}} ถูกใช้งานแล้ว' + ], + + 'USER_ID_INVALID' => 'ไม่พบหมายเลขผู้ใช้ที่ร้องขอมา', + 'USER_OR_EMAIL_INVALID' => 'ชื่อผู้ใช้หรือที่อยู่อีเมลไม่ถูกต้อง', + 'USER_OR_PASS_INVALID' => 'ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง', + + 'WELCOME' => 'ยินดีต้อนรับ {{first_name}}' +]; diff --git a/app/sprinkles/account/locale/th_TH/validate.php b/app/sprinkles/account/locale/th_TH/validate.php index 1a2e90a2b..092e0be0e 100644 --- a/app/sprinkles/account/locale/th_TH/validate.php +++ b/app/sprinkles/account/locale/th_TH/validate.php @@ -1,18 +1,19 @@ - [ - "PASSWORD_MISMATCH" => "รหัสผ่านและรหัสผ่านยืนยันของคุณจะต้องตรงกัน" - ] -]; + [ + 'PASSWORD_MISMATCH' => 'รหัสผ่านและรหัสผ่านยืนยันของคุณจะต้องตรงกัน' + ] +]; diff --git a/app/sprinkles/account/locale/tr/messages.php b/app/sprinkles/account/locale/tr/messages.php index 5213490cb..c8e8396e0 100644 --- a/app/sprinkles/account/locale/tr/messages.php +++ b/app/sprinkles/account/locale/tr/messages.php @@ -3,181 +3,182 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * Turkish message token translations for the 'account' sprinkle. * - * @package userfrosting\i18n\tr * @author Dumblledore */ - return [ - "ACCOUNT" => [ - "@TRANSLATION" => "Hesap", + 'ACCOUNT' => [ + '@TRANSLATION' => 'Hesap', - "ACCESS_DENIED" => "Hmm. görünüşe göre böyle bir şey için izne sahip değilsiniz.", + 'ACCESS_DENIED' => 'Hmm. görünüşe göre böyle bir şey için izne sahip değilsiniz.', - "DISABLED" => "Bu hesap durduruldu. Daha çok bilgi için bizimle iletişime geçin.", + 'DISABLED' => 'Bu hesap durduruldu. Daha çok bilgi için bizimle iletişime geçin.', - "EMAIL_UPDATED" => "Hesap maili güncellendi", + 'EMAIL_UPDATED' => 'Hesap maili güncellendi', - "INVALID" => "Bu hesap bulunamadı. Silinmiş olabilir. Daha çok bilgi için bizimle iletişime geçin.", + 'INVALID' => 'Bu hesap bulunamadı. Silinmiş olabilir. Daha çok bilgi için bizimle iletişime geçin.', - "MASTER_NOT_EXISTS" => "Ana hesap oluşturuluncaya kadar bir hesap oluşturamazsın!", - "MY" => "Hesabım", + 'MASTER_NOT_EXISTS' => 'Ana hesap oluşturuluncaya kadar bir hesap oluşturamazsın!', + 'MY' => 'Hesabım', - "SESSION_COMPROMISED" => [ - "@TRANSLATION" => "Oturumunuz tehlikeye atıldı. Tüm cihazlardan çıkmanız, daha sonra giriş yapmanız ve bilgilerinizin değiştirilmediğini kontrol etmeniz gerekir.", - "TITLE" => "Hesabınız tehlikeye atılmış olabilir", - "TEXT" => "Birisi bu sayfayı ele geçirmek için giriş verilerinizi kullanmış olabilir. Güvenliğiniz için tüm oturumlar günlüğe kaydedildi. Lütfen giriş yapınve şüpheli hareketler için hesabınızı kontrol edin. Ayrıca şifrenizi değiştirmek isteyebilirsiniz." + 'SESSION_COMPROMISED' => [ + '@TRANSLATION' => 'Oturumunuz tehlikeye atıldı. Tüm cihazlardan çıkmanız, daha sonra giriş yapmanız ve bilgilerinizin değiştirilmediğini kontrol etmeniz gerekir.', + 'TITLE' => 'Hesabınız tehlikeye atılmış olabilir', + 'TEXT' => 'Birisi bu sayfayı ele geçirmek için giriş verilerinizi kullanmış olabilir. Güvenliğiniz için tüm oturumlar günlüğe kaydedildi. Lütfen giriş yapınve şüpheli hareketler için hesabınızı kontrol edin. Ayrıca şifrenizi değiştirmek isteyebilirsiniz.' ], - "SESSION_EXPIRED" => "Oturumunuz sona erdi. Lütfen tekrar oturum açın.", + 'SESSION_EXPIRED' => 'Oturumunuz sona erdi. Lütfen tekrar oturum açın.', - "SETTINGS" => [ - "@TRANSLATION" => "Hesap ayarları", - "DESCRIPTION" => "E-posta, isim ve parolanız da dahil olmak üzere hesap ayarlarınızı güncelleyin.", - "UPDATED" => "Hesap ayarları güncellendi" + 'SETTINGS' => [ + '@TRANSLATION' => 'Hesap ayarları', + 'DESCRIPTION' => 'E-posta, isim ve parolanız da dahil olmak üzere hesap ayarlarınızı güncelleyin.', + 'UPDATED' => 'Hesap ayarları güncellendi' ], - "TOOLS" => "Hesap araçları", + 'TOOLS' => 'Hesap araçları', - "UNVERIFIED" => "Hesap henüz onaylanmadı. Hesap etkinleştirme talimatları için e-postalarınızı ve spam klasörünüzü kontrol edin.", + 'UNVERIFIED' => 'Hesap henüz onaylanmadı. Hesap etkinleştirme talimatları için e-postalarınızı ve spam klasörünüzü kontrol edin.', - "VERIFICATION" => [ - "NEW_LINK_SENT" => "{{email}} için yeni bir doğrulama bağlantısı e-posta ile gönderildi. Lütfen bu e-postanın gelen kutusunu ve spam klasörlerini kontrol edin.", - "RESEND" => "Doğrulama e-postasını tekrar gönder", - "COMPLETE" => "Hesabınızı başarıyla doğruladınız. Şimdi giriş yapabilirsiniz.", - "EMAIL" => "Kaydolmak için kullandığınız e-posta adresinizi giriniz, ve doğrulama e-postanızı tekrar gönderin.", - "PAGE" => "Yeni hesabınız için doğrulama e-postasını tekrar gönder.", - "SEND" => "Hesabım için doğrulama bağlantısını e-posta ile gönder", - "TOKEN_NOT_FOUND" => "Doğrulama belirteci bulunumadı / Hesap zaten doğrulandı", + 'VERIFICATION' => [ + 'NEW_LINK_SENT' => '{{email}} için yeni bir doğrulama bağlantısı e-posta ile gönderildi. Lütfen bu e-postanın gelen kutusunu ve spam klasörlerini kontrol edin.', + 'RESEND' => 'Doğrulama e-postasını tekrar gönder', + 'COMPLETE' => 'Hesabınızı başarıyla doğruladınız. Şimdi giriş yapabilirsiniz.', + 'EMAIL' => 'Kaydolmak için kullandığınız e-posta adresinizi giriniz, ve doğrulama e-postanızı tekrar gönderin.', + 'PAGE' => 'Yeni hesabınız için doğrulama e-postasını tekrar gönder.', + 'SEND' => 'Hesabım için doğrulama bağlantısını e-posta ile gönder', + 'TOKEN_NOT_FOUND' => 'Doğrulama belirteci bulunumadı / Hesap zaten doğrulandı', ] ], - "EMAIL" => [ - "INVALID" => "{{email}} için hesap yoktur.", - "IN_USE" => "E-posta {{email}} zaten kullanılıyor.", - "VERIFICATION_REQUIRED" => "E-posta (doğrulama gerekli - gerçek bir adres kullanın!)" + 'EMAIL' => [ + 'INVALID' => '{{email}} için hesap yoktur.', + 'IN_USE' => 'E-posta {{email}} zaten kullanılıyor.', + 'VERIFICATION_REQUIRED' => 'E-posta (doğrulama gerekli - gerçek bir adres kullanın!)' ], - "EMAIL_OR_USERNAME" => "Kullanıcı adı veya e-posta adresi", + 'EMAIL_OR_USERNAME' => 'Kullanıcı adı veya e-posta adresi', - "FIRST_NAME" => "Adınız", + 'FIRST_NAME' => 'Adınız', - "HEADER_MESSAGE_ROOT" => "Kök kullanıcı olarak giriş yaptın", + 'HEADER_MESSAGE_ROOT' => 'Kök kullanıcı olarak giriş yaptın', - "LAST_NAME" => "Soyadı", - "LOCALE" => [ - "ACCOUNT" => "Hesabınız için kullanılacak dil ve yerel ayar", - "INVALID" => "{{locale}} geçersiz bir yerel." + 'LAST_NAME' => 'Soyadı', + 'LOCALE' => [ + 'ACCOUNT' => 'Hesabınız için kullanılacak dil ve yerel ayar', + 'INVALID' => '{{locale}} geçersiz bir yerel.' ], - "LOGIN" => [ - "@TRANSLATION" => "Oturum Aç", - "ALREADY_COMPLETE" => "Zaten oturum açtınız!", - "SOCIAL" => "Veya şununla oturum aç", - "REQUIRED" => "Üzgünüm, bu sayfaya ulaşmak için oturum açmalısın." + 'LOGIN' => [ + '@TRANSLATION' => 'Oturum Aç', + 'ALREADY_COMPLETE' => 'Zaten oturum açtınız!', + 'SOCIAL' => 'Veya şununla oturum aç', + 'REQUIRED' => 'Üzgünüm, bu sayfaya ulaşmak için oturum açmalısın.' ], - "LOGOUT" => "Oturumu kapat", + 'LOGOUT' => 'Oturumu kapat', - "NAME" => "Ad", + 'NAME' => 'Ad', - "NAME_AND_EMAIL" => "Ad ve e-posta", + 'NAME_AND_EMAIL' => 'Ad ve e-posta', - "PAGE" => [ - "LOGIN" => [ - "DESCRIPTION" => "{{site_name}} hesabınız ile giriş yapın ya da yeni bir hesap oluşturun.", - "SUBTITLE" => "Ücretsiz üye ol veya mevcut bir hesap ile giriş yapın.", - "TITLE" => "Hadi başlayalım!", + 'PAGE' => [ + 'LOGIN' => [ + 'DESCRIPTION' => '{{site_name}} hesabınız ile giriş yapın ya da yeni bir hesap oluşturun.', + 'SUBTITLE' => 'Ücretsiz üye ol veya mevcut bir hesap ile giriş yapın.', + 'TITLE' => 'Hadi başlayalım!', ] ], - "PASSWORD" => [ - "@TRANSLATION" => "Parola", + 'PASSWORD' => [ + '@TRANSLATION' => 'Parola', - "BETWEEN" => "{{min}}-{{max}} karakterler arasında", + 'BETWEEN' => '{{min}}-{{max}} karakterler arasında', - "CONFIRM" => "Şifreyi onayla", - "CONFIRM_CURRENT" => "Lütfen şuanki parolanızı giriniz", - "CONFIRM_NEW" => "Yeni parolayı onayla", - "CONFIRM_NEW_EXPLAIN" => "Yeni parolayı tekrar gir", - "CONFIRM_NEW_HELP" => "Sadece yeni bir şifre seçerseniz gerekli", - "CREATE" => [ - "@TRANSLATION" => "Parola Oluştur", - "PAGE" => "Yeni hesabınız için bir şifre belirleyin.", - "SET" => "Parolayı Ayarla ve Giriş Yap" + 'CONFIRM' => 'Şifreyi onayla', + 'CONFIRM_CURRENT' => 'Lütfen şuanki parolanızı giriniz', + 'CONFIRM_NEW' => 'Yeni parolayı onayla', + 'CONFIRM_NEW_EXPLAIN' => 'Yeni parolayı tekrar gir', + 'CONFIRM_NEW_HELP' => 'Sadece yeni bir şifre seçerseniz gerekli', + 'CREATE' => [ + '@TRANSLATION' => 'Parola Oluştur', + 'PAGE' => 'Yeni hesabınız için bir şifre belirleyin.', + 'SET' => 'Parolayı Ayarla ve Giriş Yap' ], - "CURRENT" => "Şimdiki Parola", - "CURRENT_EXPLAIN" => "Değişiklikler için şimdiki parolanız ile onaylamalısınız", - - "FORGOTTEN" => "Unutulan Şifre", - "FORGET" => [ - "@TRANSLATION" => "Şifremi unuttum", - - "COULD_NOT_UPDATE" => "Şifre güncellenemedi.", - "EMAIL" => "Lütfen kaydolmak için kullandığınız e-posta adresini giriniz. Şifrenizi sıfırlama talimatlarıyla bir bir bağlantı e-postanıza gönderilecektir.", - "EMAIL_SEND" => "E-posta şifre sıfırlama bağlantısı", - "INVALID" => "Bu şifre sıfırlama isteği bulunamadı ya da süresi bitmiş. Lütfen isteğinizi yeniden göndermeyideneyin.", - "PAGE" => "Şifrenizi sıfırlamak için bir bağlantı oluşturun.", - "REQUEST_CANNED" => "Kayıp parola isteği iptal edildi.", - "REQUEST_SENT" => "Eğer e-posta{{email}} sistemdeki bir hesap ile eşleşirse, bir şifre yenileme bağlantısı{{email}} gönderilir." + 'CURRENT' => 'Şimdiki Parola', + 'CURRENT_EXPLAIN' => 'Değişiklikler için şimdiki parolanız ile onaylamalısınız', + + 'FORGOTTEN' => 'Unutulan Şifre', + 'FORGET' => [ + '@TRANSLATION' => 'Şifremi unuttum', + + 'COULD_NOT_UPDATE' => 'Şifre güncellenemedi.', + 'EMAIL' => 'Lütfen kaydolmak için kullandığınız e-posta adresini giriniz. Şifrenizi sıfırlama talimatlarıyla bir bir bağlantı e-postanıza gönderilecektir.', + 'EMAIL_SEND' => 'E-posta şifre sıfırlama bağlantısı', + 'INVALID' => 'Bu şifre sıfırlama isteği bulunamadı ya da süresi bitmiş. Lütfen isteğinizi yeniden göndermeyideneyin.', + 'PAGE' => 'Şifrenizi sıfırlamak için bir bağlantı oluşturun.', + 'REQUEST_CANNED' => 'Kayıp parola isteği iptal edildi.', + 'REQUEST_SENT' => 'Eğer e-posta{{email}} sistemdeki bir hesap ile eşleşirse, bir şifre yenileme bağlantısı{{email}} gönderilir.' ], - "HASH_FAILED" => "Parola karma başarısız oldu. Lütfen bir site yöneticisiyle iletişime geçin.", - "INVALID" => "Şimdiki şifre kayıt edilen şifre ile eşleşmiyor", - "NEW" => "Yeni Şifre", - "NOTHING_TO_UPDATE" => "Aynı şifre ile güncelleyemezsiniz", + 'HASH_FAILED' => 'Parola karma başarısız oldu. Lütfen bir site yöneticisiyle iletişime geçin.', + 'INVALID' => 'Şimdiki şifre kayıt edilen şifre ile eşleşmiyor', + 'NEW' => 'Yeni Şifre', + 'NOTHING_TO_UPDATE' => 'Aynı şifre ile güncelleyemezsiniz', - "RESET" => [ - "@TRANSLATION" => "Şifre sıfırlama", - "CHOOSE" => "Lütfen devam etmek için yeni bir şifre belirleyiniz.", - "PAGE" => "Hesabınız için yeni bir şifre belirleyiniz.", - "SEND" => "Yeni şifre ayarla ve giriş yap" + 'RESET' => [ + '@TRANSLATION' => 'Şifre sıfırlama', + 'CHOOSE' => 'Lütfen devam etmek için yeni bir şifre belirleyiniz.', + 'PAGE' => 'Hesabınız için yeni bir şifre belirleyiniz.', + 'SEND' => 'Yeni şifre ayarla ve giriş yap' ], - "UPDATED" => "Hesap şifresi güncellendi" + 'UPDATED' => 'Hesap şifresi güncellendi' ], - "PROFILE" => [ - "SETTINGS" => "Profil ayarları", - "UPDATED" => "Profil ayarları güncellendi" + 'PROFILE' => [ + 'SETTINGS' => 'Profil ayarları', + 'UPDATED' => 'Profil ayarları güncellendi' ], - "RATE_LIMIT_EXCEEDED" => "Bu işlem için belirlenen son oran aşıldı. Başka bir deneme yapmanıza izin verilene kadar {{delay}} bir süre beklemelisiniz.", - - "REGISTER" => "Kaydol", - "REGISTER_ME" => "Beni kaydet", - "REGISTRATION" => [ - "BROKEN" => "Üzgünüz, hesap kayıt işlemimizde bir sorun var. Lütfen destek almak için doğrudan bizimle iletişime geçin.", - "COMPLETE_TYPE1" => "Kaydınız başarıyla tamamlandı. Şimdi giriş yapabilirsiniz.", - "COMPLETE_TYPE2" => "Kaydınız başarıyla tamamlandı. Hesabınızı aktifleştirmek için bir bağlantı gönderildi{{email}}. Bu adımı tamamlayana kadar oturum açamazsınız.", - "DISABLED" => "Üzgünüz, hesap kaydı devre dışı bırakıldı.", - "LOGOUT" => "Üzgünüm, oturumunuz açıkken yeni bir hesap oluşturamazsınız. Lütfen önce oturumunuzdan çıkış yapınız.", - "WELCOME" => "Kaydolmak hızlı ve basittir." + 'RATE_LIMIT_EXCEEDED' => 'Bu işlem için belirlenen son oran aşıldı. Başka bir deneme yapmanıza izin verilene kadar {{delay}} bir süre beklemelisiniz.', + + 'REGISTER' => 'Kaydol', + 'REGISTER_ME' => 'Beni kaydet', + 'REGISTRATION' => [ + 'BROKEN' => 'Üzgünüz, hesap kayıt işlemimizde bir sorun var. Lütfen destek almak için doğrudan bizimle iletişime geçin.', + 'COMPLETE_TYPE1' => 'Kaydınız başarıyla tamamlandı. Şimdi giriş yapabilirsiniz.', + 'COMPLETE_TYPE2' => 'Kaydınız başarıyla tamamlandı. Hesabınızı aktifleştirmek için bir bağlantı gönderildi{{email}}. Bu adımı tamamlayana kadar oturum açamazsınız.', + 'DISABLED' => 'Üzgünüz, hesap kaydı devre dışı bırakıldı.', + 'LOGOUT' => 'Üzgünüm, oturumunuz açıkken yeni bir hesap oluşturamazsınız. Lütfen önce oturumunuzdan çıkış yapınız.', + 'WELCOME' => 'Kaydolmak hızlı ve basittir.' ], - "REMEMBER_ME" => "Beni hatırla!", - "REMEMBER_ME_ON_COMPUTER" => "Bu bilgisayarda beni hatırla ( genel bilgisayarlar için önerilmez)", + 'REMEMBER_ME' => 'Beni hatırla!', + 'REMEMBER_ME_ON_COMPUTER' => 'Bu bilgisayarda beni hatırla ( genel bilgisayarlar için önerilmez)', - "SIGN_IN_HERE" => "Zaten bir hesaba sahip misiniz?burada giriş yap", - "SIGNIN" => "Giriş yap", - "SIGNIN_OR_REGISTER" => "Giriş yap veya kayıt ol", - "SIGNUP" => "Üye ol", + 'SIGN_IN_HERE' => 'Zaten bir hesaba sahip misiniz?burada giriş yap', + 'SIGNIN' => 'Giriş yap', + 'SIGNIN_OR_REGISTER' => 'Giriş yap veya kayıt ol', + 'SIGNUP' => 'Üye ol', - "TOS" => "Şartlar ve Koşullar", - "TOS_AGREEMENT" => "Bir hesap ile kaydolarak {{site_title}} sen kabul edersin şartlar ve koşulları.", - "TOS_FOR" => "{{title}} için şartlar ve koşullar", + 'TOS' => 'Şartlar ve Koşullar', + 'TOS_AGREEMENT' => 'Bir hesap ile kaydolarak {{site_title}} sen kabul edersin şartlar ve koşulları.', + 'TOS_FOR' => '{{title}} için şartlar ve koşullar', - "USERNAME" => [ - "@TRANSLATION" => "Kullanıcı Adı", + 'USERNAME' => [ + '@TRANSLATION' => 'Kullanıcı Adı', - "CHOOSE" => "Benzersiz bir kullanıcı adı seç", - "INVALID" => "Geçersiz kullanıcı adı", - "IN_USE" => "{{user_name}} kullanıcı adı zaten mevcut.", - "NOT_AVAILABLE" => "{{user_name}} kullanıcı adı kullanılamaz. Farklı bir isim veya 'öneriye' tıklayın." + 'CHOOSE' => 'Benzersiz bir kullanıcı adı seç', + 'INVALID' => 'Geçersiz kullanıcı adı', + 'IN_USE' => '{{user_name}} kullanıcı adı zaten mevcut.', + 'NOT_AVAILABLE' => "{{user_name}} kullanıcı adı kullanılamaz. Farklı bir isim veya 'öneriye' tıklayın." ], - "USER_ID_INVALID" => "İstenen kullanıcı adı mevcut değil.", - "USER_OR_EMAIL_INVALID" => "Kullanıcı adı veya e-posta adresi hatalı.", - "USER_OR_PASS_INVALID" => "Kullanıcı bulunamadı ya da şifre hatalı.", + 'USER_ID_INVALID' => 'İstenen kullanıcı adı mevcut değil.', + 'USER_OR_EMAIL_INVALID' => 'Kullanıcı adı veya e-posta adresi hatalı.', + 'USER_OR_PASS_INVALID' => 'Kullanıcı bulunamadı ya da şifre hatalı.', - "WELCOME" => "Tekrar Hoşgeldiniz.{{first_name}}" + 'WELCOME' => 'Tekrar Hoşgeldiniz.{{first_name}}' ]; diff --git a/app/sprinkles/account/locale/tr/validate.php b/app/sprinkles/account/locale/tr/validate.php index 298bdbc38..9b8529478 100644 --- a/app/sprinkles/account/locale/tr/validate.php +++ b/app/sprinkles/account/locale/tr/validate.php @@ -3,17 +3,18 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * Turkish message token translations for the 'account' sprinkle. * - * @package userfrosting\i18n\tr * @author Dumblledore */ - return [ - "VALIDATE" => [ - "PASSWORD_MISMATCH" => "Şifreniz ve onaylama şifreniz eşleşmiyor.", - "USERNAME" => "Kullanıcı adınız sadece küçük harfler, sayılar, '.', '-', ve '_' içerebilir." + 'VALIDATE' => [ + 'PASSWORD_MISMATCH' => 'Şifreniz ve onaylama şifreniz eşleşmiyor.', + 'USERNAME' => "Kullanıcı adınız sadece küçük harfler, sayılar, '.', '-', ve '_' içerebilir." ] ]; diff --git a/app/sprinkles/account/locale/zh_CN/messages.php b/app/sprinkles/account/locale/zh_CN/messages.php index 60adcf05b..aaef801e3 100644 --- a/app/sprinkles/account/locale/zh_CN/messages.php +++ b/app/sprinkles/account/locale/zh_CN/messages.php @@ -3,175 +3,176 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * Chinese message token translations for the 'account' sprinkle. * - * @package userfrosting\i18n\zh_CN * @author @BruceGui (https://github.com/BruceGui) */ - return [ - "ACCOUNT" => [ - "@TRANSLATION" => "账户", + 'ACCOUNT' => [ + '@TRANSLATION' => '账户', - "ACCESS_DENIED" => "噢, 你好像没有权限这么做.", + 'ACCESS_DENIED' => '噢, 你好像没有权限这么做.', - "DISABLED" => "这个账户已被禁用. 请联系我们获取更多信息.", + 'DISABLED' => '这个账户已被禁用. 请联系我们获取更多信息.', - "EMAIL_UPDATED" => "账户邮箱更新成功", + 'EMAIL_UPDATED' => '账户邮箱更新成功', - "INVALID" => "此账户不存在. 可能已被删除. 请联系我们获取更多信息.", + 'INVALID' => '此账户不存在. 可能已被删除. 请联系我们获取更多信息.', - "MASTER_NOT_EXISTS" => "在创建超级账户之前你不能注册", - "MY" => "我的账户", + 'MASTER_NOT_EXISTS' => '在创建超级账户之前你不能注册', + 'MY' => '我的账户', - "SESSION_COMPROMISED" => "你的会话已泄露. 你应该在所有的设备上注销, 然后再登陆确保你的数据没被修改.", - "SESSION_COMPROMISED_TITLE" => "你的账户可能被盗用", - "SESSION_EXPIRED" => "会话已过期. 请重新登陆.", + 'SESSION_COMPROMISED' => '你的会话已泄露. 你应该在所有的设备上注销, 然后再登陆确保你的数据没被修改.', + 'SESSION_COMPROMISED_TITLE' => '你的账户可能被盗用', + 'SESSION_EXPIRED' => '会话已过期. 请重新登陆.', - "SETTINGS" => [ - "@TRANSLATION" => "账户设置", - "DESCRIPTION" => "更新你的账户, 包括邮箱、姓名和密码.", - "UPDATED" => "账户更新成功" + 'SETTINGS' => [ + '@TRANSLATION' => '账户设置', + 'DESCRIPTION' => '更新你的账户, 包括邮箱、姓名和密码.', + 'UPDATED' => '账户更新成功' ], - "TOOLS" => "账户工具", + 'TOOLS' => '账户工具', - "UNVERIFIED" => "你的账户还没有验证. 检查你的(垃圾)邮箱文件夹进行验证.", + 'UNVERIFIED' => '你的账户还没有验证. 检查你的(垃圾)邮箱文件夹进行验证.', - "VERIFICATION" => [ - "NEW_LINK_SENT" => "我们发送了新的验证链接 {{email}}. 请检查你的收件箱或垃圾邮件进行验证.", - "RESEND" => "重新发送验证邮件", - "COMPLETE" => "你已成功验证. 现在可以登陆了.", - "EMAIL" => "请输入你登陆时的邮箱, 然后将会发送验证邮件.", - "PAGE" => "重新发送验证邮件给你的新账户.", - "SEND" => "为我的账户发送验证邮件", - "TOKEN_NOT_FOUND" => "验证令牌不存在 / 账户已经验证", + 'VERIFICATION' => [ + 'NEW_LINK_SENT' => '我们发送了新的验证链接 {{email}}. 请检查你的收件箱或垃圾邮件进行验证.', + 'RESEND' => '重新发送验证邮件', + 'COMPLETE' => '你已成功验证. 现在可以登陆了.', + 'EMAIL' => '请输入你登陆时的邮箱, 然后将会发送验证邮件.', + 'PAGE' => '重新发送验证邮件给你的新账户.', + 'SEND' => '为我的账户发送验证邮件', + 'TOKEN_NOT_FOUND' => '验证令牌不存在 / 账户已经验证', ] ], - "EMAIL" => [ - "INVALID" => "{{email}} 没有账户注册.", - "IN_USE" => "邮箱 {{email}} 已被使用.", - "VERIFICATION_REQUIRED" => "邮箱 (需要进行验证 - 请使用一个有效的!)" + 'EMAIL' => [ + 'INVALID' => '{{email}} 没有账户注册.', + 'IN_USE' => '邮箱 {{email}} 已被使用.', + 'VERIFICATION_REQUIRED' => '邮箱 (需要进行验证 - 请使用一个有效的!)' ], - "EMAIL_OR_USERNAME" => "用户名或邮箱地址", + 'EMAIL_OR_USERNAME' => '用户名或邮箱地址', - "FIRST_NAME" => "名字", + 'FIRST_NAME' => '名字', - "HEADER_MESSAGE_ROOT" => "你现在以超级用户登陆", + 'HEADER_MESSAGE_ROOT' => '你现在以超级用户登陆', - "LAST_NAME" => "姓氏", + 'LAST_NAME' => '姓氏', - "LOCALE" => [ - "ACCOUNT" => "设置你账户的地区和语言", - "INVALID" => "{{locale}} 不是一个有效的地区." + 'LOCALE' => [ + 'ACCOUNT' => '设置你账户的地区和语言', + 'INVALID' => '{{locale}} 不是一个有效的地区.' ], - "LOGIN" => [ - "@TRANSLATION" => "登陆", - "ALREADY_COMPLETE" => "你已经登陆!", - "SOCIAL" => "用其他方式登陆", - "REQUIRED" => "对不起, 你需要登陆才能获取资源." + 'LOGIN' => [ + '@TRANSLATION' => '登陆', + 'ALREADY_COMPLETE' => '你已经登陆!', + 'SOCIAL' => '用其他方式登陆', + 'REQUIRED' => '对不起, 你需要登陆才能获取资源.' ], - "LOGOUT" => "注销", + 'LOGOUT' => '注销', - "NAME" => "名字", + 'NAME' => '名字', - "NAME_AND_EMAIL" => "名字和邮箱", + 'NAME_AND_EMAIL' => '名字和邮箱', - "PAGE" => [ - "LOGIN" => [ - "DESCRIPTION" => "用 {{site_name}} 账户登陆, 或者创建新账户.", - "SUBTITLE" => "免费注册, 或用已有账户登陆.", - "TITLE" => "让我们开始吧!", + 'PAGE' => [ + 'LOGIN' => [ + 'DESCRIPTION' => '用 {{site_name}} 账户登陆, 或者创建新账户.', + 'SUBTITLE' => '免费注册, 或用已有账户登陆.', + 'TITLE' => '让我们开始吧!', ] ], - "PASSWORD" => [ - "@TRANSLATION" => "密码", - - "BETWEEN" => "字符长度 {{min}}-{{max}} ", - - "CONFIRM" => "确认密码", - "CONFIRM_CURRENT" => "请确认当前密码", - "CONFIRM_NEW" => "确认新密码", - "CONFIRM_NEW_EXPLAIN" => "重新输入新密码", - "CONFIRM_NEW_HELP" => "选择了新密码时才需要", - "CURRENT" => "密码正确", - "CURRENT_EXPLAIN" => "你必须要确认密码再进行修改", - - "FORGOTTEN" => "忘记密码", - "FORGET" => [ - "@TRANSLATION" => "我忘记了密码", - - "COULD_NOT_UPDATE" => "无法更新密码.", - "EMAIL" => "请输入你登陆时的邮箱. 重置密码的链接将会发送给你.", - "EMAIL_SEND" => "发送重置密码链接", - "INVALID" => "这个重置密码请求无法使用, 或已过期. 请 重新发送请求.", - "PAGE" => "获取重置密码的链接.", - "REQUEST_CANNED" => "取消重置请求.", - "REQUEST_SENT" => "重置密码的链接已经发送 {{email}}." + 'PASSWORD' => [ + '@TRANSLATION' => '密码', + + 'BETWEEN' => '字符长度 {{min}}-{{max}} ', + + 'CONFIRM' => '确认密码', + 'CONFIRM_CURRENT' => '请确认当前密码', + 'CONFIRM_NEW' => '确认新密码', + 'CONFIRM_NEW_EXPLAIN' => '重新输入新密码', + 'CONFIRM_NEW_HELP' => '选择了新密码时才需要', + 'CURRENT' => '密码正确', + 'CURRENT_EXPLAIN' => '你必须要确认密码再进行修改', + + 'FORGOTTEN' => '忘记密码', + 'FORGET' => [ + '@TRANSLATION' => '我忘记了密码', + + 'COULD_NOT_UPDATE' => '无法更新密码.', + 'EMAIL' => '请输入你登陆时的邮箱. 重置密码的链接将会发送给你.', + 'EMAIL_SEND' => '发送重置密码链接', + 'INVALID' => '这个重置密码请求无法使用, 或已过期. 请 重新发送请求.', + 'PAGE' => '获取重置密码的链接.', + 'REQUEST_CANNED' => '取消重置请求.', + 'REQUEST_SENT' => '重置密码的链接已经发送 {{email}}.' ], - "RESET" => [ - "@TRANSLATION" => "重置密码", - "CHOOSE" => "请输入新密码.", - "PAGE" => "为账户设置新密码.", - "SEND" => "设置密码并登陆" + 'RESET' => [ + '@TRANSLATION' => '重置密码', + 'CHOOSE' => '请输入新密码.', + 'PAGE' => '为账户设置新密码.', + 'SEND' => '设置密码并登陆' ], - "HASH_FAILED" => "密码验证失败. 请联系网站管理.", - "INVALID" => "当前密码无法与记录匹配", - "NEW" => "新密码", - "NOTHING_TO_UPDATE" => "新密码不能与旧密码相同", - "UPDATED" => "账户密码更新成功" + 'HASH_FAILED' => '密码验证失败. 请联系网站管理.', + 'INVALID' => '当前密码无法与记录匹配', + 'NEW' => '新密码', + 'NOTHING_TO_UPDATE' => '新密码不能与旧密码相同', + 'UPDATED' => '账户密码更新成功' ], - "PROFILE" => [ - "SETTINGS" => "简介设置", - "UPDATED" => "简介设置成功" + 'PROFILE' => [ + 'SETTINGS' => '简介设置', + 'UPDATED' => '简介设置成功' ], - "REGISTER" => "注册", - "REGISTER_ME" => "注册", + 'REGISTER' => '注册', + 'REGISTER_ME' => '注册', - "REGISTRATION" => [ - "BROKEN" => "抱歉, 账户注册过程发送错误. 请联系我们寻求帮助.", - "COMPLETE_TYPE1" => "你已注册成功. 现在可以登陆了.", - "COMPLETE_TYPE2" => "成功注册. 激活链接已经发送给 {{email}}. 激活之前无法登陆.", - "DISABLED" => "抱歉, 账户注册以禁用.", - "LOGOUT" => "抱歉, 登陆时不能注册. 请先注销.", - "WELCOME" => "注册简单快速." + 'REGISTRATION' => [ + 'BROKEN' => '抱歉, 账户注册过程发送错误. 请联系我们寻求帮助.', + 'COMPLETE_TYPE1' => '你已注册成功. 现在可以登陆了.', + 'COMPLETE_TYPE2' => '成功注册. 激活链接已经发送给 {{email}}. 激活之前无法登陆.', + 'DISABLED' => '抱歉, 账户注册以禁用.', + 'LOGOUT' => '抱歉, 登陆时不能注册. 请先注销.', + 'WELCOME' => '注册简单快速.' ], - "RATE_LIMIT_EXCEEDED" => "行动速度过快. 请等 {{delay}} 秒后再尝试新的操作.", - "REMEMBER_ME" => "记住我!", - "REMEMBER_ME_ON_COMPUTER" => "在此电脑上记住我 (不推荐在公共电脑上)", + 'RATE_LIMIT_EXCEEDED' => '行动速度过快. 请等 {{delay}} 秒后再尝试新的操作.', + 'REMEMBER_ME' => '记住我!', + 'REMEMBER_ME_ON_COMPUTER' => '在此电脑上记住我 (不推荐在公共电脑上)', - "SIGNIN" => "登陆", - "SIGNIN_OR_REGISTER" => "登陆或注册", - "SIGNUP" => "注销", + 'SIGNIN' => '登陆', + 'SIGNIN_OR_REGISTER' => '登陆或注册', + 'SIGNUP' => '注销', - "TOS" => "条款和说明", - "TOS_AGREEMENT" => "在 {{site_title}} 注册, 你需要接收 条款和说明.", - "TOS_FOR" => "{{title}}的条款和说明", + 'TOS' => '条款和说明', + 'TOS_AGREEMENT' => '在 {{site_title}} 注册, 你需要接收 条款和说明.', + 'TOS_FOR' => '{{title}}的条款和说明', - "USERNAME" => [ - "@TRANSLATION" => "用户名", + 'USERNAME' => [ + '@TRANSLATION' => '用户名', - "CHOOSE" => "取一个唯一的用户名", - "INVALID" => "无效的用户名", - "IN_USE" => "用户名 {{user_name}} 已存在.", - "NOT_AVAILABLE" => "用户名 {{user_name}} 不可用. 重新选择用户名, 或者点击 '建议'." + 'CHOOSE' => '取一个唯一的用户名', + 'INVALID' => '无效的用户名', + 'IN_USE' => '用户名 {{user_name}} 已存在.', + 'NOT_AVAILABLE' => "用户名 {{user_name}} 不可用. 重新选择用户名, 或者点击 '建议'." ], - "USER_ID_INVALID" => "请求的用户不存在.", - "USER_OR_EMAIL_INVALID" => "用户名或邮箱无效.", - "USER_OR_PASS_INVALID" => "没有发现用户或密码错误.", + 'USER_ID_INVALID' => '请求的用户不存在.', + 'USER_OR_EMAIL_INVALID' => '用户名或邮箱无效.', + 'USER_OR_PASS_INVALID' => '没有发现用户或密码错误.', - "WELCOME" => "欢迎回来, {{first_name}}" + 'WELCOME' => '欢迎回来, {{first_name}}' ]; diff --git a/app/sprinkles/account/locale/zh_CN/validate.php b/app/sprinkles/account/locale/zh_CN/validate.php index 3ca368ad2..2f0c8c516 100644 --- a/app/sprinkles/account/locale/zh_CN/validate.php +++ b/app/sprinkles/account/locale/zh_CN/validate.php @@ -3,17 +3,18 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * Chinese message token translations for the 'account' sprinkle. * - * @package userfrosting\i18n\zh_CN * @author @BruceGui (https://github.com/BruceGui) */ - return [ - "VALIDATE" => [ - "PASSWORD_MISMATCH" => "密码不一致.", - "USERNAME" => "用户名必须以小写字母, 数字, '.', '-', 和 '_'组成." + 'VALIDATE' => [ + 'PASSWORD_MISMATCH' => '密码不一致.', + 'USERNAME' => "用户名必须以小写字母, 数字, '.', '-', 和 '_'组成." ] ]; diff --git a/app/sprinkles/account/routes/routes.php b/app/sprinkles/account/routes/routes.php index 8198255e0..bb89382b7 100644 --- a/app/sprinkles/account/routes/routes.php +++ b/app/sprinkles/account/routes/routes.php @@ -3,9 +3,12 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ +use UserFrosting\Sprinkle\Core\Util\NoCache; + $app->group('/account', function () { $this->get('/captcha', 'UserFrosting\Sprinkle\Account\Controller\AccountController:imageCaptcha'); @@ -54,6 +57,6 @@ $this->post('/settings/profile', 'UserFrosting\Sprinkle\Account\Controller\AccountController:profile') ->add('authGuard'); -}); +})->add(new NoCache()); $app->get('/modals/account/tos', 'UserFrosting\Sprinkle\Account\Controller\AccountController:getModalAccountTos'); diff --git a/app/sprinkles/account/schema/requests/register.yaml b/app/sprinkles/account/schema/requests/register.yaml index 75dae592a..7faf72654 100644 --- a/app/sprinkles/account/schema/requests/register.yaml +++ b/app/sprinkles/account/schema/requests/register.yaml @@ -1,75 +1,81 @@ ---- -user_name: - validators: - length: - label: "&USERNAME" - min: 1 - max: 50 - message: VALIDATE.LENGTH_RANGE - no_leading_whitespace: - label: "&USERNAME" - message: VALIDATE.NO_LEAD_WS - no_trailing_whitespace: - label: "&USERNAME" - message: VALIDATE.NO_TRAIL_WS - required: - label: "&USERNAME" - message: VALIDATE.REQUIRED - username: - label: "&USERNAME" - message: VALIDATE.USERNAME -first_name: - validators: - length: - label: "&FIRST_NAME" - min: 1 - max: 20 - message: VALIDATE.LENGTH_RANGE - required: - label: "&FIRST_NAME" - message: VALIDATE.REQUIRED -last_name: - validators: - length: - label: "&LAST_NAME" - min: 1 - max: 30 - message: VALIDATE.LENGTH_RANGE -email: - validators: - required: - label: "&EMAIL" - message: VALIDATE.REQUIRED - length: - label: "&EMAIL" - min: 1 - max: 150 - message: VALIDATE.LENGTH_RANGE - email: - message: VALIDATE.INVALID_EMAIL -password: - validators: - required: - label: "&PASSWORD" - message: VALIDATE.REQUIRED - length: - label: "&PASSWORD" - min: 12 - max: 100 - message: VALIDATE.LENGTH_RANGE -passwordc: - validators: - required: - label: "&PASSWORD.CONFIRM" - message: VALIDATE.REQUIRED - matches: - field: password - label: "&PASSWORD.CONFIRM" - message: VALIDATE.PASSWORD_MISMATCH - length: - label: "&PASSWORD.CONFIRM" - min: 12 - max: 100 - message: VALIDATE.LENGTH_RANGE -captcha: - validators: +--- +user_name: + validators: + length: + label: "&USERNAME" + min: 1 + max: 50 + message: VALIDATE.LENGTH_RANGE + no_leading_whitespace: + label: "&USERNAME" + message: VALIDATE.NO_LEAD_WS + no_trailing_whitespace: + label: "&USERNAME" + message: VALIDATE.NO_TRAIL_WS + required: + label: "&USERNAME" + message: VALIDATE.REQUIRED + username: + label: "&USERNAME" + message: VALIDATE.USERNAME +first_name: + validators: + length: + label: "&FIRST_NAME" + min: 1 + max: 20 + message: VALIDATE.LENGTH_RANGE + required: + label: "&FIRST_NAME" + message: VALIDATE.REQUIRED +last_name: + validators: + length: + label: "&LAST_NAME" + min: 1 + max: 30 + message: VALIDATE.LENGTH_RANGE +email: + validators: + required: + label: "&EMAIL" + message: VALIDATE.REQUIRED + length: + label: "&EMAIL" + min: 1 + max: 150 + message: VALIDATE.LENGTH_RANGE + email: + message: VALIDATE.INVALID_EMAIL +password: + validators: + required: + label: "&PASSWORD" + message: VALIDATE.REQUIRED + length: + label: "&PASSWORD" + min: 12 + max: 100 + message: VALIDATE.LENGTH_RANGE +passwordc: + validators: + required: + label: "&PASSWORD.CONFIRM" + message: VALIDATE.REQUIRED + matches: + field: password + label: "&PASSWORD.CONFIRM" + message: VALIDATE.PASSWORD_MISMATCH + length: + label: "&PASSWORD.CONFIRM" + min: 12 + max: 100 + message: VALIDATE.LENGTH_RANGE +locale: + validators: + required: + label: "&LOCALE" + domain: server + message: VALIDATE.REQUIRED +captcha: + validators: diff --git a/app/sprinkles/account/src/Account.php b/app/sprinkles/account/src/Account.php index 49c2de9ab..cfdbd2d38 100644 --- a/app/sprinkles/account/src/Account.php +++ b/app/sprinkles/account/src/Account.php @@ -3,8 +3,10 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account; use UserFrosting\System\Sprinkle\Sprinkle; @@ -16,5 +18,4 @@ */ class Account extends Sprinkle { - } diff --git a/app/sprinkles/account/src/Account/Registration.php b/app/sprinkles/account/src/Account/Registration.php new file mode 100644 index 000000000..10cf01102 --- /dev/null +++ b/app/sprinkles/account/src/Account/Registration.php @@ -0,0 +1,335 @@ +userdata = $userdata; + $this->ci = $ci; + + $this->setDefaults(); + } + + /** + * Register a new user + * + * @return UserInterface The created user + */ + public function register() + { + // Validate the userdata + $this->validate(); + + // Set default group + $defaultGroup = $this->ci->classMapper->staticMethod('group', 'where', 'slug', $this->defaultGroup)->first(); + + if (!$defaultGroup) { + $e = new HttpException("Account registration is not working because the default group '{$this->defaultGroup}' does not exist."); + $e->addUserMessage('ACCOUNT.REGISTRATION_BROKEN'); + throw $e; + } + + $this->setUserProperty('group_id', $defaultGroup->id); + + // Hash password + $this->hashPassword(); + + // Set verified flag + $this->setUserProperty('flag_verified', !$this->getRequireEmailVerification()); + + // All checks passed! log events/activities, create user, and send verification email (if required) + // Begin transaction - DB will be rolled back if an exception occurs + $user = Capsule::transaction(function () { + // Log throttleable event + $this->ci->throttler->logEvent('registration_attempt'); + + // Create the user + $user = $this->ci->classMapper->createInstance('user', $this->userdata); + + // Store new user to database + $user->save(); + + // Create activity record + $this->ci->userActivityLogger->info("User {$user->user_name} registered for a new account.", [ + 'type' => 'sign_up', + 'user_id' => $user->id + ]); + + // Load default roles + $defaultRoles = $this->ci->classMapper->staticMethod('role', 'whereIn', 'slug', $this->defaultRoles)->get(); + $defaultRoleIds = $defaultRoles->pluck('id')->all(); + + // Attach default roles + $user->roles()->attach($defaultRoleIds); + + // Verification email + if ($this->requireEmailVerification) { + $this->sendVerificationEmail($user); + } + + return $user; + }); + + return $user; + } + + /** + * Validate the user name and email is unique + * + * @throws HttpException If data doesn't validate + * @return bool Returns true if the data is valid + */ + public function validate() + { + // Make sure all required fields are defined + foreach ($this->requiredProperties as $property) { + if (!isset($this->userdata[$property])) { + $e = new HttpException("Account can't be registrated as '$property' is required to create a new user."); + $e->addUserMessage('USERNAME.IN_USE'); + throw $e; + } + } + + // Check if username is unique + if (!$this->usernameIsUnique($this->userdata['user_name'])) { + $e = new HttpException(); + $e->addUserMessage('USERNAME.IN_USE'); + throw $e; + } + + // Check if email is unique + if (!$this->emailIsUnique($this->userdata['email'])) { + $e = new HttpException(); + $e->addUserMessage('EMAIL.IN_USE'); + throw $e; + } + + // Validate password requirements + // !TODO + + return true; + } + + /** + * Check Unique Username + * Make sure the username is not already in use + * + * @param string $username + * @return bool Return true if username is unique + */ + public function usernameIsUnique($username) + { + return !($this->ci->classMapper->staticMethod('user', 'findUnique', $username, 'user_name')); + } + + /** + * Check Unique Email + * Make sure the email is not already in use + * + * @param string $email + * @return bool Return true if email is unique + */ + public function emailIsUnique($email) + { + return !($this->ci->classMapper->staticMethod('user', 'findUnique', $email, 'email')); + } + + /** + * Hash the user password in the userdata array + */ + protected function hashPassword() + { + $this->userdata['password'] = Password::hash($this->userdata['password']); + } + + /** + * Set default value from config + */ + protected function setDefaults() + { + $this->verified = $this->ci->config['site.registration.require_email_verification']; + $this->requireEmailVerification = $this->ci->config['site.registration.require_email_verification']; + $this->defaultGroup = $this->ci->config['site.registration.user_defaults.group']; + $this->defaultRoles = $this->ci->classMapper->staticMethod('role', 'getDefaultSlugs'); + } + + /** + * Send verification email for specified user + * + * @param UserInterface $user The user to send the email for + */ + protected function sendVerificationEmail(UserInterface $user) + { + // Try to generate a new verification request + $verification = $this->ci->repoVerification->create($user, $this->ci->config['verification.timeout']); + + // Create and send verification email + $message = new TwigMailMessage($this->ci->view, 'mail/verify-account.html.twig'); + + $message->from($this->ci->config['address_book.admin']) + ->addEmailRecipient(new EmailRecipient($user->email, $user->full_name)) + ->addParams([ + 'user' => $user, + 'token' => $verification->getToken() + ]); + + $this->ci->mailer->send($message); + } + + /** + * @return bool + */ + public function getRequireEmailVerification() + { + return $this->requireEmailVerification; + } + + /** + * @param bool $requireEmailVerification + * @return static + */ + public function setRequireEmailVerification($requireEmailVerification) + { + $this->requireEmailVerification = $requireEmailVerification; + + return $this; + } + + /** + * @return string + */ + public function getDefaultGroup() + { + return $this->defaultGroup; + } + + /** + * @param string $defaultGroup + * @return static + */ + public function setDefaultGroup($defaultGroup) + { + $this->defaultGroup = $defaultGroup; + + return $this; + } + + /** + * @return array + */ + public function getDefaultRoles() + { + return $this->defaultRoles; + } + + /** + * @param array $defaultRoles + * @return static + */ + public function setDefaultRoles($defaultRoles) + { + $this->defaultRoles = $defaultRoles; + + return $this; + } + + /** + * @return array + */ + public function getUserdata() + { + return $this->userdata; + } + + /** + * @param array $userdata + * + * @return static + */ + public function setUserdata($userdata) + { + $this->userdata = $userdata; + + return $this; + } + + /** + * Define a user property + * + * @param string $property The property to set + * @param mixed $value The property value + */ + public function setUserProperty($property, $value) + { + $this->userdata[$property] = $value; + } +} diff --git a/app/sprinkles/account/src/Authenticate/AuthGuard.php b/app/sprinkles/account/src/Authenticate/AuthGuard.php index efcfaae15..7f6ce786b 100644 --- a/app/sprinkles/account/src/Authenticate/AuthGuard.php +++ b/app/sprinkles/account/src/Authenticate/AuthGuard.php @@ -3,13 +3,14 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Authenticate; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -use Slim\Http\Body; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; use UserFrosting\Sprinkle\Account\Authenticate\Exception\AuthExpiredException; /** @@ -27,9 +28,9 @@ class AuthGuard /** * Constructor. * - * @param $authenticator Authenticator The current authentication object. + * @param Authenticator $authenticator The current authentication object. */ - public function __construct($authenticator) + public function __construct(Authenticator $authenticator) { $this->authenticator = $authenticator; } @@ -37,13 +38,12 @@ public function __construct($authenticator) /** * Invoke the AuthGuard middleware, throwing an exception if there is no authenticated user in the session. * - * @param \Psr\Http\Message\ServerRequestInterface $request PSR7 request - * @param \Psr\Http\Message\ResponseInterface $response PSR7 response - * @param callable $next Next middleware - * - * @return \Psr\Http\Message\ResponseInterface + * @param Request $request PSR7 request + * @param Response $response PSR7 response + * @param callable $next Next middleware + * @return Response */ - public function __invoke($request, $response, $next) + public function __invoke(Request $request, Response $response, $next) { if (!$this->authenticator->check()) { throw new AuthExpiredException(); diff --git a/app/sprinkles/account/src/Authenticate/Authenticator.php b/app/sprinkles/account/src/Authenticate/Authenticator.php index 5fb89201c..c298770c6 100644 --- a/app/sprinkles/account/src/Authenticate/Authenticator.php +++ b/app/sprinkles/account/src/Authenticate/Authenticator.php @@ -3,13 +3,15 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Authenticate; use Birke\Rememberme\Authenticator as RememberMe; -use Birke\Rememberme\Storage\PDOStorage as RememberMePDO; use Birke\Rememberme\Triplet as RememberMeTriplet; +use Illuminate\Cache\Repository as Cache; use Illuminate\Database\Capsule\Manager as Capsule; use UserFrosting\Session\Session; use UserFrosting\Sprinkle\Account\Authenticate\Exception\AccountDisabledException; @@ -18,9 +20,11 @@ use UserFrosting\Sprinkle\Account\Authenticate\Exception\AuthCompromisedException; use UserFrosting\Sprinkle\Account\Authenticate\Exception\AuthExpiredException; use UserFrosting\Sprinkle\Account\Authenticate\Exception\InvalidCredentialsException; -use UserFrosting\Sprinkle\Account\Database\Models\User; +use UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface; use UserFrosting\Sprinkle\Account\Facades\Password; +use UserFrosting\Sprinkle\Account\Rememberme\PDOStorage as RememberMePDO; use UserFrosting\Sprinkle\Core\Util\ClassMapper; +use UserFrosting\Support\Repository\Repository as Config; /** * Handles authentication tasks. @@ -50,6 +54,11 @@ class Authenticator */ protected $cache; + /** + * @var Capsule + */ + protected $db; + /** * @var bool */ @@ -66,7 +75,7 @@ class Authenticator protected $rememberMe; /** - * @var User + * @var UserInterface */ protected $user; @@ -81,27 +90,25 @@ class Authenticator * Create a new Authenticator object. * * @param ClassMapper $classMapper Maps generic class identifiers to specific class names. - * @param Session $session The session wrapper object that will store the user's id. - * @param Config $config Config object that contains authentication settings. - * @param mixed $cache Cache service instance + * @param Session $session The session wrapper object that will store the user's id. + * @param Config $config Config object that contains authentication settings. + * @param Cache $cache Cache service instance + * @param Capsule $db Database service instance */ - public function __construct(ClassMapper $classMapper, Session $session, $config, $cache) + public function __construct(ClassMapper $classMapper, Session $session, Config $config, Cache $cache, Capsule $db) { $this->classMapper = $classMapper; $this->session = $session; $this->config = $config; $this->cache = $cache; + $this->db = $db; // Initialize RememberMe storage - $this->rememberMeStorage = new RememberMePDO($this->config['remember_me.table']); - - // Get the actual PDO instance from Eloquent - $pdo = Capsule::connection()->getPdo(); - - $this->rememberMeStorage->setConnection($pdo); + $this->rememberMeStorage = new RememberMePDO($this->db); // Set up RememberMe $this->rememberMe = new RememberMe($this->rememberMeStorage); + // Set cookie name $cookieName = $this->config['session.name'] . '-' . $this->config['remember_me.cookie.name']; $this->rememberMe->getCookie()->setName($cookieName); @@ -115,7 +122,6 @@ public function __construct(ClassMapper $classMapper, Session $session, $config, } $this->user = null; - $this->viaRemember = false; } @@ -123,6 +129,15 @@ public function __construct(ClassMapper $classMapper, Session $session, $config, * Attempts to authenticate a user based on a supplied identity and password. * * If successful, the user's id is stored in session. + * + * @param string $identityColumn + * @param string $identityValue + * @param string $password + * @param bool $rememberMe + * @throws InvalidCredentialsException + * @throws AccountDisabledException + * @throws AccountNotVerifiedException + * @return UserInterface */ public function attempt($identityColumn, $identityValue, $password, $rememberMe = false) { @@ -151,6 +166,7 @@ public function attempt($identityColumn, $identityValue, $password, $rememberMe // Here is my password. May I please assume the identify of this user now? if (Password::verify($password, $user->password)) { $this->login($user, $rememberMe); + return $user; } else { // We know the password is at fault here (as opposed to the identity), but lets not give away the combination in case of someone bruteforcing @@ -182,12 +198,12 @@ public function guest() * Process an account login request. * * This method logs in the specified user, allowing the client to assume the user's identity for the duration of the session. - * @param User $user The user to log in. - * @param bool $rememberMe Set to true to make this a "persistent session", i.e. one that will re-login even after the session expires. + * @param UserInterface $user The user to log in. + * @param bool $rememberMe Set to true to make this a "persistent session", i.e. one that will re-login even after the session expires. * @todo Figure out a way to update the currentUser service to reflect the logged-in user *immediately* in the service provider. * As it stands, the currentUser service will still reflect a "guest user" for the remainder of the request. */ - public function login($user, $rememberMe = false) + public function login(UserInterface $user, $rememberMe = false) { $oldId = session_id(); $this->session->regenerateId(true); @@ -228,7 +244,7 @@ public function logout($complete = false) // This removes all of the user's persistent logins from the database if ($complete) { - $this->storage->cleanAllTriplets($currentUserId); + $this->rememberMeStorage->cleanAllTriplets($currentUserId); } // Clear the rememberMe cookie @@ -240,6 +256,9 @@ public function logout($complete = false) if ($currentUser) { $currentUser->onLogout(); } + + // Delete user object cache + $this->cache->forget($this->config['cache.user.key'] . $currentUserId); } $this->user = null; @@ -261,11 +280,11 @@ public function logout($complete = false) * Try to get the currently authenticated user, returning a guest user if none was found. * * Tries to re-establish a session for "remember-me" users who have been logged out due to an expired session. - * @return User|null * @throws AuthExpiredException * @throws AuthCompromisedException * @throws AccountInvalidException * @throws AccountDisabledException + * @return UserInterface|null */ public function user() { @@ -311,8 +330,8 @@ public function viaRemember() /** * Attempt to log in the client from their rememberMe token (in their cookie). * - * @return User|bool If successful, the User object of the remembered user. Otherwise, return false. * @throws AuthCompromisedException The client attempted to log in with an invalid rememberMe token. + * @return UserInterface|bool If successful, the User object of the remembered user. Otherwise, return false. */ protected function loginRememberedUser() { @@ -338,8 +357,8 @@ protected function loginRememberedUser() /** * Attempt to log in the client from the session. * - * @return User|null If successful, the User object of the user in session. Otherwise, return null. * @throws AuthExpiredException The client attempted to use an expired rememberMe token. + * @return UserInterface|null If successful, the User object of the user in session. Otherwise, return null. */ protected function loginSessionUser() { @@ -380,15 +399,20 @@ protected function validateRememberMeCookie() * Tries to load the specified user by id from the database. * * Checks that the account is valid and enabled, throwing an exception if not. - * @param int $userId - * @return User|null + * @param int $userId * @throws AccountInvalidException * @throws AccountDisabledException + * @return UserInterface|null */ protected function validateUserAccount($userId) { if ($userId) { - $user = $this->classMapper->staticMethod('user', 'find', $userId); + + // Load user from db, cache the result + $key = $this->config['cache.user.key'] . $userId; + $user = $this->cache->remember($key, $this->config['cache.user.delay'], function () use ($userId) { + return $this->classMapper->staticMethod('user', 'find', (int) $userId); + }); // If the user doesn't exist any more, throw an exception. if (!$user) { @@ -402,15 +426,15 @@ protected function validateUserAccount($userId) return $user; } else { - return null; + return; } } /** - * Flush the cache associated with a session id + * Flush the cache associated with a session id * - * @param string $id The session id - * @return bool + * @param string $id The session id + * @return bool */ public function flushSessionCache($id) { diff --git a/app/sprinkles/account/src/Authenticate/Exception/AccountDisabledException.php b/app/sprinkles/account/src/Authenticate/Exception/AccountDisabledException.php index e79ceb573..7bf831797 100644 --- a/app/sprinkles/account/src/Authenticate/Exception/AccountDisabledException.php +++ b/app/sprinkles/account/src/Authenticate/Exception/AccountDisabledException.php @@ -3,8 +3,10 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Authenticate\Exception; use UserFrosting\Support\Exception\HttpException; diff --git a/app/sprinkles/account/src/Authenticate/Exception/AccountInvalidException.php b/app/sprinkles/account/src/Authenticate/Exception/AccountInvalidException.php index 607235b54..8d2e5d9e5 100644 --- a/app/sprinkles/account/src/Authenticate/Exception/AccountInvalidException.php +++ b/app/sprinkles/account/src/Authenticate/Exception/AccountInvalidException.php @@ -3,8 +3,10 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Authenticate\Exception; use UserFrosting\Support\Exception\HttpException; diff --git a/app/sprinkles/account/src/Authenticate/Exception/AccountNotVerifiedException.php b/app/sprinkles/account/src/Authenticate/Exception/AccountNotVerifiedException.php index 7eb56a66d..e2324af1b 100644 --- a/app/sprinkles/account/src/Authenticate/Exception/AccountNotVerifiedException.php +++ b/app/sprinkles/account/src/Authenticate/Exception/AccountNotVerifiedException.php @@ -3,8 +3,10 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Authenticate\Exception; use UserFrosting\Support\Exception\HttpException; diff --git a/app/sprinkles/account/src/Authenticate/Exception/AuthCompromisedException.php b/app/sprinkles/account/src/Authenticate/Exception/AuthCompromisedException.php index df3efbeae..179a82626 100644 --- a/app/sprinkles/account/src/Authenticate/Exception/AuthCompromisedException.php +++ b/app/sprinkles/account/src/Authenticate/Exception/AuthCompromisedException.php @@ -3,8 +3,10 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Authenticate\Exception; use UserFrosting\Support\Exception\ForbiddenException; diff --git a/app/sprinkles/account/src/Authenticate/Exception/AuthExpiredException.php b/app/sprinkles/account/src/Authenticate/Exception/AuthExpiredException.php index 558374661..3f8764825 100644 --- a/app/sprinkles/account/src/Authenticate/Exception/AuthExpiredException.php +++ b/app/sprinkles/account/src/Authenticate/Exception/AuthExpiredException.php @@ -3,8 +3,10 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Authenticate\Exception; use UserFrosting\Support\Exception\HttpException; diff --git a/app/sprinkles/account/src/Authenticate/Exception/InvalidCredentialsException.php b/app/sprinkles/account/src/Authenticate/Exception/InvalidCredentialsException.php index 18d4a5c73..3cc6b2320 100644 --- a/app/sprinkles/account/src/Authenticate/Exception/InvalidCredentialsException.php +++ b/app/sprinkles/account/src/Authenticate/Exception/InvalidCredentialsException.php @@ -3,8 +3,10 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Authenticate\Exception; use UserFrosting\Support\Exception\HttpException; diff --git a/app/sprinkles/account/src/Authenticate/Hasher.php b/app/sprinkles/account/src/Authenticate/Hasher.php index e277eef83..3da32527a 100644 --- a/app/sprinkles/account/src/Authenticate/Hasher.php +++ b/app/sprinkles/account/src/Authenticate/Hasher.php @@ -3,10 +3,14 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Authenticate; +use UserFrosting\Sprinkle\Account\Util\HashFailedException; + /** * Password hashing and validation class * @@ -25,7 +29,7 @@ class Hasher * Returns the hashing type for a specified password hash. * * Automatically detects the hash type: "sha1" (for UserCake legacy accounts), "legacy" (for 0.1.x accounts), and "modern" (used for new accounts). - * @param string $password the hashed password. + * @param string $password the hashed password. * @return string "sha1"|"legacy"|"modern". */ public function getHashType($password) @@ -43,10 +47,10 @@ public function getHashType($password) /** * Hashes a plaintext password using bcrypt. * - * @param string $password the plaintext password. - * @param array $options - * @return string the hashed password. + * @param string $password the plaintext password. + * @param array $options * @throws HashFailedException + * @return string the hashed password. */ public function hash($password, array $options = []) { @@ -64,10 +68,10 @@ public function hash($password, array $options = []) /** * Verify a plaintext password against the user's hashed password. * - * @param string $password The plaintext password to verify. - * @param string $hash The hash to compare against. - * @param array $options - * @return boolean True if the password matches, false otherwise. + * @param string $password The plaintext password to verify. + * @param string $hash The hash to compare against. + * @param array $options + * @return bool True if the password matches, false otherwise. */ public function verify($password, $hash, array $options = []) { @@ -78,8 +82,7 @@ public function verify($password, $hash, array $options = []) $salt = substr($hash, 0, 25); // Extract the salt from the hash $inputHash = $salt . sha1($salt . $password); - return (hash_equals($inputHash, $hash) === true); - + return hash_equals($inputHash, $hash) === true; } elseif ($hashType == 'legacy') { // Homegrown implementation (assuming that current install has been using a cost parameter of 12) // Used for manual implementation of bcrypt. @@ -88,7 +91,7 @@ public function verify($password, $hash, array $options = []) $inputHash = crypt($password, '$2y$12$' . $salt); $correctHash = substr($hash, 0, 60); - return (hash_equals($inputHash, $correctHash) === true); + return hash_equals($inputHash, $correctHash) === true; } // Modern implementation @@ -98,7 +101,7 @@ public function verify($password, $hash, array $options = []) /** * Extract the cost value from the options array. * - * @param array $options + * @param array $options * @return int */ protected function cost(array $options = []) diff --git a/app/sprinkles/account/src/Authorize/AccessConditionExpression.php b/app/sprinkles/account/src/Authorize/AccessConditionExpression.php index dd5647e86..301153e85 100644 --- a/app/sprinkles/account/src/Authorize/AccessConditionExpression.php +++ b/app/sprinkles/account/src/Authorize/AccessConditionExpression.php @@ -3,19 +3,19 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Authorize; use Monolog\Logger; use PhpParser\Lexer\Emulative as EmulativeLexer; -use PhpParser\Node; use PhpParser\NodeTraverser; use PhpParser\Parser as Parser; use PhpParser\PrettyPrinter\Standard as StandardPrettyPrinter; use PhpParser\Error as PhpParserException; -use Psr\Http\Message\ServerRequestInterface as Request; -use UserFrosting\Sprinkle\Account\Database\Models\User; +use UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface; /** * AccessConditionExpression class @@ -28,7 +28,7 @@ class AccessConditionExpression { /** - * @var User A user object, which for convenience can be referenced as 'self' in access conditions. + * @var UserInterface A user object, which for convenience can be referenced as 'self' in access conditions. */ protected $user; @@ -65,19 +65,20 @@ class AccessConditionExpression /** * Create a new AccessConditionExpression object. * - * @param User $user A user object, which for convenience can be referenced as 'self' in access conditions. - * @param Logger $logger A Monolog logger, used to dump debugging info for authorization evaluations. - * @param bool $debug Set to true if you want debugging information printed to the auth log. + * @param ParserNodeFunctionEvaluator $nodeVisitor + * @param UserInterface $user A user object, which for convenience can be referenced as 'self' in access conditions. + * @param Logger $logger A Monolog logger, used to dump debugging info for authorization evaluations. + * @param bool $debug Set to true if you want debugging information printed to the auth log. */ - public function __construct(ParserNodeFunctionEvaluator $nodeVisitor, User $user, Logger $logger, $debug = false) + public function __construct(ParserNodeFunctionEvaluator $nodeVisitor, UserInterface $user, Logger $logger, $debug = false) { - $this->nodeVisitor = $nodeVisitor; - $this->user = $user; - $this->parser = new Parser(new EmulativeLexer); - $this->traverser = new NodeTraverser; + $this->nodeVisitor = $nodeVisitor; + $this->user = $user; + $this->parser = new Parser(new EmulativeLexer()); + $this->traverser = new NodeTraverser(); $this->traverser->addVisitor($nodeVisitor); - $this->prettyPrinter = new StandardPrettyPrinter; - $this->logger = $logger; + $this->prettyPrinter = new StandardPrettyPrinter(); + $this->logger = $logger; $this->debug = $debug; } @@ -86,9 +87,9 @@ public function __construct(ParserNodeFunctionEvaluator $nodeVisitor, User $user * * The special parameter `self` is an array of the current user's data. * This get included automatically, and so does not need to be passed in. - * @param string $condition a boolean expression composed of calls to AccessCondition functions. - * @param array[mixed] $params the parameters to be used when evaluating the expression. - * @return bool true if the condition is passed for the given parameters, otherwise returns false. + * @param string $condition a boolean expression composed of calls to AccessCondition functions. + * @param array[mixed] $params the parameters to be used when evaluating the expression. + * @return bool true if the condition is passed for the given parameters, otherwise returns false. */ public function evaluateCondition($condition, $params) { @@ -116,11 +117,11 @@ public function evaluateCondition($condition, $params) // Evaluate boolean statement. It is safe to use eval() here, because our expression has been reduced entirely to a boolean expression. $expr = $this->prettyPrinter->prettyPrintExpr($stmts[0]); - $expr_eval = "return " . $expr . ";\n"; + $expr_eval = 'return ' . $expr . ";\n"; $result = eval($expr_eval); if ($this->debug) { - $this->logger->debug("Expression '$expr' evaluates to " . ($result == true ? "true" : "false")); + $this->logger->debug("Expression '$expr' evaluates to " . ($result == true ? 'true' : 'false')); } return $result; @@ -128,11 +129,13 @@ public function evaluateCondition($condition, $params) if ($this->debug) { $this->logger->debug("Error parsing access condition '$condition':" . $e->getMessage()); } + return false; // Access fails if the access condition can't be parsed. } catch (AuthorizationException $e) { if ($this->debug) { $this->logger->debug("Error parsing access condition '$condition':" . $e->getMessage()); } + return false; } } diff --git a/app/sprinkles/account/src/Authorize/AuthorizationException.php b/app/sprinkles/account/src/Authorize/AuthorizationException.php index 251b67fe4..06b613a6f 100644 --- a/app/sprinkles/account/src/Authorize/AuthorizationException.php +++ b/app/sprinkles/account/src/Authorize/AuthorizationException.php @@ -3,8 +3,10 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Authorize; use UserFrosting\Support\Exception\ForbiddenException; @@ -19,5 +21,4 @@ */ class AuthorizationException extends ForbiddenException { - } diff --git a/app/sprinkles/account/src/Authorize/AuthorizationManager.php b/app/sprinkles/account/src/Authorize/AuthorizationManager.php index def152bfb..b30413914 100644 --- a/app/sprinkles/account/src/Authorize/AuthorizationManager.php +++ b/app/sprinkles/account/src/Authorize/AuthorizationManager.php @@ -3,12 +3,14 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Authorize; use Interop\Container\ContainerInterface; -use UserFrosting\Sprinkle\Account\Database\Models\User; +use UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface; /** * AuthorizationManager class. @@ -31,7 +33,8 @@ class AuthorizationManager /** * Create a new AuthorizationManager object. * - * @param ContainerInterface $ci The global container object, which holds all your services. + * @param ContainerInterface $ci The global container object, which holds all your services. + * @param array $callbacks */ public function __construct(ContainerInterface $ci, array $callbacks = []) { @@ -43,12 +46,13 @@ public function __construct(ContainerInterface $ci, array $callbacks = []) * Register an authorization callback, which can then be used in permission conditions. * * To add additional callbacks, simply extend the `authorizer` service in your Sprinkle's service provider. - * @param string $name + * @param string $name * @param callable $callback */ public function addCallback($name, $callback) { $this->callbacks[$name] = $callback; + return $this; } @@ -67,36 +71,37 @@ public function getCallbacks() * * Determine if this user has access to the given $slug under the given $params. * - * @param UserFrosting\Sprinkle\Account\Database\Models\User $user - * @param string $slug The permission slug to check for access. - * @param array $params[optional] An array of field names => values, specifying any additional data to provide the authorization module - * when determining whether or not this user has access. - * @return boolean True if the user has access, false otherwise. + * @param UserInterface|null $user + * @param string $slug The permission slug to check for access. + * @param array $params An array of field names => values, specifying any additional data to provide the authorization module + * when determining whether or not this user has access. + * @return bool True if the user has access, false otherwise. */ - public function checkAccess(User $user, $slug, array $params = []) + public function checkAccess($user, $slug, array $params = []) { $debug = $this->ci->config['debug.auth']; - if ($debug) { - $trace = array_slice(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3), 1); - $this->ci->authLogger->debug("Authorization check requested at: ", $trace); - $this->ci->authLogger->debug("Checking authorization for user {$user->id} ('{$user->user_name}') on permission '$slug'..."); - } - - if ($this->ci->authenticator->guest()) { + if (is_null($user) || !($user instanceof UserInterface)) { if ($debug) { - $this->ci->authLogger->debug("User is not logged in. Access denied."); + $this->ci->authLogger->debug('No user defined. Access denied.'); } + return false; } + if ($debug) { + $trace = array_slice(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3), 1); + $this->ci->authLogger->debug('Authorization check requested at: ', $trace); + $this->ci->authLogger->debug("Checking authorization for user {$user->id} ('{$user->user_name}') on permission '$slug'..."); + } + // The master (root) account has access to everything. // Need to use loose comparison for now, because some DBs return `id` as a string. - if ($user->id == $this->ci->config['reserved_user_ids.master']) { if ($debug) { - $this->ci->authLogger->debug("User is the master (root) user. Access granted."); + $this->ci->authLogger->debug('User is the master (root) user. Access granted.'); } + return true; } @@ -105,8 +110,9 @@ public function checkAccess(User $user, $slug, array $params = []) if (empty($permissions) || !isset($permissions[$slug])) { if ($debug) { - $this->ci->authLogger->debug("No matching permissions found. Access denied."); + $this->ci->authLogger->debug('No matching permissions found. Access denied.'); } + return false; } @@ -123,14 +129,15 @@ public function checkAccess(User $user, $slug, array $params = []) $pass = $ace->evaluateCondition($permission->conditions, $params); if ($pass) { if ($debug) { - $this->ci->authLogger->debug("User passed conditions '{$permission->conditions}' . Access granted."); + $this->ci->authLogger->debug("User passed conditions '{$permission->conditions}'. Access granted."); } + return true; } } if ($debug) { - $this->ci->authLogger->debug("User failed to pass any of the matched permissions. Access denied."); + $this->ci->authLogger->debug('User failed to pass any of the matched permissions. Access denied.'); } return false; @@ -139,7 +146,7 @@ public function checkAccess(User $user, $slug, array $params = []) /** * Remove extraneous information from the permission to reduce verbosity. * - * @param array + * @param array $permissions * @return array */ protected function getPermissionsArrayDebugInfo($permissions) diff --git a/app/sprinkles/account/src/Authorize/ParserNodeFunctionEvaluator.php b/app/sprinkles/account/src/Authorize/ParserNodeFunctionEvaluator.php index e8e5cde43..936dc2f1c 100644 --- a/app/sprinkles/account/src/Authorize/ParserNodeFunctionEvaluator.php +++ b/app/sprinkles/account/src/Authorize/ParserNodeFunctionEvaluator.php @@ -3,8 +3,10 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Authorize; use Monolog\Logger; @@ -22,15 +24,16 @@ */ class ParserNodeFunctionEvaluator extends NodeVisitorAbstract { - /** * @var array[callable] An array of callback functions to be used when evaluating a condition expression. */ protected $callbacks; + /** * @var \PhpParser\PrettyPrinter\Standard The PrettyPrinter object to use (initialized in the ctor) */ protected $prettyPrinter; + /** * @var array The parameters to be used when evaluating the methods in the condition expression, as an array. */ @@ -49,15 +52,15 @@ class ParserNodeFunctionEvaluator extends NodeVisitorAbstract /** * Create a new ParserNodeFunctionEvaluator object. * - * @param array $params The parameters to be used when evaluating the methods in the condition expression, as an array. - * @param Logger $logger A Monolog logger, used to dump debugging info for authorization evaluations. - * @param bool $debug Set to true if you want debugging information printed to the auth log. + * @param array $callbacks The parameters to be used when evaluating the methods in the condition expression, as an array. + * @param Logger $logger A Monolog logger, used to dump debugging info for authorization evaluations. + * @param bool $debug Set to true if you want debugging information printed to the auth log. */ - public function __construct($callbacks, $logger, $debug = false) + public function __construct($callbacks, Logger $logger, $debug = false) { $this->callbacks = $callbacks; - $this->prettyPrinter = new StandardPrettyPrinter; - $this->logger = $logger; + $this->prettyPrinter = new StandardPrettyPrinter(); + $this->logger = $logger; $this->debug = $debug; $this->params = []; } @@ -66,7 +69,7 @@ public function leaveNode(Node $node) { // Look for function calls if ($node instanceof \PhpParser\Node\Expr\FuncCall) { - $eval = new \PhpParser\Node\Scalar\LNumber; + $eval = new \PhpParser\Node\Scalar\LNumber(); // Get the method name $callbackName = $node->name->toString(); @@ -85,31 +88,31 @@ public function leaveNode(Node $node) // Resolve parameter placeholders ('variable' names (either single-word or array-dot identifiers)) if (($arg->value instanceof \PhpParser\Node\Expr\BinaryOp\Concat) || ($arg->value instanceof \PhpParser\Node\Expr\ConstFetch)) { $value = $this->resolveParamPath($argString); - $currentArgInfo['type'] = "parameter"; + $currentArgInfo['type'] = 'parameter'; $currentArgInfo['resolved_value'] = $value; // Resolve arrays } elseif ($arg->value instanceof \PhpParser\Node\Expr\Array_) { $value = $this->resolveArray($arg); - $currentArgInfo['type'] = "array"; + $currentArgInfo['type'] = 'array'; $currentArgInfo['resolved_value'] = print_r($value, true); // Resolve strings } elseif ($arg->value instanceof \PhpParser\Node\Scalar\String_) { $value = $arg->value->value; - $currentArgInfo['type'] = "string"; + $currentArgInfo['type'] = 'string'; $currentArgInfo['resolved_value'] = $value; // Resolve numbers } elseif ($arg->value instanceof \PhpParser\Node\Scalar\DNumber) { $value = $arg->value->value; - $currentArgInfo['type'] = "float"; + $currentArgInfo['type'] = 'float'; $currentArgInfo['resolved_value'] = $value; } elseif ($arg->value instanceof \PhpParser\Node\Scalar\LNumber) { $value = $arg->value->value; - $currentArgInfo['type'] = "integer"; + $currentArgInfo['type'] = 'integer'; $currentArgInfo['resolved_value'] = $value; // Anything else is simply interpreted as its literal string value } else { $value = $argString; - $currentArgInfo['type'] = "unknown"; + $currentArgInfo['type'] = 'unknown'; $currentArgInfo['resolved_value'] = $value; } @@ -133,13 +136,18 @@ public function leaveNode(Node $node) } if ($this->debug) { - $this->logger->debug("Result: " . ($result ? "1" : "0")); + $this->logger->debug('Result: ' . ($result ? '1' : '0')); } - return new \PhpParser\Node\Scalar\LNumber($result ? "1" : "0"); + return new \PhpParser\Node\Scalar\LNumber($result ? '1' : '0'); } } + /** + * Set params + * + * @param array $params + */ public function setParams($params) { $this->params = $params; @@ -148,7 +156,7 @@ public function setParams($params) /** * Resolve an array expression in a condition expression into an actual array. * - * @param string $arg the array, represented as a string. + * @param string $arg the array, represented as a string. * @return array[mixed] the array, as a plain ol' PHP array. */ private function resolveArray($arg) @@ -162,19 +170,20 @@ private function resolveArray($arg) $arr[] = $item->value->value; } } + return $arr; } /** * Resolve a parameter path (e.g. "user.id", "post", etc) into its value. * - * @param string $path the name of the parameter to resolve, based on the parameters set in this object. - * @throws Exception the path could not be resolved. Path is malformed or key does not exist. - * @return mixed the value of the specified parameter. + * @param string $path the name of the parameter to resolve, based on the parameters set in this object. + * @throws \Exception the path could not be resolved. Path is malformed or key does not exist. + * @return mixed the value of the specified parameter. */ private function resolveParamPath($path) { - $pathTokens = explode(".", $path); + $pathTokens = explode('.', $path); $value = $this->params; foreach ($pathTokens as $token) { $token = trim($token); @@ -188,6 +197,7 @@ private function resolveParamPath($path) throw new AuthorizationException("Cannot resolve the path \"$path\". Error at token \"$token\"."); } } + return $value; } } diff --git a/app/sprinkles/account/src/Bakery/CreateAdminUser.php b/app/sprinkles/account/src/Bakery/CreateAdminUser.php index cfaacef0d..ea28a6a78 100644 --- a/app/sprinkles/account/src/Bakery/CreateAdminUser.php +++ b/app/sprinkles/account/src/Bakery/CreateAdminUser.php @@ -3,21 +3,19 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Bakery; -use Illuminate\Database\Capsule\Manager as Capsule; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; -use UserFrosting\System\Bakery\BaseCommand; -use UserFrosting\System\Bakery\DatabaseTest; -use UserFrosting\System\Database\Model\Migrations; +use Symfony\Component\Console\Output\OutputInterface; +use UserFrosting\Sprinkle\Core\Bakery\Helper\DatabaseTest; +use UserFrosting\Sprinkle\Account\Account\Registration; use UserFrosting\Sprinkle\Account\Database\Models\User; -use UserFrosting\Sprinkle\Account\Database\Models\Role; -use UserFrosting\Sprinkle\Account\Facades\Password; +use UserFrosting\System\Bakery\BaseCommand; /** * Create root user CLI command. @@ -38,40 +36,52 @@ class CreateAdminUser extends BaseCommand ]; /** - * {@inheritDoc} + * {@inheritdoc} */ protected function configure() { - $this->setName("create-admin") - ->setDescription("Create the initial admin (root) user account"); + $this->setName('create-admin') + ->setDescription('Create the initial admin (root) user account') + ->addOption('username', null, InputOption::VALUE_OPTIONAL, 'The admin user username') + ->addOption('email', null, InputOption::VALUE_OPTIONAL, 'The admin user email') + ->addOption('password', null, InputOption::VALUE_OPTIONAL, 'The admin user password') + ->addOption('firstName', null, InputOption::VALUE_OPTIONAL, 'The admin user first name') + ->addOption('lastName', null, InputOption::VALUE_OPTIONAL, 'The admin user last name'); } /** - * {@inheritDoc} + * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { - $this->io->title("Root account setup"); + $this->io->title('Root account setup'); // Need the database try { - $this->io->writeln("Testing database connection", OutputInterface::VERBOSITY_VERBOSE); + $this->io->writeln('Testing database connection', OutputInterface::VERBOSITY_VERBOSE); $this->testDB(); - $this->io->writeln("Ok", OutputInterface::VERBOSITY_VERBOSE); + $this->io->writeln('Ok', OutputInterface::VERBOSITY_VERBOSE); } catch (\Exception $e) { $this->io->error($e->getMessage()); exit(1); } + /** @var \UserFrosting\Sprinkle\Core\Database\Migrator\Migrator; */ + $migrator = $this->ci->migrator; + + /** @var \UserFrosting\Sprinkle\Core\Database\Migrator\DatabaseMigrationRepository; */ + $repository = $migrator->getRepository(); + // Need migration table - if (!Capsule::schema()->hasColumn('migrations', 'id')) { - $this->io->error("Migrations doesn't appear to have been run! Make sure the database is properly migrated by using the `php bakery migrate` command."); - exit(1); + if (!$repository->repositoryExists()) { + $this->io->error("Migrations doesn't appear to have been run! Make sure the database is properly migrated by using the `php bakery migrate` command."); + exit(1); } - // Make sure the required mirgations have been run + // Make sure the required migrations have been run + $ranMigrations = $repository->getMigrationsList(); foreach ($this->dependencies as $migration) { - if (!Migrations::where('migration', $migration)->exists()) { + if (!in_array($migration, $ranMigrations)) { $this->io->error("Migration `$migration` doesn't appear to have been run! Make sure all migrations are up to date by using the `php bakery migrate` command."); exit(1); } @@ -80,74 +90,70 @@ protected function execute(InputInterface $input, OutputInterface $output) // Make sure that there are no users currently in the user table // We setup the root account here so it can be done independent of the version check if (User::count() > 0) { - $this->io->note("Table 'users' is not empty. Skipping root account setup. To set up the root account again, please truncate or drop the table and try again."); - } else { - $this->io->writeln("Please answer the following questions to create the root account:\n"); + // Don't need password confirmation if it's defined as an option + $requireConfirmation = ($input->getOption('password') == '') ? true : false; + // Get the account details - $userName = $this->askUsername(); - $email = $this->askEmail(); - $firstName = $this->askFirstName(); - $lastName = $this->askLastName(); - $password = $this->askPassword(); + $username = $this->askUsername($input->getOption('username')); + $email = $this->askEmail($input->getOption('email')); + $firstName = $this->askFirstName($input->getOption('firstName')); + $lastName = $this->askLastName($input->getOption('lastName')); + $password = $this->askPassword($input->getOption('password'), $requireConfirmation); // Ok, now we've got the info and we can create the new user. $this->io->write("\nSaving the root user details..."); - $rootUser = new User([ - "user_name" => $userName, - "email" => $email, - "first_name" => $firstName, - "last_name" => $lastName, - "password" => Password::hash($password) + $registration = new Registration($this->ci, [ + 'user_name' => $username, + 'email' => $email, + 'first_name' => $firstName, + 'last_name' => $lastName, + 'password' => $password ]); + $registration->setRequireEmailVerification(false); + $registration->setDefaultRoles(['user', 'group-admin', 'site-admin']); - $rootUser->save(); - - $defaultRoles = [ - 'user' => Role::where('slug', 'user')->first(), - 'group-admin' => Role::where('slug', 'group-admin')->first(), - 'site-admin' => Role::where('slug', 'site-admin')->first() - ]; - - foreach ($defaultRoles as $slug => $role) { - if ($role) { - $rootUser->roles()->attach($role->id); - } + try { + $rootUser = $registration->register(); + } catch (\Exception $e) { + $this->io->error($e->getMessage()); + exit(1); } - $this->io->success("Root user creation successful!"); + $this->io->success('Root user creation successful!'); } } /** - * Ask for the username + * Ask for the username and return a valid one * - * @access protected - * @return void + * @param string $username The base/default username + * @return string The validated username */ - protected function askUsername() + protected function askUsername($username = '') { - while (!isset($userName) || !$this->validateUsername($userName)) { - $userName = $this->io->ask("Choose a root username (1-50 characters, no leading or trailing whitespace)"); + while (!isset($username) || !$this->validateUsername($username)) { + $username = $this->io->ask('Choose a root username (1-50 characters, no leading or trailing whitespace)'); } - return $userName; + + return $username; } /** * Validate the username. * - * @access protected - * @param mixed $userName - * @return void + * @param string $username The input + * @return bool Is the username validated ? */ - protected function validateUsername($userName) + protected function validateUsername($username) { // Validate length - if (strlen($userName) < 1 || strlen($userName) > 50) { - $this->io->error("Username must be between 1-50 characters"); + if (strlen($username) < 1 || strlen($username) > 50) { + $this->io->error('Username must be between 1-50 characters'); + return false; } @@ -157,9 +163,10 @@ protected function validateUsername($userName) 'regexp' => "/^\S((.*\S)|)$/" ] ]; - $validate = filter_var($userName, FILTER_VALIDATE_REGEXP, $options); + $validate = filter_var($username, FILTER_VALIDATE_REGEXP, $options); if (!$validate) { $this->io->error("Username can't have any leading or trailing whitespace"); + return false; } @@ -167,37 +174,39 @@ protected function validateUsername($userName) } /** - * Ask for the email + * Ask for the email and return a valid one * - * @access protected - * @return void + * @param string $email The base/default email + * @return string The validated email */ - protected function askEmail() + protected function askEmail($email = '') { while (!isset($email) || !$this->validateEmail($email)) { - $email = $this->io->ask("Enter a valid email address (1-254 characters, must be compatible with FILTER_VALIDATE_EMAIL)"); + $email = $this->io->ask('Enter a valid email address (1-254 characters, must be compatible with FILTER_VALIDATE_EMAIL)'); } + return $email; } /** * Validate the email. * - * @access protected - * @param mixed $email - * @return void + * @param string $email The input + * @return bool Is the email validated ? */ protected function validateEmail($email) { // Validate length if (strlen($email) < 1 || strlen($email) > 254) { - $this->io->error("Email must be between 1-254 characters"); + $this->io->error('Email must be between 1-254 characters'); + return false; } // Validate format if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { - $this->io->error("Email must be compatible with FILTER_VALIDATE_EMAIL"); + $this->io->error('Email must be compatible with FILTER_VALIDATE_EMAIL'); + return false; } @@ -205,31 +214,32 @@ protected function validateEmail($email) } /** - * Ask for the first name + * Ask for the first name and return a valid one * - * @access protected - * @return void + * @param string $firstName The base/default first name + * @return string The validated first name */ - protected function askFirstName() + protected function askFirstName($firstName = '') { while (!isset($firstName) || !$this->validateFirstName($firstName)) { - $firstName = $this->io->ask("Enter the user first name (1-20 characters)"); + $firstName = $this->io->ask('Enter the user first name (1-20 characters)'); } + return $firstName; } /** - * validateFirstName function. + * Validate the first name * - * @access protected - * @param mixed $name - * @return void + * @param string $firstName The input + * @return bool Is the input validated ? */ protected function validateFirstName($firstName) { // Validate length if (strlen($firstName) < 1 || strlen($firstName) > 20) { - $this->io->error("First name must be between 1-20 characters"); + $this->io->error('First name must be between 1-20 characters'); + return false; } @@ -237,31 +247,32 @@ protected function validateFirstName($firstName) } /** - * Ask for the last name + * Ask for the last name and return a valid one * - * @access protected - * @return void + * @param string $lastName The base/default last name + * @return string The validated last name */ - protected function askLastName() + protected function askLastName($lastName = '') { while (!isset($lastName) || !$this->validateLastName($lastName)) { - $lastName = $this->io->ask("Enter the user last name (1-30 characters)"); + $lastName = $this->io->ask('Enter the user last name (1-30 characters)'); } + return $lastName; } /** - * validateLastName function. + * Validate the last name entered is valid * - * @access protected - * @param mixed $lastName - * @return void + * @param string $lastName The lastname + * @return bool Input is valid or not */ protected function validateLastName($lastName) { // Validate length if (strlen($lastName) < 1 || strlen($lastName) > 30) { - $this->io->error("Last name must be between 1-30 characters"); + $this->io->error('Last name must be between 1-30 characters'); + return false; } @@ -269,30 +280,33 @@ protected function validateLastName($lastName) } /** - * Ask for the password + * Ask for the password and return a valid one * - * @access protected - * @return void + * @param string $password The base/default password + * @param bool $requireConfirmation (default true) + * @return string The validated password */ - protected function askPassword() + protected function askPassword($password = '', $requireConfirmation = true) { - while (!isset($password) || !$this->validatePassword($password) || !$this->confirmPassword($password)) { - $password = $this->io->askHidden("Enter password (12-255 characters)"); + while (!isset($password) || !$this->validatePassword($password) || !$this->confirmPassword($password, $requireConfirmation)) { + $password = $this->io->askHidden('Enter password (12-255 characters)'); } + return $password; } /** - * validatePassword function. + * Validate password input * - * @access protected - * @param mixed $password - * @return void + * @param string $password The input + * @return bool Is the password valid or not */ protected function validatePassword($password) { + //TODO Config for this ?? if (strlen($password) < 12 || strlen($password) > 255) { - $this->io->error("Password must be between 12-255 characters"); + $this->io->error('Password must be between 12-255 characters'); + return false; } @@ -300,35 +314,40 @@ protected function validatePassword($password) } /** - * confirmPassword function. + * Ask for password confirmation * - * @access protected - * @param mixed $passwordToConfirm - * @return void + * @param string $passwordToConfirm + * @param bool $requireConfirmation (default true) + * @return bool Is the password confirmed or not */ - protected function confirmPassword($passwordToConfirm) + protected function confirmPassword($passwordToConfirm, $requireConfirmation = true) { + if (!$requireConfirmation) { + return true; + } + while (!isset($password)) { - $password = $this->io->askHidden("Please re-enter the chosen password"); + $password = $this->io->askHidden('Please re-enter the chosen password'); } + return $this->validatePasswordConfirmation($password, $passwordToConfirm); } /** - * validatePasswordConfirmation function. + * Validate the confirmation password * - * @access protected - * @param mixed $password - * @param mixed $passwordToConfirm - * @return void + * @param string $password The confirmation + * @param string $passwordToConfirm The password to confirm + * @return bool Is the confirmation password valid or not */ protected function validatePasswordConfirmation($password, $passwordToConfirm) { if ($password != $passwordToConfirm) { - $this->io->error("Passwords do not match, please try again."); + $this->io->error('Passwords do not match, please try again.'); + return false; } return true; } -} \ No newline at end of file +} diff --git a/app/sprinkles/account/src/Controller/AccountController.php b/app/sprinkles/account/src/Controller/AccountController.php index ce99370f8..46d890896 100644 --- a/app/sprinkles/account/src/Controller/AccountController.php +++ b/app/sprinkles/account/src/Controller/AccountController.php @@ -3,19 +3,21 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Controller; use Carbon\Carbon; use Illuminate\Database\Capsule\Manager as Capsule; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; -use Slim\Exception\NotFoundException as NotFoundException; use UserFrosting\Fortress\RequestDataTransformer; use UserFrosting\Fortress\RequestSchema; use UserFrosting\Fortress\ServerSideValidator; use UserFrosting\Fortress\Adapter\JqueryValidationAdapter; +use UserFrosting\Sprinkle\Account\Account\Registration; use UserFrosting\Sprinkle\Account\Controller\Exception\SpammyRequestException; use UserFrosting\Sprinkle\Account\Facades\Password; use UserFrosting\Sprinkle\Account\Util\Util as AccountUtil; @@ -25,7 +27,7 @@ use UserFrosting\Sprinkle\Core\Util\Captcha; use UserFrosting\Support\Exception\BadRequestException; use UserFrosting\Support\Exception\ForbiddenException; -use UserFrosting\Support\Exception\HttpException; +use UserFrosting\Support\Exception\NotFoundException; /** * Controller class for /account/* URLs. Handles account-related activities, including login, registration, password recovery, and account settings. @@ -40,12 +42,15 @@ class AccountController extends SimpleController * * This route is throttled by default, to discourage abusing it for account enumeration. * This route is "public access". - * Request type: GET * - * @param Request $request - * @param Response $response - * @param array $args - * @return void + * AuthGuard: false + * Route: /account/check-username + * Route Name: {none} + * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws BadRequestException */ public function checkUsername(Request $request, Response $response, $args) { @@ -68,7 +73,7 @@ public function checkUsername(Request $request, Response $response, $args) // TODO: encapsulate the communication of error messages from ServerSideValidator to the BadRequestException $e = new BadRequestException('Missing or malformed request data!'); foreach ($validator->errors() as $idx => $field) { - foreach($field as $eidx => $error) { + foreach ($field as $eidx => $error) { $e->addUserMessage($error); } } @@ -81,7 +86,7 @@ public function checkUsername(Request $request, Response $response, $args) // Throttle requests if ($delay > 0) { - return $response->withStatus(429); + return $response->withJson([], 429); } /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ @@ -95,6 +100,7 @@ public function checkUsername(Request $request, Response $response, $args) if ($classMapper->staticMethod('user', 'findUnique', $data['user_name'], 'user_name')) { $message = $translator->translate('USERNAME.NOT_AVAILABLE', $data); + return $response->write($message)->withStatus(200); } else { return $response->write('true')->withStatus(200); @@ -107,12 +113,14 @@ public function checkUsername(Request $request, Response $response, $args) * This is provided so that users can cancel a password reset request, if they made it in error or if it was not initiated by themselves. * Processes the request from the password reset link, checking that: * 1. The provided token is associated with an existing user account, who has a pending password reset request. - * Request type: GET * - * @param Request $request - * @param Response $response - * @param array $args - * @return void + * AuthGuard: false + * Route: /account/set-password/deny + * Route Name: {none} + * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args */ public function denyResetPassword(Request $request, Response $response, $args) { @@ -138,18 +146,20 @@ public function denyResetPassword(Request $request, Response $response, $args) $validator = new ServerSideValidator($schema, $this->ci->translator); if (!$validator->validate($data)) { $ms->addValidationErrors($validator); - // 400 code + redirect is perfectly fine, according to user Dilaz in #laravel - return $response->withRedirect($loginPage, 400); + + return $response->withRedirect($loginPage); } $passwordReset = $this->ci->repoPasswordReset->cancel($data['token']); if (!$passwordReset) { $ms->addMessageTranslated('danger', 'PASSWORD.FORGET.INVALID'); - return $response->withRedirect($loginPage, 400); + + return $response->withRedirect($loginPage); } $ms->addMessageTranslated('success', 'PASSWORD.FORGET.REQUEST_CANNED'); + return $response->withRedirect($loginPage); } @@ -163,14 +173,16 @@ public function denyResetPassword(Request $request, Response $response, $args) * Note that we have removed the requirement that a password reset request not already be in progress. * This is because we need to allow users to re-request a reset, even if they lose the first reset email. * This route is "public access". - * Request type: POST * @todo require additional user information * @todo prevent password reset requests for root account? * - * @param Request $request - * @param Response $response - * @param array $args - * @return void + * AuthGuard: false + * Route: /account/forgot-password + * Route Name: {none} + * Request type: POST + * @param Request $request + * @param Response $response + * @param array $args */ public function forgotPassword(Request $request, Response $response, $args) { @@ -197,7 +209,8 @@ public function forgotPassword(Request $request, Response $response, $args) $validator = new ServerSideValidator($schema, $this->ci->translator); if (!$validator->validate($data)) { $ms->addValidationErrors($validator); - return $response->withStatus(400); + + return $response->withJson([], 400); } // Throttle requests @@ -212,12 +225,13 @@ public function forgotPassword(Request $request, Response $response, $args) if ($delay > 0) { $ms->addMessageTranslated('danger', 'RATE_LIMIT_EXCEEDED', ['delay' => $delay]); - return $response->withStatus(429); + + return $response->withJson([], 429); } // All checks passed! log events/activities, update user, and send email // Begin transaction - DB will be rolled back if an exception occurs - Capsule::transaction( function() use ($classMapper, $data, $throttler, $throttleData, $config) { + Capsule::transaction(function () use ($classMapper, $data, $throttler, $throttleData, $config) { // Log throttleable event $throttler->logEvent('password_reset_request', $throttleData); @@ -237,8 +251,8 @@ public function forgotPassword(Request $request, Response $response, $args) $message->from($config['address_book.admin']) ->addEmailRecipient(new EmailRecipient($user->email, $user->full_name)) ->addParams([ - 'user' => $user, - 'token' => $passwordReset->getToken(), + 'user' => $user, + 'token' => $passwordReset->getToken(), 'request_date' => Carbon::now()->format('Y-m-d H:i:s') ]); @@ -249,19 +263,22 @@ public function forgotPassword(Request $request, Response $response, $args) // TODO: create delay to prevent timing-based attacks $ms->addMessageTranslated('success', 'PASSWORD.FORGET.REQUEST_SENT', ['email' => $data['email']]); - return $response->withStatus(200); + + return $response->withJson([], 200); } /** * Returns a modal containing account terms of service. * * This does NOT render a complete page. Instead, it renders the HTML for the form, which can be embedded in other pages. - * Request type: GET * - * @param Request $request - * @param Response $response - * @param array $args - * @return void + * AuthGuard: false + * Route: /modals/account/tos + * Route Name: {none} + * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args */ public function getModalAccountTos(Request $request, Response $response, $args) { @@ -271,12 +288,13 @@ public function getModalAccountTos(Request $request, Response $response, $args) /** * Generate a random captcha, store it to the session, and return the captcha image. * + * AuthGuard: false + * Route: /account/captcha + * Route Name: {none} * Request type: GET - * - * @param Request $request - * @param Response $response - * @param array $args - * @return void + * @param Request $request + * @param Response $response + * @param array $args */ public function imageCaptcha(Request $request, Response $response, $args) { @@ -299,19 +317,21 @@ public function imageCaptcha(Request $request, Response $response, $args) * 5. The user account is enabled and verified. * 6. The user entered a valid username/email and password. * This route, by definition, is "public access". - * Request type: POST * - * @param Request $request - * @param Response $response - * @param array $args - * @return void + * AuthGuard: false + * Route: /account/login + * Route Name: {none} + * Request type: POST + * @param Request $request + * @param Response $response + * @param array $args */ public function login(Request $request, Response $response, $args) { /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ $ms = $this->ci->alerts; - /** @var \UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; /** @var \UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */ @@ -320,7 +340,8 @@ public function login(Request $request, Response $response, $args) // Return 200 success if user is already logged in if ($authenticator->check()) { $ms->addMessageTranslated('warning', 'LOGIN.ALREADY_COMPLETE'); - return $response->withStatus(200); + + return $response->withJson([], 200); } /** @var \UserFrosting\Support\Repository\Repository $config */ @@ -340,7 +361,8 @@ public function login(Request $request, Response $response, $args) $validator = new ServerSideValidator($schema, $this->ci->translator); if (!$validator->validate($data)) { $ms->addValidationErrors($validator); - return $response->withStatus(400); + + return $response->withJson([], 400); } // Determine whether we are trying to log in with an email address or a username @@ -362,7 +384,8 @@ public function login(Request $request, Response $response, $args) $ms->addMessageTranslated('danger', 'RATE_LIMIT_EXCEEDED', [ 'delay' => $delay ]); - return $response->withStatus(429); + + return $response->withJson([], 429); } // Log throttleable event @@ -372,7 +395,8 @@ public function login(Request $request, Response $response, $args) // Note that we do this after logging throttle event, so this error counts towards throttling limit. if ($isEmail && !$config['site.login.enable_email']) { $ms->addMessageTranslated('danger', 'USER_OR_PASS_INVALID'); - return $response->withStatus(403); + + return $response->withJson([], 403); } // Try to authenticate the user. Authenticator will throw an exception on failure. @@ -392,12 +416,13 @@ public function login(Request $request, Response $response, $args) /** * Log the user out completely, including destroying any "remember me" token. * + * AuthGuard: true + * Route: /account/logout + * Route Name: {none} * Request type: GET - * - * @param Request $request - * @param Response $response - * @param array $args - * @return void + * @param Request $request + * @param Response $response + * @param array $args */ public function logout(Request $request, Response $response, $args) { @@ -406,6 +431,7 @@ public function logout(Request $request, Response $response, $args) // Return to home page $config = $this->ci->config; + return $response->withStatus(302)->withHeader('Location', $config['site.uri.public']); } @@ -414,12 +440,14 @@ public function logout(Request $request, Response $response, $args) * * This creates a simple form to allow users who forgot their password to have a time-limited password reset link emailed to them. * By default, this is a "public page" (does not require authentication). - * Request type: GET * - * @param Request $request - * @param Response $response - * @param array $args - * @return void + * AuthGuard: false + * Route: /account/forgot-password + * Route Name: forgot-password + * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args */ public function pageForgotPassword(Request $request, Response $response, $args) { @@ -436,26 +464,32 @@ public function pageForgotPassword(Request $request, Response $response, $args) ]); } - /** * Render the account registration page for UserFrosting. * * This allows new (non-authenticated) users to create a new account for themselves on your website (if enabled). * By definition, this is a "public page" (does not require authentication). - * Request type: GET * - * @param Request $request - * @param Response $response - * @param array $args - * @return void + * AuthGuard: false + * checkEnvironment + * Route: /account/register + * Route Name: register + * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If site registration is disabled */ public function pageRegister(Request $request, Response $response, $args) { /** @var \UserFrosting\Support\Repository\Repository $config */ $config = $this->ci->config; + /** @var \UserFrosting\I18n\LocalePathBuilder */ + $localePathBuilder = $this->ci->localePathBuilder; + if (!$config['site.registration.enabled']) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } /** @var \UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */ @@ -472,11 +506,18 @@ public function pageRegister(Request $request, Response $response, $args) $schema = new RequestSchema('schema://requests/register.yaml'); $validatorRegister = new JqueryValidationAdapter($schema, $this->ci->translator); + // Get locale information + $currentLocales = $localePathBuilder->getLocales(); + return $this->ci->view->render($response, 'pages/register.html.twig', [ 'page' => [ 'validators' => [ 'register' => $validatorRegister->rules('json', false) ] + ], + 'locales' => [ + 'available' => $config['site.locales.available'], + 'current' => end($currentLocales) ] ]); } @@ -486,12 +527,14 @@ public function pageRegister(Request $request, Response $response, $args) * * This is a form that allows users who lost their account verification link to have the link resent to their email address. * By default, this is a "public page" (does not require authentication). - * Request type: GET * - * @param Request $request - * @param Response $response - * @param array $args - * @return void + * AuthGuard: false + * Route: /account/resend-verification + * Route Name: {none} + * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args */ public function pageResendVerification(Request $request, Response $response, $args) { @@ -512,12 +555,14 @@ public function pageResendVerification(Request $request, Response $response, $ar * Reset password page. * * Renders the new password page for password reset requests. - * Request type: GET * - * @param Request $request - * @param Response $response - * @param array $args - * @return void + * AuthGuard: false + * Route: /account/set-password/confirm + * Route Name: {none} + * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args */ public function pageResetPassword(Request $request, Response $response, $args) { @@ -543,12 +588,14 @@ public function pageResetPassword(Request $request, Response $response, $args) * * Renders the page where new users who have had accounts created for them by another user, can set their password. * By default, this is a "public page" (does not require authentication). - * Request type: GET * - * @param Request $request - * @param Response $response - * @param array $args - * @return void + * AuthGuard: false + * Route: + * Route Name: {none} + * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args */ public function pageSetPassword(Request $request, Response $response, $args) { @@ -575,19 +622,22 @@ public function pageSetPassword(Request $request, Response $response, $args) * Provides a form for users to modify various properties of their account, such as name, email, locale, etc. * Any fields that the user does not have permission to modify will be automatically disabled. * This page requires authentication. - * Request type: GET * - * @param Request $request - * @param Response $response - * @param array $args - * @return void + * AuthGuard: true + * Route: /account/settings + * Route Name: {none} + * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws ForbiddenException If user is not authozied to access page */ public function pageSettings(Request $request, Response $response, $args) { /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ $authorizer = $this->ci->authorizer; - /** @var \UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -610,7 +660,7 @@ public function pageSettings(Request $request, Response $response, $args) return $this->ci->view->render($response, 'pages/account-settings.html.twig', [ 'locales' => $locales, - 'page' => [ + 'page' => [ 'validators' => [ 'account_settings' => $validatorAccountSettings->rules('json', false), 'profile_settings' => $validatorProfileSettings->rules('json', false) @@ -625,12 +675,15 @@ public function pageSettings(Request $request, Response $response, $args) * * This allows existing users to sign in. * By definition, this is a "public page" (does not require authentication). - * Request type: GET * - * @param Request $request - * @param Response $response - * @param array $args - * @return void + * AuthGuard: false + * checkEnvironment + * Route: /account/sign-in + * Route Name: login + * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args */ public function pageSignIn(Request $request, Response $response, $args) { @@ -667,12 +720,14 @@ public function pageSignIn(Request $request, Response $response, $args) * 1. They have the necessary permissions to update the posted field(s); * 2. The submitted data is valid. * This route requires authentication. - * Request type: POST * - * @param Request $request - * @param Response $response - * @param array $args - * @return void + * AuthGuard: true + * Route: /account/settings/profile + * Route Name: {none} + * Request type: POST + * @param Request $request + * @param Response $response + * @param array $args */ public function profile(Request $request, Response $response, $args) { @@ -682,14 +737,15 @@ public function profile(Request $request, Response $response, $args) /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ $authorizer = $this->ci->authorizer; - /** @var \UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access control for entire resource - check that the current user has permission to modify themselves // See recipe "per-field access control" for dynamic fine-grained control over which properties a user can modify. if (!$authorizer->checkAccess($currentUser, 'update_account_settings')) { $ms->addMessageTranslated('danger', 'ACCOUNT.ACCESS_DENIED'); - return $response->withStatus(403); + + return $response->withJson([], 403); } /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ @@ -725,7 +781,7 @@ public function profile(Request $request, Response $response, $args) } if ($error) { - return $response->withStatus(400); + return $response->withJson([], 400); } // Looks good, let's update with new values! @@ -740,7 +796,8 @@ public function profile(Request $request, Response $response, $args) ]); $ms->addMessageTranslated('success', 'PROFILE.UPDATED'); - return $response->withStatus(200); + + return $response->withJson([], 200); } /** @@ -757,13 +814,16 @@ public function profile(Request $request, Response $response, $args) * 7. The username and email are not already taken. * Automatically sends an activation link upon success, if account activation is enabled. * This route is "public access". - * Request type: POST * Returns the User Object for the user record that was created. * - * @param Request $request - * @param Response $response - * @param array $args - * @return void + * AuthGuard: false + * Route: /account/register + * Route Name: {none} + * Request type: POST + * @param Request $request + * @param Response $response + * @param array $args + * @throws SpammyRequestException */ public function register(Request $request, Response $response, $args) { @@ -787,13 +847,15 @@ public function register(Request $request, Response $response, $args) // Security measure: do not allow registering new users until the master account has been created. if (!$classMapper->staticMethod('user', 'find', $config['reserved_user_ids.master'])) { $ms->addMessageTranslated('danger', 'ACCOUNT.MASTER_NOT_EXISTS'); - return $response->withStatus(403); + + return $response->withJson([], 403); } // Check if registration is currently enabled if (!$config['site.registration.enabled']) { $ms->addMessageTranslated('danger', 'REGISTRATION.DISABLED'); - return $response->withStatus(403); + + return $response->withJson([], 403); } /** @var \UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */ @@ -802,7 +864,8 @@ public function register(Request $request, Response $response, $args) // Prevent the user from registering if he/she is already logged in if ($authenticator->check()) { $ms->addMessageTranslated('danger', 'REGISTRATION.LOGOUT'); - return $response->withStatus(403); + + return $response->withJson([], 403); } // Load the request schema @@ -827,18 +890,7 @@ public function register(Request $request, Response $response, $args) // Throttle requests if ($delay > 0) { - return $response->withStatus(429); - } - - // Check if username or email already exists - if ($classMapper->staticMethod('user', 'findUnique', $data['user_name'], 'user_name')) { - $ms->addMessageTranslated('danger', 'USERNAME.IN_USE', $data); - $error = true; - } - - if ($classMapper->staticMethod('user', 'findUnique', $data['email'], 'email')) { - $ms->addMessageTranslated('danger', 'EMAIL.IN_USE', $data); - $error = true; + return $response->withJson([], 429); } // Check captcha, if required @@ -851,89 +903,33 @@ public function register(Request $request, Response $response, $args) } if ($error) { - return $response->withStatus(400); + return $response->withJson([], 400); } // Remove captcha, password confirmation from object data after validation unset($data['captcha']); unset($data['passwordc']); - if ($config['site.registration.require_email_verification']) { - $data['flag_verified'] = false; - } else { - $data['flag_verified'] = true; - } - - // Load default group - $groupSlug = $config['site.registration.user_defaults.group']; - $defaultGroup = $classMapper->staticMethod('group', 'where', 'slug', $groupSlug)->first(); + // Now that we check the form, we can register the actual user + $registration = new Registration($this->ci, $data); - if (!$defaultGroup) { - $e = new HttpException("Account registration is not working because the default group '$groupSlug' does not exist."); - $e->addUserMessage('REGISTRATION.BROKEN'); - throw $e; + try { + $user = $registration->register(); + } catch (\Exception $e) { + $ms->addMessageTranslated('danger', $e->getMessage(), $data); + $error = true; } - // Set default group - $data['group_id'] = $defaultGroup->id; - - // Set default locale - $data['locale'] = $config['site.registration.user_defaults.locale']; - - // Hash password - $data['password'] = Password::hash($data['password']); - - // All checks passed! log events/activities, create user, and send verification email (if required) - // Begin transaction - DB will be rolled back if an exception occurs - Capsule::transaction( function() use ($classMapper, $data, $ms, $config, $throttler) { - // Log throttleable event - $throttler->logEvent('registration_attempt'); - - // Create the user - $user = $classMapper->createInstance('user', $data); - - // Store new user to database - $user->save(); - - // Create activity record - $this->ci->userActivityLogger->info("User {$user->user_name} registered for a new account.", [ - 'type' => 'sign_up', - 'user_id' => $user->id - ]); - - // Load default roles - $defaultRoleSlugs = $classMapper->staticMethod('role', 'getDefaultSlugs'); - $defaultRoles = $classMapper->staticMethod('role', 'whereIn', 'slug', $defaultRoleSlugs)->get(); - $defaultRoleIds = $defaultRoles->pluck('id')->all(); - - // Attach default roles - $user->roles()->attach($defaultRoleIds); - - // Verification email - if ($config['site.registration.require_email_verification']) { - // Try to generate a new verification request - $verification = $this->ci->repoVerification->create($user, $config['verification.timeout']); - - // Create and send verification email - $message = new TwigMailMessage($this->ci->view, 'mail/verify-account.html.twig'); - - $message->from($config['address_book.admin']) - ->addEmailRecipient(new EmailRecipient($user->email, $user->full_name)) - ->addParams([ - 'user' => $user, - 'token' => $verification->getToken() - ]); - - $this->ci->mailer->send($message); - - $ms->addMessageTranslated('success', 'REGISTRATION.COMPLETE_TYPE2', $user->toArray()); - } else { - // No verification required - $ms->addMessageTranslated('success', 'REGISTRATION.COMPLETE_TYPE1'); - } - }); + // Success message + if ($config['site.registration.require_email_verification']) { + // Verification required + $ms->addMessageTranslated('success', 'REGISTRATION.COMPLETE_TYPE2', $user->toArray()); + } else { + // No verification required + $ms->addMessageTranslated('success', 'REGISTRATION.COMPLETE_TYPE1'); + } - return $response->withStatus(200); + return $response->withJson([], 200); } /** @@ -945,12 +941,14 @@ public function register(Request $request, Response $response, $args) * 3. The user account is not already verified; * 4. The submitted data is valid. * This route is "public access". - * Request type: POST * - * @param Request $request - * @param Response $response - * @param array $args - * @return void + * AuthGuard: false + * Route: /account/resend-verification + * Route Name: {none} + * Request type: POST + * @param Request $request + * @param Response $response + * @param array $args */ public function resendVerification(Request $request, Response $response, $args) { @@ -977,7 +975,8 @@ public function resendVerification(Request $request, Response $response, $args) $validator = new ServerSideValidator($schema, $this->ci->translator); if (!$validator->validate($data)) { $ms->addValidationErrors($validator); - return $response->withStatus(400); + + return $response->withJson([], 400); } // Throttle requests @@ -992,12 +991,13 @@ public function resendVerification(Request $request, Response $response, $args) if ($delay > 0) { $ms->addMessageTranslated('danger', 'RATE_LIMIT_EXCEEDED', ['delay' => $delay]); - return $response->withStatus(429); + + return $response->withJson([], 429); } // All checks passed! log events/activities, create user, and send verification email (if required) // Begin transaction - DB will be rolled back if an exception occurs - Capsule::transaction( function() use ($classMapper, $data, $throttler, $throttleData, $config) { + Capsule::transaction(function () use ($classMapper, $data, $throttler, $throttleData, $config) { // Log throttleable event $throttler->logEvent('verification_request', $throttleData); @@ -1017,7 +1017,7 @@ public function resendVerification(Request $request, Response $response, $args) $message->from($config['address_book.admin']) ->addEmailRecipient(new EmailRecipient($user->email, $user->full_name)) ->addParams([ - 'user' => $user, + 'user' => $user, 'token' => $verification->getToken() ]); @@ -1026,7 +1026,8 @@ public function resendVerification(Request $request, Response $response, $args) }); $ms->addMessageTranslated('success', 'ACCOUNT.VERIFICATION.NEW_LINK_SENT', ['email' => $data['email']]); - return $response->withStatus(200); + + return $response->withJson([], 200); } /** @@ -1038,12 +1039,14 @@ public function resendVerification(Request $request, Response $response, $args) * 3. The token has not expired; * 4. The submitted data (new password) is valid. * This route is "public access". - * Request type: POST * - * @param Request $request - * @param Response $response - * @param array $args - * @return void + * AuthGuard: false + * Route: /account/set-password + * Route Name: {none} + * Request type: POST + * @param Request $request + * @param Response $response + * @param array $args */ public function setPassword(Request $request, Response $response, $args) { @@ -1070,7 +1073,8 @@ public function setPassword(Request $request, Response $response, $args) $validator = new ServerSideValidator($schema, $this->ci->translator); if (!$validator->validate($data)) { $ms->addValidationErrors($validator); - return $response->withStatus(400); + + return $response->withJson([], 400); } $forgotPasswordPage = $this->ci->router->pathFor('forgot-password'); @@ -1082,7 +1086,8 @@ public function setPassword(Request $request, Response $response, $args) if (!$passwordReset) { $ms->addMessageTranslated('danger', 'PASSWORD.FORGET.INVALID', ['url' => $forgotPasswordPage]); - return $response->withStatus(400); + + return $response->withJson([], 400); } $ms->addMessageTranslated('success', 'PASSWORD.UPDATED'); @@ -1100,7 +1105,8 @@ public function setPassword(Request $request, Response $response, $args) $authenticator->login($user); $ms->addMessageTranslated('success', 'WELCOME', $user->export()); - return $response->withStatus(200); + + return $response->withJson([], 200); } /** @@ -1111,12 +1117,14 @@ public function setPassword(Request $request, Response $response, $args) * 2. They have the necessary permissions to update the posted field(s); * 3. The submitted data is valid. * This route requires authentication. - * Request type: POST * - * @param Request $request - * @param Response $response - * @param array $args - * @return void + * AuthGuard: true + * Route: /account/settings + * Route Name: settings + * Request type: POST + * @param Request $request + * @param Response $response + * @param array $args */ public function settings(Request $request, Response $response, $args) { @@ -1126,14 +1134,15 @@ public function settings(Request $request, Response $response, $args) /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ $authorizer = $this->ci->authorizer; - /** @var \UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access control for entire resource - check that the current user has permission to modify themselves // See recipe "per-field access control" for dynamic fine-grained control over which properties a user can modify. if (!$authorizer->checkAccess($currentUser, 'update_account_settings')) { $ms->addMessageTranslated('danger', 'ACCOUNT.ACCESS_DENIED'); - return $response->withStatus(403); + + return $response->withJson([], 403); } /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ @@ -1178,7 +1187,7 @@ public function settings(Request $request, Response $response, $args) } if ($error) { - return $response->withStatus(400); + return $response->withJson([], 400); } // Hash new password, if specified @@ -1201,20 +1210,23 @@ public function settings(Request $request, Response $response, $args) ]); $ms->addMessageTranslated('success', 'ACCOUNT.SETTINGS.UPDATED'); - return $response->withStatus(200); + + return $response->withJson([], 200); } /** * Suggest an available username for a specified first/last name. * * This route is "public access". - * Request type: GET * @todo Can this route be abused for account enumeration? If so we should throttle it as well. * - * @param Request $request - * @param Response $response - * @param array $args - * @return void + * AuthGuard: false + * Route: /account/suggest-username + * Route Name: {none} + * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args */ public function suggestUsername(Request $request, Response $response, $args) { @@ -1240,12 +1252,14 @@ public function suggestUsername(Request $request, Response $response, $args) * 1. The token provided matches a user in the database; * 2. The user account is not already verified; * This route is "public access". - * Request type: GET * - * @param Request $request - * @param Response $response - * @param array $args - * @return void + * AuthGuard: false + * Route: /account/verify + * Route Name: {none} + * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args */ public function verify(Request $request, Response $response, $args) { @@ -1274,15 +1288,16 @@ public function verify(Request $request, Response $response, $args) $validator = new ServerSideValidator($schema, $this->ci->translator); if (!$validator->validate($data)) { $ms->addValidationErrors($validator); - // 400 code + redirect is perfectly fine, according to user Dilaz in #laravel - return $response->withRedirect($loginPage, 400); + + return $response->withRedirect($loginPage); } $verification = $this->ci->repoVerification->complete($data['token']); if (!$verification) { $ms->addMessageTranslated('danger', 'ACCOUNT.VERIFICATION.TOKEN_NOT_FOUND'); - return $response->withRedirect($loginPage, 400); + + return $response->withRedirect($loginPage); } $ms->addMessageTranslated('success', 'ACCOUNT.VERIFICATION.COMPLETE'); diff --git a/app/sprinkles/account/src/Controller/Exception/SpammyRequestException.php b/app/sprinkles/account/src/Controller/Exception/SpammyRequestException.php index 971336048..4bc90b894 100644 --- a/app/sprinkles/account/src/Controller/Exception/SpammyRequestException.php +++ b/app/sprinkles/account/src/Controller/Exception/SpammyRequestException.php @@ -3,8 +3,10 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Controller\Exception; use UserFrosting\Support\Exception\HttpException; @@ -16,5 +18,4 @@ */ class SpammyRequestException extends HttpException { - } diff --git a/app/sprinkles/account/src/Database/Migrations/v400/ActivitiesTable.php b/app/sprinkles/account/src/Database/Migrations/v400/ActivitiesTable.php index 4e55c7c22..aa40649c8 100644 --- a/app/sprinkles/account/src/Database/Migrations/v400/ActivitiesTable.php +++ b/app/sprinkles/account/src/Database/Migrations/v400/ActivitiesTable.php @@ -3,26 +3,26 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400; -use UserFrosting\System\Bakery\Migration; +use UserFrosting\Sprinkle\Core\Database\Migration; use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\Schema\Builder; /** * Sessions table migration * Version 4.0.0 * * See https://laravel.com/docs/5.4/migrations#tables - * @extends Migration * @author Alex Weissman (https://alexanderweissman.com) */ class ActivitiesTable extends Migration { /** - * {@inheritDoc} + * {@inheritdoc} */ public function up() { @@ -45,10 +45,10 @@ public function up() } /** - * {@inheritDoc} + * {@inheritdoc} */ public function down() { $this->schema->drop('activities'); } -} \ No newline at end of file +} diff --git a/app/sprinkles/account/src/Database/Migrations/v400/GroupsTable.php b/app/sprinkles/account/src/Database/Migrations/v400/GroupsTable.php index c74615f61..937aa67c8 100644 --- a/app/sprinkles/account/src/Database/Migrations/v400/GroupsTable.php +++ b/app/sprinkles/account/src/Database/Migrations/v400/GroupsTable.php @@ -3,14 +3,15 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400; use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\Schema\Builder; -use UserFrosting\Sprinkle\Account\Database\Models\Group; -use UserFrosting\System\Bakery\Migration; +use UserFrosting\Sprinkle\Core\Database\Migration; +use UserFrosting\Sprinkle\Core\Facades\Seeder; /** * Groups table migration @@ -18,18 +19,17 @@ * Version 4.0.0 * * See https://laravel.com/docs/5.4/migrations#tables - * @extends Migration * @author Alex Weissman (https://alexanderweissman.com) */ class GroupsTable extends Migration { /** - * {@inheritDoc} + * {@inheritdoc} */ public function up() { if (!$this->schema->hasTable('groups')) { - $this->schema->create('groups', function(Blueprint $table) { + $this->schema->create('groups', function (Blueprint $table) { $table->increments('id'); $table->string('slug'); $table->string('name'); @@ -45,35 +45,12 @@ public function up() }); // Add default groups - $groups = [ - 'terran' => new Group([ - 'slug' => 'terran', - 'name' => 'Terran', - 'description' => 'The terrans are a young species with psionic potential. The terrans of the Koprulu sector descend from the survivors of a disastrous 23rd century colonization mission from Earth.', - 'icon' => 'sc sc-terran' - ]), - 'zerg' => new Group([ - 'slug' => 'zerg', - 'name' => 'Zerg', - 'description' => 'Dedicated to the pursuit of genetic perfection, the zerg relentlessly hunt down and assimilate advanced species across the galaxy, incorporating useful genetic code into their own.', - 'icon' => 'sc sc-zerg' - ]), - 'protoss' => new Group([ - 'slug' => 'protoss', - 'name' => 'Protoss', - 'description' => 'The protoss, a.k.a. the Firstborn, are a sapient humanoid race native to Aiur. Their advanced technology complements and enhances their psionic mastery.', - 'icon' => 'sc sc-protoss' - ]) - ]; - - foreach ($groups as $slug => $group) { - $group->save(); - } + Seeder::execute('DefaultGroups'); } } /** - * {@inheritDoc} + * {@inheritdoc} */ public function down() { diff --git a/app/sprinkles/account/src/Database/Migrations/v400/PasswordResetsTable.php b/app/sprinkles/account/src/Database/Migrations/v400/PasswordResetsTable.php index e785cccb5..dcbc42b9f 100644 --- a/app/sprinkles/account/src/Database/Migrations/v400/PasswordResetsTable.php +++ b/app/sprinkles/account/src/Database/Migrations/v400/PasswordResetsTable.php @@ -3,13 +3,14 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400; use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\Schema\Builder; -use UserFrosting\System\Bakery\Migration; +use UserFrosting\Sprinkle\Core\Database\Migration; /** * password_resets table migration @@ -17,13 +18,12 @@ * Version 4.0.0 * * See https://laravel.com/docs/5.4/migrations#tables - * @extends Migration * @author Alex Weissman (https://alexanderweissman.com) */ -class passwordResetsTable extends Migration +class PasswordResetsTable extends Migration { /** - * {@inheritDoc} + * {@inheritdoc} */ public function up() { @@ -48,7 +48,7 @@ public function up() } /** - * {@inheritDoc} + * {@inheritdoc} */ public function down() { diff --git a/app/sprinkles/account/src/Database/Migrations/v400/PermissionRolesTable.php b/app/sprinkles/account/src/Database/Migrations/v400/PermissionRolesTable.php index 2c2990ca0..a3596ae71 100644 --- a/app/sprinkles/account/src/Database/Migrations/v400/PermissionRolesTable.php +++ b/app/sprinkles/account/src/Database/Migrations/v400/PermissionRolesTable.php @@ -3,13 +3,14 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400; use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\Schema\Builder; -use UserFrosting\System\Bakery\Migration; +use UserFrosting\Sprinkle\Core\Database\Migration; /** * Permission_roles table migration @@ -17,13 +18,12 @@ * Version 4.0.0 * * See https://laravel.com/docs/5.4/migrations#tables - * @extends Migration * @author Alex Weissman (https://alexanderweissman.com) */ class PermissionRolesTable extends Migration { /** - * {@inheritDoc} + * {@inheritdoc} */ public function up() { @@ -46,7 +46,7 @@ public function up() } /** - * {@inheritDoc} + * {@inheritdoc} */ public function down() { diff --git a/app/sprinkles/account/src/Database/Migrations/v400/PermissionsTable.php b/app/sprinkles/account/src/Database/Migrations/v400/PermissionsTable.php index 684b01abf..119ec1d6f 100644 --- a/app/sprinkles/account/src/Database/Migrations/v400/PermissionsTable.php +++ b/app/sprinkles/account/src/Database/Migrations/v400/PermissionsTable.php @@ -3,15 +3,16 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400; use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\Schema\Builder; use UserFrosting\Sprinkle\Account\Database\Models\Permission; -use UserFrosting\Sprinkle\Account\Database\Models\Role; -use UserFrosting\System\Bakery\Migration; +use UserFrosting\Sprinkle\Core\Database\Migration; +use UserFrosting\Sprinkle\Core\Facades\Seeder; /** * Permissions table migration @@ -20,26 +21,25 @@ * Version 4.0.0 * * See https://laravel.com/docs/5.4/migrations#tables - * @extends Migration * @author Alex Weissman (https://alexanderweissman.com) */ class PermissionsTable extends Migration { /** - * {@inheritDoc} + * {@inheritdoc} */ - public $dependencies = [ + public static $dependencies = [ '\UserFrosting\Sprinkle\Account\Database\Migrations\v400\RolesTable', '\UserFrosting\Sprinkle\Account\Database\Migrations\v400\PermissionRolesTable' ]; /** - * {@inheritDoc} + * {@inheritdoc} */ public function up() { if (!$this->schema->hasTable('permissions')) { - $this->schema->create('permissions', function(Blueprint $table) { + $this->schema->create('permissions', function (Blueprint $table) { $table->increments('id'); $table->string('slug')->comment('A code that references a specific action or URI that an assignee of this permission has access to.'); $table->string('name'); @@ -52,211 +52,18 @@ public function up() $table->charset = 'utf8'; }); } + + // Skip this if table is not empty + if (Permission::count() == 0) { + Seeder::execute('DefaultPermissions'); + } } /** - * {@inheritDoc} + * {@inheritdoc} */ public function down() { $this->schema->drop('permissions'); } - - /** - * {@inheritDoc} - */ - public function seed() - { - // Skip this if table is not empty - if (Permission::count() == 0) { - - $defaultRoleIds = [ - 'user' => Role::where('slug', 'user')->first()->id, - 'group-admin' => Role::where('slug', 'group-admin')->first()->id, - 'site-admin' => Role::where('slug', 'site-admin')->first()->id - ]; - - // Add default permissions - $permissions = [ - 'create_group' => new Permission([ - 'slug' => 'create_group', - 'name' => 'Create group', - 'conditions' => 'always()', - 'description' => 'Create a new group.' - ]), - 'create_user' => new Permission([ - 'slug' => 'create_user', - 'name' => 'Create user', - 'conditions' => 'always()', - 'description' => 'Create a new user in your own group and assign default roles.' - ]), - 'create_user_field' => new Permission([ - 'slug' => 'create_user_field', - 'name' => 'Set new user group', - 'conditions' => "subset(fields,['group'])", - 'description' => 'Set the group when creating a new user.' - ]), - 'delete_group' => new Permission([ - 'slug' => 'delete_group', - 'name' => 'Delete group', - 'conditions' => "always()", - 'description' => 'Delete a group.' - ]), - 'delete_user' => new Permission([ - 'slug' => 'delete_user', - 'name' => 'Delete user', - 'conditions' => "!has_role(user.id,{$defaultRoleIds['site-admin']}) && !is_master(user.id)", - 'description' => 'Delete users who are not Site Administrators.' - ]), - 'update_account_settings' => new Permission([ - 'slug' => 'update_account_settings', - 'name' => 'Edit user', - 'conditions' => 'always()', - 'description' => 'Edit your own account settings.' - ]), - 'update_group_field' => new Permission([ - 'slug' => 'update_group_field', - 'name' => 'Edit group', - 'conditions' => 'always()', - 'description' => 'Edit basic properties of any group.' - ]), - 'update_user_field' => new Permission([ - 'slug' => 'update_user_field', - 'name' => 'Edit user', - 'conditions' => "!has_role(user.id,{$defaultRoleIds['site-admin']}) && subset(fields,['name','email','locale','group','flag_enabled','flag_verified','password'])", - 'description' => 'Edit users who are not Site Administrators.' - ]), - 'update_user_field_group' => new Permission([ - 'slug' => 'update_user_field', - 'name' => 'Edit group user', - 'conditions' => "equals_num(self.group_id,user.group_id) && !is_master(user.id) && !has_role(user.id,{$defaultRoleIds['site-admin']}) && (!has_role(user.id,{$defaultRoleIds['group-admin']}) || equals_num(self.id,user.id)) && subset(fields,['name','email','locale','flag_enabled','flag_verified','password'])", - 'description' => 'Edit users in your own group who are not Site or Group Administrators, except yourself.' - ]), - 'uri_account_settings' => new Permission([ - 'slug' => 'uri_account_settings', - 'name' => 'Account settings page', - 'conditions' => 'always()', - 'description' => 'View the account settings page.' - ]), - 'uri_activities' => new Permission([ - 'slug' => 'uri_activities', - 'name' => 'Activity monitor', - 'conditions' => 'always()', - 'description' => 'View a list of all activities for all users.' - ]), - 'uri_dashboard' => new Permission([ - 'slug' => 'uri_dashboard', - 'name' => 'Admin dashboard', - 'conditions' => 'always()', - 'description' => 'View the administrative dashboard.' - ]), - 'uri_group' => new Permission([ - 'slug' => 'uri_group', - 'name' => 'View group', - 'conditions' => 'always()', - 'description' => 'View the group page of any group.' - ]), - 'uri_group_own' => new Permission([ - 'slug' => 'uri_group', - 'name' => 'View own group', - 'conditions' => 'equals_num(self.group_id,group.id)', - 'description' => 'View the group page of your own group.' - ]), - 'uri_groups' => new Permission([ - 'slug' => 'uri_groups', - 'name' => 'Group management page', - 'conditions' => 'always()', - 'description' => 'View a page containing a list of groups.' - ]), - 'uri_user' => new Permission([ - 'slug' => 'uri_user', - 'name' => 'View user', - 'conditions' => 'always()', - 'description' => 'View the user page of any user.' - ]), - 'uri_user_in_group' => new Permission([ - 'slug' => 'uri_user', - 'name' => 'View user', - 'conditions' => "equals_num(self.group_id,user.group_id) && !is_master(user.id) && !has_role(user.id,{$defaultRoleIds['site-admin']}) && (!has_role(user.id,{$defaultRoleIds['group-admin']}) || equals_num(self.id,user.id))", - 'description' => 'View the user page of any user in your group, except the master user and Site and Group Administrators (except yourself).' - ]), - 'uri_users' => new Permission([ - 'slug' => 'uri_users', - 'name' => 'User management page', - 'conditions' => 'always()', - 'description' => 'View a page containing a table of users.' - ]), - 'view_group_field' => new Permission([ - 'slug' => 'view_group_field', - 'name' => 'View group', - 'conditions' => "in(property,['name','icon','slug','description','users'])", - 'description' => 'View certain properties of any group.' - ]), - 'view_group_field_own' => new Permission([ - 'slug' => 'view_group_field', - 'name' => 'View group', - 'conditions' => "equals_num(self.group_id,group.id) && in(property,['name','icon','slug','description','users'])", - 'description' => 'View certain properties of your own group.' - ]), - 'view_user_field' => new Permission([ - 'slug' => 'view_user_field', - 'name' => 'View user', - 'conditions' => "in(property,['user_name','name','email','locale','theme','roles','group','activities'])", - 'description' => 'View certain properties of any user.' - ]), - 'view_user_field_group' => new Permission([ - 'slug' => 'view_user_field', - 'name' => 'View user', - 'conditions' => "equals_num(self.group_id,user.group_id) && !is_master(user.id) && !has_role(user.id,{$defaultRoleIds['site-admin']}) && (!has_role(user.id,{$defaultRoleIds['group-admin']}) || equals_num(self.id,user.id)) && in(property,['user_name','name','email','locale','roles','group','activities'])", - 'description' => 'View certain properties of any user in your own group, except the master user and Site and Group Administrators (except yourself).' - ]) - ]; - - foreach ($permissions as $slug => $permission) { - $permission->save(); - } - - // Add default mappings to permissions - $roleUser = Role::where('slug', 'user')->first(); - if ($roleUser) { - $roleUser->permissions()->sync([ - $permissions['update_account_settings']->id, - $permissions['uri_account_settings']->id, - $permissions['uri_dashboard']->id - ]); - } - - $roleSiteAdmin = Role::where('slug', 'site-admin')->first(); - if ($roleSiteAdmin) { - $roleSiteAdmin->permissions()->sync([ - $permissions['create_group']->id, - $permissions['create_user']->id, - $permissions['create_user_field']->id, - $permissions['delete_group']->id, - $permissions['delete_user']->id, - $permissions['update_user_field']->id, - $permissions['update_group_field']->id, - $permissions['uri_activities']->id, - $permissions['uri_group']->id, - $permissions['uri_groups']->id, - $permissions['uri_user']->id, - $permissions['uri_users']->id, - $permissions['view_group_field']->id, - $permissions['view_user_field']->id - ]); - } - - $roleGroupAdmin = Role::where('slug', 'group-admin')->first(); - if ($roleGroupAdmin) { - $roleGroupAdmin->permissions()->sync([ - $permissions['create_user']->id, - $permissions['update_user_field_group']->id, - $permissions['uri_group_own']->id, - $permissions['uri_user_in_group']->id, - $permissions['view_group_field_own']->id, - $permissions['view_user_field_group']->id - ]); - } - } - } } diff --git a/app/sprinkles/account/src/Database/Migrations/v400/PersistencesTable.php b/app/sprinkles/account/src/Database/Migrations/v400/PersistencesTable.php index b96e32733..2b1ffb861 100644 --- a/app/sprinkles/account/src/Database/Migrations/v400/PersistencesTable.php +++ b/app/sprinkles/account/src/Database/Migrations/v400/PersistencesTable.php @@ -3,13 +3,14 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400; use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\Schema\Builder; -use UserFrosting\System\Bakery\Migration; +use UserFrosting\Sprinkle\Core\Database\Migration; /** * Persistences table migration @@ -17,13 +18,12 @@ * Version 4.0.0 * * See https://laravel.com/docs/5.4/migrations#tables - * @extends Migration * @author Alex Weissman (https://alexanderweissman.com) */ class PersistencesTable extends Migration { /** - * {@inheritDoc} + * {@inheritdoc} */ public function up() { @@ -48,7 +48,7 @@ public function up() } /** - * {@inheritDoc} + * {@inheritdoc} */ public function down() { diff --git a/app/sprinkles/account/src/Database/Migrations/v400/RoleUsersTable.php b/app/sprinkles/account/src/Database/Migrations/v400/RoleUsersTable.php index 7f3648b0e..c6aa8cddb 100644 --- a/app/sprinkles/account/src/Database/Migrations/v400/RoleUsersTable.php +++ b/app/sprinkles/account/src/Database/Migrations/v400/RoleUsersTable.php @@ -3,13 +3,14 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400; use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\Schema\Builder; -use UserFrosting\System\Bakery\Migration; +use UserFrosting\Sprinkle\Core\Database\Migration; /** * Role_users table migration @@ -17,13 +18,12 @@ * Version 4.0.0 * * See https://laravel.com/docs/5.4/migrations#tables - * @extends Migration * @author Alex Weissman (https://alexanderweissman.com) */ class RoleUsersTable extends Migration { /** - * {@inheritDoc} + * {@inheritdoc} */ public function up() { @@ -46,7 +46,7 @@ public function up() } /** - * {@inheritDoc} + * {@inheritdoc} */ public function down() { diff --git a/app/sprinkles/account/src/Database/Migrations/v400/RolesTable.php b/app/sprinkles/account/src/Database/Migrations/v400/RolesTable.php index 9cef49415..0f65f937a 100644 --- a/app/sprinkles/account/src/Database/Migrations/v400/RolesTable.php +++ b/app/sprinkles/account/src/Database/Migrations/v400/RolesTable.php @@ -3,28 +3,28 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400; use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\Schema\Builder; -use UserFrosting\Sprinkle\Account\Database\Models\Role; -use UserFrosting\System\Bakery\Migration; +use UserFrosting\Sprinkle\Core\Database\Migration; /** * Roles table migration * Roles replace "groups" in UF 0.3.x. Users acquire permissions through roles. + * N.B.: Default roles will be added in `DefaultPermissions` seed * Version 4.0.0 * * See https://laravel.com/docs/5.4/migrations#tables - * @extends Migration * @author Alex Weissman (https://alexanderweissman.com) */ class RolesTable extends Migration { /** - * {@inheritDoc} + * {@inheritdoc} */ public function up() { @@ -42,34 +42,11 @@ public function up() $table->unique('slug'); $table->index('slug'); }); - - // Add default roles - $roles = [ - 'user' => new Role([ - 'slug' => 'user', - 'name' => 'User', - 'description' => 'This role provides basic user functionality.' - ]), - 'site-admin' => new Role([ - 'slug' => 'site-admin', - 'name' => 'Site Administrator', - 'description' => 'This role is meant for "site administrators", who can basically do anything except create, edit, or delete other administrators.' - ]), - 'group-admin' => new Role([ - 'slug' => 'group-admin', - 'name' => 'Group Administrator', - 'description' => 'This role is meant for "group administrators", who can basically do anything with users in their own group, except other administrators of that group.' - ]) - ]; - - foreach ($roles as $slug => $role) { - $role->save(); - } } } /** - * {@inheritDoc} + * {@inheritdoc} */ public function down() { diff --git a/app/sprinkles/account/src/Database/Migrations/v400/UsersTable.php b/app/sprinkles/account/src/Database/Migrations/v400/UsersTable.php index a65eeedad..958a3a8c8 100644 --- a/app/sprinkles/account/src/Database/Migrations/v400/UsersTable.php +++ b/app/sprinkles/account/src/Database/Migrations/v400/UsersTable.php @@ -3,13 +3,14 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400; use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\Schema\Builder; -use UserFrosting\System\Bakery\Migration; +use UserFrosting\Sprinkle\Core\Database\Migration; /** * Users table migration @@ -17,13 +18,12 @@ * Version 4.0.0 * * See https://laravel.com/docs/5.4/migrations#tables - * @extends Migration * @author Alex Weissman (https://alexanderweissman.com) */ class UsersTable extends Migration { /** - * {@inheritDoc} + * {@inheritdoc} */ public function up() { @@ -35,11 +35,11 @@ public function up() $table->string('first_name', 20); $table->string('last_name', 30); $table->string('locale', 10)->default('en_US')->comment('The language and locale to use for this user.'); - $table->string('theme', 100)->nullable()->comment("The user theme."); - $table->integer('group_id')->unsigned()->default(1)->comment("The id of the user group."); - $table->boolean('flag_verified')->default(1)->comment("Set to 1 if the user has verified their account via email, 0 otherwise."); - $table->boolean('flag_enabled')->default(1)->comment("Set to 1 if the user account is currently enabled, 0 otherwise. Disabled accounts cannot be logged in to, but they retain all of their data and settings."); - $table->integer('last_activity_id')->unsigned()->nullable()->comment("The id of the last activity performed by this user."); + $table->string('theme', 100)->nullable()->comment('The user theme.'); + $table->integer('group_id')->unsigned()->default(1)->comment('The id of the user group.'); + $table->boolean('flag_verified')->default(1)->comment('Set to 1 if the user has verified their account via email, 0 otherwise.'); + $table->boolean('flag_enabled')->default(1)->comment('Set to 1 if the user account is currently enabled, 0 otherwise. Disabled accounts cannot be logged in to, but they retain all of their data and settings.'); + $table->integer('last_activity_id')->unsigned()->nullable()->comment('The id of the last activity performed by this user.'); $table->string('password', 255); $table->softDeletes(); $table->timestamps(); @@ -60,7 +60,7 @@ public function up() } /** - * {@inheritDoc} + * {@inheritdoc} */ public function down() { diff --git a/app/sprinkles/account/src/Database/Migrations/v400/VerificationsTable.php b/app/sprinkles/account/src/Database/Migrations/v400/VerificationsTable.php index fa54da6ea..49d1825b4 100644 --- a/app/sprinkles/account/src/Database/Migrations/v400/VerificationsTable.php +++ b/app/sprinkles/account/src/Database/Migrations/v400/VerificationsTable.php @@ -3,13 +3,14 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400; use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\Schema\Builder; -use UserFrosting\System\Bakery\Migration; +use UserFrosting\Sprinkle\Core\Database\Migration; /** * Verifications table migration @@ -17,13 +18,12 @@ * Version 4.0.0 * * See https://laravel.com/docs/5.4/migrations#tables - * @extends Migration * @author Alex Weissman (https://alexanderweissman.com) */ class VerificationsTable extends Migration { /** - * {@inheritDoc} + * {@inheritdoc} */ public function up() { @@ -48,7 +48,7 @@ public function up() } /** - * {@inheritDoc} + * {@inheritdoc} */ public function down() { diff --git a/app/sprinkles/account/src/Database/Migrations/v420/AddingForeignKeys.php b/app/sprinkles/account/src/Database/Migrations/v420/AddingForeignKeys.php new file mode 100644 index 000000000..0ed908868 --- /dev/null +++ b/app/sprinkles/account/src/Database/Migrations/v420/AddingForeignKeys.php @@ -0,0 +1,83 @@ + [ + 'user_id' => ['id', 'users'] + ], + 'password_resets' => [ + 'user_id' => ['id', 'users'] + ], + 'permission_roles' => [ + 'permission_id' => ['id', 'permissions'], + 'role_id' => ['id', 'roles'] + ], + 'persistences' => [ + 'user_id' => ['id', 'users'] + ], + 'role_users' => [ + 'user_id' => ['id', 'users'], + 'role_id' => ['id', 'roles'] + ], + 'users' => [ + 'group_id' => ['id', 'groups'], + 'last_activity_id' => ['id', 'activities'] + ], + 'verifications' => [ + 'user_id' => ['id', 'users'], + ], + ]; + + /** + * {@inheritdoc} + */ + public function up() + { + foreach ($this->tables as $tableName => $keys) { + if ($this->schema->hasTable($tableName)) { + $this->schema->table($tableName, function (Blueprint $table) use ($keys) { + foreach ($keys as $key => $data) { + $table->foreign($key)->references($data[0])->on($data[1]); + } + }); + } + } + } + + /** + * {@inheritdoc} + */ + public function down() + { + foreach ($this->tables as $tableName => $keys) { + if ($this->schema->hasTable($tableName)) { + $this->schema->table($tableName, function (Blueprint $table) use ($keys) { + foreach ($keys as $key => $data) { + $table->dropForeign([$key]); + } + }); + } + } + } +} diff --git a/app/sprinkles/account/src/Database/Models/Activity.php b/app/sprinkles/account/src/Database/Models/Activity.php index d5be589ec..a2202b9b8 100644 --- a/app/sprinkles/account/src/Database/Models/Activity.php +++ b/app/sprinkles/account/src/Database/Models/Activity.php @@ -3,11 +3,14 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Database\Models; use Illuminate\Database\Capsule\Manager as Capsule; +use Illuminate\Database\Eloquent\Builder; use UserFrosting\Sprinkle\Core\Database\Models\Model; /** @@ -26,18 +29,20 @@ class Activity extends Model /** * @var string The name of the table for the current model. */ - protected $table = "activities"; + protected $table = 'activities'; protected $fillable = [ - "ip_address", - "user_id", - "type", - "occurred_at", - "description" + 'ip_address', + 'user_id', + 'type', + 'occurred_at', + 'description' ]; /** * Joins the activity's user, so we can do things like sort, search, paginate, etc. + * + * @param Builder $query */ public function scopeJoinUser($query) { @@ -51,7 +56,8 @@ public function scopeJoinUser($query) /** * Add clauses to select the most recent event of each type for each user, to the query. * - * @return \Illuminate\Database\Query\Builder + * @param Builder $query + * @return Builder */ public function scopeMostRecentEvents($query) { @@ -63,10 +69,11 @@ public function scopeMostRecentEvents($query) /** * Add clauses to select the most recent event of a given type for each user, to the query. * - * @param string $type The type of event, matching the `event_type` field in the user_event table. - * @return \Illuminate\Database\Query\Builder + * @param Builder $query + * @param string $type The type of event, matching the `event_type` field in the user_event table. + * @return Builder */ - public function scopeMostRecentEventsByType($query, $type) + public function scopeMostRecentEventsByType(Builder $query, $type) { return $query->select('user_id', Capsule::raw('MAX(occurred_at) as occurred_at')) ->where('type', $type) @@ -75,10 +82,12 @@ public function scopeMostRecentEventsByType($query, $type) /** * Get the user associated with this activity. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function user() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = static::$ci->classMapper; return $this->belongsTo($classMapper->getClassMapping('user'), 'user_id'); diff --git a/app/sprinkles/account/src/Database/Models/Events/DeleteUserCacheEvent.php b/app/sprinkles/account/src/Database/Models/Events/DeleteUserCacheEvent.php new file mode 100644 index 000000000..375f9a3b9 --- /dev/null +++ b/app/sprinkles/account/src/Database/Models/Events/DeleteUserCacheEvent.php @@ -0,0 +1,30 @@ +id); + } +} diff --git a/app/sprinkles/account/src/Database/Models/Group.php b/app/sprinkles/account/src/Database/Models/Group.php index f10e066c1..d5925fbad 100644 --- a/app/sprinkles/account/src/Database/Models/Group.php +++ b/app/sprinkles/account/src/Database/Models/Group.php @@ -3,11 +3,12 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Database\Models; -use Illuminate\Database\Capsule\Manager as Capsule; use UserFrosting\Sprinkle\Core\Database\Models\Model; /** @@ -15,7 +16,6 @@ * * Represents a group object as stored in the database. * - * @package UserFrosting * @author Alex Weissman * @see http://www.userfrosting.com/tutorials/lesson-3-data-model/ * @@ -29,13 +29,13 @@ class Group extends Model /** * @var string The name of the table for the current model. */ - protected $table = "groups"; + protected $table = 'groups'; protected $fillable = [ - "slug", - "name", - "description", - "icon" + 'slug', + 'name', + 'description', + 'icon' ]; /** @@ -61,7 +61,7 @@ public function delete() */ public function users() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = static::$ci->classMapper; return $this->hasMany($classMapper->getClassMapping('user'), 'group_id'); diff --git a/app/sprinkles/account/src/Database/Models/Interfaces/UserInterface.php b/app/sprinkles/account/src/Database/Models/Interfaces/UserInterface.php new file mode 100644 index 000000000..5c8e5f1b0 --- /dev/null +++ b/app/sprinkles/account/src/Database/Models/Interfaces/UserInterface.php @@ -0,0 +1,164 @@ +full_name` + * + * @return string + */ + public function getFullNameAttribute(); + + /** + * Retrieve the cached permissions dictionary for this user. + * + * @return array + */ + public function getCachedPermissions(); + + /** + * Retrieve the cached permissions dictionary for this user. + * + * @return $this + */ + public function reloadCachedPermissions(); + + /** + * Get the amount of time, in seconds, that has elapsed since the last activity of a certain time for this user. + * + * @param string $type The type of activity to search for. + * @return int + */ + public function getSecondsSinceLastActivity($type); + + /** + * Return this user's group. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function group(); + + /** + * Returns whether or not this user is the master user. + * + * @return bool + */ + public function isMaster(); + + /** + * Get the most recent activity for this user, based on the user's last_activity_id. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function lastActivity(); + + /** + * Find the most recent activity for this user of a particular type. + * + * @param string $type + * @return \Illuminate\Database\Eloquent\Builder + */ + public function lastActivityOfType($type = null); + + /** + * Get the most recent time for a specified activity type for this user. + * + * @param string $type + * @return string|null The last activity time, as a SQL formatted time (YYYY-MM-DD HH:MM:SS), or null if an activity of this type doesn't exist. + */ + public function lastActivityTime($type); + + /** + * Performs tasks to be done after this user has been successfully authenticated. + * + * By default, adds a new sign-in activity and updates any legacy hash. + * @param mixed[] $params Optional array of parameters used for this event handler. + * @todo Transition to Laravel Event dispatcher to handle this + */ + public function onLogin($params = []); + + /** + * Performs tasks to be done after this user has been logged out. + * + * By default, adds a new sign-out activity. + * @param mixed[] $params Optional array of parameters used for this event handler. + * @todo Transition to Laravel Event dispatcher to handle this + */ + public function onLogout($params = []); + + /** + * Get all password reset requests for this user. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function passwordResets(); + + /** + * Get all of the permissions this user has, via its roles. + * + * @return \UserFrosting\Sprinkle\Core\Database\Relations\BelongsToManyThrough + */ + public function permissions(); + + /** + * Get all roles to which this user belongs. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function roles(); + + /** + * Query scope to get all users who have a specific role. + * + * @param Builder $query + * @param int $roleId + * @return Builder + */ + public function scopeForRole($query, $roleId); + + /** + * Joins the user's most recent activity directly, so we can do things like sort, search, paginate, etc. + * + * @param Builder $query + * @return Builder + */ + public function scopeJoinLastActivity($query); +} diff --git a/app/sprinkles/account/src/Database/Models/PasswordReset.php b/app/sprinkles/account/src/Database/Models/PasswordReset.php index ac8a930d2..3f7c2b646 100644 --- a/app/sprinkles/account/src/Database/Models/PasswordReset.php +++ b/app/sprinkles/account/src/Database/Models/PasswordReset.php @@ -3,11 +3,12 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Database\Models; -use Illuminate\Database\Capsule\Manager as Capsule; use UserFrosting\Sprinkle\Core\Database\Models\Model; /** @@ -26,14 +27,14 @@ class PasswordReset extends Model /** * @var string The name of the table for the current model. */ - protected $table = "password_resets"; + protected $table = 'password_resets'; protected $fillable = [ - "user_id", - "hash", - "completed", - "expires_at", - "completed_at" + 'user_id', + 'hash', + 'completed', + 'expires_at', + 'completed_at' ]; /** @@ -42,7 +43,7 @@ class PasswordReset extends Model public $timestamps = true; /** - * Stores the raw (unhashed) token when created, so that it can be emailed out to the user. NOT persisted. + * @var string Stores the raw (unhashed) token when created, so that it can be emailed out to the user. NOT persisted. */ protected $token; @@ -55,20 +56,24 @@ public function getToken() } /** - * @param string $value + * @param string $value + * @return self */ public function setToken($value) { $this->token = $value; + return $this; } /** * Get the user associated with this reset request. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function user() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = static::$ci->classMapper; return $this->belongsTo($classMapper->getClassMapping('user'), 'user_id'); diff --git a/app/sprinkles/account/src/Database/Models/Permission.php b/app/sprinkles/account/src/Database/Models/Permission.php index 463af8d5a..ff372e616 100644 --- a/app/sprinkles/account/src/Database/Models/Permission.php +++ b/app/sprinkles/account/src/Database/Models/Permission.php @@ -3,11 +3,13 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Database\Models; -use Illuminate\Database\Capsule\Manager as Capsule; +use Illuminate\Database\Eloquent\Builder; use UserFrosting\Sprinkle\Core\Database\Models\Model; /** @@ -25,13 +27,13 @@ class Permission extends Model /** * @var string The name of the table for the current model. */ - protected $table = "permissions"; + protected $table = 'permissions'; protected $fillable = [ - "slug", - "name", - "conditions", - "description" + 'slug', + 'name', + 'conditions', + 'description' ]; /** @@ -41,7 +43,6 @@ class Permission extends Model /** * Delete this permission from the database, removing associations with roles. - * */ public function delete() { @@ -61,7 +62,7 @@ public function delete() */ public function roles() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = static::$ci->classMapper; return $this->belongsToMany($classMapper->getClassMapping('role'), 'permission_roles', 'permission_id', 'role_id')->withTimestamps(); @@ -70,9 +71,9 @@ public function roles() /** * Query scope to get all permissions assigned to a specific role. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param int $roleId - * @return \Illuminate\Database\Eloquent\Builder + * @param Builder $query + * @param int $roleId + * @return Builder */ public function scopeForRole($query, $roleId) { @@ -85,9 +86,9 @@ public function scopeForRole($query, $roleId) /** * Query scope to get all permissions NOT associated with a specific role. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param int $roleId - * @return \Illuminate\Database\Eloquent\Builder + * @param Builder $query + * @param int $roleId + * @return Builder */ public function scopeNotForRole($query, $roleId) { @@ -104,7 +105,7 @@ public function scopeNotForRole($query, $roleId) */ public function users() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = static::$ci->classMapper; return $this->belongsToManyThrough( diff --git a/app/sprinkles/account/src/Database/Models/Persistence.php b/app/sprinkles/account/src/Database/Models/Persistence.php new file mode 100644 index 000000000..8137aa511 --- /dev/null +++ b/app/sprinkles/account/src/Database/Models/Persistence.php @@ -0,0 +1,69 @@ +classMapper; + + return $this->hasOne( + $classMapper->getClassMapping('user') + ); + } + + /** + * Scope a query to only include not expired entries + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeNotExpired($query) + { + return $query->where('expires_at', '>', Carbon::now()); + } +} diff --git a/app/sprinkles/account/src/Database/Models/Role.php b/app/sprinkles/account/src/Database/Models/Role.php index ce9cb8cad..81e8b585a 100644 --- a/app/sprinkles/account/src/Database/Models/Role.php +++ b/app/sprinkles/account/src/Database/Models/Role.php @@ -3,11 +3,13 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Database\Models; -use Illuminate\Database\Capsule\Manager as Capsule; +use Illuminate\Database\Eloquent\Builder; use UserFrosting\Sprinkle\Core\Database\Models\Model; /** @@ -24,12 +26,12 @@ class Role extends Model /** * @var string The name of the table for the current model. */ - protected $table = "roles"; + protected $table = 'roles'; protected $fillable = [ - "slug", - "name", - "description" + 'slug', + 'name', + 'description' ]; /** @@ -39,7 +41,6 @@ class Role extends Model /** * Delete this role from the database, removing associations with permissions and users. - * */ public function delete() { @@ -60,7 +61,7 @@ public function delete() */ public static function getDefaultSlugs() { - /** @var UserFrosting\Config $config */ + /** @var \UserFrosting\Support\Repository\Repository $config */ $config = static::$ci->config; return array_map('trim', array_keys($config['site.registration.user_defaults.roles'], true)); @@ -71,7 +72,7 @@ public static function getDefaultSlugs() */ public function permissions() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = static::$ci->classMapper; return $this->belongsToMany($classMapper->getClassMapping('permission'), 'permission_roles', 'role_id', 'permission_id')->withTimestamps(); @@ -80,9 +81,9 @@ public function permissions() /** * Query scope to get all roles assigned to a specific user. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param int $userId - * @return \Illuminate\Database\Eloquent\Builder + * @param Builder $query + * @param int $userId + * @return Builder */ public function scopeForUser($query, $userId) { @@ -97,7 +98,7 @@ public function scopeForUser($query, $userId) */ public function users() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = static::$ci->classMapper; return $this->belongsToMany($classMapper->getClassMapping('user'), 'role_users', 'role_id', 'user_id'); diff --git a/app/sprinkles/account/src/Database/Models/User.php b/app/sprinkles/account/src/Database/Models/User.php index 235f2efd3..3f3981eda 100644 --- a/app/sprinkles/account/src/Database/Models/User.php +++ b/app/sprinkles/account/src/Database/Models/User.php @@ -3,13 +3,16 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Database\Models; use Carbon\Carbon; -use Illuminate\Database\Capsule\Manager as Capsule; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\SoftDeletes; +use UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface; use UserFrosting\Sprinkle\Account\Facades\Password; use UserFrosting\Sprinkle\Core\Database\Models\Model; use UserFrosting\Sprinkle\Core\Facades\Debug; @@ -36,7 +39,7 @@ * @property string password * @property timestamp deleted_at */ -class User extends Model +class User extends Model implements UserInterface { use SoftDeletes; @@ -90,6 +93,15 @@ class User extends Model 'full_name' ]; + /** + * Events used to handle the user object cache on update and deletion + * @var array + */ + protected $events = [ + 'saved' => Events\DeleteUserCacheEvent::class, + 'deleted' => Events\DeleteUserCacheEvent::class + ]; + /** * Cached dictionary of permissions for the user. * @@ -110,8 +122,9 @@ class User extends Model * We add relations here so that Twig will be able to find them. * See http://stackoverflow.com/questions/29514081/cannot-access-eloquent-attributes-on-twig/35908957#35908957 * Every property in __get must also be implemented here for Twig to recognize it. - * @param string $name the name of the property to check. - * @return bool true if the property is defined, false otherwise. + * + * @param string $name the name of the property to check. + * @return bool true if the property is defined, false otherwise. */ public function __isset($name) { @@ -129,9 +142,9 @@ public function __isset($name) /** * Get a property for this object. * - * @param string $name the name of the property to retrieve. - * @throws Exception the property does not exist for this object. - * @return string the associated property. + * @param string $name the name of the property to retrieve. + * @throws \Exception the property does not exist for this object. + * @return string the associated property. */ public function __get($name) { @@ -139,7 +152,8 @@ public function __get($name) return $this->lastActivityTime('sign_in'); } elseif ($name == 'avatar') { // Use Gravatar as the user avatar - $hash = md5(strtolower(trim( $this->email))); + $hash = md5(strtolower(trim($this->email))); + return 'https://www.gravatar.com/avatar/' . $hash . '?d=mm'; } else { return parent::__get($name); @@ -153,7 +167,7 @@ public function __get($name) */ public function activities() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = static::$ci->classMapper; return $this->hasMany($classMapper->getClassMapping('activity'), 'user_id'); @@ -162,12 +176,12 @@ public function activities() /** * Delete this user from the database, along with any linked roles and activities. * - * @param bool $hardDelete Set to true to completely remove the user and all associated objects. + * @param bool $hardDelete Set to true to completely remove the user and all associated objects. * @return bool true if the deletion was successful, false otherwise. */ public function delete($hardDelete = false) { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = static::$ci->classMapper; if ($hardDelete) { @@ -197,9 +211,9 @@ public function delete($hardDelete = false) * Determines whether a user exists, including checking soft-deleted records * * @deprecated since 4.1.7 This method conflicts with and overrides the Builder::exists() method. Use Model::findUnique instead. - * @param mixed $value - * @param string $identifier - * @param bool $checkDeleted set to true to include soft-deleted records + * @param mixed $value + * @param string $identifier + * @param bool $checkDeleted set to true to include soft-deleted records * @return User|null */ public static function exists($value, $identifier = 'user_name', $checkDeleted = true) @@ -256,7 +270,7 @@ public function reloadCachedPermissions() /** * Get the amount of time, in seconds, that has elapsed since the last activity of a certain time for this user. * - * @param string $type The type of activity to search for. + * @param string $type The type of activity to search for. * @return int */ public function getSecondsSinceLastActivity($type) @@ -275,7 +289,7 @@ public function getSecondsSinceLastActivity($type) */ public function group() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = static::$ci->classMapper; return $this->belongsTo($classMapper->getClassMapping('group'), 'group_id'); @@ -291,7 +305,7 @@ public function isMaster() $masterId = static::$ci->config['reserved_user_ids.master']; // Need to use loose comparison for now, because some DBs return `id` as a string - return ($this->id == $masterId); + return $this->id == $masterId; } /** @@ -301,7 +315,7 @@ public function isMaster() */ public function lastActivity() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = static::$ci->classMapper; return $this->belongsTo($classMapper->getClassMapping('activity'), 'last_activity_id'); @@ -310,12 +324,12 @@ public function lastActivity() /** * Find the most recent activity for this user of a particular type. * - * @param string $type + * @param string $type * @return \Illuminate\Database\Eloquent\Builder */ public function lastActivityOfType($type = null) { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = static::$ci->classMapper; $query = $this->hasOne($classMapper->getClassMapping('activity'), 'user_id'); @@ -330,7 +344,7 @@ public function lastActivityOfType($type = null) /** * Get the most recent time for a specified activity type for this user. * - * @param string $type + * @param string $type * @return string|null The last activity time, as a SQL formatted time (YYYY-MM-DD HH:MM:SS), or null if an activity of this type doesn't exist. */ public function lastActivityTime($type) @@ -338,6 +352,7 @@ public function lastActivityTime($type) $result = $this->activities() ->where('type', $type) ->max('occurred_at'); + return $result ? $result : null; } @@ -360,15 +375,15 @@ public function onLogin($params = []) if ($passwordType != 'modern') { if (!isset($params['password'])) { - Debug::debug('Notice: Unhashed password must be supplied to update to modern password hashing.'); + Debug::notice('Notice: Unhashed password must be supplied to update to modern password hashing.'); } else { // Hash the user's password and update $passwordHash = Password::hash($params['password']); if ($passwordHash === null) { - Debug::debug('Notice: outdated password hash could not be updated because the new hashing algorithm is not supported. Are you running PHP >= 5.3.7?'); + Debug::notice('Notice: outdated password hash could not be updated because the new hashing algorithm is not supported.'); } else { $this->password = $passwordHash; - Debug::debug('Notice: outdated password hash has been automatically updated to modern hashing.'); + Debug::notice('Notice: outdated password hash has been automatically updated to modern hashing.'); } } } @@ -402,7 +417,7 @@ public function onLogout($params = []) */ public function passwordResets() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = static::$ci->classMapper; return $this->hasMany($classMapper->getClassMapping('password_reset'), 'user_id'); @@ -415,7 +430,7 @@ public function passwordResets() */ public function permissions() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = static::$ci->classMapper; return $this->belongsToManyThrough( @@ -437,7 +452,7 @@ public function permissions() */ public function roles() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = static::$ci->classMapper; return $this->belongsToMany($classMapper->getClassMapping('role'), 'role_users', 'user_id', 'role_id')->withTimestamps(); @@ -446,9 +461,9 @@ public function roles() /** * Query scope to get all users who have a specific role. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param int $roleId - * @return \Illuminate\Database\Eloquent\Builder + * @param Builder $query + * @param int $roleId + * @return Builder */ public function scopeForRole($query, $roleId) { @@ -461,8 +476,8 @@ public function scopeForRole($query, $roleId) /** * Joins the user's most recent activity directly, so we can do things like sort, search, paginate, etc. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @return \Illuminate\Database\Eloquent\Builder + * @param Builder $query + * @return Builder */ public function scopeJoinLastActivity($query) { diff --git a/app/sprinkles/account/src/Database/Models/Verification.php b/app/sprinkles/account/src/Database/Models/Verification.php index cd5166dae..441890d37 100644 --- a/app/sprinkles/account/src/Database/Models/Verification.php +++ b/app/sprinkles/account/src/Database/Models/Verification.php @@ -3,11 +3,12 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Database\Models; -use Illuminate\Database\Capsule\Manager as Capsule; use UserFrosting\Sprinkle\Core\Database\Models\Model; /** @@ -26,14 +27,14 @@ class Verification extends Model /** * @var string The name of the table for the current model. */ - protected $table = "verifications"; + protected $table = 'verifications'; protected $fillable = [ - "user_id", - "hash", - "completed", - "expires_at", - "completed_at" + 'user_id', + 'hash', + 'completed', + 'expires_at', + 'completed_at' ]; /** @@ -42,27 +43,37 @@ class Verification extends Model public $timestamps = true; /** - * Stores the raw (unhashed) token when created, so that it can be emailed out to the user. NOT persisted. + * @var string Stores the raw (unhashed) token when created, so that it can be emailed out to the user. NOT persisted. */ protected $token; + /** + * @return string + */ public function getToken() { return $this->token; } + /** + * @param string $value + * @return self + */ public function setToken($value) { $this->token = $value; + return $this; } /** * Get the user associated with this verification request. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function user() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = static::$ci->classMapper; return $this->belongsTo($classMapper->getClassMapping('user'), 'user_id'); diff --git a/app/sprinkles/account/src/Database/Seeds/DefaultGroups.php b/app/sprinkles/account/src/Database/Seeds/DefaultGroups.php new file mode 100644 index 000000000..3888caa67 --- /dev/null +++ b/app/sprinkles/account/src/Database/Seeds/DefaultGroups.php @@ -0,0 +1,61 @@ +getGroups(); + + foreach ($groups as $group) { + // Don't save if already exist + if (Group::where('slug', $group->slug)->first() == null) { + $group->save(); + } + } + } + + /** + * @return array Groups to seed + */ + protected function getGroups() + { + return [ + new Group([ + 'slug' => 'terran', + 'name' => 'Terran', + 'description' => 'The terrans are a young species with psionic potential. The terrans of the Koprulu sector descend from the survivors of a disastrous 23rd century colonization mission from Earth.', + 'icon' => 'sc sc-terran' + ]), + new Group([ + 'slug' => 'zerg', + 'name' => 'Zerg', + 'description' => 'Dedicated to the pursuit of genetic perfection, the zerg relentlessly hunt down and assimilate advanced species across the galaxy, incorporating useful genetic code into their own.', + 'icon' => 'sc sc-zerg' + ]), + new Group([ + 'slug' => 'protoss', + 'name' => 'Protoss', + 'description' => 'The protoss, a.k.a. the Firstborn, are a sapient humanoid race native to Aiur. Their advanced technology complements and enhances their psionic mastery.', + 'icon' => 'sc sc-protoss' + ]) + ]; + } +} diff --git a/app/sprinkles/account/src/Database/Seeds/DefaultPermissions.php b/app/sprinkles/account/src/Database/Seeds/DefaultPermissions.php new file mode 100644 index 000000000..c29eb55dd --- /dev/null +++ b/app/sprinkles/account/src/Database/Seeds/DefaultPermissions.php @@ -0,0 +1,253 @@ +getPermissions(); + $this->savePermissions($permissions); + + // Add default mappings to permissions + $this->syncPermissionsRole($permissions); + } + + /** + * @return array Permissions to seed + */ + protected function getPermissions() + { + $defaultRoleIds = [ + 'user' => Role::where('slug', 'user')->first()->id, + 'group-admin' => Role::where('slug', 'group-admin')->first()->id, + 'site-admin' => Role::where('slug', 'site-admin')->first()->id + ]; + + return [ + 'create_group' => new Permission([ + 'slug' => 'create_group', + 'name' => 'Create group', + 'conditions' => 'always()', + 'description' => 'Create a new group.' + ]), + 'create_user' => new Permission([ + 'slug' => 'create_user', + 'name' => 'Create user', + 'conditions' => 'always()', + 'description' => 'Create a new user in your own group and assign default roles.' + ]), + 'create_user_field' => new Permission([ + 'slug' => 'create_user_field', + 'name' => 'Set new user group', + 'conditions' => "subset(fields,['group'])", + 'description' => 'Set the group when creating a new user.' + ]), + 'delete_group' => new Permission([ + 'slug' => 'delete_group', + 'name' => 'Delete group', + 'conditions' => 'always()', + 'description' => 'Delete a group.' + ]), + 'delete_user' => new Permission([ + 'slug' => 'delete_user', + 'name' => 'Delete user', + 'conditions' => "!has_role(user.id,{$defaultRoleIds['site-admin']}) && !is_master(user.id)", + 'description' => 'Delete users who are not Site Administrators.' + ]), + 'update_account_settings' => new Permission([ + 'slug' => 'update_account_settings', + 'name' => 'Edit user', + 'conditions' => 'always()', + 'description' => 'Edit your own account settings.' + ]), + 'update_group_field' => new Permission([ + 'slug' => 'update_group_field', + 'name' => 'Edit group', + 'conditions' => 'always()', + 'description' => 'Edit basic properties of any group.' + ]), + 'update_user_field' => new Permission([ + 'slug' => 'update_user_field', + 'name' => 'Edit user', + 'conditions' => "!has_role(user.id,{$defaultRoleIds['site-admin']}) && subset(fields,['name','email','locale','group','flag_enabled','flag_verified','password'])", + 'description' => 'Edit users who are not Site Administrators.' + ]), + 'update_user_field_group' => new Permission([ + 'slug' => 'update_user_field', + 'name' => 'Edit group user', + 'conditions' => "equals_num(self.group_id,user.group_id) && !is_master(user.id) && !has_role(user.id,{$defaultRoleIds['site-admin']}) && (!has_role(user.id,{$defaultRoleIds['group-admin']}) || equals_num(self.id,user.id)) && subset(fields,['name','email','locale','flag_enabled','flag_verified','password'])", + 'description' => 'Edit users in your own group who are not Site or Group Administrators, except yourself.' + ]), + 'uri_account_settings' => new Permission([ + 'slug' => 'uri_account_settings', + 'name' => 'Account settings page', + 'conditions' => 'always()', + 'description' => 'View the account settings page.' + ]), + 'uri_activities' => new Permission([ + 'slug' => 'uri_activities', + 'name' => 'Activity monitor', + 'conditions' => 'always()', + 'description' => 'View a list of all activities for all users.' + ]), + 'uri_dashboard' => new Permission([ + 'slug' => 'uri_dashboard', + 'name' => 'Admin dashboard', + 'conditions' => 'always()', + 'description' => 'View the administrative dashboard.' + ]), + 'uri_group' => new Permission([ + 'slug' => 'uri_group', + 'name' => 'View group', + 'conditions' => 'always()', + 'description' => 'View the group page of any group.' + ]), + 'uri_group_own' => new Permission([ + 'slug' => 'uri_group', + 'name' => 'View own group', + 'conditions' => 'equals_num(self.group_id,group.id)', + 'description' => 'View the group page of your own group.' + ]), + 'uri_groups' => new Permission([ + 'slug' => 'uri_groups', + 'name' => 'Group management page', + 'conditions' => 'always()', + 'description' => 'View a page containing a list of groups.' + ]), + 'uri_user' => new Permission([ + 'slug' => 'uri_user', + 'name' => 'View user', + 'conditions' => 'always()', + 'description' => 'View the user page of any user.' + ]), + 'uri_user_in_group' => new Permission([ + 'slug' => 'uri_user', + 'name' => 'View user', + 'conditions' => "equals_num(self.group_id,user.group_id) && !is_master(user.id) && !has_role(user.id,{$defaultRoleIds['site-admin']}) && (!has_role(user.id,{$defaultRoleIds['group-admin']}) || equals_num(self.id,user.id))", + 'description' => 'View the user page of any user in your group, except the master user and Site and Group Administrators (except yourself).' + ]), + 'uri_users' => new Permission([ + 'slug' => 'uri_users', + 'name' => 'User management page', + 'conditions' => 'always()', + 'description' => 'View a page containing a table of users.' + ]), + 'view_group_field' => new Permission([ + 'slug' => 'view_group_field', + 'name' => 'View group', + 'conditions' => "in(property,['name','icon','slug','description','users'])", + 'description' => 'View certain properties of any group.' + ]), + 'view_group_field_own' => new Permission([ + 'slug' => 'view_group_field', + 'name' => 'View group', + 'conditions' => "equals_num(self.group_id,group.id) && in(property,['name','icon','slug','description','users'])", + 'description' => 'View certain properties of your own group.' + ]), + 'view_user_field' => new Permission([ + 'slug' => 'view_user_field', + 'name' => 'View user', + 'conditions' => "in(property,['user_name','name','email','locale','theme','roles','group','activities'])", + 'description' => 'View certain properties of any user.' + ]), + 'view_user_field_group' => new Permission([ + 'slug' => 'view_user_field', + 'name' => 'View user', + 'conditions' => "equals_num(self.group_id,user.group_id) && !is_master(user.id) && !has_role(user.id,{$defaultRoleIds['site-admin']}) && (!has_role(user.id,{$defaultRoleIds['group-admin']}) || equals_num(self.id,user.id)) && in(property,['user_name','name','email','locale','roles','group','activities'])", + 'description' => 'View certain properties of any user in your own group, except the master user and Site and Group Administrators (except yourself).' + ]) + ]; + } + + /** + * Save permissions + * @param array $permissions + */ + protected function savePermissions(array $permissions) + { + foreach ($permissions as $slug => $permission) { + + // Trying to find if the permission already exist + $existingPermission = Permission::where(['slug' => $permission->slug, 'conditions' => $permission->conditions])->first(); + + // Don't save if already exist, use existing permission reference + // otherwise to re-sync permissions and roles + if ($existingPermission == null) { + $permission->save(); + } else { + $permissions[$slug] = $existingPermission; + } + } + } + + /** + * Sync permissions with default roles + * @param array $permissions + */ + protected function syncPermissionsRole(array $permissions) + { + $roleUser = Role::where('slug', 'user')->first(); + if ($roleUser) { + $roleUser->permissions()->sync([ + $permissions['update_account_settings']->id, + $permissions['uri_account_settings']->id, + $permissions['uri_dashboard']->id + ]); + } + + $roleSiteAdmin = Role::where('slug', 'site-admin')->first(); + if ($roleSiteAdmin) { + $roleSiteAdmin->permissions()->sync([ + $permissions['create_group']->id, + $permissions['create_user']->id, + $permissions['create_user_field']->id, + $permissions['delete_group']->id, + $permissions['delete_user']->id, + $permissions['update_user_field']->id, + $permissions['update_group_field']->id, + $permissions['uri_activities']->id, + $permissions['uri_group']->id, + $permissions['uri_groups']->id, + $permissions['uri_user']->id, + $permissions['uri_users']->id, + $permissions['view_group_field']->id, + $permissions['view_user_field']->id + ]); + } + + $roleGroupAdmin = Role::where('slug', 'group-admin')->first(); + if ($roleGroupAdmin) { + $roleGroupAdmin->permissions()->sync([ + $permissions['create_user']->id, + $permissions['update_user_field_group']->id, + $permissions['uri_group_own']->id, + $permissions['uri_user_in_group']->id, + $permissions['view_group_field_own']->id, + $permissions['view_user_field_group']->id + ]); + } + } +} diff --git a/app/sprinkles/account/src/Database/Seeds/DefaultRoles.php b/app/sprinkles/account/src/Database/Seeds/DefaultRoles.php new file mode 100644 index 000000000..a3edead75 --- /dev/null +++ b/app/sprinkles/account/src/Database/Seeds/DefaultRoles.php @@ -0,0 +1,58 @@ +getRoles(); + + foreach ($roles as $role) { + // Don't save if already exist + if (Role::where('slug', $role->slug)->first() == null) { + $role->save(); + } + } + } + + /** + * @return array Roles to seed + */ + protected function getRoles() + { + return [ + new Role([ + 'slug' => 'user', + 'name' => 'User', + 'description' => 'This role provides basic user functionality.' + ]), + new Role([ + 'slug' => 'site-admin', + 'name' => 'Site Administrator', + 'description' => 'This role is meant for "site administrators", who can basically do anything except create, edit, or delete other administrators.' + ]), + new Role([ + 'slug' => 'group-admin', + 'name' => 'Group Administrator', + 'description' => 'This role is meant for "group administrators", who can basically do anything with users in their own group, except other administrators of that group.' + ]) + ]; + } +} diff --git a/app/sprinkles/account/src/Error/Handler/AuthCompromisedExceptionHandler.php b/app/sprinkles/account/src/Error/Handler/AuthCompromisedExceptionHandler.php index 330ca65a6..066ceebb8 100644 --- a/app/sprinkles/account/src/Error/Handler/AuthCompromisedExceptionHandler.php +++ b/app/sprinkles/account/src/Error/Handler/AuthCompromisedExceptionHandler.php @@ -3,8 +3,10 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Error\Handler; use UserFrosting\Sprinkle\Core\Error\Handler\HttpExceptionHandler; @@ -20,7 +22,7 @@ class AuthCompromisedExceptionHandler extends HttpExceptionHandler /** * Render a generic, user-friendly response without sensitive debugging information. * - * @return ResponseInterface + * @return \Psr\Http\Message\ResponseInterface */ public function renderGenericResponse() { diff --git a/app/sprinkles/account/src/Error/Handler/AuthExpiredExceptionHandler.php b/app/sprinkles/account/src/Error/Handler/AuthExpiredExceptionHandler.php index c651f77df..5f00fb1fb 100644 --- a/app/sprinkles/account/src/Error/Handler/AuthExpiredExceptionHandler.php +++ b/app/sprinkles/account/src/Error/Handler/AuthExpiredExceptionHandler.php @@ -3,10 +3,13 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Error\Handler; +use Psr\Http\Message\ResponseInterface; use UserFrosting\Sprinkle\Core\Error\Handler\HttpExceptionHandler; /** @@ -19,6 +22,8 @@ class AuthExpiredExceptionHandler extends HttpExceptionHandler { /** * Custom handling for requests that did not pass authentication. + * + * @return ResponseInterface */ public function handle() { @@ -33,11 +38,11 @@ public function handle() $path = $uri->getPath(); $query = $uri->getQuery(); $fragment = $uri->getFragment(); - + $path = $path . ($query ? '?' . $query : '') . ($fragment ? '#' . $fragment : ''); - + $loginPage = $this->ci->router->pathFor('login', [], [ 'redirect' => $path ]); diff --git a/app/sprinkles/account/src/Error/Handler/ForbiddenExceptionHandler.php b/app/sprinkles/account/src/Error/Handler/ForbiddenExceptionHandler.php index e22f02bf5..9a55efa9b 100644 --- a/app/sprinkles/account/src/Error/Handler/ForbiddenExceptionHandler.php +++ b/app/sprinkles/account/src/Error/Handler/ForbiddenExceptionHandler.php @@ -3,8 +3,10 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Error\Handler; use UserFrosting\Sprinkle\Core\Error\Handler\HttpExceptionHandler; @@ -25,7 +27,7 @@ class ForbiddenExceptionHandler extends HttpExceptionHandler protected function determineUserMessages() { return [ - new UserMessage("ACCOUNT.ACCESS_DENIED") + new UserMessage('ACCOUNT.ACCESS_DENIED') ]; } } diff --git a/app/sprinkles/account/src/Facades/Password.php b/app/sprinkles/account/src/Facades/Password.php index e5bf9674e..c8bf4f44d 100644 --- a/app/sprinkles/account/src/Facades/Password.php +++ b/app/sprinkles/account/src/Facades/Password.php @@ -3,8 +3,10 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Facades; use UserFrosting\System\Facade; diff --git a/app/sprinkles/account/src/Log/UserActivityDatabaseHandler.php b/app/sprinkles/account/src/Log/UserActivityDatabaseHandler.php index d7ceeef74..3833094b8 100644 --- a/app/sprinkles/account/src/Log/UserActivityDatabaseHandler.php +++ b/app/sprinkles/account/src/Log/UserActivityDatabaseHandler.php @@ -3,8 +3,10 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Log; use UserFrosting\Sprinkle\Core\Log\DatabaseHandler; @@ -17,7 +19,7 @@ class UserActivityDatabaseHandler extends DatabaseHandler { /** - * {@inheritDoc} + * {@inheritdoc} */ protected function write(array $record) { diff --git a/app/sprinkles/account/src/Log/UserActivityProcessor.php b/app/sprinkles/account/src/Log/UserActivityProcessor.php index 257527010..3d251ff2a 100644 --- a/app/sprinkles/account/src/Log/UserActivityProcessor.php +++ b/app/sprinkles/account/src/Log/UserActivityProcessor.php @@ -3,11 +3,11 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ -namespace UserFrosting\Sprinkle\Account\Log; -use Monolog\Logger; +namespace UserFrosting\Sprinkle\Account\Log; /** * Monolog processor for constructing the user activity message. @@ -29,10 +29,14 @@ public function __construct($userId) $this->userId = $userId; } + /** + * @param array $record + * @return array + */ public function __invoke(array $record) { $additionalFields = [ - 'ip_address' => $_SERVER['REMOTE_ADDR'], + 'ip_address' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null, 'user_id' => $this->userId, 'occurred_at' => $record['datetime'], 'description' => $record['message'] diff --git a/app/sprinkles/account/src/Rememberme/PDOStorage.php b/app/sprinkles/account/src/Rememberme/PDOStorage.php new file mode 100644 index 000000000..ce67e0499 --- /dev/null +++ b/app/sprinkles/account/src/Rememberme/PDOStorage.php @@ -0,0 +1,122 @@ +db = $db; + } + + /** + * @param mixed $credential + * @param string $token + * @param string $persistentToken + * @return int + */ + public function findTriplet($credential, $token, $persistentToken) + { + $result = Persistence::notExpired()->where([ + 'user_id' => $credential, + 'persistent_token' => sha1($persistentToken) + ])->first(); + + if (!$result) { + return self::TRIPLET_NOT_FOUND; + } elseif ($result->token === sha1($token)) { + return self::TRIPLET_FOUND; + } + + return self::TRIPLET_INVALID; + } + + /** + * @param mixed $credential + * @param string $token + * @param string $persistentToken + * @param int $expire + */ + public function storeTriplet($credential, $token, $persistentToken, $expire = 0) + { + $persistence = new Persistence([ + 'user_id' => $credential, + 'token' => sha1($token), + 'persistent_token' => sha1($persistentToken), + 'expires_at' => date('Y-m-d H:i:s', $expire) + ]); + $persistence->save(); + } + + /** + * @param mixed $credential + * @param string $persistentToken + */ + public function cleanTriplet($credential, $persistentToken) + { + Persistence::where([ + 'user_id' => $credential, + 'persistent_token' => sha1($persistentToken) + ])->delete(); + } + + /** + * Replace current token after successful authentication + * + * @param mixed $credential + * @param string $token + * @param string $persistentToken + * @param int $expire + */ + public function replaceTriplet($credential, $token, $persistentToken, $expire = 0) + { + try { + Capsule::transaction(function () use ($credential, $token, $persistentToken, $expire) { + $this->cleanTriplet($credential, $persistentToken); + $this->storeTriplet($credential, $token, $persistentToken, $expire); + }); + } catch (\PDOException $e) { + throw $e; + } + } + + /** + * @param mixed $credential + */ + public function cleanAllTriplets($credential) + { + Persistence::where('user_id', $credential)->delete(); + } + + /** + * Remove all expired triplets of all users. + * + * @param int $expiryTime Timestamp, all tokens before this time will be deleted + */ + public function cleanExpiredTokens($expiryTime) + { + Persistence::where('expires_at', '<', date('Y-m-d H:i:s', $expiryTime))->delete(); + } +} diff --git a/app/sprinkles/account/src/Repository/PasswordResetRepository.php b/app/sprinkles/account/src/Repository/PasswordResetRepository.php index 2dcffd3c2..389f8bb6c 100644 --- a/app/sprinkles/account/src/Repository/PasswordResetRepository.php +++ b/app/sprinkles/account/src/Repository/PasswordResetRepository.php @@ -3,8 +3,10 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Repository; use UserFrosting\Sprinkle\Account\Facades\Password; @@ -18,12 +20,12 @@ class PasswordResetRepository extends TokenRepository { /** - * {@inheritDoc} + * {@inheritdoc} */ protected $modelIdentifier = 'password_reset'; /** - * {@inheritDoc} + * {@inheritdoc} */ protected function updateUser($user, $args) { diff --git a/app/sprinkles/account/src/Repository/TokenRepository.php b/app/sprinkles/account/src/Repository/TokenRepository.php index a2994396b..8b1560660 100644 --- a/app/sprinkles/account/src/Repository/TokenRepository.php +++ b/app/sprinkles/account/src/Repository/TokenRepository.php @@ -3,12 +3,14 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Repository; use Carbon\Carbon; -use UserFrosting\Sprinkle\Account\Database\Models\User; +use UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface; use UserFrosting\Sprinkle\Core\Database\Models\Model; use UserFrosting\Sprinkle\Core\Util\ClassMapper; @@ -21,7 +23,6 @@ */ abstract class TokenRepository { - /** * @var ClassMapper */ @@ -41,7 +42,7 @@ abstract class TokenRepository * Create a new TokenRepository object. * * @param ClassMapper $classMapper Maps generic class identifiers to specific class names. - * @param string $algorithm The hashing algorithm to use when storing generated tokens. + * @param string $algorithm The hashing algorithm to use when storing generated tokens. */ public function __construct(ClassMapper $classMapper, $algorithm = 'sha512') { @@ -52,7 +53,7 @@ public function __construct(ClassMapper $classMapper, $algorithm = 'sha512') /** * Cancels a specified token by removing it from the database. * - * @param int $token The token to remove. + * @param int $token The token to remove. * @return Model|false */ public function cancel($token) @@ -78,8 +79,8 @@ public function cancel($token) /** * Completes a token-based process, invoking updateUser() in the child object to do the actual action. * - * @param int $token The token to complete. - * @param mixed[] $userParams An optional list of parameters to pass to updateUser(). + * @param int $token The token to complete. + * @param mixed[] $userParams An optional list of parameters to pass to updateUser(). * @return Model|false */ public function complete($token, $userParams = []) @@ -120,11 +121,11 @@ public function complete($token, $userParams = []) /** * Create a new token for a specified user. * - * @param User $user The user object to associate with this token. - * @param int $timeout The time, in seconds, after which this token should expire. - * @return Model The model (PasswordReset, Verification, etc) object that stores the token. + * @param UserInterface $user The user object to associate with this token. + * @param int $timeout The time, in seconds, after which this token should expire. + * @return Model The model (PasswordReset, Verification, etc) object that stores the token. */ - public function create(User $user, $timeout) + public function create(UserInterface $user, $timeout) { // Remove any previous tokens for this user $this->removeExisting($user); @@ -156,11 +157,11 @@ public function create(User $user, $timeout) /** * Determine if a specified user has an incomplete and unexpired token. * - * @param User $user The user object to look up. - * @param int $token Optionally, try to match a specific token. + * @param UserInterface $user The user object to look up. + * @param int $token Optionally, try to match a specific token. * @return Model|false */ - public function exists(User $user, $token = null) + public function exists(UserInterface $user, $token = null) { $model = $this->classMapper ->staticMethod($this->modelIdentifier, 'where', 'user_id', $user->id) @@ -179,10 +180,10 @@ public function exists(User $user, $token = null) /** * Delete all existing tokens from the database for a particular user. * - * @param User $user + * @param UserInterface $user * @return int */ - protected function removeExisting(User $user) + protected function removeExisting(UserInterface $user) { return $this->classMapper ->staticMethod($this->modelIdentifier, 'where', 'user_id', $user->id) @@ -206,16 +207,17 @@ public function removeExpired() * Generate a new random token for this user. * * This generates a token to use for verifying a new account, resetting a lost password, etc. - * @param string $gen specify an existing token that, if we happen to generate the same value, we should regenerate on. + * @param string $gen specify an existing token that, if we happen to generate the same value, we should regenerate on. * @return string */ protected function generateRandomToken($gen = null) { do { $gen = md5(uniqid(mt_rand(), false)); - } while($this->classMapper + } while ($this->classMapper ->staticMethod($this->modelIdentifier, 'where', 'hash', hash($this->algorithm, $gen)) ->first()); + return $gen; } @@ -223,8 +225,9 @@ protected function generateRandomToken($gen = null) * Modify the user during the token completion process. * * This method is called during complete(), and is a way for concrete implementations to modify the user. - * @param User $user the user object to modify. - * @return mixed[] $args the list of parameters that were supplied to the call to `complete()` + * @param UserInterface $user the user object to modify. + * @param mixed[] $args + * @return mixed[] $args the list of parameters that were supplied to the call to `complete()` */ - abstract protected function updateUser($user, $args); + abstract protected function updateUser(UserInterface $user, $args); } diff --git a/app/sprinkles/account/src/Repository/VerificationRepository.php b/app/sprinkles/account/src/Repository/VerificationRepository.php index b0cf0486f..d421185f2 100644 --- a/app/sprinkles/account/src/Repository/VerificationRepository.php +++ b/app/sprinkles/account/src/Repository/VerificationRepository.php @@ -3,8 +3,10 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Repository; /** @@ -16,12 +18,12 @@ class VerificationRepository extends TokenRepository { /** - * {@inheritDoc} + * {@inheritdoc} */ protected $modelIdentifier = 'verification'; /** - * {@inheritDoc} + * {@inheritdoc} */ protected function updateUser($user, $args) { diff --git a/app/sprinkles/account/src/ServicesProvider/ServicesProvider.php b/app/sprinkles/account/src/ServicesProvider/ServicesProvider.php index 4c3ab1527..2f1692229 100644 --- a/app/sprinkles/account/src/ServicesProvider/ServicesProvider.php +++ b/app/sprinkles/account/src/ServicesProvider/ServicesProvider.php @@ -3,14 +3,14 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\ServicesProvider; -use Birke\Rememberme\Authenticator as RememberMe; use Illuminate\Database\Capsule\Manager as Capsule; -use Monolog\Formatter\LineFormatter; -use Monolog\Handler\ErrorLogHandler; +use Interop\Container\ContainerInterface; use Monolog\Handler\StreamHandler; use Monolog\Logger; use Psr\Http\Message\ResponseInterface as Response; @@ -25,7 +25,6 @@ use UserFrosting\Sprinkle\Account\Repository\PasswordResetRepository; use UserFrosting\Sprinkle\Account\Repository\VerificationRepository; use UserFrosting\Sprinkle\Account\Twig\AccountExtension; -use UserFrosting\Sprinkle\Core\Facades\Debug; use UserFrosting\Sprinkle\Core\Log\MixedFormatter; /** @@ -38,28 +37,19 @@ class ServicesProvider /** * Register UserFrosting's account services. * - * @param Container $container A DI container implementing ArrayAccess and container-interop. + * @param ContainerInterface $container A DI container implementing ArrayAccess and container-interop. */ - public function register($container) + public function register(ContainerInterface $container) { /** * Extend the asset manager service to see assets for the current user's theme. + * + * @return \UserFrosting\Assets\Assets */ $container->extend('assets', function ($assets, $c) { - // Register paths for user theme, if a user is logged in - // We catch any authorization-related exceptions, so that error pages can be rendered. - try { - /** @var UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */ - $authenticator = $c->authenticator; - $currentUser = $c->currentUser; - } catch (\Exception $e) { - return $assets; - } - - if ($authenticator->check()) { - $c->sprinkleManager->addResource('assets', $currentUser->theme); - } + // Force load the current user to add it's theme assets ressources + $currentUser = $c->currentUser; return $assets; }); @@ -68,6 +58,8 @@ public function register($container) * Extend the 'classMapper' service to register model classes. * * Mappings added: User, Group, Role, Permission, Activity, PasswordReset, Verification + * + * @return \UserFrosting\Sprinkle\Core\Util\ClassMapper */ $container->extend('classMapper', function ($classMapper, $c) { $classMapper->setClassMapping('user', 'UserFrosting\Sprinkle\Account\Database\Models\User'); @@ -77,6 +69,7 @@ public function register($container) $classMapper->setClassMapping('activity', 'UserFrosting\Sprinkle\Account\Database\Models\Activity'); $classMapper->setClassMapping('password_reset', 'UserFrosting\Sprinkle\Account\Database\Models\PasswordReset'); $classMapper->setClassMapping('verification', 'UserFrosting\Sprinkle\Account\Database\Models\Verification'); + return $classMapper; }); @@ -84,6 +77,8 @@ public function register($container) * Extends the 'errorHandler' service with custom exception handlers. * * Custom handlers added: ForbiddenExceptionHandler + * + * @return \UserFrosting\Sprinkle\Core\Error\ExceptionHandlerManager */ $container->extend('errorHandler', function ($handler, $c) { // Register the ForbiddenExceptionHandler. @@ -92,29 +87,28 @@ public function register($container) $handler->registerHandler('\UserFrosting\Sprinkle\Account\Authenticate\Exception\AuthExpiredException', '\UserFrosting\Sprinkle\Account\Error\Handler\AuthExpiredExceptionHandler'); // Register the AuthCompromisedExceptionHandler. $handler->registerHandler('\UserFrosting\Sprinkle\Account\Authenticate\Exception\AuthCompromisedException', '\UserFrosting\Sprinkle\Account\Error\Handler\AuthCompromisedExceptionHandler'); + return $handler; }); /** * Extends the 'localePathBuilder' service, adding any locale files from the user theme. * + * @return \UserFrosting\I18n\LocalePathBuilder */ $container->extend('localePathBuilder', function ($pathBuilder, $c) { // Add paths for user theme, if a user is logged in // We catch any authorization-related exceptions, so that error pages can be rendered. try { - /** @var UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */ + /** @var \UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */ $authenticator = $c->authenticator; $currentUser = $c->currentUser; } catch (\Exception $e) { return $pathBuilder; } + // Add user locale if ($authenticator->check()) { - // Add paths to locale files for user theme - $themePath = $c->sprinkleManager->addResource('locale', $currentUser->theme); - - // Add user locale $pathBuilder->addLocales($currentUser->locale); } @@ -125,6 +119,8 @@ public function register($container) * Extends the 'view' service with the AccountExtension for Twig. * * Adds account-specific functions, globals, filters, etc to Twig, and the path to templates for the user theme. + * + * @return \Slim\Views\Twig */ $container->extend('view', function ($view, $c) { $twig = $view->getEnvironment(); @@ -134,21 +130,21 @@ public function register($container) // Add paths for user theme, if a user is logged in // We catch any authorization-related exceptions, so that error pages can be rendered. try { - /** @var UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */ + /** @var \UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */ $authenticator = $c->authenticator; $currentUser = $c->currentUser; } catch (\Exception $e) { return $view; } + // Register user theme template with Twig Loader if ($authenticator->check()) { - $theme = $currentUser->theme; - $themePath = $c->sprinkleManager->addResource('templates', $theme); + $themePath = $c->locator->findResource('templates://', true, false); if ($themePath) { $loader = $twig->getLoader(); $loader->prependPath($themePath); // Add namespaced path as well - $loader->addPath($themePath, $theme); + $loader->addPath($themePath, $currentUser->theme); } } @@ -159,6 +155,8 @@ public function register($container) * Authentication service. * * Supports logging in users, remembering their sessions, etc. + * + * @return \UserFrosting\Sprinkle\Account\Authenticate\Authenticator */ $container['authenticator'] = function ($c) { $classMapper = $c->classMapper; @@ -167,20 +165,21 @@ public function register($container) $cache = $c->cache; // Force database connection to boot up - $c->db; + $db = $c->db; - // Fix RememberMe table name - $config['remember_me.table.tableName'] = Capsule::connection()->getTablePrefix() . $config['remember_me.table.tableName']; + $authenticator = new Authenticator($classMapper, $session, $config, $cache, $db); - $authenticator = new Authenticator($classMapper, $session, $config, $cache); return $authenticator; }; /** * Sets up the AuthGuard middleware, used to limit access to authenticated users for certain routes. + * + * @return \UserFrosting\Sprinkle\Account\Authenticate\AuthGuard */ $container['authGuard'] = function ($c) { $authenticator = $c->authenticator; + return new AuthGuard($authenticator); }; @@ -188,6 +187,8 @@ public function register($container) * Authorization check logging with Monolog. * * Extend this service to push additional handlers onto the 'auth' log stack. + * + * @return \Monolog\Logger */ $container['authLogger'] = function ($c) { $logger = new Logger('auth'); @@ -208,6 +209,8 @@ public function register($container) * Authorization service. * * Determines permissions for user actions. Extend this service to add additional access condition callbacks. + * + * @return \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ $container['authorizer'] = function ($c) { $config = $c->config; @@ -224,19 +227,19 @@ public function register($container) /** * Check if the specified values are identical to one another (strict comparison). - * @param mixed $val1 the first value to compare. - * @param mixed $val2 the second value to compare. - * @return bool true if the values are strictly equal, false otherwise. + * @param mixed $val1 the first value to compare. + * @param mixed $val2 the second value to compare. + * @return bool true if the values are strictly equal, false otherwise. */ 'equals' => function ($val1, $val2) { - return ($val1 === $val2); + return $val1 === $val2; }, /** * Check if the specified values are numeric, and if so, if they are equal to each other. - * @param mixed $val1 the first value to compare. - * @param mixed $val2 the second value to compare. - * @return bool true if the values are numeric and equal, false otherwise. + * @param mixed $val1 the first value to compare. + * @param mixed $val2 the second value to compare. + * @return bool true if the values are numeric and equal, false otherwise. */ 'equals_num' => function ($val1, $val2) { if (!is_numeric($val1)) { @@ -246,14 +249,14 @@ public function register($container) return false; } - return ($val1 == $val2); + return $val1 == $val2; }, /** * Check if the specified user (by user_id) has a particular role. * - * @param int $user_id the id of the user. - * @param int $role_id the id of the role. + * @param int $user_id the id of the user. + * @param int $role_id the id of the role. * @return bool true if the user has the role, false otherwise. */ 'has_role' => function ($user_id, $role_id) { @@ -266,9 +269,9 @@ public function register($container) /** * Check if the specified value $needle is in the values of $haystack. * - * @param mixed $needle the value to look for in $haystack - * @param array[mixed] $haystack the array of values to search. - * @return bool true if $needle is present in the values of $haystack, false otherwise. + * @param mixed $needle the value to look for in $haystack + * @param array[mixed] $haystack the array of values to search. + * @return bool true if $needle is present in the values of $haystack, false otherwise. */ 'in' => function ($needle, $haystack) { return in_array($needle, $haystack); @@ -277,32 +280,33 @@ public function register($container) /** * Check if the specified user (by user_id) is in a particular group. * - * @param int $user_id the id of the user. - * @param int $group_id the id of the group. + * @param int $user_id the id of the user. + * @param int $group_id the id of the group. * @return bool true if the user is in the group, false otherwise. */ 'in_group' => function ($user_id, $group_id) { $user = User::find($user_id); - return ($user->group_id == $group_id); + + return $user->group_id == $group_id; }, /** * Check if the specified user (by user_id) is the master user. * - * @param int $user_id the id of the user. + * @param int $user_id the id of the user. * @return bool true if the user id is equal to the id of the master account, false otherwise. */ 'is_master' => function ($user_id) use ($config) { // Need to use loose comparison for now, because some DBs return `id` as a string - return ($user_id == $config['reserved_user_ids.master']); + return $user_id == $config['reserved_user_ids.master']; }, /** * Check if all values in the array $needle are present in the values of $haystack. * - * @param array[mixed] $needle the array whose values we should look for in $haystack - * @param array[mixed] $haystack the array of values to search. - * @return bool true if every value in $needle is present in the values of $haystack, false otherwise. + * @param array[mixed] $needle the array whose values we should look for in $haystack + * @param array[mixed] $haystack the array of values to search. + * @return bool true if every value in $needle is present in the values of $haystack, false otherwise. */ 'subset' => function ($needle, $haystack) { return count($needle) == count(array_intersect($needle, $haystack)); @@ -312,9 +316,9 @@ public function register($container) * Check if all keys of the array $needle are present in the values of $haystack. * * This function is useful for whitelisting an array of key-value parameters. - * @param array[mixed] $needle the array whose keys we should look for in $haystack - * @param array[mixed] $haystack the array of values to search. - * @return bool true if every key in $needle is present in the values of $haystack, false otherwise. + * @param array[mixed] $needle the array whose keys we should look for in $haystack + * @param array[mixed] $haystack the array of values to search. + * @return bool true if every key in $needle is present in the values of $haystack, false otherwise. */ 'subset_keys' => function ($needle, $haystack) { return count($needle) == count(array_intersect(array_keys($needle), $haystack)); @@ -322,65 +326,84 @@ public function register($container) ]; $authorizer = new AuthorizationManager($c, $callbacks); + return $authorizer; }; /** * Loads the User object for the currently logged-in user. + * + * @return \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface */ $container['currentUser'] = function ($c) { $authenticator = $c->authenticator; + $currentUser = $authenticator->user(); - return $authenticator->user(); + // Add user theme sprinkles ressources + if ($authenticator->check() && $currentUser->theme) { + $c->sprinkleManager->addSprinkleResources($currentUser->theme); + } + + return $currentUser; }; + /** + * Password Hasher service + * + * @return \UserFrosting\Sprinkle\Account\Authenticate\Hasher + */ $container['passwordHasher'] = function ($c) { $hasher = new Hasher(); + return $hasher; }; /** * Returns a callback that forwards to dashboard if user is already logged in. + * + * @return callable */ $container['redirect.onAlreadyLoggedIn'] = function ($c) { /** * This method is invoked when a user attempts to perform certain public actions when they are already logged in. * * @todo Forward to user's landing page or last visited page - * @param \Psr\Http\Message\ServerRequestInterface $request - * @param \Psr\Http\Message\ResponseInterface $response - * @param array $args + * @param \Psr\Http\Message\ServerRequestInterface $request + * @param \Psr\Http\Message\ResponseInterface $response + * @param array $args * @return \Psr\Http\Message\ResponseInterface */ return function (Request $request, Response $response, array $args) use ($c) { $redirect = $c->router->pathFor('dashboard'); - - return $response->withRedirect($redirect, 302); + + return $response->withRedirect($redirect); }; }; /** * Returns a callback that handles setting the `UF-Redirect` header after a successful login. + * + * @return callable */ $container['redirect.onLogin'] = function ($c) { /** * This method is invoked when a user completes the login process. * * Returns a callback that handles setting the `UF-Redirect` header after a successful login. - * @param \Psr\Http\Message\ServerRequestInterface $request - * @param \Psr\Http\Message\ResponseInterface $response - * @param array $args + * @param \Psr\Http\Message\ServerRequestInterface $request + * @param \Psr\Http\Message\ResponseInterface $response + * @param array $args * @return \Psr\Http\Message\ResponseInterface */ return function (Request $request, Response $response, array $args) use ($c) { // Backwards compatibility for the deprecated determineRedirectOnLogin service if ($c->has('determineRedirectOnLogin')) { $determineRedirectOnLogin = $c->determineRedirectOnLogin; - + return $determineRedirectOnLogin($response)->withStatus(200); } - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ $authorizer = $c->authorizer; $currentUser = $c->authenticator->user(); @@ -395,23 +418,29 @@ public function register($container) /** * Repository for password reset requests. + * + * @return \UserFrosting\Sprinkle\Account\Repository\PasswordResetRepository */ $container['repoPasswordReset'] = function ($c) { $classMapper = $c->classMapper; $config = $c->config; $repo = new PasswordResetRepository($classMapper, $config['password_reset.algorithm']); + return $repo; }; /** * Repository for verification requests. + * + * @return \UserFrosting\Sprinkle\Account\Repository\VerificationRepository */ $container['repoVerification'] = function ($c) { $classMapper = $c->classMapper; $config = $c->config; $repo = new VerificationRepository($classMapper, $config['verification.algorithm']); + return $repo; }; @@ -419,6 +448,8 @@ public function register($container) * Logger for logging the current user's activities to the database. * * Extend this service to push additional handlers onto the 'userActivity' log stack. + * + * @return \Monolog\Logger */ $container['userActivityLogger'] = function ($c) { $classMapper = $c->classMapper; diff --git a/app/sprinkles/account/src/Twig/AccountExtension.php b/app/sprinkles/account/src/Twig/AccountExtension.php index 12bacba5a..69625a953 100644 --- a/app/sprinkles/account/src/Twig/AccountExtension.php +++ b/app/sprinkles/account/src/Twig/AccountExtension.php @@ -3,13 +3,14 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Twig; use Interop\Container\ContainerInterface; -use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator; -use Slim\Http\Uri; +use UserFrosting\Support\Repository\Repository as Config; /** * Extends Twig functionality for the Account sprinkle. @@ -18,10 +19,19 @@ */ class AccountExtension extends \Twig_Extension implements \Twig_Extension_GlobalsInterface { - + /** + * @var ContainerInterface + */ protected $services; + + /** + * @var Config + */ protected $config; + /** + * @param ContainerInterface $services + */ public function __construct(ContainerInterface $services) { $this->services = $services; @@ -35,7 +45,7 @@ public function getName() public function getFunctions() { - return array( + return [ // Add Twig function for checking permissions during dynamic menu rendering new \Twig_SimpleFunction('checkAccess', function ($slug, $params = []) { $authorizer = $this->services->authorizer; @@ -45,9 +55,10 @@ public function getFunctions() }), new \Twig_SimpleFunction('checkAuthenticated', function () { $authenticator = $this->services->authenticator; + return $authenticator->check(); }) - ); + ]; } public function getGlobals() diff --git a/app/sprinkles/account/src/Util/HashFailedException.php b/app/sprinkles/account/src/Util/HashFailedException.php index a0b37d173..d394166c5 100644 --- a/app/sprinkles/account/src/Util/HashFailedException.php +++ b/app/sprinkles/account/src/Util/HashFailedException.php @@ -3,8 +3,10 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Util; use UserFrosting\Support\Exception\HttpException; diff --git a/app/sprinkles/account/src/Util/Util.php b/app/sprinkles/account/src/Util/Util.php index 6452990c6..db3257cab 100644 --- a/app/sprinkles/account/src/Util/Util.php +++ b/app/sprinkles/account/src/Util/Util.php @@ -3,10 +3,13 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Account\Util; +use UserFrosting\Sprinkle\Core\Util\ClassMapper; use UserFrosting\Sprinkle\Core\Util\Util as CoreUtil; /** @@ -20,8 +23,13 @@ class Util { /** * Generate a random, unique username from a list of adjectives and nouns. + * + * @param ClassMapper $classMapper + * @param int $maxLength + * @param int $maxTries + * @return string */ - static public function randomUniqueUsername($classMapper, $maxLength, $maxTries = 10) + public static function randomUniqueUsername(ClassMapper $classMapper, $maxLength, $maxTries = 10) { for ($n = 1; $n <= 3; $n++) { for ($m = 0; $m < 10; $m++) { @@ -35,5 +43,4 @@ static public function randomUniqueUsername($classMapper, $maxLength, $maxTries return ''; } - } diff --git a/app/sprinkles/account/templates/pages/register.html.twig b/app/sprinkles/account/templates/pages/register.html.twig index bd155bab4..c181a870f 100644 --- a/app/sprinkles/account/templates/pages/register.html.twig +++ b/app/sprinkles/account/templates/pages/register.html.twig @@ -1,105 +1,116 @@ -{% extends "pages/abstract/base.html.twig" %} - -{# Overrides blocks in head of base template #} -{% block page_title %}{{translate('REGISTER')}}{% endblock %} - -{% block page_description %}{{translate('PAGE.LOGIN.DESCRIPTION', {'site_name': site.title })}}{% endblock %} - -{% block body_attributes %} - class="hold-transition login-page" -{% endblock %} - -{% block content %} - - -{% endblock %} - -{% block scripts_page %} - - - - - {{ assets.js('js/pages/register') | raw }} -{% endblock %} +{% extends "pages/abstract/base.html.twig" %} + +{# Overrides blocks in head of base template #} +{% block page_title %}{{translate('REGISTER')}}{% endblock %} + +{% block page_description %}{{translate('PAGE.LOGIN.DESCRIPTION', {'site_name': site.title })}}{% endblock %} + +{% block body_attributes %} + class="hold-transition login-page" +{% endblock %} + +{% block content %} + + +{% endblock %} + +{% block scripts_page %} + + + + + {{ assets.js('js/pages/register') | raw }} +{% endblock %} diff --git a/app/sprinkles/account/templates/pages/sign-in.html.twig b/app/sprinkles/account/templates/pages/sign-in.html.twig index 2fb6e1c25..cd4341fb6 100644 --- a/app/sprinkles/account/templates/pages/sign-in.html.twig +++ b/app/sprinkles/account/templates/pages/sign-in.html.twig @@ -11,16 +11,20 @@ {% block content %} diff --git a/app/sprinkles/account/tests/Integration/AuthenticatorTest.php b/app/sprinkles/account/tests/Integration/AuthenticatorTest.php new file mode 100644 index 000000000..38ddde710 --- /dev/null +++ b/app/sprinkles/account/tests/Integration/AuthenticatorTest.php @@ -0,0 +1,354 @@ +setupTestDatabase(); + $this->refreshDatabase(); + } + + /** + * @return Authenticator + */ + public function testConstructor() + { + $authenticator = $this->getAuthenticator(); + $this->assertInstanceOf(Authenticator::class, $authenticator); + + return $authenticator; + } + + /** + * @depends testConstructor + * @param Authenticator $authenticator + */ + public function testLogin(Authenticator $authenticator) + { + // Create a test user + $testUser = $this->createTestUser(); + + // Test session to avoid false positive + $key = $this->ci->config['session.keys.current_user_id']; + $this->assertNull($this->ci->session[$key]); + $this->assertNotSame($testUser->id, $this->ci->session[$key]); + + // Login the test user + $authenticator->login($testUser, false); + + // Test session to see if user was logged in + $this->assertNotNull($this->ci->session[$key]); + $this->assertSame($testUser->id, $this->ci->session[$key]); + + // Must logout to avoid test issue + $authenticator->logout(true); + + // We'll test the logout system works too while we're at it (and depend on it) + $key = $this->ci->config['session.keys.current_user_id']; + $this->assertNull($this->ci->session[$key]); + $this->assertNotSame($testUser->id, $this->ci->session[$key]); + } + + /** + * @depends testConstructor + * @expectedException \UserFrosting\Sprinkle\Account\Authenticate\Exception\AccountInvalidException + * @param Authenticator $authenticator + */ + public function testValidateUserAccountThrowAccountInvalidException(Authenticator $authenticator) + { + $this->invokeMethod($authenticator, 'validateUserAccount', [99999999]); + } + + /** + * @depends testConstructor + * @param Authenticator $authenticator + */ + public function testValidateUserAccountRetunNullOnFalseArgument(Authenticator $authenticator) + { + $user = $this->invokeMethod($authenticator, 'validateUserAccount', [false]); + $this->assertNull($user); + } + + /** + * @depends testConstructor + * @expectedException \UserFrosting\Sprinkle\Account\Authenticate\Exception\AccountInvalidException + * @param Authenticator $authenticator + */ + public function testValidateUserAccountThrowExceptionArgumentNotInt(Authenticator $authenticator) + { + $this->invokeMethod($authenticator, 'validateUserAccount', ['stringIsNotInt']); + } + + /** + * @depends testConstructor + * @param Authenticator $authenticator + */ + public function testValidateUserAccount(Authenticator $authenticator) + { + $testUser = $this->createTestUser(); + $user = $this->invokeMethod($authenticator, 'validateUserAccount', [$testUser->id]); + $this->assertSame($testUser->id, $user->id); + } + + /** + * @depends testConstructor + * @expectedException \UserFrosting\Sprinkle\Account\Authenticate\Exception\AccountDisabledException + * @param Authenticator $authenticator + */ + public function testValidateUserAccountWithAccountDisabledException(Authenticator $authenticator) + { + $testUser = $this->createTestUser(); + $testUser->flag_enabled = false; + $testUser->save(); + $this->invokeMethod($authenticator, 'validateUserAccount', [$testUser->id]); + } + + /** + * @depends testConstructor + * @param Authenticator $authenticator + */ + public function testLoginWithRememberMe(Authenticator $authenticator) + { + // Create a test user + $testUser = $this->createTestUser(); + + // Test session to avoid false positive + $key = $this->ci->config['session.keys.current_user_id']; + $this->assertNull($this->ci->session[$key]); + $this->assertNotSame($testUser->id, $this->ci->session[$key]); + + $authenticator->login($testUser, true); + + // Test session to see if user was logged in + $this->assertNotNull($this->ci->session[$key]); + $this->assertSame($testUser->id, $this->ci->session[$key]); + + // We'll manually delete the session, + $this->ci->session[$key] = null; + $this->assertNull($this->ci->session[$key]); + $this->assertNotSame($testUser->id, $this->ci->session[$key]); + + // Go througt the loginRememberedUser process + // First, we'll simulate a page refresh by creating a new authenticator + $authenticator = $this->getAuthenticator(); + $user = $authenticator->user(); + + // If loginRememberedUser returns a PDOException, `user` will return a null user + $this->assertNotNull($user); + $this->assertEquals($testUser->id, $user->id); + $this->assertEquals($testUser->id, $this->ci->session[$key]); + $this->assertTrue($authenticator->viaRemember()); + + // Must logout to avoid test issue + $authenticator->logout(); + } + + /** + * @depends testConstructor + * @depends testLogin + * @param Authenticator $authenticator + */ + public function testAttempt_withUserName(Authenticator $authenticator) + { + // Create a test user + $testUser = $this->createTestUser(); + + // Faker doesn't hash the password. Let's do that now + $unhashed = $testUser->password; + $testUser->password = Password::hash($testUser->password); + $testUser->save(); + + // Attempt to log him in + $currentUser = $authenticator->attempt('user_name', $testUser->user_name, $unhashed, false); + $this->assertSame($testUser->id, $currentUser->id); + $this->assertFalse($authenticator->viaRemember()); + + // Must logout to avoid test issue + $authenticator->logout(); + } + + /** + * @depends testConstructor + * @depends testAttempt_withUserName + * @param Authenticator $authenticator + */ + public function testAttempt_withEmail(Authenticator $authenticator) + { + // Faker doesn't hash the password. Let's do that now + $password = 'FooBar'; + + // Create a test user + $testUser = $this->createTestUser(false, false, ['password' => Password::hash($password)]); + + // Attempt to log him in + $currentUser = $authenticator->attempt('email', $testUser->email, $password, false); + $this->assertSame($testUser->id, $currentUser->id); + $this->assertFalse($authenticator->viaRemember()); + + // Must logout to avoid test issue + $authenticator->logout(); + } + + /** + * @depends testConstructor + * @depends testAttempt_withUserName + * @expectedException \UserFrosting\Sprinkle\Account\Authenticate\Exception\InvalidCredentialsException + * @param Authenticator $authenticator + */ + public function testAttempt_withNoUser(Authenticator $authenticator) + { + $currentUser = $authenticator->attempt('user_name', 'fooBar', 'barFoo', false); + } + + /** + * @depends testConstructor + * @depends testAttempt_withUserName + * @expectedException \UserFrosting\Sprinkle\Account\Authenticate\Exception\InvalidCredentialsException + * @param Authenticator $authenticator + */ + public function testAttempt_withNoPassword(Authenticator $authenticator) + { + $testUser = $this->createTestUser(false, false, ['password' => '']); + $currentUser = $authenticator->attempt('email', $testUser->email, 'fooBar', false); + } + + /** + * @depends testConstructor + * @depends testAttempt_withUserName + * @expectedException \UserFrosting\Sprinkle\Account\Authenticate\Exception\AccountDisabledException + * @param Authenticator $authenticator + */ + public function testAttempt_withFlagEnabledFalse(Authenticator $authenticator) + { + $password = 'FooBar'; + $testUser = $this->createTestUser(false, false, [ + 'password' => Password::hash($password), + 'flag_enabled' => 0 + ]); + + $currentUser = $authenticator->attempt('user_name', $testUser->user_name, $password, false); + } + + /** + * @depends testConstructor + * @depends testAttempt_withUserName + * @expectedException \UserFrosting\Sprinkle\Account\Authenticate\Exception\AccountNotVerifiedException + * @param Authenticator $authenticator + */ + public function testAttempt_withFlagVerifiedFalse(Authenticator $authenticator) + { + $password = 'FooBar'; + $testUser = $this->createTestUser(false, false, [ + 'password' => Password::hash($password), + 'flag_verified' => 0 + ]); + + $currentUser = $authenticator->attempt('user_name', $testUser->user_name, $password, false); + } + + /** + * @depends testConstructor + * @depends testAttempt_withUserName + * @param Authenticator $authenticator + */ + public function testAttempt_withFlagVerifiedFalseNoEmailVerification(Authenticator $authenticator) + { + // Force email verification to false + $this->ci->config['site.registration.require_email_verification'] = false; + + // Forcing config requires to recreate the authentificator + $authenticator = $this->getAuthenticator(); + + $password = 'FooBar'; + $testUser = $this->createTestUser(false, false, [ + 'password' => Password::hash($password), + 'flag_verified' => 0 + ]); + + $currentUser = $authenticator->attempt('user_name', $testUser->user_name, $password, false); + $this->assertSame($testUser->id, $currentUser->id); + $this->assertFalse($authenticator->viaRemember()); + + // Must logout to avoid test issue + $authenticator->logout(); + } + + /** + * @depends testConstructor + * @depends testAttempt_withUserName + * @expectedException \UserFrosting\Sprinkle\Account\Authenticate\Exception\InvalidCredentialsException + * @param Authenticator $authenticator + */ + public function testAttempt_withBadPassword(Authenticator $authenticator) + { + $password = 'FooBar'; + $testUser = $this->createTestUser(false, false, [ + 'password' => Password::hash($password) + ]); + + $currentUser = $authenticator->attempt('user_name', $testUser->user_name, 'BarFoo', false); + } + + /** + * @depends testConstructor + * @param Authenticator $authenticator + */ + public function testCheckWithNoUser(Authenticator $authenticator) + { + // We don't have a user by default + $this->assertFalse($authenticator->check()); + $this->assertTrue($authenticator->guest()); + } + + /** + * @depends testConstructor + */ + public function testCheckWithLoggedInUser() + { + $testUser = $this->createTestUser(false, true); + $authenticator = $this->getAuthenticator(); + + $this->assertTrue($authenticator->check()); + $this->assertFalse($authenticator->guest()); + $this->assertSame($testUser->id, $authenticator->user()->id); + } + + /** + * @return Authenticator + */ + protected function getAuthenticator() + { + return new Authenticator($this->ci->classMapper, $this->ci->session, $this->ci->config, $this->ci->cache, $this->ci->db); + } +} diff --git a/app/sprinkles/account/tests/Integration/AuthorizationManagerTest.php b/app/sprinkles/account/tests/Integration/AuthorizationManagerTest.php new file mode 100644 index 000000000..01ae32d4c --- /dev/null +++ b/app/sprinkles/account/tests/Integration/AuthorizationManagerTest.php @@ -0,0 +1,226 @@ +setupTestDatabase(); + $this->refreshDatabase(); + } + + public function tearDown() + { + parent::tearDown(); + m::close(); + } + + /** + * @return AuthorizationManager + */ + public function testConstructor() + { + $manager = $this->getManager(); + $this->assertInstanceOf(AuthorizationManager::class, $manager); + + return $manager; + } + + /** + * @depends testConstructor + * @param AuthorizationManager $manager + */ + public function testAddCallback(AuthorizationManager $manager) + { + $this->assertEmpty($manager->getCallbacks()); + $this->assertInstanceOf(AuthorizationManager::class, $manager->addCallback('foo', function () { + })); + $callbacks = $manager->getCallbacks(); + $this->assertNotEmpty($callbacks); + $this->assertEquals(['foo' => function () { + }], $callbacks); + } + + /** + * Test the service. Will have the predefined callbacks + */ + public function testService() + { + $this->assertInstanceOf(AuthorizationManager::class, $this->ci->authorizer); + } + + /** + * @depends testConstructor + * @expectedException \ArgumentCountError + * @param AuthorizationManager $manager + * REQUIRES PHP 7.1 or better + */ + /*public function testCheckAccess_withOutUser(AuthorizationManager $manager) + { + $manager->checkAccess(); + }*/ + + /** + * @depends testConstructor + */ + public function testCheckAccess_withNullUser() + { + $this->getMockAuthLogger()->shouldReceive('debug')->once()->with('No user defined. Access denied.'); + $this->assertFalse($this->getManager()->checkAccess(null, 'foo')); + } + + /** + * @depends testConstructor + */ + public function testCheckAccess_withBadUserType() + { + $this->getMockAuthLogger()->shouldReceive('debug')->once()->with('No user defined. Access denied.'); + $this->assertFalse($this->getManager()->checkAccess(123, 'foo')); + } + + /** + * By default, `checkAccess` is null. Test to make sure we don't break the + * "current user is guest" thing + * @depends testCheckAccess_withNullUser + */ + public function testCheckAccess_withNullCurrentUser() + { + $this->getMockAuthLogger()->shouldReceive('debug')->once()->with('No user defined. Access denied.'); + $user = $this->ci->currentUser; + $this->assertNull($user); + $this->assertFalse($this->getManager()->checkAccess($user, 'foo')); + } + + /** + * @depends testConstructor + */ + public function testCheckAccess_withNormalUser() + { + $user = $this->createTestUser(false); + + // Setup authLogger expectations + $authLogger = $this->getMockAuthLogger(); + $authLogger->shouldReceive('debug')->once()->with('No matching permissions found. Access denied.'); + $authLogger->shouldReceive('debug')->times(2); + + $this->assertFalse($this->getManager()->checkAccess($user, 'blah')); + } + + /** + * Once logged in, `currentUser` will not be null + * @depends testCheckAccess_withNormalUser + */ + public function testCheckAccess_withCurrentUser() + { + $user = $this->createTestUser(false, true); + $this->assertNotNull($this->ci->currentUser); + $this->assertSame($user, $this->ci->currentUser); + + // Setup authLogger expectations + $authLogger = $this->getMockAuthLogger(); + $authLogger->shouldReceive('debug')->once()->with('No matching permissions found. Access denied.'); + $authLogger->shouldReceive('debug')->times(2); + + $this->assertFalse($this->getManager()->checkAccess($this->ci->currentUser, 'foo')); + } + + /** + * @depends testService + * @depends testCheckAccess_withNormalUser + */ + public function testCheckAccess_withMasterUser() + { + $user = $this->createTestUser(true); + + // Setup authLogger expectations + $authLogger = $this->getMockAuthLogger(); + $authLogger->shouldReceive('debug')->once()->with('User is the master (root) user. Access granted.'); + $authLogger->shouldReceive('debug')->times(2); + + $this->assertTrue($this->getManager()->checkAccess($user, 'foo')); + } + + /** + * @depends testCheckAccess_withNormalUser + */ + public function testCheckAccess_withNormalUserWithPermission() + { + // Create a non admin user and give him the 'foo' permission + $user = $this->createTestUser(); + $this->giveUserTestPermission($user, 'foo'); + + // Setup authLogger expectations + $authLogger = $this->getMockAuthLogger(); + $authLogger->shouldReceive('debug')->once()->with("Evaluating callback 'always'..."); + $authLogger->shouldReceive('debug')->once()->with("User passed conditions 'always()'. Access granted."); + $authLogger->shouldReceive('debug')->times(6); + + $this->assertTrue($this->ci->authorizer->checkAccess($user, 'foo')); + } + + /** + * @depends testCheckAccess_withNormalUserWithPermission + */ + public function testCheckAccess_withNormalUserWithFailedPermission() + { + // Create a non admin user and give him the 'foo' permission + $user = $this->createTestUser(); + $this->giveUserTestPermission($user, 'foo', 'is_master(self.id)'); + + // Setup authLogger expectations + $authLogger = $this->getMockAuthLogger(); + $authLogger->shouldReceive('debug')->once()->with('User failed to pass any of the matched permissions. Access denied.'); + $authLogger->shouldReceive('debug')->times(7); + + $this->assertFalse($this->ci->authorizer->checkAccess($user, 'foo')); + } + + /** + * @return AuthorizationManager + */ + protected function getManager() + { + return new AuthorizationManager($this->ci, []); + } + + /** + * We'll test using the `debug.auth` on and a mock authLogger to not get our + * dirty test into the real log + * @return \Monolog\Logger + */ + protected function getMockAuthLogger() + { + $this->ci->config['debug.auth'] = true; + $this->ci->authLogger = m::mock('\Monolog\Logger'); + + return $this->ci->authLogger; + } +} diff --git a/app/sprinkles/account/tests/Unit/FactoriesTest.php b/app/sprinkles/account/tests/Unit/FactoriesTest.php index ee2bf23fb..841772d54 100644 --- a/app/sprinkles/account/tests/Unit/FactoriesTest.php +++ b/app/sprinkles/account/tests/Unit/FactoriesTest.php @@ -3,28 +3,46 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ -namespace UserFrosting\Tests\Unit; + +namespace UserFrosting\Sprinkle\Account\Tests\Unit; use UserFrosting\Tests\TestCase; -use UserFrosting\Tests\DatabaseTransactions; +use UserFrosting\Sprinkle\Core\Tests\TestDatabase; +use UserFrosting\Sprinkle\Core\Tests\RefreshDatabase; /** * FactoriesTest class. * Tests the factories defined in this sprinkle are working - * - * @extends TestCase */ class FactoriesTest extends TestCase { - use DatabaseTransactions; + use TestDatabase; + use RefreshDatabase; + + /** + * Setup TestDatabase + */ + public function setUp() + { + // Boot parent TestCase, which will set up the database and connections for us. + parent::setUp(); + + // Setup test database + $this->setupTestDatabase(); + $this->refreshDatabase(); + } - function testUserFactory() + /** + * Test the user factory + */ + public function testUserFactory() { $fm = $this->ci->factory; $user = $fm->create('UserFrosting\Sprinkle\Account\Database\Models\User'); - $this->assertInstanceOf('UserFrosting\Sprinkle\Account\Database\Models\User', $user); + $this->assertInstanceOf('UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface', $user); } } diff --git a/app/sprinkles/account/tests/Unit/HasherTest.php b/app/sprinkles/account/tests/Unit/HasherTest.php index 711e3cb05..4bc6c11f6 100644 --- a/app/sprinkles/account/tests/Unit/HasherTest.php +++ b/app/sprinkles/account/tests/Unit/HasherTest.php @@ -3,20 +3,23 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ -namespace UserFrosting\Tests\Unit; + +namespace UserFrosting\Sprinkle\Account\Tests\Unit; use UserFrosting\Sprinkle\Account\Authenticate\Hasher; use UserFrosting\Tests\TestCase; /** * Tests the password Hasher class. - * - * @extends TestCase */ class HasherTest extends TestCase { + /** + * @var string + */ protected $plainText = 'hodleth'; /** @@ -34,9 +37,12 @@ class HasherTest extends TestCase */ protected $modernHash = '$2y$10$ucxLwloFso6wJoct1baBQefdrttws/taEYvavi6qoPsw/vd1u4Mha'; + /** + * testGetHashType + */ public function testGetHashType() { - $hasher = new Hasher; + $hasher = new Hasher(); $type = $hasher->getHashType($this->modernHash); @@ -51,18 +57,24 @@ public function testGetHashType() $this->assertEquals('sha1', $type); } + /** + * testVerify + */ public function testVerify() { - $hasher = new Hasher; + $hasher = new Hasher(); $this->assertTrue($hasher->verify($this->plainText, $this->modernHash)); $this->assertTrue($hasher->verify($this->plainText, $this->legacyHash)); $this->assertTrue($hasher->verify($this->plainText, $this->userCakeHash)); } + /** + * testVerifyReject + */ public function testVerifyReject() { - $hasher = new Hasher; + $hasher = new Hasher(); $this->assertFalse($hasher->verify('selleth', $this->modernHash)); $this->assertFalse($hasher->verify('selleth', $this->legacyHash)); diff --git a/app/sprinkles/account/tests/Unit/PDOStorageTest.php b/app/sprinkles/account/tests/Unit/PDOStorageTest.php new file mode 100644 index 000000000..faa5819b5 --- /dev/null +++ b/app/sprinkles/account/tests/Unit/PDOStorageTest.php @@ -0,0 +1,153 @@ +setupTestDatabase(); + $this->refreshDatabase(); + + // Create a test user + $this->testUser = $this->createTestUser(); + + $this->storage = new PDOStorage($this->ci->db); + } + + public function testFindTripletReturnsFoundIfDataMatches() + { + $this->insertTestData(); + $result = $this->storage->findTriplet($this->testUser->id, $this->validToken, $this->validPersistentToken); + $this->assertEquals(StorageInterface::TRIPLET_FOUND, $result); + } + + public function testFindTripletReturnsNotFoundIfNoDataMatches() + { + $result = $this->storage->findTriplet($this->testUser->id, $this->validToken, $this->validPersistentToken); + $this->assertEquals(StorageInterface::TRIPLET_NOT_FOUND, $result); + } + + public function testFindTripletReturnsInvalidTokenIfTokenIsInvalid() + { + $this->insertTestData(); + $result = $this->storage->findTriplet($this->testUser->id, $this->invalidToken, $this->validPersistentToken); + $this->assertEquals(StorageInterface::TRIPLET_INVALID, $result); + } + + public function testStoreTripletSavesValuesIntoDatabase() + { + $this->storage->storeTriplet($this->testUser->id, $this->validToken, $this->validPersistentToken, strtotime($this->expire)); + $row = Persistence::select(['user_id', 'token', 'persistent_token', 'expires_at'])->first()->toArray(); + $this->assertEquals([$this->testUser->id, $this->validDBToken, $this->validDBPersistentToken, $this->expire], array_values($row)); + } + + public function testCleanTripletRemovesEntryFromDatabase() + { + $this->insertTestData(); + $this->storage->cleanTriplet($this->testUser->id, $this->validPersistentToken); + $this->assertEquals(0, Persistence::count()); + } + + public function testCleanAllTripletsRemovesAllEntriesWithMatchingCredentialsFromDatabase() + { + $this->insertTestData(); + $persistence = new Persistence([ + 'user_id' => $this->testUser->id, + 'token' => 'dummy', + 'persistent_token' => 'dummy', + 'expires_at' => null + ]); + $persistence->save(); + $this->storage->cleanAllTriplets($this->testUser->id); + $this->assertEquals(0, Persistence::count()); + } + + public function testReplaceTripletRemovesAndSavesEntryFromDatabase() + { + $this->insertTestData(); + $this->storage->replaceTriplet($this->testUser->id, $this->invalidToken, $this->validPersistentToken, strtotime($this->expire)); + $this->assertEquals(1, Persistence::count()); + $row = Persistence::select(['user_id', 'token', 'persistent_token', 'expires_at'])->first()->toArray(); + $this->assertEquals([$this->testUser->id, $this->invalidDBToken, $this->validDBPersistentToken, $this->expire], array_values($row)); + } + + public function testCleanExpiredTokens() + { + $this->insertTestData(); + $persistence = new Persistence([ + 'user_id' => $this->testUser->id, + 'token' => 'dummy', + 'persistent_token' => 'dummy', + 'expires_at' => Carbon::now()->subHour(1) + ]); + $persistence->save(); + $this->assertEquals(2, Persistence::count()); + $this->storage->cleanExpiredTokens(Carbon::now()->timestamp); + $this->assertEquals(1, Persistence::count()); + } + + /** + * Insert test dataset + * @return Persistence + */ + protected function insertTestData() + { + $persistence = new Persistence([ + 'user_id' => $this->testUser->id, + 'token' => $this->validDBToken, + 'persistent_token' => $this->validDBPersistentToken, + 'expires_at' => $this->expire + ]); + $persistence->save(); + + return $persistence; + } +} diff --git a/app/sprinkles/account/tests/Unit/RegistrationTest.php b/app/sprinkles/account/tests/Unit/RegistrationTest.php new file mode 100644 index 000000000..dead5af3f --- /dev/null +++ b/app/sprinkles/account/tests/Unit/RegistrationTest.php @@ -0,0 +1,132 @@ +setupTestDatabase(); + $this->refreshDatabase(); + } + + /** + * testNormalRegistration + */ + public function testNormalRegistration() + { + // userActivityLogger will receive something, but won't be able to handle it since there's no session. So we mock it + $this->ci->userActivityLogger = m::mock('\Monolog\Logger'); + $this->ci->userActivityLogger->shouldReceive('info')->once(); + + // Tests can't mail properly + $this->ci->config['site.registration.require_email_verification'] = false; + + // Genereate user data + $fakeUserData = [ + 'user_name' => 'FooBar', + 'first_name' => 'Foo', + 'last_name' => 'Bar', + 'email' => 'Foo@Bar.com', + 'password' => 'FooBarFooBar123' + ]; + + // Get class + $registration = new Registration($this->ci, $fakeUserData); + $this->assertInstanceOf(Registration::class, $registration); + + // Register user + $user = $registration->register(); + + // Registration should return a valid user + $this->assertInstanceOf(UserInterface::class, $user); + $this->assertEquals('FooBar', $user->user_name); + + // We try to register the same user again. Should throw an error + $registration = new Registration($this->ci, $fakeUserData); + $this->expectException(HttpException::class); + $validation = $registration->validate(); + + // Should throw email error if we change the username + $fakeUserData['user_name'] = 'BarFoo'; + $registration = new Registration($this->ci, $fakeUserData); + $this->expectException(HttpException::class); + $validation = $registration->validate(); + } + + /** + * Test validation works + */ + public function testValidation() + { + // Reset database + $this->refreshDatabase(); + + $registration = new Registration($this->ci, [ + 'user_name' => 'FooBar', + 'first_name' => 'Foo', + 'last_name' => 'Bar', + 'email' => 'Foo@Bar.com', + 'password' => 'FooBarFooBar123' + ]); + + // Validate user. Shouldn't tell us the username is already in use since we reset the database + $validation = $registration->validate(); + $this->assertTrue($validation); + } + + /** + * Test the $requiredProperties property + */ + public function testMissingFields() + { + // Reset database + $this->refreshDatabase(); + + $registration = new Registration($this->ci, [ + 'user_name' => 'FooBar', + //'first_name' => 'Foo', + 'last_name' => 'Bar', + 'email' => 'Foo@Bar.com', + 'password' => 'FooBarFooBar123' + ]); + + // Validate user. Shouldn't tell us the username is already in use since we reset the database + $this->expectException(HttpException::class); + $validation = $registration->validate(); + } +} diff --git a/app/sprinkles/account/tests/withTestUser.php b/app/sprinkles/account/tests/withTestUser.php new file mode 100644 index 000000000..a2727fa66 --- /dev/null +++ b/app/sprinkles/account/tests/withTestUser.php @@ -0,0 +1,112 @@ +ci->currentUser = $user; + $this->ci->authenticator->login($user); + + // Log user out when we're done with testing to prevent session persistence + $this->beforeApplicationDestroyed(function () use ($user) { + $this->logoutCurrentUser($user); + }); + } + + /** + * Logout + */ + protected function logoutCurrentUser() + { + $this->ci->authenticator->logout(); + } + + /** + * Create a test user with no settings/permissions for a controller test + * @param bool $isMaster Does this user have root access? Will bypass all permissions + * @param bool $login Login this user, setting him as the currentUser + * @param array $params User account params + * @return User + */ + protected function createTestUser($isMaster = false, $login = false, array $params = []) + { + if ($isMaster) { + $user_id = $this->ci->config['reserved_user_ids.master']; + } else { + $user_id = rand(0, 1222); + } + + $params = array_merge(['id' => $user_id], $params); + + $fm = $this->ci->factory; + $user = $fm->create(User::class, $params); + + if ($login) { + $this->loginUser($user); + } + + return $user; + } + + /** + * Gives a user a new test permission + * @param UserInterface $user + * @param string $slug + * @param string $conditions + * @return Permission + */ + protected function giveUserTestPermission(UserInterface $user, $slug, $conditions = 'always()') + { + /** @var \League\FactoryMuffin\FactoryMuffin $fm */ + $fm = $this->ci->factory; + + $permission = $fm->create(Permission::class, [ + 'slug' => $slug, + 'conditions' => $conditions + ]); + + // Add the permission to the user + $this->giveUserPermission($user, $permission); + + return $permission; + } + + /** + * Add the test permission to a Role, then the role to the user + * @param UserInterface $user + * @param Permission $permission + * @return Role The intermidiate role + */ + protected function giveUserPermission(UserInterface $user, Permission $permission) + { + /** @var \League\FactoryMuffin\FactoryMuffin $fm */ + $fm = $this->ci->factory; + + $role = $fm->create(Role::class); + $role->permissions()->attach($permission); + $user->roles()->attach($role); + + return $role; + } +} diff --git a/app/sprinkles/admin/assets/userfrosting/js/pages/dashboard.js b/app/sprinkles/admin/assets/userfrosting/js/pages/dashboard.js index f2b8a4f08..109045f73 100644 --- a/app/sprinkles/admin/assets/userfrosting/js/pages/dashboard.js +++ b/app/sprinkles/admin/assets/userfrosting/js/pages/dashboard.js @@ -6,7 +6,9 @@ */ $(document).ready(function() { - $('.js-clear-cache').click(function() { + $('.js-clear-cache').click(function(e) { + e.preventDefault(); + $("body").ufModal({ sourceUrl: site.uri.public + "/modals/dashboard/clear-cache", ajaxParams: { @@ -28,22 +30,28 @@ $(document).ready(function() { }); // Table of site activities - $("#widget-activities").ufTable({ - dataUrl: site.uri.public + "/api/activities", - useLoadingTransition: site.uf_table.use_loading_transition - }); + var activities = $("#widget-activities"); + if (activities.length) { + activities.ufTable({ + dataUrl: site.uri.public + "/api/activities", + useLoadingTransition: site.uf_table.use_loading_transition + }); + } // Table of users in current user's group - $("#widget-group-users").ufTable({ - dataUrl: site.uri.public + "/api/groups/g/" + page.group_slug + "/users", - useLoadingTransition: site.uf_table.use_loading_transition - }); + var groupUsers = $("#widget-group-users"); + if (groupUsers.length) { + groupUsers.ufTable({ + dataUrl: site.uri.public + "/api/groups/g/" + page.group_slug + "/users", + useLoadingTransition: site.uf_table.use_loading_transition + }); - // Bind user creation button - bindUserCreationButton($("#widget-group-users")); + // Bind user creation button + bindUserCreationButton(groupUsers); - // Bind user table buttons - $("#widget-group-users").on("pagerComplete.ufTable", function () { - bindUserButtons($(this)); - }); + // Bind user table buttons + groupUsers.on("pagerComplete.ufTable", function () { + bindUserButtons($(this)); + }); + } }); diff --git a/app/sprinkles/admin/assets/userfrosting/js/pages/group.js b/app/sprinkles/admin/assets/userfrosting/js/pages/group.js index a1ca959a5..66b35b9fc 100644 --- a/app/sprinkles/admin/assets/userfrosting/js/pages/group.js +++ b/app/sprinkles/admin/assets/userfrosting/js/pages/group.js @@ -9,7 +9,7 @@ $(document).ready(function() { // Control buttons - bindGroupButtons($("#view-group")); + bindGroupButtons($("#view-group"), { delete_redirect: page.delete_redirect }); // Table of users in this group $("#widget-group-users").ufTable({ diff --git a/app/sprinkles/admin/assets/userfrosting/js/pages/role.js b/app/sprinkles/admin/assets/userfrosting/js/pages/role.js index 8dae7f50f..bcec074da 100644 --- a/app/sprinkles/admin/assets/userfrosting/js/pages/role.js +++ b/app/sprinkles/admin/assets/userfrosting/js/pages/role.js @@ -9,7 +9,7 @@ $(document).ready(function() { // Control buttons - bindRoleButtons($("#view-role")); + bindRoleButtons($("#view-role"), { delete_redirect: page.delete_redirect }); $("#widget-role-permissions").ufTable({ dataUrl: site.uri.public + '/api/roles/r/' + page.role_slug + '/permissions', diff --git a/app/sprinkles/admin/assets/userfrosting/js/pages/user.js b/app/sprinkles/admin/assets/userfrosting/js/pages/user.js index 70acf7c92..fd328c8b3 100644 --- a/app/sprinkles/admin/assets/userfrosting/js/pages/user.js +++ b/app/sprinkles/admin/assets/userfrosting/js/pages/user.js @@ -9,7 +9,7 @@ $(document).ready(function() { // Control buttons - bindUserButtons($("#view-user")); + bindUserButtons($("#view-user"), { delete_redirect: page.delete_redirect }); // Table of activities $("#widget-user-activities").ufTable({ diff --git a/app/sprinkles/admin/assets/userfrosting/js/widgets/groups.js b/app/sprinkles/admin/assets/userfrosting/js/widgets/groups.js index d701d8145..47c9d15e7 100644 --- a/app/sprinkles/admin/assets/userfrosting/js/widgets/groups.js +++ b/app/sprinkles/admin/assets/userfrosting/js/widgets/groups.js @@ -43,7 +43,7 @@ function attachGroupForm() { // Set up the form for submission form.ufForm({ - validators: page.validators + validator: page.validators }).on("submitSuccess.ufForm", function() { // Reload page on success window.location.reload(); @@ -53,8 +53,12 @@ function attachGroupForm() { /** * Link group action buttons, for example in a table or on a specific group's page. + * @param {module:jQuery} el jQuery wrapped element to target. + * @param {{delete_redirect: string}} options Options used to modify behaviour of button actions. */ -function bindGroupButtons(el) { +function bindGroupButtons(el, options) { + if (!options) options = {}; + /** * Link row buttons after table is loaded. */ @@ -63,7 +67,9 @@ function bindGroupButtons(el) { * Buttons that launch a modal dialog */ // Edit group details button - el.find('.js-group-edit').click(function() { + el.find('.js-group-edit').click(function(e) { + e.preventDefault(); + $("body").ufModal({ sourceUrl: site.uri.public + "/modals/groups/edit", ajaxParams: { @@ -76,7 +82,9 @@ function bindGroupButtons(el) { }); // Delete group button - el.find('.js-group-delete').click(function() { + el.find('.js-group-delete').click(function(e) { + e.preventDefault(); + $("body").ufModal({ sourceUrl: site.uri.public + "/modals/groups/confirm-delete", ajaxParams: { @@ -85,14 +93,15 @@ function bindGroupButtons(el) { msgTarget: $("#alerts-page") }); - $("body").on('renderSuccess.ufModal', function (data) { + $("body").on('renderSuccess.ufModal', function () { var modal = $(this).ufModal('getModal'); var form = modal.find('.js-form'); form.ufForm() .on("submitSuccess.ufForm", function() { - // Reload page on success - window.location.reload(); + // Navigate or reload page on success + if (options.delete_redirect) window.location.href = options.delete_redirect; + else window.location.reload(); }); }); }); @@ -100,7 +109,9 @@ function bindGroupButtons(el) { function bindGroupCreationButton(el) { // Link create button - el.find('.js-group-create').click(function() { + el.find('.js-group-create').click(function(e) { + e.preventDefault(); + $("body").ufModal({ sourceUrl: site.uri.public + "/modals/groups/create", msgTarget: $("#alerts-page") diff --git a/app/sprinkles/admin/assets/userfrosting/js/widgets/roles.js b/app/sprinkles/admin/assets/userfrosting/js/widgets/roles.js index 0e32651d9..0fcfb6abd 100644 --- a/app/sprinkles/admin/assets/userfrosting/js/widgets/roles.js +++ b/app/sprinkles/admin/assets/userfrosting/js/widgets/roles.js @@ -34,7 +34,7 @@ function attachRoleForm() { // Set up the form for submission form.ufForm({ - validators: page.validators + validator: page.validators }).on("submitSuccess.ufForm", function() { // Reload page on success window.location.reload(); @@ -44,14 +44,20 @@ function attachRoleForm() { /** * Link role action buttons, for example in a table or on a specific role's page. + * @param {module:jQuery} el jQuery wrapped element to target. + * @param {{delete_redirect: string}} options Options used to modify behaviour of button actions. */ -function bindRoleButtons(el) { +function bindRoleButtons(el, options) { + if (!options) options = {}; + /** * Link row buttons after table is loaded. */ // Manage permissions button - el.find('.js-role-permissions').click(function() { + el.find('.js-role-permissions').click(function(e) { + e.preventDefault(); + var slug = $(this).data('slug'); $("body").ufModal({ sourceUrl: site.uri.public + "/modals/roles/permissions", @@ -88,8 +94,8 @@ function bindRoleButtons(el) { }); // Set up form for submission - form.ufForm({ - }).on("submitSuccess.ufForm", function() { + form.ufForm() + .on("submitSuccess.ufForm", function() { // Reload page on success window.location.reload(); }); @@ -100,7 +106,9 @@ function bindRoleButtons(el) { * Buttons that launch a modal dialog */ // Edit role details button - el.find('.js-role-edit').click(function() { + el.find('.js-role-edit').click(function(e) { + e.preventDefault(); + $("body").ufModal({ sourceUrl: site.uri.public + "/modals/roles/edit", ajaxParams: { @@ -113,7 +121,9 @@ function bindRoleButtons(el) { }); // Delete role button - el.find('.js-role-delete').click(function() { + el.find('.js-role-delete').click(function(e) { + e.preventDefault(); + $("body").ufModal({ sourceUrl: site.uri.public + "/modals/roles/confirm-delete", ajaxParams: { @@ -128,8 +138,9 @@ function bindRoleButtons(el) { form.ufForm() .on("submitSuccess.ufForm", function() { - // Reload page on success - window.location.reload(); + // Navigate or reload page on success + if (options.delete_redirect) window.location.href = options.delete_redirect; + else window.location.reload(); }); }); }); @@ -137,7 +148,9 @@ function bindRoleButtons(el) { function bindRoleCreationButton(el) { // Link create button - el.find('.js-role-create').click(function() { + el.find('.js-role-create').click(function(e) { + e.preventDefault(); + $("body").ufModal({ sourceUrl: site.uri.public + "/modals/roles/create", msgTarget: $("#alerts-page") diff --git a/app/sprinkles/admin/assets/userfrosting/js/widgets/users.js b/app/sprinkles/admin/assets/userfrosting/js/widgets/users.js index 2e153e51a..6c05b91c7 100644 --- a/app/sprinkles/admin/assets/userfrosting/js/widgets/users.js +++ b/app/sprinkles/admin/assets/userfrosting/js/widgets/users.js @@ -17,7 +17,7 @@ function attachUserForm() { // Set up the form for submission form.ufForm({ - validators: page.validators + validator: page.validators }).on("submitSuccess.ufForm", function() { // Reload page on success window.location.reload(); @@ -125,14 +125,19 @@ function updateUser(userName, fieldName, fieldValue) { /** * Link user action buttons, for example in a table or on a specific user's page. + * @param {module:jQuery} el jQuery wrapped element to target. + * @param {{delete_redirect: string}} options Options used to modify behaviour of button actions. */ - function bindUserButtons(el) { + function bindUserButtons(el, options) { + if (!options) options = {}; /** * Buttons that launch a modal dialog */ // Edit general user details button - el.find('.js-user-edit').click(function() { + el.find('.js-user-edit').click(function(e) { + e.preventDefault(); + $("body").ufModal({ sourceUrl: site.uri.public + "/modals/users/edit", ajaxParams: { @@ -145,7 +150,9 @@ function updateUser(userName, fieldName, fieldValue) { }); // Manage user roles button - el.find('.js-user-roles').click(function() { + el.find('.js-user-roles').click(function(e) { + e.preventDefault(); + var userName = $(this).data('user_name'); $("body").ufModal({ sourceUrl: site.uri.public + "/modals/users/roles", @@ -182,8 +189,8 @@ function updateUser(userName, fieldName, fieldValue) { }); // Set up form for submission - form.ufForm({ - }).on("submitSuccess.ufForm", function() { + form.ufForm() + .on("submitSuccess.ufForm", function() { // Reload page on success window.location.reload(); }); @@ -191,7 +198,9 @@ function updateUser(userName, fieldName, fieldValue) { }); // Change user password button - el.find('.js-user-password').click(function() { + el.find('.js-user-password').click(function(e) { + e.preventDefault(); + var userName = $(this).data('user_name'); $("body").ufModal({ sourceUrl: site.uri.public + "/modals/users/password", @@ -201,13 +210,13 @@ function updateUser(userName, fieldName, fieldValue) { msgTarget: $("#alerts-page") }); - $("body").on('renderSuccess.ufModal', function (data) { + $("body").on('renderSuccess.ufModal', function () { var modal = $(this).ufModal('getModal'); var form = modal.find('.js-form'); // Set up form for submission form.ufForm({ - validators: page.validators + validator: page.validators }).on("submitSuccess.ufForm", function() { // Reload page on success window.location.reload(); @@ -216,7 +225,9 @@ function updateUser(userName, fieldName, fieldValue) { toggleChangePasswordMode(modal, userName, 'link'); // On submission, submit either the PUT request, or POST for a password reset, depending on the toggle state - modal.find("input[name='change_password_mode']").click(function() { + modal.find("input[name='change_password_mode']").click(function(e) { + e.preventDefault(); + var changePasswordMode = $(this).val(); toggleChangePasswordMode(modal, userName, changePasswordMode); }); @@ -224,7 +235,9 @@ function updateUser(userName, fieldName, fieldValue) { }); // Delete user button - el.find('.js-user-delete').click(function() { + el.find('.js-user-delete').click(function(e) { + e.preventDefault(); + $("body").ufModal({ sourceUrl: site.uri.public + "/modals/users/confirm-delete", ajaxParams: { @@ -233,14 +246,15 @@ function updateUser(userName, fieldName, fieldValue) { msgTarget: $("#alerts-page") }); - $("body").on('renderSuccess.ufModal', function (data) { + $("body").on('renderSuccess.ufModal', function () { var modal = $(this).ufModal('getModal'); var form = modal.find('.js-form'); form.ufForm() .on("submitSuccess.ufForm", function() { - // Reload page on success - window.location.reload(); + // Navigate or reload page on success + if (options.delete_redirect) window.location.href = options.delete_redirect; + else window.location.reload(); }); }); }); @@ -248,7 +262,9 @@ function updateUser(userName, fieldName, fieldValue) { /** * Direct action buttons */ - el.find('.js-user-activate').click(function() { + el.find('.js-user-activate').click(function(e) { + e.preventDefault(); + var btn = $(this); updateUser(btn.data('user_name'), 'flag_verified', '1'); }); @@ -266,7 +282,9 @@ function updateUser(userName, fieldName, fieldValue) { function bindUserCreationButton(el) { // Link create button - el.find('.js-user-create').click(function() { + el.find('.js-user-create').click(function(e) { + e.preventDefault(); + $("body").ufModal({ sourceUrl: site.uri.public + "/modals/users/create", msgTarget: $("#alerts-page") diff --git a/app/sprinkles/admin/composer.json b/app/sprinkles/admin/composer.json index 8ccd5c0a7..a326ee6a5 100644 --- a/app/sprinkles/admin/composer.json +++ b/app/sprinkles/admin/composer.json @@ -18,5 +18,10 @@ "psr-4": { "UserFrosting\\Sprinkle\\Admin\\": "src/" } + }, + "autoload-dev": { + "psr-4": { + "UserFrosting\\Sprinkle\\Admin\\Tests\\": "tests/" + } } } diff --git a/app/sprinkles/admin/locale/ar/messages.php b/app/sprinkles/admin/locale/ar/messages.php index d4a3a446d..8c57858df 100644 --- a/app/sprinkles/admin/locale/ar/messages.php +++ b/app/sprinkles/admin/locale/ar/messages.php @@ -3,133 +3,134 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * Modern Standard Arabic message token translations for the 'admin' sprinkle. * - * @package userfrosting\i18n\ar * @author Alexander Weissman and Abdullah Seba */ - return [ - "ACTIVITY" => [ - 1 => "نشاط", - 2 => "أنشطة", + 'ACTIVITY' => [ + 1 => 'نشاط', + 2 => 'أنشطة', - "LAST" => "النشاط الاخير", - "PAGE" => "قائمة من أنشطة المستخدم", - "TIME" => "وقت نشاط" + 'LAST' => 'النشاط الاخير', + 'PAGE' => 'قائمة من أنشطة المستخدم', + 'TIME' => 'وقت نشاط' ], - "CACHE" => [ - "CLEAR" => "مسح ذاكرة التخزين", - "CLEAR_CONFIRM" => "هل أنت متأكد أنك تريد مسح ذاكرة التخزين بالموقع؟", - "CLEAR_CONFIRM_YES" => "نعم، إمسح ذاكرة التخزين", - "CLEARED" => "تم مسح ذاكرة التخزين بنجاح" + 'CACHE' => [ + 'CLEAR' => 'مسح ذاكرة التخزين', + 'CLEAR_CONFIRM' => 'هل أنت متأكد أنك تريد مسح ذاكرة التخزين بالموقع؟', + 'CLEAR_CONFIRM_YES' => 'نعم، إمسح ذاكرة التخزين', + 'CLEARED' => 'تم مسح ذاكرة التخزين بنجاح' ], - "DASHBOARD" => "لوحة القيادة", - "NO_FEATURES_YET" => "لا يبدو أن أي ميزات تم إعدادها لهذا الحساب حتى الآن. ربما لم يتم تنفيذها بعد، أو ربما شخص نسي أن يعطيك الوصول. في كلتا الحالتين، نحن سعداء أن يكون لك على متن!", - "DELETE_MASTER" => "لا يمكنك حذف الحساب الرئيسي", - "DELETION_SUCCESSFUL" => "المستعمل {{user_name}} حذف بنجاح", - "DETAILS_UPDATED" => "جدد تفاصيل الحساب للمستخدم {{user_name}}", - "DISABLE_MASTER" => "لا يمكنك تعطيل الحساب الرئيسي", - "DISABLE_SUCCESSFUL" => "حساب المستخدم {{user_name}} عطيل بنجاح", - - "ENABLE_SUCCESSFUL" => "حساب المستخدم {{user_name}} مكين بنجاح", - - "GROUP" => [ - 1 => "مجموعة", - 2 => "مجموعات", - - "CREATE" => "إنشاء مجموعة", - "DELETE" => "حذف مجموعة", - "DELETE_CONFIRM" => "هل أنت متأكد أنك تريد حذف مجموعة {{name}}?", - "DELETE_YES" => "نعم، إحذف مجموعة", - "EDIT" => "تعديل مجموعة", - "ICON" => "رمز المجموعة", - "ICON_EXPLAIN" => "رمز المستخدمين في المجموعه", - "INFO_PAGE" => "صفحة معلومات المجموعة ل {{name}}", + 'DASHBOARD' => 'لوحة القيادة', + 'NO_FEATURES_YET' => 'لا يبدو أن أي ميزات تم إعدادها لهذا الحساب حتى الآن. ربما لم يتم تنفيذها بعد، أو ربما شخص نسي أن يعطيك الوصول. في كلتا الحالتين، نحن سعداء أن يكون لك على متن!', + 'DELETE_MASTER' => 'لا يمكنك حذف الحساب الرئيسي', + 'DELETION_SUCCESSFUL' => 'المستعمل {{user_name}} حذف بنجاح', + 'DETAILS_UPDATED' => 'جدد تفاصيل الحساب للمستخدم {{user_name}}', + 'DISABLE_MASTER' => 'لا يمكنك تعطيل الحساب الرئيسي', + 'DISABLE_SUCCESSFUL' => 'حساب المستخدم {{user_name}} عطيل بنجاح', + + 'ENABLE_SUCCESSFUL' => 'حساب المستخدم {{user_name}} مكين بنجاح', + + 'GROUP' => [ + 1 => 'مجموعة', + 2 => 'مجموعات', + + 'CREATE' => 'إنشاء مجموعة', + 'DELETE' => 'حذف مجموعة', + 'DELETE_CONFIRM' => 'هل أنت متأكد أنك تريد حذف مجموعة {{name}}?', + 'DELETE_YES' => 'نعم، إحذف مجموعة', + 'EDIT' => 'تعديل مجموعة', + 'ICON' => 'رمز المجموعة', + 'ICON_EXPLAIN' => 'رمز المستخدمين في المجموعه', + 'INFO_PAGE' => 'صفحة معلومات المجموعة ل {{name}}', //"MANAGE" => "Manage group", - "NAME" => "أسم المجموعة", - "NAME_EXPLAIN" => "ادخال اسم للمجموعة", - "PAGE_DESCRIPTION" => "قائمة المجموعات لموقعك يوفر أدوات لإدارة التحرير وحذف مجموعات" + 'NAME' => 'أسم المجموعة', + 'NAME_EXPLAIN' => 'ادخال اسم للمجموعة', + 'PAGE_DESCRIPTION' => 'قائمة المجموعات لموقعك يوفر أدوات لإدارة التحرير وحذف مجموعات' ], - "MANUALLY_ACTIVATED" => "تم تفعيل حساب{{user_name}}", - "MASTER_ACCOUNT_EXISTS" => "الحساب الرئيسي موجود بالفعل", - "MIGRATION" => [ - "REQUIRED" => "تحديث قاعدة البيانات مطلوب" + 'MANUALLY_ACTIVATED' => 'تم تفعيل حساب{{user_name}}', + 'MASTER_ACCOUNT_EXISTS' => 'الحساب الرئيسي موجود بالفعل', + 'MIGRATION' => [ + 'REQUIRED' => 'تحديث قاعدة البيانات مطلوب' ], - "PERMISSION" => [ - 1 => "الإذن", - 2 => "مأذونيات", + 'PERMISSION' => [ + 1 => 'الإذن', + 2 => 'مأذونيات', - "ASSIGN_NEW" => "تعيين إذن جديد", - "HOOK_CONDITION" => "الظروف", - "MANAGE" => "إدارة المأذونات", - "PAGE_DESCRIPTION" => "قائمة المأذونات لموقعك", - "UPDATE" => "تحديث المأذونات" + 'ASSIGN_NEW' => 'تعيين إذن جديد', + 'HOOK_CONDITION' => 'الظروف', + 'MANAGE' => 'إدارة المأذونات', + 'PAGE_DESCRIPTION' => 'قائمة المأذونات لموقعك', + 'UPDATE' => 'تحديث المأذونات' ], - "ROLE" => [ - 1 => "وظيفة", - 2 => "وظائف", - - "ASSIGN_NEW" => "تعيين دور جديد", - "CREATE" => "إنشاء دور", - "DELETE" => "حذف دور", - "DELETE_CONFIRM" => "هل أنت متأكد أنك تريد حذف الدور {{name}}?", - "DELETE_YES" => "نعم، حذف دور", - "EDIT" => "إدارة دور", - "INFO_PAGE" => "صفحة معلومات دور {{name}}", - "MANAGE" => "إدارة الوظائف", - "NAME" => "اسم", - "NAME_EXPLAIN" => "أدخل اسما للدور", - "PAGE_DESCRIPTION" => "قائمة الوظائف لموقعك", - "UPDATED" => "تحديث الوظائف" + 'ROLE' => [ + 1 => 'وظيفة', + 2 => 'وظائف', + + 'ASSIGN_NEW' => 'تعيين دور جديد', + 'CREATE' => 'إنشاء دور', + 'DELETE' => 'حذف دور', + 'DELETE_CONFIRM' => 'هل أنت متأكد أنك تريد حذف الدور {{name}}?', + 'DELETE_YES' => 'نعم، حذف دور', + 'EDIT' => 'إدارة دور', + 'INFO_PAGE' => 'صفحة معلومات دور {{name}}', + 'MANAGE' => 'إدارة الوظائف', + 'NAME' => 'اسم', + 'NAME_EXPLAIN' => 'أدخل اسما للدور', + 'PAGE_DESCRIPTION' => 'قائمة الوظائف لموقعك', + 'UPDATED' => 'تحديث الوظائف' ], - "SYSTEM_INFO" => [ - "@TRANSLATION" => "معلومات الجهاز", - - "DB_NAME" => "اسم قاعدة البيانات", - "DB_VERSION" => "إصدار قاعدة البيانات", - "DIRECTORY" => "دليل المشروع", - "PHP_VERSION" => "الإصدار PHP", - "SERVER" => "برنامج الخادم", - "SPRINKLES" => "sprinkles المحمل", - "UF_VERSION" => "إصدار UserFrosting", - "URL" => "رابط قاعدة الموقع" + 'SYSTEM_INFO' => [ + '@TRANSLATION' => 'معلومات الجهاز', + + 'DB_NAME' => 'اسم قاعدة البيانات', + 'DB_VERSION' => 'إصدار قاعدة البيانات', + 'DIRECTORY' => 'دليل المشروع', + 'PHP_VERSION' => 'الإصدار PHP', + 'SERVER' => 'برنامج الخادم', + 'SPRINKLES' => 'sprinkles المحمل', + 'UF_VERSION' => 'إصدار UserFrosting', + 'URL' => 'رابط قاعدة الموقع' ], - "USER" => [ - 1 => "مستخدم", - 2 => "المستخدمين", + 'USER' => [ + 1 => 'مستخدم', + 2 => 'المستخدمين', - "ADMIN" => [ - "CHANGE_PASSWORD" => "تغيير كلمة المرور للمستخدم", - "SEND_PASSWORD_LINK" => "إرسال المستخدم وصلة من شأنها أن تسمح لهم لاختيار كلمة المرور الخاصة بهم", - "SET_PASSWORD" => "تعيين كلمة المرور الخاصة بالمستخدم" + 'ADMIN' => [ + 'CHANGE_PASSWORD' => 'تغيير كلمة المرور للمستخدم', + 'SEND_PASSWORD_LINK' => 'إرسال المستخدم وصلة من شأنها أن تسمح لهم لاختيار كلمة المرور الخاصة بهم', + 'SET_PASSWORD' => 'تعيين كلمة المرور الخاصة بالمستخدم' ], - "ACTIVATE" => "تفعيل المستخدم", - "CREATE" => "إنشاء مستخدم", - "DELETE" => "مسح المستخدم", - "DELETE_CONFIRM" => "هل أنت متأكد أنك تريد حذف المستخدم {{name}}?", - "DELETE_YES" => "نعم، حذف المستخدم", - "DISABLE" => "تعطيل المستخدم ", - "EDIT" => "إدارة المستخدم", - "ENABLE" => "تمكين المستخدم", - "INFO_PAGE" => "صفحة معلومات المستخدم {{name}}", - "PAGE_DESCRIPTION" => "قائمة المستخدمين لموقعك", - "LATEST" => "أحدث المستخدمين", - "VIEW_ALL" => "عرض جميع المستخدمين" + 'ACTIVATE' => 'تفعيل المستخدم', + 'CREATE' => 'إنشاء مستخدم', + 'DELETE' => 'مسح المستخدم', + 'DELETE_CONFIRM' => 'هل أنت متأكد أنك تريد حذف المستخدم {{name}}?', + 'DELETE_YES' => 'نعم، حذف المستخدم', + 'DISABLE' => 'تعطيل المستخدم ', + 'EDIT' => 'إدارة المستخدم', + 'ENABLE' => 'تمكين المستخدم', + 'INFO_PAGE' => 'صفحة معلومات المستخدم {{name}}', + 'PAGE_DESCRIPTION' => 'قائمة المستخدمين لموقعك', + 'LATEST' => 'أحدث المستخدمين', + 'VIEW_ALL' => 'عرض جميع المستخدمين' ], - "X_USER" => [ - 0 => "لا يوجد اي مستخدمين", - 1 => "{{plural}} مستخدم", - 2 => "{{plural}} المستخدمين" + 'X_USER' => [ + 0 => 'لا يوجد اي مستخدمين', + 1 => '{{plural}} مستخدم', + 2 => '{{plural}} المستخدمين' ] ]; diff --git a/app/sprinkles/admin/locale/de_DE/messages.php b/app/sprinkles/admin/locale/de_DE/messages.php index 6e21ab503..575a73a18 100644 --- a/app/sprinkles/admin/locale/de_DE/messages.php +++ b/app/sprinkles/admin/locale/de_DE/messages.php @@ -3,159 +3,160 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * German message token translations for the 'admin' sprinkle. * - * @package userfrosting\i18n\de * @author X-Anonymous-Y * @author kevinrombach * @author splitt3r */ - return [ - "ACTIVITY" => [ - 1 => "Aktivität", - 2 => "Aktivitäten", + 'ACTIVITY' => [ + 1 => 'Aktivität', + 2 => 'Aktivitäten', - "LAST" => "Letzte Aktivität", - "PAGE" => "Eine Auflistung der Benutzeraktivitäten", - "TIME" => "Aktivitätszeit" + 'LAST' => 'Letzte Aktivität', + 'PAGE' => 'Eine Auflistung der Benutzeraktivitäten', + 'TIME' => 'Aktivitätszeit' ], - "CACHE" => [ - "CLEAR" => "Cache löschen", - "CLEAR_CONFIRM" => "Sind Sie sicher, dass Sie den Seiten-Cache löschen möchten?", - "CLEAR_CONFIRM_YES" => "Ja, Cache löschen", - "CLEARED" => "Cache wurde erfolgreich gelöscht!" + 'CACHE' => [ + 'CLEAR' => 'Cache löschen', + 'CLEAR_CONFIRM' => 'Sind Sie sicher, dass Sie den Seiten-Cache löschen möchten?', + 'CLEAR_CONFIRM_YES' => 'Ja, Cache löschen', + 'CLEARED' => 'Cache wurde erfolgreich gelöscht!' ], - "DASHBOARD" => "Übersicht", - "NO_FEATURES_YET" => "Es sieht aus, als wären für Ihren Account noch keine Funktionen aktiviert... bisher. Entweder sie wurden bisher noch nicht implementiert, oder Ihnen fehlen noch die Berechtigungen. Trotzdem ist es schön, dass Sie auf unsere Seite gekommen sind!", - "DELETE_MASTER" => "Sie können das Root-Konto nicht löschen!", - "DELETION_SUCCESSFUL" => "Benutzer {{user_name}} wurde erfolgreich gelöscht.", - "DETAILS_UPDATED" => "Konto-Daten für {{user_name}} aktualisiert.", - "DISABLE_MASTER" => "Sie können das Root-Konto nicht deaktivieren!", - "DISABLE_SELF" => "Sie können Ihr eigenes Konto nicht deaktivieren!", - "DISABLE_SUCCESSFUL" => "Konto von {{user_name}} wurde erfolgreich deaktiviert.", - - "ENABLE_SUCCESSFUL" => "Konto von {{user_name}} wurde erfolgreich aktiviert.", - - "GROUP" => [ - 1 => "Gruppe", - 2 => "Gruppen", - - "CREATE" => "Gruppe erstellen", - "CREATION_SUCCESSFUL" => "Die Gruppe {{name}} wurde erfolgreich erstellt", - "DELETE" => "Gruppe löschen", - "DELETE_CONFIRM" => "Möchten Sie die Gruppe {{name}} wirklich löschen?", - "DELETE_DEFAULT" => "Sie können die Gruppe {{name}} nicht löschen, da es die Standardgruppe für neu registrierte Benutzer ist.", - "DELETE_YES" => "Ja, Gruppe löschen", - "DELETION_SUCCESSFUL" => "Die Gruppe {{name}} wurde erfolgreich gelöscht", - "EDIT" => "Gruppe bearbeiten", - "ICON" => "Gruppensymbol", - "ICON_EXPLAIN" => "Symbol für Gruppenmitglieder", - "INFO_PAGE" => "Gruppeninformationsseite für {{name}}", - "MANAGE" => "Gruppe verwalten", - "NAME" => "Gruppenname", - "NAME_EXPLAIN" => "Geben Sie einen Namen für die Gruppe ein", - "NOT_EMPTY" => "Sie können das nicht tun, denn es sind noch Benutzer mit der Gruppe {{name}} verbunden.", - "PAGE_DESCRIPTION" => "Eine Liste der Gruppen für Ihre Website. Bietet Verwaltungstools für das Bearbeiten und Löschen von Gruppen.", - "SUMMARY" => "Gruppen Zusammenfassung", - "UPDATE" => "Details für die Gruppe {{name}} aktualisiert" + 'DASHBOARD' => 'Übersicht', + 'NO_FEATURES_YET' => 'Es sieht aus, als wären für Ihren Account noch keine Funktionen aktiviert... bisher. Entweder sie wurden bisher noch nicht implementiert, oder Ihnen fehlen noch die Berechtigungen. Trotzdem ist es schön, dass Sie auf unsere Seite gekommen sind!', + 'DELETE_MASTER' => 'Sie können das Root-Konto nicht löschen!', + 'DELETION_SUCCESSFUL' => 'Benutzer {{user_name}} wurde erfolgreich gelöscht.', + 'DETAILS_UPDATED' => 'Konto-Daten für {{user_name}} aktualisiert.', + 'DISABLE_MASTER' => 'Sie können das Root-Konto nicht deaktivieren!', + 'DISABLE_SELF' => 'Sie können Ihr eigenes Konto nicht deaktivieren!', + 'DISABLE_SUCCESSFUL' => 'Konto von {{user_name}} wurde erfolgreich deaktiviert.', + + 'ENABLE_SUCCESSFUL' => 'Konto von {{user_name}} wurde erfolgreich aktiviert.', + + 'GROUP' => [ + 1 => 'Gruppe', + 2 => 'Gruppen', + + 'CREATE' => 'Gruppe erstellen', + 'CREATION_SUCCESSFUL' => 'Die Gruppe {{name}} wurde erfolgreich erstellt', + 'DELETE' => 'Gruppe löschen', + 'DELETE_CONFIRM' => 'Möchten Sie die Gruppe {{name}} wirklich löschen?', + 'DELETE_DEFAULT' => 'Sie können die Gruppe {{name}} nicht löschen, da es die Standardgruppe für neu registrierte Benutzer ist.', + 'DELETE_YES' => 'Ja, Gruppe löschen', + 'DELETION_SUCCESSFUL' => 'Die Gruppe {{name}} wurde erfolgreich gelöscht', + 'EDIT' => 'Gruppe bearbeiten', + 'ICON' => 'Gruppensymbol', + 'ICON_EXPLAIN' => 'Symbol für Gruppenmitglieder', + 'INFO_PAGE' => 'Gruppeninformationsseite für {{name}}', + 'MANAGE' => 'Gruppe verwalten', + 'NAME' => 'Gruppenname', + 'NAME_EXPLAIN' => 'Geben Sie einen Namen für die Gruppe ein', + 'NOT_EMPTY' => 'Sie können das nicht tun, denn es sind noch Benutzer mit der Gruppe {{name}} verbunden.', + 'PAGE_DESCRIPTION' => 'Eine Liste der Gruppen für Ihre Website. Bietet Verwaltungstools für das Bearbeiten und Löschen von Gruppen.', + 'SUMMARY' => 'Gruppen Zusammenfassung', + 'UPDATE' => 'Details für die Gruppe {{name}} aktualisiert' ], - "MANUALLY_ACTIVATED" => "{{user_name}}'s Konto wurde manuell aktiviert.", - "MASTER_ACCOUNT_EXISTS" => "Das Root-Konto existiert bereits!", - "MIGRATION" => [ - "REQUIRED" => "Datenbankaktualisierung erforderlich" + 'MANUALLY_ACTIVATED' => "{{user_name}}'s Konto wurde manuell aktiviert.", + 'MASTER_ACCOUNT_EXISTS' => 'Das Root-Konto existiert bereits!', + 'MIGRATION' => [ + 'REQUIRED' => 'Datenbankaktualisierung erforderlich' ], - "PERMISSION" => [ - 1 => "Berechtigung", - 2 => "Berechtigungen", - - "ASSIGN_NEW" => "Neue Berechtigung zuweisen", - "HOOK_CONDITION" => "Haken/Bedingungen", - "ID" => "Berechtigungs-ID", - "INFO_PAGE" => "Berechtigungs Informationen für '{{name}}'", - "MANAGE" => "Berechtigungen verwalten", - "NOTE_READ_ONLY" => "Bitte beachten Sie: Berechtigungen werden als \"Teil des Quelltexts\" gesehen und können hier nicht bearbeitet werden. Um Berechtigungen hinzuzufügen, zu bearbeiten, oder zu löschen, benutzen Sie bitte folgende Dokumentation zur Datenbank Migration.", - "PAGE_DESCRIPTION" => "Eine Liste der Berechtigungen für Ihre Website. Bietet Verwaltungstools zum Bearbeiten und Löschen von Berechtigungen.", - "SUMMARY" => "Berechtigungs Zusammenfassung", - "UPDATE" => "Berechtigungen aktualisieren", - "VIA_ROLES" => "Besitzt die Berechtigung durch die Rolle" + 'PERMISSION' => [ + 1 => 'Berechtigung', + 2 => 'Berechtigungen', + + 'ASSIGN_NEW' => 'Neue Berechtigung zuweisen', + 'HOOK_CONDITION' => 'Haken/Bedingungen', + 'ID' => 'Berechtigungs-ID', + 'INFO_PAGE' => "Berechtigungs Informationen für '{{name}}'", + 'MANAGE' => 'Berechtigungen verwalten', + 'NOTE_READ_ONLY' => 'Bitte beachten Sie: Berechtigungen werden als "Teil des Quelltexts" gesehen und können hier nicht bearbeitet werden. Um Berechtigungen hinzuzufügen, zu bearbeiten, oder zu löschen, benutzen Sie bitte folgende Dokumentation zur Datenbank Migration.', + 'PAGE_DESCRIPTION' => 'Eine Liste der Berechtigungen für Ihre Website. Bietet Verwaltungstools zum Bearbeiten und Löschen von Berechtigungen.', + 'SUMMARY' => 'Berechtigungs Zusammenfassung', + 'UPDATE' => 'Berechtigungen aktualisieren', + 'VIA_ROLES' => 'Besitzt die Berechtigung durch die Rolle' ], - "ROLE" => [ - 1 => "Rolle", - 2 => "Rollen", - - "ASSIGN_NEW" => "Neue Rolle zuweisen", - "CREATE" => "Rolle erstellen", - "CREATION_SUCCESSFUL" => "Die Rolle {{name}} wurde erfolgreich erstellt", - "DELETE" => "Rolle löschen", - "DELETE_CONFIRM" => "Sind Sie sicher, dass Sie die Rolle {{name}} löschen möchten?", - "DELETE_DEFAULT" => "Sie können die Rolle {{name}} nicht löschen, da es eine Standardrolle für neu registrierte Benutzer ist.", - "DELETE_YES" => "Ja, Rolle löschen", - "DELETION_SUCCESSFUL" => "Die Rolle {{name}} wurde erfolgreich gelöscht", - "EDIT" => "Rolle bearbeiten", - "HAS_USERS" => "Sie können das nicht machen weil es noch Benutzer gibt, die die Rolle {{name}} haben.", - "INFO_PAGE" => "Rolleninformationsseite für {{name}}", - "MANAGE" => "Rollen verwalten", - "NAME" => "Name", - "NAME_EXPLAIN" => "Geben Sie einen Namen für die Rolle ein", - "NAME_IN_USE" => "Eine Rolle mit dem Namen {{name}} existiert bereits", - "PAGE_DESCRIPTION" => "Eine Liste der Rollen für Ihre Website. Bietet Verwaltungstools zum Bearbeiten und Löschen von Rollen.", - "PERMISSIONS_UPDATED" => "Berechtigungen für die Rolle {{name}} aktualisiert", - "SUMMARY" => "Rollen Zusammenfassung", - "UPDATED" => "Rollen aktualisieren" + 'ROLE' => [ + 1 => 'Rolle', + 2 => 'Rollen', + + 'ASSIGN_NEW' => 'Neue Rolle zuweisen', + 'CREATE' => 'Rolle erstellen', + 'CREATION_SUCCESSFUL' => 'Die Rolle {{name}} wurde erfolgreich erstellt', + 'DELETE' => 'Rolle löschen', + 'DELETE_CONFIRM' => 'Sind Sie sicher, dass Sie die Rolle {{name}} löschen möchten?', + 'DELETE_DEFAULT' => 'Sie können die Rolle {{name}} nicht löschen, da es eine Standardrolle für neu registrierte Benutzer ist.', + 'DELETE_YES' => 'Ja, Rolle löschen', + 'DELETION_SUCCESSFUL' => 'Die Rolle {{name}} wurde erfolgreich gelöscht', + 'EDIT' => 'Rolle bearbeiten', + 'HAS_USERS' => 'Sie können das nicht machen weil es noch Benutzer gibt, die die Rolle {{name}} haben.', + 'INFO_PAGE' => 'Rolleninformationsseite für {{name}}', + 'MANAGE' => 'Rollen verwalten', + 'NAME' => 'Name', + 'NAME_EXPLAIN' => 'Geben Sie einen Namen für die Rolle ein', + 'NAME_IN_USE' => 'Eine Rolle mit dem Namen {{name}} existiert bereits', + 'PAGE_DESCRIPTION' => 'Eine Liste der Rollen für Ihre Website. Bietet Verwaltungstools zum Bearbeiten und Löschen von Rollen.', + 'PERMISSIONS_UPDATED' => 'Berechtigungen für die Rolle {{name}} aktualisiert', + 'SUMMARY' => 'Rollen Zusammenfassung', + 'UPDATED' => 'Rollen aktualisieren' ], - "SYSTEM_INFO" => [ - "@TRANSLATION" => "System Information", - - "DB_NAME" => "Name der Datenbank", - "DB_VERSION" => "Datenbankversion", - "DIRECTORY" => "Projektverzeichnis", - "PHP_VERSION" => "PHP-Version", - "SERVER" => "Web-Server-Software", - "SPRINKLES" => "Geladene Sprinkles", - "UF_VERSION" => "UserFrosting Version", - "URL" => "Website-Stamm-Url" + 'SYSTEM_INFO' => [ + '@TRANSLATION' => 'System Information', + + 'DB_NAME' => 'Name der Datenbank', + 'DB_VERSION' => 'Datenbankversion', + 'DIRECTORY' => 'Projektverzeichnis', + 'PHP_VERSION' => 'PHP-Version', + 'SERVER' => 'Web-Server-Software', + 'SPRINKLES' => 'Geladene Sprinkles', + 'UF_VERSION' => 'UserFrosting Version', + 'URL' => 'Website-Stamm-Url' ], - "TOGGLE_COLUMNS" => "Spalten anpassen", + 'TOGGLE_COLUMNS' => 'Spalten anpassen', - "USER" => [ - 1 => "Benutzer", - 2 => "Benutzer", + 'USER' => [ + 1 => 'Benutzer', + 2 => 'Benutzer', - "ADMIN" => [ - "CHANGE_PASSWORD" => "Benutzerpasswort ändern", - "SEND_PASSWORD_LINK" => "Senden Sie dem Benutzer einen Link, der ihnen erlaubt, ihr eigenes Passwort zu wählen", - "SET_PASSWORD" => "Setzen Sie das Passwort des Benutzers als" + 'ADMIN' => [ + 'CHANGE_PASSWORD' => 'Benutzerpasswort ändern', + 'SEND_PASSWORD_LINK' => 'Senden Sie dem Benutzer einen Link, der ihnen erlaubt, ihr eigenes Passwort zu wählen', + 'SET_PASSWORD' => 'Setzen Sie das Passwort des Benutzers als' ], - "ACTIVATE" => "Benutzer aktivieren", - "CREATE" => "Benutzer erstellen", - "CREATED" => "Benutzer {{user_name}} wurde erfolgreich erstellt", - "DELETE" => "Benutzer löschen", - "DELETE_CONFIRM" => "Sind Sie sicher, dass Sie den Benutzer {{name}} löschen möchten?", - "DELETE_YES" => "Ja, Benutzer löschen", - "DISABLE" => "Benutzer deaktivieren", - "EDIT" => "Benutzer bearbeiten", - "ENABLE" => "Benutzer aktivieren", - "INFO_PAGE" => "Benutzerinformationsseite für {{name}}", - "LATEST" => "Neueste Benutzer", - "PAGE_DESCRIPTION" => "Eine Liste der Benutzer für Ihre Website. Bietet Management-Tools, einschließlich der Möglichkeit, Benutzerdaten bearbeiten, manuell aktivieren, Benutzer aktivieren/deaktivieren, und vieles mehr.", - "SUMMARY" => "Benutzer Zusammenfassung", - "VIEW_ALL" => "Alle Benutzer anzeigen", - "WITH_PERMISSION" => "Benutzer mit dieser Berechtigung" + 'ACTIVATE' => 'Benutzer aktivieren', + 'CREATE' => 'Benutzer erstellen', + 'CREATED' => 'Benutzer {{user_name}} wurde erfolgreich erstellt', + 'DELETE' => 'Benutzer löschen', + 'DELETE_CONFIRM' => 'Sind Sie sicher, dass Sie den Benutzer {{name}} löschen möchten?', + 'DELETE_YES' => 'Ja, Benutzer löschen', + 'DISABLE' => 'Benutzer deaktivieren', + 'EDIT' => 'Benutzer bearbeiten', + 'ENABLE' => 'Benutzer aktivieren', + 'INFO_PAGE' => 'Benutzerinformationsseite für {{name}}', + 'LATEST' => 'Neueste Benutzer', + 'PAGE_DESCRIPTION' => 'Eine Liste der Benutzer für Ihre Website. Bietet Management-Tools, einschließlich der Möglichkeit, Benutzerdaten bearbeiten, manuell aktivieren, Benutzer aktivieren/deaktivieren, und vieles mehr.', + 'SUMMARY' => 'Benutzer Zusammenfassung', + 'VIEW_ALL' => 'Alle Benutzer anzeigen', + 'WITH_PERMISSION' => 'Benutzer mit dieser Berechtigung' ], - "X_USER" => [ - 0 => "Keine Benutzer", - 1 => "{{plural}} Benutzer", - 2 => "{{plural}} Benutzer" + 'X_USER' => [ + 0 => 'Keine Benutzer', + 1 => '{{plural}} Benutzer', + 2 => '{{plural}} Benutzer' ] ]; diff --git a/app/sprinkles/admin/locale/el/messages.php b/app/sprinkles/admin/locale/el/messages.php new file mode 100644 index 000000000..60838e1a6 --- /dev/null +++ b/app/sprinkles/admin/locale/el/messages.php @@ -0,0 +1,160 @@ + [ + 1 => "Δραστηριότητα", + 2 => "Δραστηριότητες", + + "LAST" => "Τελευταία δραστηριότητα", + "PAGE" => "Λίστα δραστηριοτήτων των χρηστών", + "TIME" => "Χρόνος δραστηριότητας" + ], + + "CACHE" => [ + "CLEAR" => "Διαγραφή της προσωρινής μνήμης", + "CLEAR_CONFIRM" => "Είστε βέβαιοι ότι θέλετε να εκκαθαρίσετε την προσωρινή μνήμη του ιστότοπου;", + "CLEAR_CONFIRM_YES" => "Ναι, διαγραφή της προσωρινής μνήμης", + "CLEARED" => "Η προσωρινή μνήμη διαγράφηκε με επιτυχία!" + ], + + "DASHBOARD" => "Πίνακας ελέγχου", + "NO_FEATURES_YET" => "Δεν φαίνεται να έχει ρυθμιστεί καμία λειτουργία για αυτόν τον λογαριασμό ... ακόμα. ίσως δεν έχουν υλοποιηθεί ακόμη, ή ίσως κάποιος να ξέχασε να σας δώσει πρόσβαση. Χαιρόμαστε πάντως που είσαι μαζί μας! ", + "DELETE_MASTER" => "Δεν μπορείτε να διαγράψετε τον κύριο λογαριασμό!", + "DELETION_SUCCESSFUL" => "Ο χρήστης {{user_name}} διαγράφηκε.", + "DETAILS_UPDATED" => "Ενημέρωθηκαν οι πληροφορίες λογαριασμού για τον χρήστη {{user_name}}", + "DISABLE_MASTER" => "Δεν μπορείτε να απενεργοποιήσετε τον κύριο λογαριασμό!", + "DISABLE_SELF" => "Δεν μπορείτε να απενεργοποιήσετε τον δικό σας λογαριασμό!", + "DISABLE_SUCCESSFUL" => "Ο λογαριασμός του χρήστη {{user_name}} απενεργοποιήθηκε.", + + "ENABLE_SUCCESSFUL" => "Ο λογαριασμός του χρήστη {{user_name}} ενεργοποιήθηκε.", + + "GROUP" => [ + 1 => "Ομάδα", + 2 => "Ομάδες", + + "CREATE" => "Δημιουργία ομάδας", + "CREATION_SUCCESSFUL" => "Η ομάδα {{name}} δημιουργήθηκε", + "DELETE" => "Διαγραφή ομάδας", + "DELETE_CONFIRM" => "Είστε βέβαιοι ότι θέλετε να διαγράψετε την ομάδα {{name}}?", + "DELETE_DEFAULT" => "Δεν μπορείτε να διαγράψετε την ομάδα {{name}} επειδή είναι η προεπιλεγμένη ομάδα για τους νεοεισερχόμενους χρήστες.", + "DELETE_YES" => "Ναι, διαγραφή ομάδας", + "DELETION_SUCCESSFUL" => "Η ομάδα {{name}} διαγράφηκε", + "EDIT" => "Επεξεργασία ομάδας", + "ICON" => "Εικονίδιο ομάδας", + "ICON_EXPLAIN" => "Εικονίδιο για τα μέλη της ομάδας", + "INFO_PAGE" => "Σελίδα πληροφοριών για την ομάδα {{name}}", + "MANAGE" => "Διαχείριση ομάδας", + "NAME" => "Όνομα ομάδας", + "NAME_EXPLAIN" => "Παρακαλώ εισάγετε ένα όνομα για την ομάδα", + "NOT_EMPTY" => "Δεν μπορείτε να το κάνετε αυτό επειδή εξακολουθούν να υπάρχουν χρήστες που είναι μέλη της ομάδας {{name}}.", + "PAGE_DESCRIPTION" => "Λίστα των ομάδων του ιστότοπού σας. Παρέχει εργαλεία διαχείρισης για την επεξεργασία και τη διαγραφή ομάδων.", + "SUMMARY" => "Σύνοψη ομάδας", + "UPDATE" => "Ενημερώθηκαν οι λεπτομέρειες της ομάδας {{name}}" + ], + + "MANUALLY_ACTIVATED" => "Ο λογαριασμός του {{user_name}}'s ενεργοποίηθηκε χειροκίνητα", + "MASTER_ACCOUNT_EXISTS" => "Υπάρχει ήδη ο κύριος λογαριασμός!", + "MIGRATION" => [ + "REQUIRED" => "Απαιτείται ενημέρωση της βάσης δεδομένων" + ], + + "PERMISSION" => [ + 1 => "Δικαίωμα", + 2 => "Δικαιώματα", + + "ASSIGN_NEW" => "Αντιστοίχιση νέου δικαιώματος", + "HOOK_CONDITION" => "Hook/Conditions", + "ID" => "ID δικαιώματος", + "INFO_PAGE" => "Πληροφορίες δικαιώματων για '{{name}}'", + "MANAGE" => "Διαχείριση δικαιώματων", + "NOTE_READ_ONLY" => "Σημείωση: Τα δικαιώματα θεωρούνται ως \"κομμάτι του κώδικα\" και δεν μπορούν να τροποποιηθούν για το interface. Για να προσθέσετε, αφαιρέσετε, ή τροποποιήσετε δικαιώματα , θα πρέπει οι δημιουργοί του ιστότοπου να ακολουθήσουν την διαδικασία database migration.", + "PAGE_DESCRIPTION" => "Λίστα δικαιωμάτων για τον ιστότοπό σας. Παρέχει εργαλεία διαχείρισης για την επεξεργασία και τη διαγραφή των δικαιωμάτων", + "SUMMARY" => "Περίληψη δικαιωμάτος", + "UPDATE" => "Ενημέρωση δικαιωμάτων", + "VIA_ROLES" => "Έχει άδεια βάσει ρόλου" + ], + + "ROLE" => [ + 1 => "Ρόλος", + 2 => "Ρόλοι", + + "ASSIGN_NEW" => "Ανάθεση νέου ρόλου", + "CREATE" => "Δημιουργία ρόλου", + "CREATION_SUCCESSFUL" => "Επιτυχής δημιουργία ρόλου {{name}}", + "DELETE" => "Διαγραφή ρόλου", + "DELETE_CONFIRM" => "Είστε βέβαιοι ότι θέλετε να διαγράψετε το ρόλο {{name}}?", + "DELETE_DEFAULT" => "Δεν μπορείτε να διαγράψετε το ρόλο {{name}} επειδή είναι ένας προεπιλεγμένος ρόλος για τους νέους χρήστες.", + "DELETE_YES" => "Ναι, διαγραφή ρόλου", + "DELETION_SUCCESSFUL" => "Ο ρόλος {{name}} διαγράφηκε", + "EDIT" => "Επεξεργασία ρόλου", + "HAS_USERS" => "Δεν μπορείτε να το κάνετε επειδή υπάρχουν ακόμα χρήστες που έχουν τον ρόλο {{name}}.", + "INFO_PAGE" => "Σελίδα πληροφοριών του ρόλου {{name}}", + "MANAGE" => "Διαχείριση ρόλου", + "NAME" => "Όνομα", + "NAME_EXPLAIN" => "Παρακαλώ εισάγετε ένα όνομα για το ρόλο", + "NAME_IN_USE" => "Υπάρχει ήδη ρόλος με όνομα {{name}}", + "PAGE_DESCRIPTION" => "Λίστα των ρόλων για τον ιστότοπό σας.Παρέχει εργαλεία διαχείρισης για επεξεργασία και διαγραφή ρόλων.", + "PERMISSIONS_UPDATED" => "Τα δικαιώματα ενημερώθηκαν για τον ρόλο {{name}}", + "SUMMARY" => "Περίληψη ρόλου", + "UPDATED" => "Οι λεπτομέρειες του ρόλου {{name}} ενημερώθηκαν" + ], + + "SYSTEM_INFO" => [ + "@TRANSLATION" => "Πληροφορίες συστήματος", + + "DB_NAME" => "Όνομα βάσης δεδομένων", + "DB_VERSION" => "Έκδοση βάσης δεδομένων", + "DIRECTORY" => "Κατάλογος project", + "PHP_VERSION" => "Έκδοση PHP", + "SERVER" => "Λογισμικό διακομιστή Web", + "SPRINKLES" => "Φορτωμένα sprinkles", + "UF_VERSION" => "Έκδοση UserFrosting", + "URL" => "Site root url" + ], + + "TOGGLE_COLUMNS" => "Εναλλαγή στηλών", + + "USER" => [ + 1 => "Χρήστης", + 2 => "Χρήστες", + + "ADMIN" => [ + "CHANGE_PASSWORD" => "Αλλαγή κωδικού χρήστη", + "SEND_PASSWORD_LINK" => "Αποστολή στον χρήστη ενός συνδέσμου που θα του επιτρέψει να επιλέξει κωδικό πρόσβασης", + "SET_PASSWORD" => "Ορίστε τον κωδικό πρόσβασης του χρήστη ως" + ], + + "ACTIVATE" => "Ενεργοποίηση χρήστη", + "CREATE" => "Δημιουργία χρήστη", + "CREATED" => "Ο χρήστης {{user_name}} δημιουργήθηκε", + "DELETE" => "Διαγραφή χρήστη", + "DELETE_CONFIRM" => "Είστε βέβαιοι ότι θέλετε να διαγράψετε τον χρήστη {{name}}?", + "DELETE_YES" => "Ναι, διαγραφή χρήστη", + "DELETED" => "Ο χρήστης διαγράφηκε", + "DISABLE" => "Απενεργοποίηση χρήστη", + "EDIT" => "Επεξεργασία χρήστη", + "ENABLE" => "Ενεργοποίηση χρήστη", + "INFO_PAGE" => "Σελίδα πληροφοριών για τον χρήστη {{name}}", + "LATEST" => "Πρόσφατοι χρήστες", + "PAGE_DESCRIPTION" => "Λίστα χρηστών για τον ιστότοπό σας.Παρέχει εργαλεία διαχείρισης, συμπεριλαμβανομένης της δυνατότητας επεξεργασίας λεπτομερειών χρηστών, χειροκίνητης ενεργοποίησης χρηστών, ενεργοποίησης / απενεργοποίησης χρηστών και πολλών άλλων.", + "SUMMARY" => "Περίληψη λογαριασμού", + "VIEW_ALL" => "Προβολή όλων των χρηστών", + "WITH_PERMISSION" => "Χρήστες με αυτό το δικαιώμα" + ], + "X_USER" => [ + 0 => "No χρήστες", + 1 => "{{plural}} χρήστης", + 2 => "{{plural}} χρήστες" + ] +]; diff --git a/app/sprinkles/admin/locale/en_US/messages.php b/app/sprinkles/admin/locale/en_US/messages.php index a21e32525..b36fa3fac 100644 --- a/app/sprinkles/admin/locale/en_US/messages.php +++ b/app/sprinkles/admin/locale/en_US/messages.php @@ -3,158 +3,159 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * US English message token translations for the 'admin' sprinkle. * - * @package userfrosting\i18n\en_US * @author Alexander Weissman */ - return [ - "ACTIVITY" => [ - 1 => "Activity", - 2 => "Activities", + 'ACTIVITY' => [ + 1 => 'Activity', + 2 => 'Activities', - "LAST" => "Last Activity", - "PAGE" => "A listing of user activities", - "TIME" => "Activity Time" + 'LAST' => 'Last Activity', + 'PAGE' => 'A listing of user activities', + 'TIME' => 'Activity Time' ], - "CACHE" => [ - "CLEAR" => "Clear cache", - "CLEAR_CONFIRM" => "Are you sure you want to clear the site cache?", - "CLEAR_CONFIRM_YES" => "Yes, clear cache", - "CLEARED" => "Cache cleared successfully !" + 'CACHE' => [ + 'CLEAR' => 'Clear cache', + 'CLEAR_CONFIRM' => 'Are you sure you want to clear the site cache?', + 'CLEAR_CONFIRM_YES' => 'Yes, clear cache', + 'CLEARED' => 'Cache cleared successfully !' ], - "DASHBOARD" => "Dashboard", - "NO_FEATURES_YET" => "It doesn't look like any features have been set up for this account...yet. Maybe they haven't been implemented yet, or maybe someone forgot to give you access. Either way, we're glad to have you aboard!", - "DELETE_MASTER" => "You cannot delete the master account!", - "DELETION_SUCCESSFUL" => "User {{user_name}} has been successfully deleted.", - "DETAILS_UPDATED" => "Account details updated for user {{user_name}}", - "DISABLE_MASTER" => "You cannot disable the master account!", - "DISABLE_SELF" => "You cannot disable your own account!", - "DISABLE_SUCCESSFUL" => "Account for user {{user_name}} has been successfully disabled.", - - "ENABLE_SUCCESSFUL" => "Account for user {{user_name}} has been successfully enabled.", - - "GROUP" => [ - 1 => "Group", - 2 => "Groups", - - "CREATE" => "Create group", - "CREATION_SUCCESSFUL" => "Successfully created group {{name}}", - "DELETE" => "Delete group", - "DELETE_CONFIRM" => "Are you sure you want to delete the group {{name}}?", - "DELETE_DEFAULT" => "You can't delete the group {{name}} because it is the default group for newly registered users.", - "DELETE_YES" => "Yes, delete group", - "DELETION_SUCCESSFUL" => "Successfully deleted group {{name}}", - "EDIT" => "Edit group", - "ICON" => "Group icon", - "ICON_EXPLAIN" => "Icon for group members", - "INFO_PAGE" => "Group information page for {{name}}", - "MANAGE" => "Manage group", - "NAME" => "Group name", - "NAME_EXPLAIN" => "Please enter a name for the group", - "NOT_EMPTY" => "You can't do that because there are still users associated with the group {{name}}.", - "PAGE_DESCRIPTION" => "A listing of the groups for your site. Provides management tools for editing and deleting groups.", - "SUMMARY" => "Group Summary", - "UPDATE" => "Details updated for group {{name}}" + 'DASHBOARD' => 'Dashboard', + 'NO_FEATURES_YET' => "It doesn't look like any features have been set up for this account...yet. Maybe they haven't been implemented yet, or maybe someone forgot to give you access. Either way, we're glad to have you aboard!", + 'DELETE_MASTER' => 'You cannot delete the master account!', + 'DELETION_SUCCESSFUL' => 'User {{user_name}} has been successfully deleted.', + 'DETAILS_UPDATED' => 'Account details updated for user {{user_name}}', + 'DISABLE_MASTER' => 'You cannot disable the master account!', + 'DISABLE_SELF' => 'You cannot disable your own account!', + 'DISABLE_SUCCESSFUL' => 'Account for user {{user_name}} has been successfully disabled.', + + 'ENABLE_SUCCESSFUL' => 'Account for user {{user_name}} has been successfully enabled.', + + 'GROUP' => [ + 1 => 'Group', + 2 => 'Groups', + + 'CREATE' => 'Create group', + 'CREATION_SUCCESSFUL' => 'Successfully created group {{name}}', + 'DELETE' => 'Delete group', + 'DELETE_CONFIRM' => 'Are you sure you want to delete the group {{name}}?', + 'DELETE_DEFAULT' => "You can't delete the group {{name}} because it is the default group for newly registered users.", + 'DELETE_YES' => 'Yes, delete group', + 'DELETION_SUCCESSFUL' => 'Successfully deleted group {{name}}', + 'EDIT' => 'Edit group', + 'ICON' => 'Group icon', + 'ICON_EXPLAIN' => 'Icon for group members', + 'INFO_PAGE' => 'Group information page for {{name}}', + 'MANAGE' => 'Manage group', + 'NAME' => 'Group name', + 'NAME_EXPLAIN' => 'Please enter a name for the group', + 'NOT_EMPTY' => "You can't do that because there are still users associated with the group {{name}}.", + 'PAGE_DESCRIPTION' => 'A listing of the groups for your site. Provides management tools for editing and deleting groups.', + 'SUMMARY' => 'Group Summary', + 'UPDATE' => 'Details updated for group {{name}}' ], - "MANUALLY_ACTIVATED" => "{{user_name}}'s account has been manually activated", - "MASTER_ACCOUNT_EXISTS" => "The master account already exists!", - "MIGRATION" => [ - "REQUIRED" => "Database update required" + 'MANUALLY_ACTIVATED' => "{{user_name}}'s account has been manually activated", + 'MASTER_ACCOUNT_EXISTS' => 'The master account already exists!', + 'MIGRATION' => [ + 'REQUIRED' => 'Database update required' ], - "PERMISSION" => [ - 1 => "Permission", - 2 => "Permissions", - - "ASSIGN_NEW" => "Assign new permission", - "HOOK_CONDITION" => "Hook/Conditions", - "ID" => "Permission ID", - "INFO_PAGE" => "Permission information page for '{{name}}'", - "MANAGE" => "Manage permissions", - "NOTE_READ_ONLY" => "Please note: permissions are considered \"part of the code\" and cannot be modified through the interface. To add, remove, or modify permissions, the site maintainers will need to use a database migration.", - "PAGE_DESCRIPTION" => "A listing of the permissions for your site. Provides management tools for editing and deleting permissions.", - "SUMMARY" => "Permission Summary", - "UPDATE" => "Update permissions", - "VIA_ROLES" => "Has permission via roles" + 'PERMISSION' => [ + 1 => 'Permission', + 2 => 'Permissions', + + 'ASSIGN_NEW' => 'Assign new permission', + 'HOOK_CONDITION' => 'Hook/Conditions', + 'ID' => 'Permission ID', + 'INFO_PAGE' => "Permission information page for '{{name}}'", + 'MANAGE' => 'Manage permissions', + 'NOTE_READ_ONLY' => 'Please note: permissions are considered "part of the code" and cannot be modified through the interface. To add, remove, or modify permissions, the site maintainers will need to use a database migration.', + 'PAGE_DESCRIPTION' => 'A listing of the permissions for your site. Provides management tools for editing and deleting permissions.', + 'SUMMARY' => 'Permission Summary', + 'UPDATE' => 'Update permissions', + 'VIA_ROLES' => 'Has permission via roles' ], - "ROLE" => [ - 1 => "Role", - 2 => "Roles", - - "ASSIGN_NEW" => "Assign new role", - "CREATE" => "Create role", - "CREATION_SUCCESSFUL" => "Successfully created role {{name}}", - "DELETE" => "Delete role", - "DELETE_CONFIRM" => "Are you sure you want to delete the role {{name}}?", - "DELETE_DEFAULT" => "You can't delete the role {{name}} because it is a default role for newly registered users.", - "DELETE_YES" => "Yes, delete role", - "DELETION_SUCCESSFUL" => "Successfully deleted role {{name}}", - "EDIT" => "Edit role", - "HAS_USERS" => "You can't do that because there are still users who have the role {{name}}.", - "INFO_PAGE" => "Role information page for {{name}}", - "MANAGE" => "Manage Roles", - "NAME" => "Name", - "NAME_EXPLAIN" => "Please enter a name for the role", - "NAME_IN_USE" => "A role named {{name}} already exist", - "PAGE_DESCRIPTION" => "A listing of the roles for your site. Provides management tools for editing and deleting roles.", - "PERMISSIONS_UPDATED" => "Permissions updated for role {{name}}", - "SUMMARY" => "Role Summary", - "UPDATED" => "Details updated for role {{name}}" + 'ROLE' => [ + 1 => 'Role', + 2 => 'Roles', + + 'ASSIGN_NEW' => 'Assign new role', + 'CREATE' => 'Create role', + 'CREATION_SUCCESSFUL' => 'Successfully created role {{name}}', + 'DELETE' => 'Delete role', + 'DELETE_CONFIRM' => 'Are you sure you want to delete the role {{name}}?', + 'DELETE_DEFAULT' => "You can't delete the role {{name}} because it is a default role for newly registered users.", + 'DELETE_YES' => 'Yes, delete role', + 'DELETION_SUCCESSFUL' => 'Successfully deleted role {{name}}', + 'EDIT' => 'Edit role', + 'HAS_USERS' => "You can't do that because there are still users who have the role {{name}}.", + 'INFO_PAGE' => 'Role information page for {{name}}', + 'MANAGE' => 'Manage Roles', + 'NAME' => 'Name', + 'NAME_EXPLAIN' => 'Please enter a name for the role', + 'NAME_IN_USE' => 'A role named {{name}} already exist', + 'PAGE_DESCRIPTION' => 'A listing of the roles for your site. Provides management tools for editing and deleting roles.', + 'PERMISSIONS_UPDATED' => 'Permissions updated for role {{name}}', + 'SUMMARY' => 'Role Summary', + 'UPDATED' => 'Details updated for role {{name}}' ], - "SYSTEM_INFO" => [ - "@TRANSLATION" => "System information", - - "DB_NAME" => "Database name", - "DB_VERSION" => "Database version", - "DIRECTORY" => "Project directory", - "PHP_VERSION" => "PHP version", - "SERVER" => "Webserver software", - "SPRINKLES" => "Loaded sprinkles", - "UF_VERSION" => "UserFrosting version", - "URL" => "Site root url" + 'SYSTEM_INFO' => [ + '@TRANSLATION' => 'System information', + + 'DB_NAME' => 'Database name', + 'DB_VERSION' => 'Database version', + 'DIRECTORY' => 'Project directory', + 'PHP_VERSION' => 'PHP version', + 'SERVER' => 'Webserver software', + 'SPRINKLES' => 'Loaded sprinkles', + 'UF_VERSION' => 'UserFrosting version', + 'URL' => 'Site root url' ], - "TOGGLE_COLUMNS" => "Toggle columns", + 'TOGGLE_COLUMNS' => 'Toggle columns', - "USER" => [ - 1 => "User", - 2 => "Users", + 'USER' => [ + 1 => 'User', + 2 => 'Users', - "ADMIN" => [ - "CHANGE_PASSWORD" => "Change User Password", - "SEND_PASSWORD_LINK" => "Send the user a link that will allow them to choose their own password", - "SET_PASSWORD" => "Set the user's password as" + 'ADMIN' => [ + 'CHANGE_PASSWORD' => 'Change User Password', + 'SEND_PASSWORD_LINK' => 'Send the user a link that will allow them to choose their own password', + 'SET_PASSWORD' => "Set the user's password as" ], - "ACTIVATE" => "Activate user", - "CREATE" => "Create user", - "CREATED" => "User {{user_name}} has been successfully created", - "DELETE" => "Delete user", - "DELETE_CONFIRM" => "Are you sure you want to delete the user {{name}}?", - "DELETE_YES" => "Yes, delete user", - "DELETED" => "User deleted", - "DISABLE" => "Disable user", - "EDIT" => "Edit user", - "ENABLE" => "Enable user", - "INFO_PAGE" => "User information page for {{name}}", - "LATEST" => "Latest Users", - "PAGE_DESCRIPTION" => "A listing of the users for your site. Provides management tools including the ability to edit user details, manually activate users, enable/disable users, and more.", - "SUMMARY" => "Account Summary", - "VIEW_ALL" => "View all users", - "WITH_PERMISSION" => "Users with this permission" + 'ACTIVATE' => 'Activate user', + 'CREATE' => 'Create user', + 'CREATED' => 'User {{user_name}} has been successfully created', + 'DELETE' => 'Delete user', + 'DELETE_CONFIRM' => 'Are you sure you want to delete the user {{name}}?', + 'DELETE_YES' => 'Yes, delete user', + 'DELETED' => 'User deleted', + 'DISABLE' => 'Disable user', + 'EDIT' => 'Edit user', + 'ENABLE' => 'Enable user', + 'INFO_PAGE' => 'User information page for {{name}}', + 'LATEST' => 'Latest Users', + 'PAGE_DESCRIPTION' => 'A listing of the users for your site. Provides management tools including the ability to edit user details, manually activate users, enable/disable users, and more.', + 'SUMMARY' => 'Account Summary', + 'VIEW_ALL' => 'View all users', + 'WITH_PERMISSION' => 'Users with this permission' ], - "X_USER" => [ - 0 => "No users", - 1 => "{{plural}} user", - 2 => "{{plural}} users" + 'X_USER' => [ + 0 => 'No users', + 1 => '{{plural}} user', + 2 => '{{plural}} users' ] ]; diff --git a/app/sprinkles/admin/locale/es_ES/messages.php b/app/sprinkles/admin/locale/es_ES/messages.php index 1f8303fc5..c62c408e8 100755 --- a/app/sprinkles/admin/locale/es_ES/messages.php +++ b/app/sprinkles/admin/locale/es_ES/messages.php @@ -3,162 +3,163 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * Spanish message token translations for the 'admin' sprinkle. * - * @package userfrosting\i18n\es_ES * @author rafa31gz */ - return [ - "ACTIVITY" => [ - 1 => "Actividad", - 2 => "Actividades", + 'ACTIVITY' => [ + 1 => 'Actividad', + 2 => 'Actividades', - "LAST" => "Última actividad", - "PAGE" => "Una lista de las actividades del usuario", - "TIME" => "Tiempo de Actividad" + 'LAST' => 'Última actividad', + 'PAGE' => 'Una lista de las actividades del usuario', + 'TIME' => 'Tiempo de Actividad' ], - "ADMIN" => [ - "PANEL" => "Panel de administración" + 'ADMIN' => [ + 'PANEL' => 'Panel de administración' ], - "CACHE" => [ - "CLEAR" => "Limpiar cache", - "CLEAR_CONFIRM" => "¿Estás seguro de que deseas borrar la caché del sitio?", - "CLEAR_CONFIRM_YES" => "Sí, borrar caché", - "CLEARED" => "¡Cache borrado correctamente!" + 'CACHE' => [ + 'CLEAR' => 'Limpiar cache', + 'CLEAR_CONFIRM' => '¿Estás seguro de que deseas borrar la caché del sitio?', + 'CLEAR_CONFIRM_YES' => 'Sí, borrar caché', + 'CLEARED' => '¡Cache borrado correctamente!' ], - "DASHBOARD" => "Tablero", - "NO_FEATURES_YET" => "No parece que se hayan configurado funciones para esta cuenta ... todavía. Tal vez no se han implementado todavía, o tal vez alguien se olvidó de darle acceso. De cualquier manera, ¡estamos encantados de tenerte a bordo!", - "DELETE_MASTER" => "¡No puedes eliminar la cuenta principal!", - "DELETION_SUCCESSFUL" => "El usuario {{user_name}} se ha eliminado correctamente.", - "DETAILS_UPDATED" => "Detalles de la cuenta actualizados para el usuario {{user_name}} ", - "DISABLE_MASTER" => "¡No puedes deshabilitar la cuenta principal!", - "DISABLE_SELF" => "¡No puedes inhabilitar tu propia cuenta!", - "DISABLE_SUCCESSFUL" => "La cuenta para el usuario {{user_name}} se ha desactivado correctamente.", - - "ENABLE_SUCCESSFUL" => "La cuenta para el usuario {{user_name}} se ha habilitado correctamente.", - - "GROUP" => [ - 1 => "Grupo", - 2 => "Grupos", - - "CREATE" => "Crear un grupo", - "CREATION_SUCCESSFUL" => "Grupo creado correctamente {{name}} ", - "DELETE" => "Borrar un grupo", - "DELETE_CONFIRM" => "¿Seguro que quieres eliminar el grupo {{name}} ?", - "DELETE_DEFAULT" => "No puedes eliminar el grupo {{name}} porque es el grupo predeterminado para los usuarios recién registrados.", - "DELETE_YES" => "Sí, eliminar grupo", - "DELETION_SUCCESSFUL" => "Grupo eliminado correctamente {{name}} ", - "EDIT" => "Editar grupo", - "ICON" => "Icono de grupo", - "ICON_EXPLAIN" => "Icono para los miembros del grupo", - "INFO_PAGE" => "Página de información de grupo para {{name}}", - "MANAGE" => "Administrar grupo", - "NAME" => "Nombre del grupo", - "NAME_EXPLAIN" => "Introduce un nombre para el grupo", - "NOT_EMPTY" => "No puedes hacerlo porque todavía hay usuarios asociados con el grupo {{name}} .", - "PAGE_DESCRIPTION" => "Un listado de los grupos para tu sitio. Proporciona herramientas de administración para editar y eliminar grupos.", - "SUMMARY" => "Resumen del grupo", - "UPDATE" => "Detalles actualizados para el grupo {{name}} " + 'DASHBOARD' => 'Tablero', + 'NO_FEATURES_YET' => 'No parece que se hayan configurado funciones para esta cuenta ... todavía. Tal vez no se han implementado todavía, o tal vez alguien se olvidó de darle acceso. De cualquier manera, ¡estamos encantados de tenerte a bordo!', + 'DELETE_MASTER' => '¡No puedes eliminar la cuenta principal!', + 'DELETION_SUCCESSFUL' => 'El usuario {{user_name}} se ha eliminado correctamente.', + 'DETAILS_UPDATED' => 'Detalles de la cuenta actualizados para el usuario {{user_name}} ', + 'DISABLE_MASTER' => '¡No puedes deshabilitar la cuenta principal!', + 'DISABLE_SELF' => '¡No puedes inhabilitar tu propia cuenta!', + 'DISABLE_SUCCESSFUL' => 'La cuenta para el usuario {{user_name}} se ha desactivado correctamente.', + + 'ENABLE_SUCCESSFUL' => 'La cuenta para el usuario {{user_name}} se ha habilitado correctamente.', + + 'GROUP' => [ + 1 => 'Grupo', + 2 => 'Grupos', + + 'CREATE' => 'Crear un grupo', + 'CREATION_SUCCESSFUL' => 'Grupo creado correctamente {{name}} ', + 'DELETE' => 'Borrar un grupo', + 'DELETE_CONFIRM' => '¿Seguro que quieres eliminar el grupo {{name}} ?', + 'DELETE_DEFAULT' => 'No puedes eliminar el grupo {{name}} porque es el grupo predeterminado para los usuarios recién registrados.', + 'DELETE_YES' => 'Sí, eliminar grupo', + 'DELETION_SUCCESSFUL' => 'Grupo eliminado correctamente {{name}} ', + 'EDIT' => 'Editar grupo', + 'ICON' => 'Icono de grupo', + 'ICON_EXPLAIN' => 'Icono para los miembros del grupo', + 'INFO_PAGE' => 'Página de información de grupo para {{name}}', + 'MANAGE' => 'Administrar grupo', + 'NAME' => 'Nombre del grupo', + 'NAME_EXPLAIN' => 'Introduce un nombre para el grupo', + 'NOT_EMPTY' => 'No puedes hacerlo porque todavía hay usuarios asociados con el grupo {{name}} .', + 'PAGE_DESCRIPTION' => 'Un listado de los grupos para tu sitio. Proporciona herramientas de administración para editar y eliminar grupos.', + 'SUMMARY' => 'Resumen del grupo', + 'UPDATE' => 'Detalles actualizados para el grupo {{name}} ' ], - "MANUALLY_ACTIVATED" => "La cuenta de {{user_name}} se ha activado manualmente", - "MASTER_ACCOUNT_EXISTS" => "¡La cuenta maestra ya existe!", - "MIGRATION" => [ - "REQUIRED" => "Se requiere actualizar la base de datos" + 'MANUALLY_ACTIVATED' => 'La cuenta de {{user_name}} se ha activado manualmente', + 'MASTER_ACCOUNT_EXISTS' => '¡La cuenta maestra ya existe!', + 'MIGRATION' => [ + 'REQUIRED' => 'Se requiere actualizar la base de datos' ], - "PERMISSION" => [ - 1 => "Permiso", - 2 => "Permisos", - - "ASSIGN_NEW" => "Asignar nuevo permiso", - "HOOK_CONDITION" => "Hook/Condiciones", - "ID" => "ID de permiso", - "INFO_PAGE" => "Página de autor del permiso de '{{name}}'", - "MANAGE" => "Administrar permisos", - "NOTE_READ_ONLY" => " Ten en cuenta: los permisos se consideran \"parte del código\" y no se pueden modificar a través de la interfaz. Para agregar, eliminar o modificar permisos, los mantenedores del sitio necesitarán usar una migración de la base de datos . ", - "PAGE_DESCRIPTION" => "Una lista de los permisos para tu sitio. Proporciona herramientas de administración para editar y eliminar permisos.", - "SUMMARY" => "Resumen del permiso", - "UPDATE" => "Actualizar permisos", - "VIA_ROLES" => "Tiene permiso para los roles" + 'PERMISSION' => [ + 1 => 'Permiso', + 2 => 'Permisos', + + 'ASSIGN_NEW' => 'Asignar nuevo permiso', + 'HOOK_CONDITION' => 'Hook/Condiciones', + 'ID' => 'ID de permiso', + 'INFO_PAGE' => "Página de autor del permiso de '{{name}}'", + 'MANAGE' => 'Administrar permisos', + 'NOTE_READ_ONLY' => ' Ten en cuenta: los permisos se consideran "parte del código" y no se pueden modificar a través de la interfaz. Para agregar, eliminar o modificar permisos, los mantenedores del sitio necesitarán usar una migración de la base de datos . ', + 'PAGE_DESCRIPTION' => 'Una lista de los permisos para tu sitio. Proporciona herramientas de administración para editar y eliminar permisos.', + 'SUMMARY' => 'Resumen del permiso', + 'UPDATE' => 'Actualizar permisos', + 'VIA_ROLES' => 'Tiene permiso para los roles' ], - "ROLE" => [ - 1 => "Rol(funcion)", - 2 => "Roles(funciones)", - - "ASSIGN_NEW" => "Asignar nueva rol", - "CREATE" => "Crear un rol", - "CREATION_SUCCESSFUL" => "Función creada correctamente {{name}} ", - "DELETE" => "Eliminar rol", - "DELETE_CONFIRM" => "¿Seguro que quieres eliminar la función {{name}} ?", - "DELETE_DEFAULT" => "No puedes eliminar el rol {{name}} porque es un rol predeterminado para los usuarios recién registrados.", - "DELETE_YES" => "Sí, borrar función", - "DELETION_SUCCESSFUL" => "Se ha eliminado la función {{nombre}} ", - "EDIT" => "Editar función", - "HAS_USERS" => "No puedes hacerlo porque todavía hay usuarios que tienen el rol {{name}} .", - "INFO_PAGE" => "Página de información de funciones de {{name}}", - "MANAGE" => "Administrar roles", - "NAME" => "Nombre", - "NAME_EXPLAIN" => "Ingresa un nombre para el rol", - "NAME_IN_USE" => "Ya existe un rol denominado {{name}} ", - "PAGE_DESCRIPTION" => "Una lista de las funciones de tu sitio. Proporciona herramientas de administración para editar y eliminar roles.", - "PERMISSIONS_UPDATED" => "Permisos actualizados para el rol {{name}} ", - "SUMMARY" => "Resumen del rol", - "UPDATED" => "Detalles actualizados para el rol {{name}} " + 'ROLE' => [ + 1 => 'Rol(funcion)', + 2 => 'Roles(funciones)', + + 'ASSIGN_NEW' => 'Asignar nueva rol', + 'CREATE' => 'Crear un rol', + 'CREATION_SUCCESSFUL' => 'Función creada correctamente {{name}} ', + 'DELETE' => 'Eliminar rol', + 'DELETE_CONFIRM' => '¿Seguro que quieres eliminar la función {{name}} ?', + 'DELETE_DEFAULT' => 'No puedes eliminar el rol {{name}} porque es un rol predeterminado para los usuarios recién registrados.', + 'DELETE_YES' => 'Sí, borrar función', + 'DELETION_SUCCESSFUL' => 'Se ha eliminado la función {{nombre}} ', + 'EDIT' => 'Editar función', + 'HAS_USERS' => 'No puedes hacerlo porque todavía hay usuarios que tienen el rol {{name}} .', + 'INFO_PAGE' => 'Página de información de funciones de {{name}}', + 'MANAGE' => 'Administrar roles', + 'NAME' => 'Nombre', + 'NAME_EXPLAIN' => 'Ingresa un nombre para el rol', + 'NAME_IN_USE' => 'Ya existe un rol denominado {{name}} ', + 'PAGE_DESCRIPTION' => 'Una lista de las funciones de tu sitio. Proporciona herramientas de administración para editar y eliminar roles.', + 'PERMISSIONS_UPDATED' => 'Permisos actualizados para el rol {{name}} ', + 'SUMMARY' => 'Resumen del rol', + 'UPDATED' => 'Detalles actualizados para el rol {{name}} ' ], - "SYSTEM_INFO" => [ - "@TRANSLATION" => "Información del sistema", - - "DB_NAME" => "Nombre de la base de datos", - "DB_VERSION" => "Versión de base de datos", - "DIRECTORY" => "Directorio del proyecto", - "PHP_VERSION" => "Versión de PHP", - "SERVER" => "Software de servidor Web", - "SPRINKLES" => "Sprinkles cargados", - "UF_VERSION" => "UserFrosting versión", - "URL" => "URL root del sitio" + 'SYSTEM_INFO' => [ + '@TRANSLATION' => 'Información del sistema', + + 'DB_NAME' => 'Nombre de la base de datos', + 'DB_VERSION' => 'Versión de base de datos', + 'DIRECTORY' => 'Directorio del proyecto', + 'PHP_VERSION' => 'Versión de PHP', + 'SERVER' => 'Software de servidor Web', + 'SPRINKLES' => 'Sprinkles cargados', + 'UF_VERSION' => 'UserFrosting versión', + 'URL' => 'URL root del sitio' ], - "TOGGLE_COLUMNS" => "Alternar columnas", - "NO_DATA" => "No puede quedar vacio.", + 'TOGGLE_COLUMNS' => 'Alternar columnas', + 'NO_DATA' => 'No puede quedar vacio.', - "USER" => [ - 1 => "Usuario", - 2 => "Usuarios", + 'USER' => [ + 1 => 'Usuario', + 2 => 'Usuarios', - "ADMIN" => [ - "CHANGE_PASSWORD" => "Cambiar contraseña de usuario", - "SEND_PASSWORD_LINK" => "Enviar al usuario un enlace que les permita elegir su propia contraseña", - "SET_PASSWORD" => "Establece la contraseña del usuario como" + 'ADMIN' => [ + 'CHANGE_PASSWORD' => 'Cambiar contraseña de usuario', + 'SEND_PASSWORD_LINK' => 'Enviar al usuario un enlace que les permita elegir su propia contraseña', + 'SET_PASSWORD' => 'Establece la contraseña del usuario como' ], - "ACTIVATE" => "Activar usuario", - "CREATE" => "Crear usuario", - "CREATED" => "Se ha creado correctamente el usuario {{user_name}} ", - "DELETE" => "Borrar usuario", - "DELETE_CONFIRM" => "¿Seguro que deseas eliminar el usuario {{name}} ?", - "DELETE_YES" => "Sí, eliminar usuario", - "DISABLE" => "Deshabilitar usuario", - "EDIT" => "Editar usuario", - "ENABLE" => "Habilitar usuario", - "INFO_PAGE" => "Página de información de usuario de {{name}}", - "LATEST" => "Usuarios más recientes", - "PAGE_DESCRIPTION" => "Una lista de los usuarios para tu sitio. Proporciona herramientas de administración que incluyen la capacidad de editar detalles de usuario, activar manualmente usuarios, habilitar / deshabilitar usuarios y más.", - "SUMMARY" => "Resumen de la cuenta", - "VIEW_ALL" => "Ver todos los usuarios", - "WITH_PERMISSION" => "Usuarios con este permiso" + 'ACTIVATE' => 'Activar usuario', + 'CREATE' => 'Crear usuario', + 'CREATED' => 'Se ha creado correctamente el usuario {{user_name}} ', + 'DELETE' => 'Borrar usuario', + 'DELETE_CONFIRM' => '¿Seguro que deseas eliminar el usuario {{name}} ?', + 'DELETE_YES' => 'Sí, eliminar usuario', + 'DISABLE' => 'Deshabilitar usuario', + 'EDIT' => 'Editar usuario', + 'ENABLE' => 'Habilitar usuario', + 'INFO_PAGE' => 'Página de información de usuario de {{name}}', + 'LATEST' => 'Usuarios más recientes', + 'PAGE_DESCRIPTION' => 'Una lista de los usuarios para tu sitio. Proporciona herramientas de administración que incluyen la capacidad de editar detalles de usuario, activar manualmente usuarios, habilitar / deshabilitar usuarios y más.', + 'SUMMARY' => 'Resumen de la cuenta', + 'VIEW_ALL' => 'Ver todos los usuarios', + 'WITH_PERMISSION' => 'Usuarios con este permiso' ], - "X_USER" => [ - 0 => "No hay usuarios", - 1 => "{{plural}} usuario", - 2 => "{{plural}} usuarios" + 'X_USER' => [ + 0 => 'No hay usuarios', + 1 => '{{plural}} usuario', + 2 => '{{plural}} usuarios' ] ]; diff --git a/app/sprinkles/admin/locale/fa/messages.php b/app/sprinkles/admin/locale/fa/messages.php index 75a8dee1c..5a38ad65c 100644 --- a/app/sprinkles/admin/locale/fa/messages.php +++ b/app/sprinkles/admin/locale/fa/messages.php @@ -1,158 +1,158 @@ [ - 1 => "فعالیت", - 2 => "فعالیت ها", - - "LAST" => "آخرین فعالیت", - "PAGE" => "لیستی از فعالیت های کاربر", - "TIME" => "زمان فعالیت" - ], - - "CACHE" => [ - "CLEAR" => "پاک سازی کش", - "CLEAR_CONFIRM" => "آیا مطمئن هستید که میخواهید کش سایت را پاک سازی کنید؟", - "CLEAR_CONFIRM_YES" => "بله، کش پاک سازی شود", - "CLEARED" => "کش با موفقیت پاک سازی شد" - ], - - "DASHBOARD" => "کارتابل", - "DELETE_MASTER" => "شما نمیتوانید کاربر اصلی را حذف کنید", - "DELETION_SUCCESSFUL" => "{{user_name}} با موفقیت حذف شد.", - "DETAILS_UPDATED" => "جزئیات {{user_name}} با موفقیت ذخیره شد.", - "DISABLE_MASTER" => "شما نمیتوانید کاربر اصلی را غیر فعال کنید.", - "DISABLE_SUCCESSFUL" => "حساب کاربری {{user_name}} با موفقیت غیر فعال شد.", - - "ENABLE_SUCCESSFUL" => "حساب کاربری {{user_name}} با موفقیت فعال شد.", - - "GROUP" => [ - 1 => "گروه", - 2 => "گروه ها", - - "CREATE" => "اضافه کردن گروه", - "CREATION_SUCCESSFUL" => "گروه {{name}} با موفقیت اضافه شد", - "DELETE" => "حذف گروه", - "DELETE_CONFIRM" => "آیا مطمئن هستید که میخواهید گروه {{name}} را حذف کنید؟", - "DELETE_DEFAULT" => "شما نمیتوانید گروه {{name}} را حذف کنید چون به عنوان گروه پیش فرض برای کاربران جدید انتخاب شده است.", - "DELETE_YES" => "بله، گروه حذف شود", - "DELETION_SUCCESSFUL" => "گروه {{name}} با موفقیت حذف شد.", - "EDIT" => "ویرایش گروه", - "ICON" => "آیکن گروه", - "ICON_EXPLAIN" => "آیکن برای اعضای گروه", - "INFO_PAGE" => "صفحه توضیحات گروه برای {{name}}", - "MANAGE" => "مدیریت گروه", - "NAME" => "نام گروه", - "NAME_EXPLAIN" => "لطفا نام گروه را وارد کنید", - "NOT_EMPTY" => "نمیتوان این کار را کرد چون هنوز کاربرانی عضو گروه {{name}} هستند.", - "PAGE_DESCRIPTION" => "لیست گروه های وب سایت شما. امکان مدیریت این گروه ها در این صفحه وجود دارد.", - "SUMMARY" => "توضیحات گروه", - "UPDATE" => "اطلاعات گروه {{name}} به روز رسانی شد." + 'ACTIVITY' => [ + 1 => 'فعالیت', + 2 => 'فعالیت ها', + + 'LAST' => 'آخرین فعالیت', + 'PAGE' => 'لیستی از فعالیت های کاربر', + 'TIME' => 'زمان فعالیت' + ], + + 'CACHE' => [ + 'CLEAR' => 'پاک سازی کش', + 'CLEAR_CONFIRM' => 'آیا مطمئن هستید که میخواهید کش سایت را پاک سازی کنید؟', + 'CLEAR_CONFIRM_YES' => 'بله، کش پاک سازی شود', + 'CLEARED' => 'کش با موفقیت پاک سازی شد' + ], + + 'DASHBOARD' => 'کارتابل', + 'DELETE_MASTER' => 'شما نمیتوانید کاربر اصلی را حذف کنید', + 'DELETION_SUCCESSFUL' => '{{user_name}} با موفقیت حذف شد.', + 'DETAILS_UPDATED' => 'جزئیات {{user_name}} با موفقیت ذخیره شد.', + 'DISABLE_MASTER' => 'شما نمیتوانید کاربر اصلی را غیر فعال کنید.', + 'DISABLE_SUCCESSFUL' => 'حساب کاربری {{user_name}} با موفقیت غیر فعال شد.', + + 'ENABLE_SUCCESSFUL' => 'حساب کاربری {{user_name}} با موفقیت فعال شد.', + + 'GROUP' => [ + 1 => 'گروه', + 2 => 'گروه ها', + + 'CREATE' => 'اضافه کردن گروه', + 'CREATION_SUCCESSFUL' => 'گروه {{name}} با موفقیت اضافه شد', + 'DELETE' => 'حذف گروه', + 'DELETE_CONFIRM' => 'آیا مطمئن هستید که میخواهید گروه {{name}} را حذف کنید؟', + 'DELETE_DEFAULT' => 'شما نمیتوانید گروه {{name}} را حذف کنید چون به عنوان گروه پیش فرض برای کاربران جدید انتخاب شده است.', + 'DELETE_YES' => 'بله، گروه حذف شود', + 'DELETION_SUCCESSFUL' => 'گروه {{name}} با موفقیت حذف شد.', + 'EDIT' => 'ویرایش گروه', + 'ICON' => 'آیکن گروه', + 'ICON_EXPLAIN' => 'آیکن برای اعضای گروه', + 'INFO_PAGE' => 'صفحه توضیحات گروه برای {{name}}', + 'MANAGE' => 'مدیریت گروه', + 'NAME' => 'نام گروه', + 'NAME_EXPLAIN' => 'لطفا نام گروه را وارد کنید', + 'NOT_EMPTY' => 'نمیتوان این کار را کرد چون هنوز کاربرانی عضو گروه {{name}} هستند.', + 'PAGE_DESCRIPTION' => 'لیست گروه های وب سایت شما. امکان مدیریت این گروه ها در این صفحه وجود دارد.', + 'SUMMARY' => 'توضیحات گروه', + 'UPDATE' => 'اطلاعات گروه {{name}} به روز رسانی شد.' ], - "MANUALLY_ACTIVATED" => "حساب کاربری {{user_name}} بصورت دستی فعال شد.", - "MASTER_ACCOUNT_EXISTS" => "حساب کاربری اصلی وجود دارد!", - "MIGRATION" => [ - "REQUIRED" => "به روز رسانی پایگاه داده ها باید انجام شود" + 'MANUALLY_ACTIVATED' => 'حساب کاربری {{user_name}} بصورت دستی فعال شد.', + 'MASTER_ACCOUNT_EXISTS' => 'حساب کاربری اصلی وجود دارد!', + 'MIGRATION' => [ + 'REQUIRED' => 'به روز رسانی پایگاه داده ها باید انجام شود' ], - "PERMISSION" => [ - 1 => "دسترسی", - 2 => "دسترسی ها", - - "ASSIGN_NEW" => "دادن دسترسی", - "HOOK_CONDITION" => "قلاب/شرط", - "ID" => "آی دی دسترسی", - "INFO_PAGE" => "توضیحات دسترسی {{name}}", - "MANAGE" => "مدیریت دسترسی ها", - "NOTE_READ_ONLY" => "توجه بفرماییددسترسی ها بخشی از کد میباشند و آن ها را نمیتوان از اینترفیس تغییر داد. برای این تغییرات، مبایستی که مدیر، از دیتابیس مایگریشن استفاده کند. ", - "PAGE_DESCRIPTION" => "لیست دسترسی های وب سایت شما. امکان مدیریت این دسترسی ها در این صفحه وجود دارد.", - "SUMMARY" => "توضیحات دسترسی ها", - "UPDATE" => "به روز رسانی دسترسی ها", - "VIA_ROLES" => "از طریق وظیفه ها دسترسی دارد" + 'PERMISSION' => [ + 1 => 'دسترسی', + 2 => 'دسترسی ها', + + 'ASSIGN_NEW' => 'دادن دسترسی', + 'HOOK_CONDITION' => 'قلاب/شرط', + 'ID' => 'آی دی دسترسی', + 'INFO_PAGE' => 'توضیحات دسترسی {{name}}', + 'MANAGE' => 'مدیریت دسترسی ها', + 'NOTE_READ_ONLY' => 'توجه بفرماییددسترسی ها بخشی از کد میباشند و آن ها را نمیتوان از اینترفیس تغییر داد. برای این تغییرات، مبایستی که مدیر، از دیتابیس مایگریشن استفاده کند. ', + 'PAGE_DESCRIPTION' => 'لیست دسترسی های وب سایت شما. امکان مدیریت این دسترسی ها در این صفحه وجود دارد.', + 'SUMMARY' => 'توضیحات دسترسی ها', + 'UPDATE' => 'به روز رسانی دسترسی ها', + 'VIA_ROLES' => 'از طریق وظیفه ها دسترسی دارد' ], - "ROLE" => [ - 1 => "وظیفه", - 2 => "وظیفه ها", - - "ASSIGN_NEW" => "دادن وظیفه", - "CREATE" => "ساخت وظیفه", - "CREATION_SUCCESSFUL" => "وظیفه {{name}} با موفقیت ساخته شد", - "DELETE" => "حذف وظیفه", - "DELETE_CONFIRM" => "اطمینان دارید که میخواهید وظیفه {{name}} را حذف کنید؟", - "DELETE_DEFAULT" => "شما نمیتوانید وظیفه {{name}} را حذف کنید زیرا کاربرانی که تازه ثبت نام کنند، این وظیفه را دریافت خواهند کرد.", - "DELETE_YES" => "بله، وظیفه حذف شود", - "DELETION_SUCCESSFUL" => "وظیفه {{name}} با موفقیت حذف شد", - "EDIT" => "ویرایش وظیفه", - "HAS_USERS" => "نمیتوانید این کار را انجام دهید زیرا کاربرانی وظیفه {{name}} را هنوز دارند.", - "INFO_PAGE" => "صفحه توضیحات وظیفه {{name}}", - "MANAGE" => "مدیریت وظیفه ها", - "NAME" => "نام", - "NAME_EXPLAIN" => "لطفا برای وظیفه نامی انتخاب کنید", - "NAME_IN_USE" => "وظیفه ای با نام {{name}} موجود است", - "PAGE_DESCRIPTION" => "لیست وظیفه های وب سایت شما. امکان مدیریت این وظیفه ها در این صفحه وجود دارد.", - "PERMISSIONS_UPDATED" => "دسترسی ها برای وظیفه {{name}} به روز رسانی شد", - "SUMMARY" => "خلاصه وظیفه", - "UPDATED" => "اطلاعات وظیفه {{name}} به روز رسانی شد" + 'ROLE' => [ + 1 => 'وظیفه', + 2 => 'وظیفه ها', + + 'ASSIGN_NEW' => 'دادن وظیفه', + 'CREATE' => 'ساخت وظیفه', + 'CREATION_SUCCESSFUL' => 'وظیفه {{name}} با موفقیت ساخته شد', + 'DELETE' => 'حذف وظیفه', + 'DELETE_CONFIRM' => 'اطمینان دارید که میخواهید وظیفه {{name}} را حذف کنید؟', + 'DELETE_DEFAULT' => 'شما نمیتوانید وظیفه {{name}} را حذف کنید زیرا کاربرانی که تازه ثبت نام کنند، این وظیفه را دریافت خواهند کرد.', + 'DELETE_YES' => 'بله، وظیفه حذف شود', + 'DELETION_SUCCESSFUL' => 'وظیفه {{name}} با موفقیت حذف شد', + 'EDIT' => 'ویرایش وظیفه', + 'HAS_USERS' => 'نمیتوانید این کار را انجام دهید زیرا کاربرانی وظیفه {{name}} را هنوز دارند.', + 'INFO_PAGE' => 'صفحه توضیحات وظیفه {{name}}', + 'MANAGE' => 'مدیریت وظیفه ها', + 'NAME' => 'نام', + 'NAME_EXPLAIN' => 'لطفا برای وظیفه نامی انتخاب کنید', + 'NAME_IN_USE' => 'وظیفه ای با نام {{name}} موجود است', + 'PAGE_DESCRIPTION' => 'لیست وظیفه های وب سایت شما. امکان مدیریت این وظیفه ها در این صفحه وجود دارد.', + 'PERMISSIONS_UPDATED' => 'دسترسی ها برای وظیفه {{name}} به روز رسانی شد', + 'SUMMARY' => 'خلاصه وظیفه', + 'UPDATED' => 'اطلاعات وظیفه {{name}} به روز رسانی شد' ], - "SYSTEM_INFO" => [ - "@TRANSLATION" => "توضیحات سیستم", - - "DB_NAME" => "نام پایگاه داده", - "DB_VERSION" => "نسخه پایگاه داده", - "DIRECTORY" => "دایرکتوری پروژه", - "PHP_VERSION" => "نسخه پی اچ پی", - "SERVER" => "نرمافزار وب سرور", - "SPRINKLES" => "اسپرینکل های بارگذاری شده", - "UF_VERSION" => "نسخه یوزرفروستینگ", - "URL" => "آدرس رووت وب سایت" + 'SYSTEM_INFO' => [ + '@TRANSLATION' => 'توضیحات سیستم', + + 'DB_NAME' => 'نام پایگاه داده', + 'DB_VERSION' => 'نسخه پایگاه داده', + 'DIRECTORY' => 'دایرکتوری پروژه', + 'PHP_VERSION' => 'نسخه پی اچ پی', + 'SERVER' => 'نرمافزار وب سرور', + 'SPRINKLES' => 'اسپرینکل های بارگذاری شده', + 'UF_VERSION' => 'نسخه یوزرفروستینگ', + 'URL' => 'آدرس رووت وب سایت' ], - "TOGGLE_COLUMNS" => "تغییر ستون", + 'TOGGLE_COLUMNS' => 'تغییر ستون', - "USER" => [ - 1 => "کاربر", - 2 => "کاربران", + 'USER' => [ + 1 => 'کاربر', + 2 => 'کاربران', - "ADMIN" => [ - "CHANGE_PASSWORD" => "تغییر گذرواژه کاربر", - "SEND_PASSWORD_LINK" => "برای کاربر ایمیلی ارسال شود تا گذرواژه خود را تغییر دهد", - "SET_PASSWORD" => "گذرواژه کاربر را انتخاب کنید" + 'ADMIN' => [ + 'CHANGE_PASSWORD' => 'تغییر گذرواژه کاربر', + 'SEND_PASSWORD_LINK' => 'برای کاربر ایمیلی ارسال شود تا گذرواژه خود را تغییر دهد', + 'SET_PASSWORD' => 'گذرواژه کاربر را انتخاب کنید' ], - "ACTIVATE" => "کاربر فعال", - "CREATE" => "اضافه کردن کاربر", - "CREATED" => "کاربر {{user_name}} با موفقیت اضافه شد", - "DELETE" => "حذف کاربر", - "DELETE_CONFIRM" => "آیا اطمینان دارید که میخواهید کاربر {{name}} را حذف کنید؟", - "DELETE_YES" => "بله، کاربر حذف شود", - "DISABLE" => "غیر فعال سازی کاربر", - "EDIT" => "ویرایش کاربر", - "ENABLE" => "فعال سازی کاربر", - "INFO_PAGE" => "صفحه توضیحات کاربر {{name}}", - "LATEST" => "آخرین کاربران", - "PAGE_DESCRIPTION" => "لیستی از کاربران سایت. این صفحه به شما امکان ویرایش، فعال سازی و غیر فعال سازی کاربران را می دهد.", - "SUMMARY" => "خلاصه حساب", - "VIEW_ALL" => "تماشای همه ی کاربران", - "WITH_PERMISSION" => "کاربرانی که این دسترسی را دارند" + 'ACTIVATE' => 'کاربر فعال', + 'CREATE' => 'اضافه کردن کاربر', + 'CREATED' => 'کاربر {{user_name}} با موفقیت اضافه شد', + 'DELETE' => 'حذف کاربر', + 'DELETE_CONFIRM' => 'آیا اطمینان دارید که میخواهید کاربر {{name}} را حذف کنید؟', + 'DELETE_YES' => 'بله، کاربر حذف شود', + 'DISABLE' => 'غیر فعال سازی کاربر', + 'EDIT' => 'ویرایش کاربر', + 'ENABLE' => 'فعال سازی کاربر', + 'INFO_PAGE' => 'صفحه توضیحات کاربر {{name}}', + 'LATEST' => 'آخرین کاربران', + 'PAGE_DESCRIPTION' => 'لیستی از کاربران سایت. این صفحه به شما امکان ویرایش، فعال سازی و غیر فعال سازی کاربران را می دهد.', + 'SUMMARY' => 'خلاصه حساب', + 'VIEW_ALL' => 'تماشای همه ی کاربران', + 'WITH_PERMISSION' => 'کاربرانی که این دسترسی را دارند' ], - "X_USER" => [ - 0 => "هیچ کاربری", - 1 => "{{plural}} کاربر", - 2 => "{{plural}} کاربر" + 'X_USER' => [ + 0 => 'هیچ کاربری', + 1 => '{{plural}} کاربر', + 2 => '{{plural}} کاربر' ] ]; diff --git a/app/sprinkles/admin/locale/fr_FR/messages.php b/app/sprinkles/admin/locale/fr_FR/messages.php index 82bdf3ef9..6833da354 100644 --- a/app/sprinkles/admin/locale/fr_FR/messages.php +++ b/app/sprinkles/admin/locale/fr_FR/messages.php @@ -3,145 +3,147 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * French message token translations for the 'admin' sprinkle. * - * @package userfrosting\i18n\fr * @author Louis Charette */ - + return [ - "ACTIVITY" => [ - 1 => "Activité", - 2 => "Activités", + 'ACTIVITY' => [ + 1 => 'Activité', + 2 => 'Activités', - "LAST" => "Dernière activité", - "PAGE" => "Une liste des activités des utilisateurs", - "TIME" => "Date de l'activité" + 'LAST' => 'Dernière activité', + 'PAGE' => 'Une liste des activités des utilisateurs', + 'TIME' => "Date de l'activité" ], - "CACHE" => [ - "CLEAR" => "Vider le cache", - "CLEAR_CONFIRM" => "Voulez-vous vraiment supprimer le cache du site?", - "CLEAR_CONFIRM_YES" => "Oui, vider le cache", - "CLEARED" => "Cache effacé avec succès !" + 'CACHE' => [ + 'CLEAR' => 'Vider le cache', + 'CLEAR_CONFIRM' => 'Voulez-vous vraiment supprimer le cache du site?', + 'CLEAR_CONFIRM_YES' => 'Oui, vider le cache', + 'CLEARED' => 'Cache effacé avec succès !' ], - "DASHBOARD" => "Tableau de bord", - "DELETE_MASTER" => "Vous ne pouvez pas supprimer le compte principal !", - "DELETION_SUCCESSFUL" => "L'utilisateur {{user_name}} a été supprimé avec succès.", - "DETAILS_UPDATED" => "Les détails du compte de {{user_name}} ont été mis à jour", - "DISABLE_MASTER" => "Vous ne pouvez pas désactiver le compte principal !", - "DISABLE_SELF" => "Vous ne pouvez pas désactiver votre propre compte !", - "DISABLE_SUCCESSFUL" => "Le compte de l'utilisateur {{user_name}} a été désactivé avec succès.", - - "ENABLE_SUCCESSFUL" => "Le compte de l'utilisateur {{user_name}} a été activé avec succès.", - - "GROUP" => [ - 1 => "Groupe", - 2 => "Groupes", - - "CREATE" => "Créer un groupe", - "CREATION_SUCCESSFUL" => "Successfully created group {{name}}", - "DELETE" => "Supprimer le groupe", - "DELETE_CONFIRM" => "Êtes-vous certain de vouloir supprimer le groupe {{name}}?", - "DELETE_DEFAULT" => "Vous ne pouvez pas supprimer le groupe {{name}} parce que c'est le groupe par défaut pour les utilisateurs nouvellement enregistrés.", - "DELETE_YES" => "Oui, supprimer le groupe", - "DELETION_SUCCESSFUL" => "Groupe {{name}} supprimé avec succès", - "EDIT" => "Modifier le groupe", - "ICON" => "Icône", - "ICON_EXPLAIN" => "Icône des membres du groupe", - "INFO_PAGE" => "Informations sur le groupe {{name}}", - "MANAGE" => "Gérer le groupe", - "NAME" => "Nom du groupe", - "NAME_EXPLAIN" => "Spécifiez le nom du groupe", - "NOT_EMPTY" => "Vous ne pouvez pas le faire car il y a encore des utilisateurs associés au groupe {{name}}.", - "PAGE_DESCRIPTION" => "Une liste des groupes pour votre site. Fournit des outils de gestion pour éditer et supprimer des groupes.", - "UPDATE" => "Les détails du groupe {{name}} ont été enregistrés" + 'DASHBOARD' => 'Tableau de bord', + 'DELETE_MASTER' => 'Vous ne pouvez pas supprimer le compte principal !', + 'DELETION_SUCCESSFUL' => "L'utilisateur {{user_name}} a été supprimé avec succès.", + 'DETAILS_UPDATED' => 'Les détails du compte de {{user_name}} ont été mis à jour', + 'DISABLE_MASTER' => 'Vous ne pouvez pas désactiver le compte principal !', + 'DISABLE_SELF' => 'Vous ne pouvez pas désactiver votre propre compte !', + 'DISABLE_SUCCESSFUL' => "Le compte de l'utilisateur {{user_name}} a été désactivé avec succès.", + + 'ENABLE_SUCCESSFUL' => "Le compte de l'utilisateur {{user_name}} a été activé avec succès.", + + 'GROUP' => [ + 1 => 'Groupe', + 2 => 'Groupes', + + 'CREATE' => 'Créer un groupe', + 'CREATION_SUCCESSFUL' => 'Successfully created group {{name}}', + 'DELETE' => 'Supprimer le groupe', + 'DELETE_CONFIRM' => 'Êtes-vous certain de vouloir supprimer le groupe {{name}}?', + 'DELETE_DEFAULT' => "Vous ne pouvez pas supprimer le groupe {{name}} parce que c'est le groupe par défaut pour les utilisateurs nouvellement enregistrés.", + 'DELETE_YES' => 'Oui, supprimer le groupe', + 'DELETION_SUCCESSFUL' => 'Groupe {{name}} supprimé avec succès', + 'EDIT' => 'Modifier le groupe', + 'ICON' => 'Icône', + 'ICON_EXPLAIN' => 'Icône des membres du groupe', + 'INFO_PAGE' => 'Informations sur le groupe {{name}}', + 'MANAGE' => 'Gérer le groupe', + 'NAME' => 'Nom du groupe', + 'NAME_EXPLAIN' => 'Spécifiez le nom du groupe', + 'NOT_EMPTY' => 'Vous ne pouvez pas le faire car il y a encore des utilisateurs associés au groupe {{name}}.', + 'PAGE_DESCRIPTION' => 'Une liste des groupes pour votre site. Fournit des outils de gestion pour éditer et supprimer des groupes.', + 'UPDATE' => 'Les détails du groupe {{name}} ont été enregistrés' ], - "MANUALLY_ACTIVATED" => "Le compte de {{user_name}} a été activé manuellement", - "MASTER_ACCOUNT_EXISTS" => "Le compte principal existe déjà !", - "MIGRATION" => [ - "REQUIRED" => "Mise à jour de la base de données requise" + 'MANUALLY_ACTIVATED' => 'Le compte de {{user_name}} a été activé manuellement', + 'MASTER_ACCOUNT_EXISTS' => 'Le compte principal existe déjà !', + 'MIGRATION' => [ + 'REQUIRED' => 'Mise à jour de la base de données requise' ], - "PERMISSION" => [ - 1 => "Autorisation", - 2 => "Autorisations", + 'PERMISSION' => [ + 1 => 'Autorisation', + 2 => 'Autorisations', - "ASSIGN_NEW" => "Assigner une nouvelle autorisation", - "HOOK_CONDITION" => "Hook/Conditions", - "MANAGE" => "Gestion des autorisations", - "PAGE_DESCRIPTION" => "Une liste des autorisations pour votre site. Fournit des outils de gestion pour modifier et supprimer des autorisations.", - "UPDATE" => "Mettre à jour les autorisations" + 'ASSIGN_NEW' => 'Assigner une nouvelle autorisation', + 'HOOK_CONDITION' => 'Hook/Conditions', + 'MANAGE' => 'Gestion des autorisations', + 'PAGE_DESCRIPTION' => 'Une liste des autorisations pour votre site. Fournit des outils de gestion pour modifier et supprimer des autorisations.', + 'UPDATE' => 'Mettre à jour les autorisations' ], - "ROLE" => [ - 1 => "Rôle", - 2 => "Rôles", - - "ASSIGN_NEW" => "Assigner un nouveau rôle", - "CREATE" => "Créer un rôle", - "CREATION_SUCCESSFUL" => "Rôle {{name}} créé avec succès", - "DELETE" => "Supprimer le rôle", - "DELETE_CONFIRM" => "Êtes-vous certain de vouloir supprimer le rôle {{name}}?", - "DELETE_DEFAULT" => "Vous ne pouvez pas supprimer le rôle {{name}} parce que c'est un rôle par défaut pour les utilisateurs nouvellement enregistrés.", - "DELETE_YES" => "Oui, supprimer le rôle", - "DELETION_SUCCESSFUL" => "Rôle {{name}} supprimé avec succès", - "EDIT" => "Modifier le rôle", - "HAS_USERS" => "Vous ne pouvez pas le faire parce qu'il y a encore des utilisateurs qui ont le rôle {{name}}.", - "INFO_PAGE" => "Page d'information pour le rôle {{name}}", - "MANAGE" => "Gérer les rôles", - "NAME" => "Nom du rôle", - "NAME_EXPLAIN" => "Spécifiez le nom du rôle", - "NAME_IN_USE" => "Un rôle nommé {{name}} existe déjà", - "PAGE_DESCRIPTION" => "Une liste des rôles de votre site. Fournit des outils de gestion pour modifier et supprimer des rôles.", - "PERMISSIONS_UPDATED" => "Autorisations mises à jour pour le rôle {{name}}", - "UPDATED" => "Détails mis à jour pour le rôle {{name}}" + 'ROLE' => [ + 1 => 'Rôle', + 2 => 'Rôles', + + 'ASSIGN_NEW' => 'Assigner un nouveau rôle', + 'CREATE' => 'Créer un rôle', + 'CREATION_SUCCESSFUL' => 'Rôle {{name}} créé avec succès', + 'DELETE' => 'Supprimer le rôle', + 'DELETE_CONFIRM' => 'Êtes-vous certain de vouloir supprimer le rôle {{name}}?', + 'DELETE_DEFAULT' => "Vous ne pouvez pas supprimer le rôle {{name}} parce que c'est un rôle par défaut pour les utilisateurs nouvellement enregistrés.", + 'DELETE_YES' => 'Oui, supprimer le rôle', + 'DELETION_SUCCESSFUL' => 'Rôle {{name}} supprimé avec succès', + 'EDIT' => 'Modifier le rôle', + 'HAS_USERS' => "Vous ne pouvez pas le faire parce qu'il y a encore des utilisateurs qui ont le rôle {{name}}.", + 'INFO_PAGE' => "Page d'information pour le rôle {{name}}", + 'MANAGE' => 'Gérer les rôles', + 'NAME' => 'Nom du rôle', + 'NAME_EXPLAIN' => 'Spécifiez le nom du rôle', + 'NAME_IN_USE' => 'Un rôle nommé {{name}} existe déjà', + 'PAGE_DESCRIPTION' => 'Une liste des rôles de votre site. Fournit des outils de gestion pour modifier et supprimer des rôles.', + 'PERMISSIONS_UPDATED' => 'Autorisations mises à jour pour le rôle {{name}}', + 'UPDATED' => 'Détails mis à jour pour le rôle {{name}}' ], - "SYSTEM_INFO" => [ - "@TRANSLATION" => "Informations sur le système", - - "DB_NAME" => "Base de donnée", - "DB_VERSION" => "Version DB", - "DIRECTORY" => "Répertoire du projet", - "PHP_VERSION" => "Version de PHP", - "SERVER" => "Logiciel server", - "SPRINKLES" => "Sprinkles chargés", - "UF_VERSION" => "Version de UserFrosting", - "URL" => "Url racine" + 'SYSTEM_INFO' => [ + '@TRANSLATION' => 'Informations sur le système', + + 'DB_NAME' => 'Base de donnée', + 'DB_VERSION' => 'Version DB', + 'DIRECTORY' => 'Répertoire du projet', + 'PHP_VERSION' => 'Version de PHP', + 'SERVER' => 'Logiciel server', + 'SPRINKLES' => 'Sprinkles chargés', + 'UF_VERSION' => 'Version de UserFrosting', + 'URL' => 'Url racine' ], - "USER" => [ - 1 => "Utilisateur", - 2 => "Utilisateurs", + 'USER' => [ + 1 => 'Utilisateur', + 2 => 'Utilisateurs', - "ADMIN" => [ - "CHANGE_PASSWORD" => "Changer le mot de passe", - "SEND_PASSWORD_LINK" => "Envoyer à l'utilisateur un lien qui lui permettra de choisir son propre mot de passe", - "SET_PASSWORD" => "Définissez le mot de passe de l'utilisateur comme" + 'ADMIN' => [ + 'CHANGE_PASSWORD' => 'Changer le mot de passe', + 'SEND_PASSWORD_LINK' => "Envoyer à l'utilisateur un lien qui lui permettra de choisir son propre mot de passe", + 'SET_PASSWORD' => "Définissez le mot de passe de l'utilisateur comme" ], - "ACTIVATE" => "Autoriser l'utilisateur", - "CREATE" => "Créer un utilisateur", - "CREATED" => "L'utilisateur {{user_name}} a été créé avec succès", - "DELETE" => "Supprimer l'utilisateur", - "DELETE_CONFIRM" => "Êtes-vous certain de vouloir supprimer l'utilisateur {{name}}?", - "DELETE_YES" => "Oui, supprimer l'utilisateur", - "DISABLE" => "Désactiver l'utilisateur", - "EDIT" => "Modifier l'utilisateur", - "ENABLE" => "Activer l'utilisateur", - "INFO_PAGE" => "Page d'information de l'utilisateur pour {{name}}", - "PAGE_DESCRIPTION" => "Une liste des utilisateurs de votre site. Fournit des outils de gestion incluant la possibilité de modifier les détails de l'utilisateur, d'activer manuellement les utilisateurs, d'activer / désactiver les utilisateurs et plus.", - "LATEST" => "Derniers utilisateurs", - "VIEW_ALL" => "Voir tous les utilisateurs" + 'ACTIVATE' => "Autoriser l'utilisateur", + 'CREATE' => 'Créer un utilisateur', + 'CREATED' => "L'utilisateur {{user_name}} a été créé avec succès", + 'DELETE' => "Supprimer l'utilisateur", + 'DELETE_CONFIRM' => "Êtes-vous certain de vouloir supprimer l'utilisateur {{name}}?", + 'DELETE_YES' => "Oui, supprimer l'utilisateur", + 'DISABLE' => "Désactiver l'utilisateur", + 'EDIT' => "Modifier l'utilisateur", + 'ENABLE' => "Activer l'utilisateur", + 'INFO_PAGE' => "Page d'information de l'utilisateur pour {{name}}", + 'PAGE_DESCRIPTION' => "Une liste des utilisateurs de votre site. Fournit des outils de gestion incluant la possibilité de modifier les détails de l'utilisateur, d'activer manuellement les utilisateurs, d'activer / désactiver les utilisateurs et plus.", + 'LATEST' => 'Derniers utilisateurs', + 'VIEW_ALL' => 'Voir tous les utilisateurs' ], - "X_USER" => [ - 0 => "Aucun utilisateur", - 1 => "{{plural}} utilisateur", - 2 => "{{plural}} utilisateurs" + 'X_USER' => [ + 0 => 'Aucun utilisateur', + 1 => '{{plural}} utilisateur', + 2 => '{{plural}} utilisateurs' ] -]; \ No newline at end of file +]; diff --git a/app/sprinkles/admin/locale/it_IT/messages.php b/app/sprinkles/admin/locale/it_IT/messages.php index 7d17d8cd3..413ba36e1 100644 --- a/app/sprinkles/admin/locale/it_IT/messages.php +++ b/app/sprinkles/admin/locale/it_IT/messages.php @@ -3,158 +3,159 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * Italian message token translations for the 'admin' sprinkle. * This translation was generated with Google translate. Please contribute if you are a native speaker. * - * @package userfrosting\i18n\it * @author Alexander Weissman * @author Pietro Marangon (@Pe46dro) */ - return [ - "ACTIVITY" => [ - 1 => "Attività", - 2 => "Attività", + 'ACTIVITY' => [ + 1 => 'Attività', + 2 => 'Attività', - "LAST" => "Ultima attività", - "PAGE" => "Un elenco delle attività degli utenti", - "TIME" => "Tempo di attività" + 'LAST' => 'Ultima attività', + 'PAGE' => 'Un elenco delle attività degli utenti', + 'TIME' => 'Tempo di attività' ], - "CACHE" => [ - "CLEAR" => "Elimina cache", - "CLEAR_CONFIRM" => "Sei sicuro di voler eliminare la cache del sito?", - "CLEAR_CONFIRM_YES" => "Sì, elimina la cache", - "CLEARED" => "La cache è stata eliminata correttamente!" + 'CACHE' => [ + 'CLEAR' => 'Elimina cache', + 'CLEAR_CONFIRM' => 'Sei sicuro di voler eliminare la cache del sito?', + 'CLEAR_CONFIRM_YES' => 'Sì, elimina la cache', + 'CLEARED' => 'La cache è stata eliminata correttamente!' ], - "DASHBOARD" => "Pannello di Controllo", - "NO_FEATURES_YET" => "Sembra che nessuna funzione sia stata creata per questo account... ancora. Forse non sono ancora state implementate o qualcuno ha dimenticato di dartene accesso. In entrambi i casi, siamo contenti di averti qui!", - "DELETE_MASTER" => "Non puoi eliminare l'account principale!", - "DELETION_SUCCESSFUL" => "Hai eliminato l'utente utente {{user_name}}.", - "DETAILS_UPDATED" => "Dettagli account aggiornati per l'utente {{user_name}}", - "DISABLE_MASTER" => "Non puoi disattivare l'account principale!", - "DISABLE_SELF" => "Non puoi disattivare il tuo account!", - "DISABLE_SUCCESSFUL" => "Account dell'utente {{user_name}} disattivato con successo!", - "ENABLE_SUCCESSFUL" => "Account dell'utente {{user_name}} attivato con successo.", - - "GROUP" => [ - 1 => "Gruppo", - 2 => "Gruppi", - - "CREATE" => "Crea un gruppo", - "CREATION_SUCCESSFUL" => "Gruppo {{name}} creato con successo", - "DELETE" => "Elimina gruppo", - "DELETE_CONFIRM" => "Sei sicuro di voler eliminare il gruppo {{name}}?", - "DELETE_DEFAULT" => "Non puoi eliminare il gruppo {{name}} perché è il gruppo predefinito per gli utenti appena registrati.", - "DELETE_YES" => "Sì, elimina il gruppo", - "DELETION_SUCCESSFUL" => "Gruppo {{name}} eliminato con successo", - "EDIT" => "Modifica gruppo", - "ICON" => "Icona del gruppo", - "ICON_EXPLAIN" => "Icona per i membri del gruppo", - "INFO_PAGE" => "Pagina informazioni del Gruppo per {{name}}", - "MANAGE" => "Gestisci gruppo", - "NAME" => "Nome gruppo", - "NAME_EXPLAIN" => "Inserisci un nome per il gruppo", - "NOT_EMPTY" => "Non puoi farlo perché ci sono ancora utenti associati al gruppo {{name}}.", - "PAGE_DESCRIPTION" => "Un elenco dei gruppi per il tuo sito. Fornisce strumenti di gestione per la modifica e l'eliminazione di gruppi.", - "SUMMARY" => "Riepilogo Gruppo", - "UPDATE" => "Dettagli del gruppo {{name}} aggiornati." + 'DASHBOARD' => 'Pannello di Controllo', + 'NO_FEATURES_YET' => 'Sembra che nessuna funzione sia stata creata per questo account... ancora. Forse non sono ancora state implementate o qualcuno ha dimenticato di dartene accesso. In entrambi i casi, siamo contenti di averti qui!', + 'DELETE_MASTER' => "Non puoi eliminare l'account principale!", + 'DELETION_SUCCESSFUL' => "Hai eliminato l'utente utente {{user_name}}.", + 'DETAILS_UPDATED' => "Dettagli account aggiornati per l'utente {{user_name}}", + 'DISABLE_MASTER' => "Non puoi disattivare l'account principale!", + 'DISABLE_SELF' => 'Non puoi disattivare il tuo account!', + 'DISABLE_SUCCESSFUL' => "Account dell'utente {{user_name}} disattivato con successo!", + 'ENABLE_SUCCESSFUL' => "Account dell'utente {{user_name}} attivato con successo.", + + 'GROUP' => [ + 1 => 'Gruppo', + 2 => 'Gruppi', + + 'CREATE' => 'Crea un gruppo', + 'CREATION_SUCCESSFUL' => 'Gruppo {{name}} creato con successo', + 'DELETE' => 'Elimina gruppo', + 'DELETE_CONFIRM' => 'Sei sicuro di voler eliminare il gruppo {{name}}?', + 'DELETE_DEFAULT' => 'Non puoi eliminare il gruppo {{name}} perché è il gruppo predefinito per gli utenti appena registrati.', + 'DELETE_YES' => 'Sì, elimina il gruppo', + 'DELETION_SUCCESSFUL' => 'Gruppo {{name}} eliminato con successo', + 'EDIT' => 'Modifica gruppo', + 'ICON' => 'Icona del gruppo', + 'ICON_EXPLAIN' => 'Icona per i membri del gruppo', + 'INFO_PAGE' => 'Pagina informazioni del Gruppo per {{name}}', + 'MANAGE' => 'Gestisci gruppo', + 'NAME' => 'Nome gruppo', + 'NAME_EXPLAIN' => 'Inserisci un nome per il gruppo', + 'NOT_EMPTY' => 'Non puoi farlo perché ci sono ancora utenti associati al gruppo {{name}}.', + 'PAGE_DESCRIPTION' => "Un elenco dei gruppi per il tuo sito. Fornisce strumenti di gestione per la modifica e l'eliminazione di gruppi.", + 'SUMMARY' => 'Riepilogo Gruppo', + 'UPDATE' => 'Dettagli del gruppo {{name}} aggiornati.' ], - "MANUALLY_ACTIVATED" => "{{user_name}} è stato attivato manualmente", - "MASTER_ACCOUNT_EXISTS" => "L'account primario esiste già!", - "MIGRATION" => [ - "REQUIRED" => "È necessario aggiornare il database" + 'MANUALLY_ACTIVATED' => '{{user_name}} è stato attivato manualmente', + 'MASTER_ACCOUNT_EXISTS' => "L'account primario esiste già!", + 'MIGRATION' => [ + 'REQUIRED' => 'È necessario aggiornare il database' ], - "PERMISSION" => [ - 1 => "Permesso", - 2 => "Permessi", - - "ASSIGN_NEW" => "Assegna nuovo permesso", - "HOOK_CONDITION" => "Hook/Condizioni", - "ID" => "ID permesso", - "INFO_PAGE" => "Pagina di informazioni sulle autorizzazioni per {{name}}", - "MANAGE" => "Gestione delle autorizzazioni", - "NOTE_READ_ONLY" => "Nota: le autorizzazioni sono considerate \"parte del codice\" e non possono essere modificate tramite l'interfaccia. Per aggiungere, rimuovere o modificare le autorizzazioni, i gestori del sito devono utilizzare migrazione del database.", - "PAGE_DESCRIPTION" => "Un elenco delle autorizzazioni per il tuo sito. Fornisce strumenti di gestione per la modifica e l'eliminazione delle autorizzazioni.", - "SUMMARY" => "Sommario autorizzazioni", - "UPDATE" => "Aggiorna autorizzazioni", - "VIA_ROLES" => "Ha i permessi tramite i ruoli" + 'PERMISSION' => [ + 1 => 'Permesso', + 2 => 'Permessi', + + 'ASSIGN_NEW' => 'Assegna nuovo permesso', + 'HOOK_CONDITION' => 'Hook/Condizioni', + 'ID' => 'ID permesso', + 'INFO_PAGE' => 'Pagina di informazioni sulle autorizzazioni per {{name}}', + 'MANAGE' => 'Gestione delle autorizzazioni', + 'NOTE_READ_ONLY' => "Nota: le autorizzazioni sono considerate \"parte del codice\" e non possono essere modificate tramite l'interfaccia. Per aggiungere, rimuovere o modificare le autorizzazioni, i gestori del sito devono utilizzare migrazione del database.", + 'PAGE_DESCRIPTION' => "Un elenco delle autorizzazioni per il tuo sito. Fornisce strumenti di gestione per la modifica e l'eliminazione delle autorizzazioni.", + 'SUMMARY' => 'Sommario autorizzazioni', + 'UPDATE' => 'Aggiorna autorizzazioni', + 'VIA_ROLES' => 'Ha i permessi tramite i ruoli' ], - "ROLE" => [ - 1 => "Ruolo", - 2 => "Ruoli", - - "ASSIGN_NEW" => "Assegna nuovo ruolo", - "CREATE" => "Crea ruolo", - "CREATION_SUCCESSFUL" => "Creato con successo il ruolo {{name}}", - "DELETE" => "Elimina ruolo", - "DELETE_CONFIRM" => "Sei sicuro di voler eliminare il ruolo {{name}}?", - "DELETE_DEFAULT" => "Non puoi eliminare il ruolo {{name}} perché è un ruolo predefinito per gli utenti appena registrati.", - "DELETE_YES" => "Sì, elimina il ruolo", - "DELETION_SUCCESSFUL" => "Ruolo {{name}} eliminato", - "EDIT" => "Modifica ruolo", - "HAS_USERS" => "Non puoi farlo perché ci sono ancora utenti che hanno il ruolo {{name}}.", - "INFO_PAGE" => "Pagina di informazioni sui ruoli per {{name}}", - "MANAGE" => "Gestisci Ruoli", - "NAME" => "Nome", - "NAME_EXPLAIN" => "Inserisci un nome per il ruolo", - "NAME_IN_USE" => "Esiste già un ruolo denominato {{name}}", - "PAGE_DESCRIPTION" => "Un elenco dei ruoli per il tuo sito. Fornisce strumenti di gestione per la modifica e l'eliminazione di ruoli.", - "PERMISSIONS_UPDATED" => "Autorizzazioni aggiornate per il ruolo {{name}}", - "SUMMARY" => "Riepilogo dei Ruoli", - "UPDATED" => "Dettagli aggiornati per il ruolo {{name}}" + 'ROLE' => [ + 1 => 'Ruolo', + 2 => 'Ruoli', + + 'ASSIGN_NEW' => 'Assegna nuovo ruolo', + 'CREATE' => 'Crea ruolo', + 'CREATION_SUCCESSFUL' => 'Creato con successo il ruolo {{name}}', + 'DELETE' => 'Elimina ruolo', + 'DELETE_CONFIRM' => 'Sei sicuro di voler eliminare il ruolo {{name}}?', + 'DELETE_DEFAULT' => 'Non puoi eliminare il ruolo {{name}} perché è un ruolo predefinito per gli utenti appena registrati.', + 'DELETE_YES' => 'Sì, elimina il ruolo', + 'DELETION_SUCCESSFUL' => 'Ruolo {{name}} eliminato', + 'EDIT' => 'Modifica ruolo', + 'HAS_USERS' => 'Non puoi farlo perché ci sono ancora utenti che hanno il ruolo {{name}}.', + 'INFO_PAGE' => 'Pagina di informazioni sui ruoli per {{name}}', + 'MANAGE' => 'Gestisci Ruoli', + 'NAME' => 'Nome', + 'NAME_EXPLAIN' => 'Inserisci un nome per il ruolo', + 'NAME_IN_USE' => 'Esiste già un ruolo denominato {{name}}', + 'PAGE_DESCRIPTION' => "Un elenco dei ruoli per il tuo sito. Fornisce strumenti di gestione per la modifica e l'eliminazione di ruoli.", + 'PERMISSIONS_UPDATED' => 'Autorizzazioni aggiornate per il ruolo {{name}}', + 'SUMMARY' => 'Riepilogo dei Ruoli', + 'UPDATED' => 'Dettagli aggiornati per il ruolo {{name}}' ], - "SYSTEM_INFO" => [ - "@TRANSLATION" => "Informazioni sul sistema", - - "DB_NAME" => "Nome del database", - "DB_VERSION" => "Versione del database", - "DIRECTORY" => "Directory del progetto", - "PHP_VERSION" => "Versione PHP", - "SERVER" => "Software Webserver", - "SPRINKLES" => "Sprinkles caricati", - "UF_VERSION" => "Versione UserFrosting", - "URL" => "Url della root del sito" + 'SYSTEM_INFO' => [ + '@TRANSLATION' => 'Informazioni sul sistema', + + 'DB_NAME' => 'Nome del database', + 'DB_VERSION' => 'Versione del database', + 'DIRECTORY' => 'Directory del progetto', + 'PHP_VERSION' => 'Versione PHP', + 'SERVER' => 'Software Webserver', + 'SPRINKLES' => 'Sprinkles caricati', + 'UF_VERSION' => 'Versione UserFrosting', + 'URL' => 'Url della root del sito' ], - "TOGGLE_COLUMNS" => "Scambia le colonne", + 'TOGGLE_COLUMNS' => 'Scambia le colonne', - "USER" => [ - 1 => "Utente", - 2 => "Utenti", + 'USER' => [ + 1 => 'Utente', + 2 => 'Utenti', - "ADMIN" => [ - "CHANGE_PASSWORD" => "Cambia password utente", - "SEND_PASSWORD_LINK" => "Invia all'utente un link che gli permetterà di scegliere una nuova password.", - "SET_PASSWORD" => "Impostare la password dell'utente come" + 'ADMIN' => [ + 'CHANGE_PASSWORD' => 'Cambia password utente', + 'SEND_PASSWORD_LINK' => "Invia all'utente un link che gli permetterà di scegliere una nuova password.", + 'SET_PASSWORD' => "Impostare la password dell'utente come" ], - "ACTIVATE" => "Attiva utente", - "CREATE" => "Creare utente", - "CREATED" => "Account utente {{user_name}} creato.", - "DELETE" => "Elimina utente", - "DELETE_CONFIRM" => "Sei sicuro di voler eliminare l'utente {{name}}?", - "DELETE_YES" => "Sì, elimina l'utente", - "DISABLE" => "Disabilita l'utente", - "EDIT" => "Modifica utente", - "ENABLE" => "Abilita utente", - "INFO_PAGE" => "Pagina informazioni utente per {{name}}", - "LATEST" => "Ultimi Utenti", - "PAGE_DESCRIPTION" => "Un elenco degli utenti del tuo sito. Fornisce strumenti di gestione, tra cui la possibilità di modificare i dettagli utente, attivare manualmente gli utenti, abilitare / disabilitare gli utenti e altro ancora.", - "SUMMARY" => "Riepilogo account", - "VIEW_ALL" => "Visualizza tutti gli utenti", - "WITH_PERMISSION" => "Utenti con questa autorizzazione" + 'ACTIVATE' => 'Attiva utente', + 'CREATE' => 'Creare utente', + 'CREATED' => 'Account utente {{user_name}} creato.', + 'DELETE' => 'Elimina utente', + 'DELETE_CONFIRM' => "Sei sicuro di voler eliminare l'utente {{name}}?", + 'DELETE_YES' => "Sì, elimina l'utente", + 'DISABLE' => "Disabilita l'utente", + 'EDIT' => 'Modifica utente', + 'ENABLE' => 'Abilita utente', + 'INFO_PAGE' => 'Pagina informazioni utente per {{name}}', + 'LATEST' => 'Ultimi Utenti', + 'PAGE_DESCRIPTION' => 'Un elenco degli utenti del tuo sito. Fornisce strumenti di gestione, tra cui la possibilità di modificare i dettagli utente, attivare manualmente gli utenti, abilitare / disabilitare gli utenti e altro ancora.', + 'SUMMARY' => 'Riepilogo account', + 'VIEW_ALL' => 'Visualizza tutti gli utenti', + 'WITH_PERMISSION' => 'Utenti con questa autorizzazione' ], - "X_USER" => [ - 0 => "Nessun utente", - 1 => "{{plural}} utente", - 2 => "{{plural}} utenti" + 'X_USER' => [ + 0 => 'Nessun utente', + 1 => '{{plural}} utente', + 2 => '{{plural}} utenti' ] ]; diff --git a/app/sprinkles/admin/locale/pt_PT/messages.php b/app/sprinkles/admin/locale/pt_PT/messages.php index 0faf818d5..aa1799059 100644 --- a/app/sprinkles/admin/locale/pt_PT/messages.php +++ b/app/sprinkles/admin/locale/pt_PT/messages.php @@ -3,137 +3,138 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * Portuguese message token translations for the 'admin' sprinkle. * - * @package userfrosting\i18n\pt * @author Bruno Silva (brunomnsilva@gmail.com) */ - return [ - "ACTIVITY" => [ - 1 => "Atividade", - 2 => "Atividades", + 'ACTIVITY' => [ + 1 => 'Atividade', + 2 => 'Atividades', - "LAST" => "Última atividade", - "PAGE" => "Lista de atividade dos utilizadores", - "TIME" => "Tempo da Atividade" + 'LAST' => 'Última atividade', + 'PAGE' => 'Lista de atividade dos utilizadores', + 'TIME' => 'Tempo da Atividade' ], - "CACHE" => [ - "CLEAR" => "Limpar cache", - "CLEAR_CONFIRM" => "Tem a certeza que pretende limpar a cache do site?", - "CLEAR_CONFIRM_YES" => "Sim, limpar cache", - "CLEARED" => "Cache limpa com sucesso!" + 'CACHE' => [ + 'CLEAR' => 'Limpar cache', + 'CLEAR_CONFIRM' => 'Tem a certeza que pretende limpar a cache do site?', + 'CLEAR_CONFIRM_YES' => 'Sim, limpar cache', + 'CLEARED' => 'Cache limpa com sucesso!' ], - "DASHBOARD" => "Painel de Controlo", - "DELETE_MASTER" => "Não pode apagar a conta principal!", - "DELETION_SUCCESSFUL" => "Utilizador {{user_name}} foi removido com sucesso.", - "DETAILS_UPDATED" => "Detalhes de conta atualizados para o utilizador {{user_name}}", - "DISABLE_MASTER" => "Não pode desativar a conta principal!", - "DISABLE_SUCCESSFUL" => "Conta do utilizador {{user_name}} foi desativada com sucesso.", - - "ENABLE_SUCCESSFUL" => "Conta do utilizador {{user_name}} foi ativada com sucesso.", - - "GROUP" => [ - 1 => "Grupo", - 2 => "Grupos", - - "CREATE" => "Criar grupo", - "CREATION_SUCCESSFUL" => "Grupo criado com sucesso", - "DELETE" => "Remover grupo", - "DELETION_SUCCESSFUL" => "Grupo removido com sucesso", - "DELETE_CONFIRM" => "Tem a certeza que pretende remover o grupo {{name}}?", - "DELETE_YES" => "Sim, remover grupo", - "EDIT" => "Editar grupo", - "ICON" => "Icon do grupo", - "ICON_EXPLAIN" => "Icon para membros do grupo", - "INFO_PAGE" => "Página informativa do grupo {{name}}", + 'DASHBOARD' => 'Painel de Controlo', + 'DELETE_MASTER' => 'Não pode apagar a conta principal!', + 'DELETION_SUCCESSFUL' => 'Utilizador {{user_name}} foi removido com sucesso.', + 'DETAILS_UPDATED' => 'Detalhes de conta atualizados para o utilizador {{user_name}}', + 'DISABLE_MASTER' => 'Não pode desativar a conta principal!', + 'DISABLE_SUCCESSFUL' => 'Conta do utilizador {{user_name}} foi desativada com sucesso.', + + 'ENABLE_SUCCESSFUL' => 'Conta do utilizador {{user_name}} foi ativada com sucesso.', + + 'GROUP' => [ + 1 => 'Grupo', + 2 => 'Grupos', + + 'CREATE' => 'Criar grupo', + 'CREATION_SUCCESSFUL' => 'Grupo criado com sucesso', + 'DELETE' => 'Remover grupo', + 'DELETION_SUCCESSFUL' => 'Grupo removido com sucesso', + 'DELETE_CONFIRM' => 'Tem a certeza que pretende remover o grupo {{name}}?', + 'DELETE_YES' => 'Sim, remover grupo', + 'EDIT' => 'Editar grupo', + 'ICON' => 'Icon do grupo', + 'ICON_EXPLAIN' => 'Icon para membros do grupo', + 'INFO_PAGE' => 'Página informativa do grupo {{name}}', //"MANAGE" => "Manage group", - "NAME" => "Nome do grupo", - "NAME_EXPLAIN" => "Por favor introduza um nome para o grupo", - "PAGE_DESCRIPTION" => "Lista de grupos do site. Contém opções para editar e remover grupos." + 'NAME' => 'Nome do grupo', + 'NAME_EXPLAIN' => 'Por favor introduza um nome para o grupo', + 'PAGE_DESCRIPTION' => 'Lista de grupos do site. Contém opções para editar e remover grupos.' ], - "MANUALLY_ACTIVATED" => "A conta de {{user_name}} foi ativada manualmente.", - "MASTER_ACCOUNT_EXISTS" => "A contra principal já existe!", - "MIGRATION" => [ - "REQUIRED" => "É necessário uma atualização da base de dados." + 'MANUALLY_ACTIVATED' => 'A conta de {{user_name}} foi ativada manualmente.', + 'MASTER_ACCOUNT_EXISTS' => 'A contra principal já existe!', + 'MIGRATION' => [ + 'REQUIRED' => 'É necessário uma atualização da base de dados.' ], - "PERMISSION" => [ - 1 => "Permissão", - 2 => "Permissões", + 'PERMISSION' => [ + 1 => 'Permissão', + 2 => 'Permissões', - "ASSIGN_NEW" => "Atribuir nova permissão", - "HOOK_CONDITION" => "Hook/Condições", - "MANAGE" => "Gerir permissões", - "PAGE_DESCRIPTION" => "Lista de permissões do site. Contém opções para editar e remover permissões.", - "UPDATE" => "Atualizar permissões" + 'ASSIGN_NEW' => 'Atribuir nova permissão', + 'HOOK_CONDITION' => 'Hook/Condições', + 'MANAGE' => 'Gerir permissões', + 'PAGE_DESCRIPTION' => 'Lista de permissões do site. Contém opções para editar e remover permissões.', + 'UPDATE' => 'Atualizar permissões' ], - "ROLE" => [ - 1 => "Cargo", - 2 => "Cargos", - - "ASSIGN_NEW" => "Atribuir novo cargo", - "CREATE" => "Criar cargo", - "CREATION_SUCCESSFUL" => "Cargo criado com sucesso", - "DELETE" => "Remover cargo", - "DELETION_SUCCESSFUL" => "Cargo removido com sucesso", - "DELETE_CONFIRM" => "Tem a certeza que pretende remover o cargo {{name}}?", - "DELETE_YES" => "Sim, remover cargo", - "EDIT" => "Editar cargo", - "INFO_PAGE" => "Página informativa do cargo {{name}}", - "MANAGE" => "Gerir cargos", - "NAME" => "Nome", - "NAME_EXPLAIN" => "Por favor introduza um nome para o cargo", - "PAGE_DESCRIPTION" => "Lista de cargos do site. Contém opções para editar e remover cargos.", - "UPDATE" => "Atualizar cargos", - "UPDATED" => "Cargo {{name}} atualizado" + 'ROLE' => [ + 1 => 'Cargo', + 2 => 'Cargos', + + 'ASSIGN_NEW' => 'Atribuir novo cargo', + 'CREATE' => 'Criar cargo', + 'CREATION_SUCCESSFUL' => 'Cargo criado com sucesso', + 'DELETE' => 'Remover cargo', + 'DELETION_SUCCESSFUL' => 'Cargo removido com sucesso', + 'DELETE_CONFIRM' => 'Tem a certeza que pretende remover o cargo {{name}}?', + 'DELETE_YES' => 'Sim, remover cargo', + 'EDIT' => 'Editar cargo', + 'INFO_PAGE' => 'Página informativa do cargo {{name}}', + 'MANAGE' => 'Gerir cargos', + 'NAME' => 'Nome', + 'NAME_EXPLAIN' => 'Por favor introduza um nome para o cargo', + 'PAGE_DESCRIPTION' => 'Lista de cargos do site. Contém opções para editar e remover cargos.', + 'UPDATE' => 'Atualizar cargos', + 'UPDATED' => 'Cargo {{name}} atualizado' ], - "SYSTEM_INFO" => [ - "@TRANSLATION" => "Informação do sistema", - - "DB_NAME" => "Nome da base de dados", - "DB_VERSION" => "Versão da base de dados", - "DIRECTORY" => "Diretório do projeto", - "PHP_VERSION" => "Versão PHP", - "SERVER" => "Software do servidor web", - "SPRINKLES" => "Sprinkles carregados", - "UF_VERSION" => "Versão do UserFrosting", - "URL" => "Raiz (url) do site" + 'SYSTEM_INFO' => [ + '@TRANSLATION' => 'Informação do sistema', + + 'DB_NAME' => 'Nome da base de dados', + 'DB_VERSION' => 'Versão da base de dados', + 'DIRECTORY' => 'Diretório do projeto', + 'PHP_VERSION' => 'Versão PHP', + 'SERVER' => 'Software do servidor web', + 'SPRINKLES' => 'Sprinkles carregados', + 'UF_VERSION' => 'Versão do UserFrosting', + 'URL' => 'Raiz (url) do site' ], - "USER" => [ - 1 => "Utilizador", - 2 => "Utilizadores", + 'USER' => [ + 1 => 'Utilizador', + 2 => 'Utilizadores', - "ADMIN" => [ - "CHANGE_PASSWORD" => "Alterar password", - "SEND_PASSWORD_LINK" => "Enviar um link ao utilizador que lhe permita escolher a sua password", - "SET_PASSWORD" => "Definir a password do utilizador como" + 'ADMIN' => [ + 'CHANGE_PASSWORD' => 'Alterar password', + 'SEND_PASSWORD_LINK' => 'Enviar um link ao utilizador que lhe permita escolher a sua password', + 'SET_PASSWORD' => 'Definir a password do utilizador como' ], - "ACTIVATE" => "Ativar utilizador", - "CREATE" => "Criar utilizador", - "DELETE" => "Remover utilizador", - "DELETE_CONFIRM" => "Tem a certeza que pretende remover o utilizador {{name}}?", - "DELETE_YES" => "Sim, remover utilizador", - "DISABLE" => "Desativar utilizador", - "EDIT" => "Editar utilizador", - "ENABLE" => "Ativar utilizador", - "INFO_PAGE" => "Página informativa do utilizador {{name}}", - "PAGE_DESCRIPTION" => "Lista de utilizadores do site. Contém opções para editar detalhes, ativar/desativar utilizadores e outras.", - "LATEST" => "Últimos Utilizadores", - "VIEW_ALL" => "Ver todos os utilizadores" + 'ACTIVATE' => 'Ativar utilizador', + 'CREATE' => 'Criar utilizador', + 'DELETE' => 'Remover utilizador', + 'DELETE_CONFIRM' => 'Tem a certeza que pretende remover o utilizador {{name}}?', + 'DELETE_YES' => 'Sim, remover utilizador', + 'DISABLE' => 'Desativar utilizador', + 'EDIT' => 'Editar utilizador', + 'ENABLE' => 'Ativar utilizador', + 'INFO_PAGE' => 'Página informativa do utilizador {{name}}', + 'PAGE_DESCRIPTION' => 'Lista de utilizadores do site. Contém opções para editar detalhes, ativar/desativar utilizadores e outras.', + 'LATEST' => 'Últimos Utilizadores', + 'VIEW_ALL' => 'Ver todos os utilizadores' ], - "X_USER" => [ - 0 => "Nenhum utilizador", - 1 => "{{plural}} utilizador", - 2 => "{{plural}} utilizadores" + 'X_USER' => [ + 0 => 'Nenhum utilizador', + 1 => '{{plural}} utilizador', + 2 => '{{plural}} utilizadores' ] ]; diff --git a/app/sprinkles/admin/locale/ru_RU/messages.php b/app/sprinkles/admin/locale/ru_RU/messages.php index d6f6e1a8f..6169cce38 100644 --- a/app/sprinkles/admin/locale/ru_RU/messages.php +++ b/app/sprinkles/admin/locale/ru_RU/messages.php @@ -3,158 +3,159 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * Russian message token translations for the 'admin' sprinkle. * - * @package userfrosting\i18n\ru_RU * @author @rendername */ - return [ - "ACTIVITY" => [ - 1 => "Активность", - 2 => "Активность", + 'ACTIVITY' => [ + 1 => 'Активность', + 2 => 'Активность', - "LAST" => "Последняя активность", - "PAGE" => "Список действий пользователя", - "TIME" => "Время действия" + 'LAST' => 'Последняя активность', + 'PAGE' => 'Список действий пользователя', + 'TIME' => 'Время действия' ], - "CACHE" => [ - "CLEAR" => "Очистить кэш", - "CLEAR_CONFIRM" => "Уверены, что хотите очистить кэш сайта?", - "CLEAR_CONFIRM_YES" => "Да, очистить кэш", - "CLEARED" => "Кэш успешно очищен !" + 'CACHE' => [ + 'CLEAR' => 'Очистить кэш', + 'CLEAR_CONFIRM' => 'Уверены, что хотите очистить кэш сайта?', + 'CLEAR_CONFIRM_YES' => 'Да, очистить кэш', + 'CLEARED' => 'Кэш успешно очищен !' ], - "DASHBOARD" => "Панель управления", - "NO_FEATURES_YET" => "Похоже, некоторые функции не были настроены для этого аккаунта... пока. Возможно, они еще не реализованы, или, может быть, кто-то забыл дать вам доступ. В любом случае, мы рады вас видеть здесь!", - "DELETE_MASTER" => "Нельзя удалить главный аккаунт!", - "DELETION_SUCCESSFUL" => "Пользователь {{user_name}} был успешно удален.", - "DETAILS_UPDATED" => "Данные для аккаунта {{user_name}} обновлены", - "DISABLE_MASTER" => "Нельзя отключить главный аккаунт!", - "DISABLE_SELF" => "Вы не можете отключить собственный аккаунт!", - "DISABLE_SUCCESSFUL" => "Пользователь {{user_name}} был успешно отключен.", - - "ENABLE_SUCCESSFUL" => "Пользователь {{user_name}} был успешно включен.", - - "GROUP" => [ - 1 => "Группа", - 2 => "Группы", - - "CREATE" => "Создать группу", - "CREATION_SUCCESSFUL" => "Успешно создана группа {{name}}", - "DELETE" => "Удалить группу", - "DELETE_CONFIRM" => "Вы уверены, что вы хотите удалить группу {{name}}?", - "DELETE_DEFAULT" => "Нельзя удалить группу {{name}}, потому что это группа по умолчанию для новых пользователей.", - "DELETE_YES" => "Да, удалить группу", - "DELETION_SUCCESSFUL" => "Успешно удалена группа {{name}}", - "EDIT" => "Редактор группы", - "ICON" => "Значок группы", - "ICON_EXPLAIN" => "Значок для членов группы", - "INFO_PAGE" => "Описание группы {{name}}", - "MANAGE" => "Управление группой", - "NAME" => "Имя группы", - "NAME_EXPLAIN" => "Пожалуйста, введите имя группы", - "NOT_EMPTY" => "Вы не можете сделать это, потому что до сих пор есть пользователи, связанные с группой {{name}}.", - "PAGE_DESCRIPTION" => "Список групп для вашего сайта. Предоставляет инструменты управления для редактирования и удаления групп.", - "SUMMARY" => "Резюме группы", - "UPDATE" => "Информация обновлена для группы {{name}}" + 'DASHBOARD' => 'Панель управления', + 'NO_FEATURES_YET' => 'Похоже, некоторые функции не были настроены для этого аккаунта... пока. Возможно, они еще не реализованы, или, может быть, кто-то забыл дать вам доступ. В любом случае, мы рады вас видеть здесь!', + 'DELETE_MASTER' => 'Нельзя удалить главный аккаунт!', + 'DELETION_SUCCESSFUL' => 'Пользователь {{user_name}} был успешно удален.', + 'DETAILS_UPDATED' => 'Данные для аккаунта {{user_name}} обновлены', + 'DISABLE_MASTER' => 'Нельзя отключить главный аккаунт!', + 'DISABLE_SELF' => 'Вы не можете отключить собственный аккаунт!', + 'DISABLE_SUCCESSFUL' => 'Пользователь {{user_name}} был успешно отключен.', + + 'ENABLE_SUCCESSFUL' => 'Пользователь {{user_name}} был успешно включен.', + + 'GROUP' => [ + 1 => 'Группа', + 2 => 'Группы', + + 'CREATE' => 'Создать группу', + 'CREATION_SUCCESSFUL' => 'Успешно создана группа {{name}}', + 'DELETE' => 'Удалить группу', + 'DELETE_CONFIRM' => 'Вы уверены, что вы хотите удалить группу {{name}}?', + 'DELETE_DEFAULT' => 'Нельзя удалить группу {{name}}, потому что это группа по умолчанию для новых пользователей.', + 'DELETE_YES' => 'Да, удалить группу', + 'DELETION_SUCCESSFUL' => 'Успешно удалена группа {{name}}', + 'EDIT' => 'Редактор группы', + 'ICON' => 'Значок группы', + 'ICON_EXPLAIN' => 'Значок для членов группы', + 'INFO_PAGE' => 'Описание группы {{name}}', + 'MANAGE' => 'Управление группой', + 'NAME' => 'Имя группы', + 'NAME_EXPLAIN' => 'Пожалуйста, введите имя группы', + 'NOT_EMPTY' => 'Вы не можете сделать это, потому что до сих пор есть пользователи, связанные с группой {{name}}.', + 'PAGE_DESCRIPTION' => 'Список групп для вашего сайта. Предоставляет инструменты управления для редактирования и удаления групп.', + 'SUMMARY' => 'Резюме группы', + 'UPDATE' => 'Информация обновлена для группы {{name}}' ], - "MANUALLY_ACTIVATED" => "{{user_name}} аккаунт был активирован вручную", - "MASTER_ACCOUNT_EXISTS" => "Мастер-аккаунт уже существует!", - "MIGRATION" => [ - "REQUIRED" => "Необходимо обновление базы данных" + 'MANUALLY_ACTIVATED' => '{{user_name}} аккаунт был активирован вручную', + 'MASTER_ACCOUNT_EXISTS' => 'Мастер-аккаунт уже существует!', + 'MIGRATION' => [ + 'REQUIRED' => 'Необходимо обновление базы данных' ], - "PERMISSION" => [ - 1 => "Доступ", - 2 => "Права доступа", - - "ASSIGN_NEW" => "Назначить новые права", - "HOOK_CONDITION" => "Привязки/Условия", - "ID" => "ID доступа", - "INFO_PAGE" => "Информация о доступах для '{{name}}'", - "MANAGE" => "Управление правами", - "NOTE_READ_ONLY" => "Пожалуйста, обратите внимание: права и доступы считаются \"частью кода\" и не могут быть изменены через интерфейс. Чтобы добавить, удалить или изменить доступы, нужно будет использовать сайт базы данных миграции.", - "PAGE_DESCRIPTION" => "Список прав доступа для вашего сайта. Предоставляет инструменты управления для редактирования и удаления прав.", - "SUMMARY" => "Сводка доступов", - "UPDATE" => "Обновление прав", - "VIA_ROLES" => "Имеет права через роли" + 'PERMISSION' => [ + 1 => 'Доступ', + 2 => 'Права доступа', + + 'ASSIGN_NEW' => 'Назначить новые права', + 'HOOK_CONDITION' => 'Привязки/Условия', + 'ID' => 'ID доступа', + 'INFO_PAGE' => "Информация о доступах для '{{name}}'", + 'MANAGE' => 'Управление правами', + 'NOTE_READ_ONLY' => 'Пожалуйста, обратите внимание: права и доступы считаются "частью кода" и не могут быть изменены через интерфейс. Чтобы добавить, удалить или изменить доступы, нужно будет использовать сайт базы данных миграции.', + 'PAGE_DESCRIPTION' => 'Список прав доступа для вашего сайта. Предоставляет инструменты управления для редактирования и удаления прав.', + 'SUMMARY' => 'Сводка доступов', + 'UPDATE' => 'Обновление прав', + 'VIA_ROLES' => 'Имеет права через роли' ], - "ROLE" => [ - 1 => "Роль", - 2 => "Роли", - - "ASSIGN_NEW" => "Назначение новой роли", - "CREATE" => "Создать роль", - "CREATION_SUCCESSFUL" => "Успешно создана роль {{name}}", - "DELETE" => "Удалить роль", - "DELETE_CONFIRM" => "Вы уверены, что вы хотите удалить роль {{name}}?", - "DELETE_DEFAULT" => "Невозможно удалить роль {{name}}, потому что это роль по умолчанию для новых пользователей.", - "DELETE_YES" => "Да, удалить роль", - "DELETION_SUCCESSFUL" => "Успешно удалена роль {{name}}", - "EDIT" => "Изменить роль", - "HAS_USERS" => "Вы не можете сделать это, потому что до сих пор есть пользователи, имеющие роль {{name}}.", - "INFO_PAGE" => "Информации роли для {{name}}", - "MANAGE" => "Управление ролями", - "NAME" => "Имя", - "NAME_EXPLAIN" => "Пожалуйста, укажите имя для роли", - "NAME_IN_USE" => "Роль с именем {{name}} уже существует", - "PAGE_DESCRIPTION" => "Список ролей для вашего сайта. Предоставляет инструменты управления для редактирования и удаления ролей.", - "PERMISSIONS_UPDATED" => "Права обновлены для роли {{name}}", - "SUMMARY" => "Основная информация", - "UPDATED" => "Информация для роли {{name}}" + 'ROLE' => [ + 1 => 'Роль', + 2 => 'Роли', + + 'ASSIGN_NEW' => 'Назначение новой роли', + 'CREATE' => 'Создать роль', + 'CREATION_SUCCESSFUL' => 'Успешно создана роль {{name}}', + 'DELETE' => 'Удалить роль', + 'DELETE_CONFIRM' => 'Вы уверены, что вы хотите удалить роль {{name}}?', + 'DELETE_DEFAULT' => 'Невозможно удалить роль {{name}}, потому что это роль по умолчанию для новых пользователей.', + 'DELETE_YES' => 'Да, удалить роль', + 'DELETION_SUCCESSFUL' => 'Успешно удалена роль {{name}}', + 'EDIT' => 'Изменить роль', + 'HAS_USERS' => 'Вы не можете сделать это, потому что до сих пор есть пользователи, имеющие роль {{name}}.', + 'INFO_PAGE' => 'Информации роли для {{name}}', + 'MANAGE' => 'Управление ролями', + 'NAME' => 'Имя', + 'NAME_EXPLAIN' => 'Пожалуйста, укажите имя для роли', + 'NAME_IN_USE' => 'Роль с именем {{name}} уже существует', + 'PAGE_DESCRIPTION' => 'Список ролей для вашего сайта. Предоставляет инструменты управления для редактирования и удаления ролей.', + 'PERMISSIONS_UPDATED' => 'Права обновлены для роли {{name}}', + 'SUMMARY' => 'Основная информация', + 'UPDATED' => 'Информация для роли {{name}}' ], - "SYSTEM_INFO" => [ - "@TRANSLATION" => "Системная информация", - - "DB_NAME" => "Имя базы данных", - "DB_VERSION" => "Версия базы данных", - "DIRECTORY" => "Каталог проекта", - "PHP_VERSION" => "Версия PHP", - "SERVER" => "ПО Сервера", - "SPRINKLES" => "Загружены модули", - "UF_VERSION" => "Версия UserFrosting", - "URL" => "URL-адрес сайта" + 'SYSTEM_INFO' => [ + '@TRANSLATION' => 'Системная информация', + + 'DB_NAME' => 'Имя базы данных', + 'DB_VERSION' => 'Версия базы данных', + 'DIRECTORY' => 'Каталог проекта', + 'PHP_VERSION' => 'Версия PHP', + 'SERVER' => 'ПО Сервера', + 'SPRINKLES' => 'Загружены модули', + 'UF_VERSION' => 'Версия UserFrosting', + 'URL' => 'URL-адрес сайта' ], - "TOGGLE_COLUMNS" => "Переключатель столбцов", + 'TOGGLE_COLUMNS' => 'Переключатель столбцов', - "USER" => [ - 1 => "Пользователь", - 2 => "Пользователи", + 'USER' => [ + 1 => 'Пользователь', + 2 => 'Пользователи', - "ADMIN" => [ - "CHANGE_PASSWORD" => "Сменить пароль пользователя", - "SEND_PASSWORD_LINK" => "Отправить пользователю ссылку, которая позволит выбрать свой собственный пароль", - "SET_PASSWORD" => "Установите пароль пользователя как" + 'ADMIN' => [ + 'CHANGE_PASSWORD' => 'Сменить пароль пользователя', + 'SEND_PASSWORD_LINK' => 'Отправить пользователю ссылку, которая позволит выбрать свой собственный пароль', + 'SET_PASSWORD' => 'Установите пароль пользователя как' ], - "ACTIVATE" => "Активировать пользователя", - "CREATE" => "Создать пользователя", - "CREATED" => "Пользователь {{user_name}} был успешно создан", - "DELETE" => "Удалить пользователя", - "DELETE_CONFIRM" => "Вы уверены, что вы хотите удалить пользователя {{name}}?", - "DELETE_YES" => "Да, удалить пользователя", - "DELETED" => "Пользователь удален", - "DISABLE" => "Отключить пользователя", - "EDIT" => "Изменить пользователя", - "ENABLE" => "Включить пользователя", - "INFO_PAGE" => "Информация о пользователе для {{name}}", - "LATEST" => "Последние пользователи", - "PAGE_DESCRIPTION" => "Список пользователей для вашего сайта. Предоставляет средства управления, включая возможность редактирования сведений о пользователях, ручной активации пользователей, включения/отключения пользователей и многое другое.", - "SUMMARY" => "Сводка аккаунта", - "VIEW_ALL" => "Все пользователи", - "WITH_PERMISSION" => "Пользователи с этим доступом" + 'ACTIVATE' => 'Активировать пользователя', + 'CREATE' => 'Создать пользователя', + 'CREATED' => 'Пользователь {{user_name}} был успешно создан', + 'DELETE' => 'Удалить пользователя', + 'DELETE_CONFIRM' => 'Вы уверены, что вы хотите удалить пользователя {{name}}?', + 'DELETE_YES' => 'Да, удалить пользователя', + 'DELETED' => 'Пользователь удален', + 'DISABLE' => 'Отключить пользователя', + 'EDIT' => 'Изменить пользователя', + 'ENABLE' => 'Включить пользователя', + 'INFO_PAGE' => 'Информация о пользователе для {{name}}', + 'LATEST' => 'Последние пользователи', + 'PAGE_DESCRIPTION' => 'Список пользователей для вашего сайта. Предоставляет средства управления, включая возможность редактирования сведений о пользователях, ручной активации пользователей, включения/отключения пользователей и многое другое.', + 'SUMMARY' => 'Сводка аккаунта', + 'VIEW_ALL' => 'Все пользователи', + 'WITH_PERMISSION' => 'Пользователи с этим доступом' ], - "X_USER" => [ - 0 => "Нет пользователей", - 1 => "{{plural}} пользователя", - 2 => "{{plural}} пользователей" + 'X_USER' => [ + 0 => 'Нет пользователей', + 1 => '{{plural}} пользователя', + 2 => '{{plural}} пользователей' ] ]; diff --git a/app/sprinkles/admin/locale/th_TH/messages.php b/app/sprinkles/admin/locale/th_TH/messages.php index 546232d25..e5b0c950d 100644 --- a/app/sprinkles/admin/locale/th_TH/messages.php +++ b/app/sprinkles/admin/locale/th_TH/messages.php @@ -1,134 +1,135 @@ - [ - 1 => "กิจกรรม", - 2 => "กิจกรรม", - - "LAST" => "กิจกรรมล่าสุด", - "PAGE" => "รายการกิจกรรมของผู้ใช้", - "TIME" => "เวลาที่ทำกิจกรรม" - ], - - "CACHE" => [ - "CLEAR" => "ล้างแคช", - "CLEAR_CONFIRM" => "คุณแน่ใจหรือที่จะล้างแคชของเว็บ?", - "CLEAR_CONFIRM_YES" => "ใช่ ล้างแคชเลย", - "CLEARED" => "ล้างแคชเรียบร้อยแล้ว!" - ], - - "DASHBOARD" => "แผงควบคุม", - "DELETE_MASTER" => "คุณไม่สามารถลบบัญชีหลักได้!", - "DELETION_SUCCESSFUL" => "ลบผู้ใช้ {{user_name}} เรียบร้อยแล้ว", - "DETAILS_UPDATED" => "ปรับปรุงรายระเอียดบัญชีให้กับ {{user_name}} แล้ว", - "DISABLE_MASTER" => "คุณไม่สามารถปิดการใช้งานบัญชีหลัก!", - "DISABLE_SUCCESSFUL" => "ปิดการใช้งานบัญชีของผู้ใช้ {{user_name}} เรียบร้อยแล้ว", - - "ENABLE_SUCCESSFUL" => "เปิดการใช้งานบัญชีของผู้ใช้ {{user_name}} เรียบร้อยแล้ว", - - "GROUP" => [ - 1 => "กลุ่ม", - 2 => "กลุ่ม", - - "CREATE" => "สร้างกลุ่ม", - "DELETE" => "ลบกลุ่ม", - "DELETE_CONFIRM" => "คุณแน่ใจหรือที่จะลบกลุ่ม {{name}}?", - "DELETE_YES" => "ใช่ ลบกลุ่มนี้เลย", - "EDIT" => "แก้ไขกลุ่ม", - "ICON" => "ไอคอนกลุ่ม", - "ICON_EXPLAIN" => "ไอคอนสำหรับสมาชิกกลุ่ม", - "INFO_PAGE" => "หน้าข้อมูลกลุ่มสำหรับ {{name}}", - //"MANAGE" => "Manage group", - "NAME" => "ชื่อกลุ่ม", - "NAME_EXPLAIN" => "กรุณาตั้งชื่อสำหรับกลุ่มนี้", - "PAGE_DESCRIPTION" => "รายชื่อกลุ่มในเว็บของคุณ ประกอบไปด้วยเครื่องมือในการจัดการสำหรับการแก้ไขและลบกลุ่ม" - ], - - "MANUALLY_ACTIVATED" => "บัญชีของ {{user_name}} ได้เปิดใช้งานเองแล้ว", - "MASTER_ACCOUNT_EXISTS" => "มีบัญชีหลักอยู่แล้ว!", - "MIGRATION" => [ - "REQUIRED" => "ต้องการการปรับปรุงฐานข้อมูล" - ], - - "PERMISSION" => [ - 1 => "สิทธิการเข้าถึง", - 2 => "สิทธิการเข้าถึง", - - "ASSIGN_NEW" => "กำหนดสิทธิการเข้าถึงใหม่", - "HOOK_CONDITION" => "ข้อกำหนด/เงื่อนไข", - "MANAGE" => "จัดการสิทธิการเข้าถึง", - "PAGE_DESCRIPTION" => "รายการสิทธิการเข้าถึงในเว็บของคุณ ประกอบไปด้วยเครื่องมือในการจัดการสำหรับการแก้ไขและลบสิทธิการเข้าถึง", - "UPDATE" => "ปรับปรุงสิทธิการเข้าถึง" - ], - - "ROLE" => [ - 1 => "ตำแหน่ง", - 2 => "ตำแหน่ง", - - "ASSIGN_NEW" => "กำหนดตำแหน่งใหม่", - "CREATE" => "สร้างตำแหน่ง", - "DELETE" => "ลบตำแหน่ง", - "DELETE_CONFIRM" => "คุณแน่ใจหรือที่จะลบตำแหน่ง {{name}}?", - "DELETE_YES" => "ใช่ ลบตำแหน่งนี้เลย", - "EDIT" => "แก้ไขตำแหน่ง", - "INFO_PAGE" => "หน้าข้อมูลตำแหน่งสำหรับ {{name}}", - "MANAGE" => "จัดการตำแหน่ง", - "NAME" => "ชื่อ", - "NAME_EXPLAIN" => "กรุณาตั้งชื่อสำหรับตำแหน่งนี้", - "PAGE_DESCRIPTION" => "รายชื่อตำแหน่งในเว็บของคุณ ประกอบไปด้วยเครื่องมือในการจัดการสำหรับแก้ไขและลบตำแหน่ง", - "UPDATED" => "ปรับปรุงตำแหน่ง" - ], - - "SYSTEM_INFO" => [ - "@TRANSLATION" => "ข้อมูลระบบ", - - "DB_NAME" => "ชื่อฐานข้อมูล", - "DB_VERSION" => "เวอร์ชั่นฐานข้อมูล", - "DIRECTORY" => "ไดเรกทอรีของโปรเจค", - "PHP_VERSION" => "เวอร์ชั่น PHP", - "SERVER" => "ซอฟต์แวร์เว็บเซิร์ฟเวอร์", - "SPRINKLES" => "Sprinkles ที่ถูกโหลด", - "UF_VERSION" => "เวอร์ชั่น UserFrosting", - "URL" => "URL ของรากเว็บไซต์" - ], - - "USER" => [ - 1 => "ผู้ใช้", - 2 => "ผู้ใช้", - - "ADMIN" => [ - "CHANGE_PASSWORD" => "เปลี่ยนรหัสผ่านผู้ใช้", - "SEND_PASSWORD_LINK" => "ส่งลิงก์ที่จะอนุญาตให้ผู้ใช้เลือกรหัสผ่านเองให้กับผู้ใช้", - "SET_PASSWORD" => "ตั้งรหัสผ่านของผู้ใช้เป็น" - ], - - "ACTIVATE" => "เปิดใช้งานผู้ใช้", - "CREATE" => "สร้างผู้ใช้", - "DELETE" => "ลบผู้ใช้", - "DELETE_CONFIRM" => "คุณแน่ใจหรือที่จะลบผู้ใช้ {{name}}?", - "DELETE_YES" => "ใช่ ลบผู้ใช้นี้เลย", - "DISABLE" => "ปิดการใช้งานผู้ใช้", - "EDIT" => "แก้ไขผู้ใช้", - "ENABLE" => "เปิดการใช้งานผู้ใช้", - "INFO_PAGE" => "หน้าข้อมูลของผู้ใช้ {{name}}", - "PAGE_DESCRIPTION" => "รายชื่อผู้ใช้ในเว็บของคุณ ประกอบไปด้วยเครื่องมือสำหรับการจัดการ รวมทั้งความสามารถในการแก้ไขรายละเอียดผู้ใช้ การเปิดใช้งานผู้ใช้ การเปิด/ปิดบัญชีผู้ใช้และอื่น ๆ", - "LATEST" => "ผู้ใช้ล่าสุด", - "VIEW_ALL" => "ดูผู้ใช้ทั้งหมด" - ], - "X_USER" => [ - 0 => "ไม่มีผู้ใช้", - 1 => "{{plural}} ผู้ใช้", - 2 => "{{plural}} ผู้ใช้" - ] -]; \ No newline at end of file + [ + 1 => 'กิจกรรม', + 2 => 'กิจกรรม', + + 'LAST' => 'กิจกรรมล่าสุด', + 'PAGE' => 'รายการกิจกรรมของผู้ใช้', + 'TIME' => 'เวลาที่ทำกิจกรรม' + ], + + 'CACHE' => [ + 'CLEAR' => 'ล้างแคช', + 'CLEAR_CONFIRM' => 'คุณแน่ใจหรือที่จะล้างแคชของเว็บ?', + 'CLEAR_CONFIRM_YES' => 'ใช่ ล้างแคชเลย', + 'CLEARED' => 'ล้างแคชเรียบร้อยแล้ว!' + ], + + 'DASHBOARD' => 'แผงควบคุม', + 'DELETE_MASTER' => 'คุณไม่สามารถลบบัญชีหลักได้!', + 'DELETION_SUCCESSFUL' => 'ลบผู้ใช้ {{user_name}} เรียบร้อยแล้ว', + 'DETAILS_UPDATED' => 'ปรับปรุงรายระเอียดบัญชีให้กับ {{user_name}} แล้ว', + 'DISABLE_MASTER' => 'คุณไม่สามารถปิดการใช้งานบัญชีหลัก!', + 'DISABLE_SUCCESSFUL' => 'ปิดการใช้งานบัญชีของผู้ใช้ {{user_name}} เรียบร้อยแล้ว', + + 'ENABLE_SUCCESSFUL' => 'เปิดการใช้งานบัญชีของผู้ใช้ {{user_name}} เรียบร้อยแล้ว', + + 'GROUP' => [ + 1 => 'กลุ่ม', + 2 => 'กลุ่ม', + + 'CREATE' => 'สร้างกลุ่ม', + 'DELETE' => 'ลบกลุ่ม', + 'DELETE_CONFIRM' => 'คุณแน่ใจหรือที่จะลบกลุ่ม {{name}}?', + 'DELETE_YES' => 'ใช่ ลบกลุ่มนี้เลย', + 'EDIT' => 'แก้ไขกลุ่ม', + 'ICON' => 'ไอคอนกลุ่ม', + 'ICON_EXPLAIN' => 'ไอคอนสำหรับสมาชิกกลุ่ม', + 'INFO_PAGE' => 'หน้าข้อมูลกลุ่มสำหรับ {{name}}', + //"MANAGE" => "Manage group", + 'NAME' => 'ชื่อกลุ่ม', + 'NAME_EXPLAIN' => 'กรุณาตั้งชื่อสำหรับกลุ่มนี้', + 'PAGE_DESCRIPTION' => 'รายชื่อกลุ่มในเว็บของคุณ ประกอบไปด้วยเครื่องมือในการจัดการสำหรับการแก้ไขและลบกลุ่ม' + ], + + 'MANUALLY_ACTIVATED' => 'บัญชีของ {{user_name}} ได้เปิดใช้งานเองแล้ว', + 'MASTER_ACCOUNT_EXISTS' => 'มีบัญชีหลักอยู่แล้ว!', + 'MIGRATION' => [ + 'REQUIRED' => 'ต้องการการปรับปรุงฐานข้อมูล' + ], + + 'PERMISSION' => [ + 1 => 'สิทธิการเข้าถึง', + 2 => 'สิทธิการเข้าถึง', + + 'ASSIGN_NEW' => 'กำหนดสิทธิการเข้าถึงใหม่', + 'HOOK_CONDITION' => 'ข้อกำหนด/เงื่อนไข', + 'MANAGE' => 'จัดการสิทธิการเข้าถึง', + 'PAGE_DESCRIPTION' => 'รายการสิทธิการเข้าถึงในเว็บของคุณ ประกอบไปด้วยเครื่องมือในการจัดการสำหรับการแก้ไขและลบสิทธิการเข้าถึง', + 'UPDATE' => 'ปรับปรุงสิทธิการเข้าถึง' + ], + + 'ROLE' => [ + 1 => 'ตำแหน่ง', + 2 => 'ตำแหน่ง', + + 'ASSIGN_NEW' => 'กำหนดตำแหน่งใหม่', + 'CREATE' => 'สร้างตำแหน่ง', + 'DELETE' => 'ลบตำแหน่ง', + 'DELETE_CONFIRM' => 'คุณแน่ใจหรือที่จะลบตำแหน่ง {{name}}?', + 'DELETE_YES' => 'ใช่ ลบตำแหน่งนี้เลย', + 'EDIT' => 'แก้ไขตำแหน่ง', + 'INFO_PAGE' => 'หน้าข้อมูลตำแหน่งสำหรับ {{name}}', + 'MANAGE' => 'จัดการตำแหน่ง', + 'NAME' => 'ชื่อ', + 'NAME_EXPLAIN' => 'กรุณาตั้งชื่อสำหรับตำแหน่งนี้', + 'PAGE_DESCRIPTION' => 'รายชื่อตำแหน่งในเว็บของคุณ ประกอบไปด้วยเครื่องมือในการจัดการสำหรับแก้ไขและลบตำแหน่ง', + 'UPDATED' => 'ปรับปรุงตำแหน่ง' + ], + + 'SYSTEM_INFO' => [ + '@TRANSLATION' => 'ข้อมูลระบบ', + + 'DB_NAME' => 'ชื่อฐานข้อมูล', + 'DB_VERSION' => 'เวอร์ชั่นฐานข้อมูล', + 'DIRECTORY' => 'ไดเรกทอรีของโปรเจค', + 'PHP_VERSION' => 'เวอร์ชั่น PHP', + 'SERVER' => 'ซอฟต์แวร์เว็บเซิร์ฟเวอร์', + 'SPRINKLES' => 'Sprinkles ที่ถูกโหลด', + 'UF_VERSION' => 'เวอร์ชั่น UserFrosting', + 'URL' => 'URL ของรากเว็บไซต์' + ], + + 'USER' => [ + 1 => 'ผู้ใช้', + 2 => 'ผู้ใช้', + + 'ADMIN' => [ + 'CHANGE_PASSWORD' => 'เปลี่ยนรหัสผ่านผู้ใช้', + 'SEND_PASSWORD_LINK' => 'ส่งลิงก์ที่จะอนุญาตให้ผู้ใช้เลือกรหัสผ่านเองให้กับผู้ใช้', + 'SET_PASSWORD' => 'ตั้งรหัสผ่านของผู้ใช้เป็น' + ], + + 'ACTIVATE' => 'เปิดใช้งานผู้ใช้', + 'CREATE' => 'สร้างผู้ใช้', + 'DELETE' => 'ลบผู้ใช้', + 'DELETE_CONFIRM' => 'คุณแน่ใจหรือที่จะลบผู้ใช้ {{name}}?', + 'DELETE_YES' => 'ใช่ ลบผู้ใช้นี้เลย', + 'DISABLE' => 'ปิดการใช้งานผู้ใช้', + 'EDIT' => 'แก้ไขผู้ใช้', + 'ENABLE' => 'เปิดการใช้งานผู้ใช้', + 'INFO_PAGE' => 'หน้าข้อมูลของผู้ใช้ {{name}}', + 'PAGE_DESCRIPTION' => 'รายชื่อผู้ใช้ในเว็บของคุณ ประกอบไปด้วยเครื่องมือสำหรับการจัดการ รวมทั้งความสามารถในการแก้ไขรายละเอียดผู้ใช้ การเปิดใช้งานผู้ใช้ การเปิด/ปิดบัญชีผู้ใช้และอื่น ๆ', + 'LATEST' => 'ผู้ใช้ล่าสุด', + 'VIEW_ALL' => 'ดูผู้ใช้ทั้งหมด' + ], + 'X_USER' => [ + 0 => 'ไม่มีผู้ใช้', + 1 => '{{plural}} ผู้ใช้', + 2 => '{{plural}} ผู้ใช้' + ] +]; diff --git a/app/sprinkles/admin/locale/tr/messages.php b/app/sprinkles/admin/locale/tr/messages.php index 25d5633e1..5d4a455e6 100644 --- a/app/sprinkles/admin/locale/tr/messages.php +++ b/app/sprinkles/admin/locale/tr/messages.php @@ -3,158 +3,159 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * Turkish message token translations for the 'admin' sprinkle. * - * @package userfrosting\i18n\tr * @author Dumblledore */ - return [ - "ACTIVITY" => [ - 1 => "Etkinlik", - 2 => "Etkinlikler", + 'ACTIVITY' => [ + 1 => 'Etkinlik', + 2 => 'Etkinlikler', - "LAST" => "Son Etkinlik", - "PAGE" => "Kullanıcı etkinliklerinin listesi", - "TIME" => "Aktivite zamanı" + 'LAST' => 'Son Etkinlik', + 'PAGE' => 'Kullanıcı etkinliklerinin listesi', + 'TIME' => 'Aktivite zamanı' ], - "CACHE" => [ - "CLEAR" => "Önbelleği temizle", - "CLEAR_CONFIRM" => "Site önbelleğini temizlemek istediğine emin misin?", - "CLEAR_CONFIRM_YES" => "Evet, önbelleği temizle", - "CLEARED" => "Önbellek temizlenmesi başarıyla tamamlandı!" + 'CACHE' => [ + 'CLEAR' => 'Önbelleği temizle', + 'CLEAR_CONFIRM' => 'Site önbelleğini temizlemek istediğine emin misin?', + 'CLEAR_CONFIRM_YES' => 'Evet, önbelleği temizle', + 'CLEARED' => 'Önbellek temizlenmesi başarıyla tamamlandı!' ], - "DASHBOARD" => "Pano", - "NO_FEATURES_YET" => "Bu hesap için herhangi bir özellik ayarlanmış gibi görünmüyor... Henüz. Belki de henüz uygulanmadı, veya belki birisi size erişim vermeyi unutttu. Her iki durumda da sizi aramızda gördüğümüze sevindik!", - "DELETE_MASTER" => "Ana hesabı silemezsiniz!", - "DELETION_SUCCESSFUL" => "Kullanıcı{{user_name}} silme işlemi başarıyla tamamlandı.", - "DETAILS_UPDATED" => "Kullanıcı{{user_name}} için güncel hesap detayları", - "DISABLE_MASTER" => "Ana hesabı devre dışı bırakamazsınız!", - "DISABLE_SELF" => "Kendi hesabınızın etkinliğini sonlandıramazsınız!", - "DISABLE_SUCCESSFUL" => "Kullanıcı hesabın {{user_name}}başarıyla devre dışı bırakıldı.", - - "ENABLE_SUCCESSFUL" => "Kullanıcı hesabın{{user_name}}başarıyla etkinleştirildi.", - - "GROUP" => [ - 1 => "Grup", - 2 => "Grıplar", - - "CREATE" => "Grup oluşturmak", - "CREATION_SUCCESSFUL" => "Grup oluşturma başarılı{{name}}", - "DELETE" => "Grubu sil", - "DELETE_CONFIRM" => "Grubu silmek istediğine emin misin{{name}}?", - "DELETE_DEFAULT" => "Grubu silemezsin{{name}} çünkü o yeni kayıtlanan kullanıcılar için varsayılan grup.", - "DELETE_YES" => "Evet, grubu sil", - "DELETION_SUCCESSFUL" => "Grup silme başarılı{{name}}", - "EDIT" => "Grubu düzenle", - "ICON" => "Grup ikonu", - "ICON_EXPLAIN" => "Grup iyileri için ikon", - "INFO_PAGE" => "Grup bilgisi sayfası {{name}} için", - "MANAGE" => "Grubu yönet", - "NAME" => "Grup adı", - "NAME_EXPLAIN" => "Lütfen grup için bir isim giriniz", - "NOT_EMPTY" => "Bunu yapamazsınız çünkü hala grupla ilişkili kullanıcılar var{{name}}.", - "PAGE_DESCRIPTION" => "Siten için grupların bir listesi. Grupları silmek ve düzenlemek için yönetim araçları sağlar.", - "SUMMARY" => "Grup özeti", - "UPDATE" => "Grup için detaylar güncellendi{{name}}" + 'DASHBOARD' => 'Pano', + 'NO_FEATURES_YET' => 'Bu hesap için herhangi bir özellik ayarlanmış gibi görünmüyor... Henüz. Belki de henüz uygulanmadı, veya belki birisi size erişim vermeyi unutttu. Her iki durumda da sizi aramızda gördüğümüze sevindik!', + 'DELETE_MASTER' => 'Ana hesabı silemezsiniz!', + 'DELETION_SUCCESSFUL' => 'Kullanıcı{{user_name}} silme işlemi başarıyla tamamlandı.', + 'DETAILS_UPDATED' => 'Kullanıcı{{user_name}} için güncel hesap detayları', + 'DISABLE_MASTER' => 'Ana hesabı devre dışı bırakamazsınız!', + 'DISABLE_SELF' => 'Kendi hesabınızın etkinliğini sonlandıramazsınız!', + 'DISABLE_SUCCESSFUL' => 'Kullanıcı hesabın {{user_name}}başarıyla devre dışı bırakıldı.', + + 'ENABLE_SUCCESSFUL' => 'Kullanıcı hesabın{{user_name}}başarıyla etkinleştirildi.', + + 'GROUP' => [ + 1 => 'Grup', + 2 => 'Grıplar', + + 'CREATE' => 'Grup oluşturmak', + 'CREATION_SUCCESSFUL' => 'Grup oluşturma başarılı{{name}}', + 'DELETE' => 'Grubu sil', + 'DELETE_CONFIRM' => 'Grubu silmek istediğine emin misin{{name}}?', + 'DELETE_DEFAULT' => 'Grubu silemezsin{{name}} çünkü o yeni kayıtlanan kullanıcılar için varsayılan grup.', + 'DELETE_YES' => 'Evet, grubu sil', + 'DELETION_SUCCESSFUL' => 'Grup silme başarılı{{name}}', + 'EDIT' => 'Grubu düzenle', + 'ICON' => 'Grup ikonu', + 'ICON_EXPLAIN' => 'Grup iyileri için ikon', + 'INFO_PAGE' => 'Grup bilgisi sayfası {{name}} için', + 'MANAGE' => 'Grubu yönet', + 'NAME' => 'Grup adı', + 'NAME_EXPLAIN' => 'Lütfen grup için bir isim giriniz', + 'NOT_EMPTY' => 'Bunu yapamazsınız çünkü hala grupla ilişkili kullanıcılar var{{name}}.', + 'PAGE_DESCRIPTION' => 'Siten için grupların bir listesi. Grupları silmek ve düzenlemek için yönetim araçları sağlar.', + 'SUMMARY' => 'Grup özeti', + 'UPDATE' => 'Grup için detaylar güncellendi{{name}}' ], - "MANUALLY_ACTIVATED" => "{{user_name}}'ın hesabı el ile aktifleştirildi", - "MASTER_ACCOUNT_EXISTS" => "Ana hesap zaten mevcut!", - "MIGRATION" => [ - "REQUIRED" => "Veritabanını güncellemek gerek" + 'MANUALLY_ACTIVATED' => "{{user_name}}'ın hesabı el ile aktifleştirildi", + 'MASTER_ACCOUNT_EXISTS' => 'Ana hesap zaten mevcut!', + 'MIGRATION' => [ + 'REQUIRED' => 'Veritabanını güncellemek gerek' ], - "PERMISSION" => [ - 1 => "İzin", - 2 => "İzinler", - - "ASSIGN_NEW" => "Yeni izin ata", - "HOOK_CONDITION" => "Kanca/Koşullar", - "ID" => "İzin Kimliği", - "INFO_PAGE" => "{{name}} için izin bilgi sayfası", - "MANAGE" => "İzinleri yönet", - "NOTE_READ_ONLY" => "Lütfen Dikkat izinler ''bir kodun parçası'' olarak kabul edilir ve arayüz aracılığıyla değiştirilemez. İzln eklemek, kaldırmak ya da değiştirmek için site bakımcıları bir veritabanı geçişi kullanmalıdır", - "PAGE_DESCRIPTION" => "Siteniz için izinlerin bir listesi. Düzenleme yapmak ve izinleri kaldırmak yönetim araçları temin eder.", - "SUMMARY" => "İzin Özeti", - "UPDATE" => "İzinlerin Güncellenmesi", - "VIA_ROLES" => "Roller ile izin alımı" + 'PERMISSION' => [ + 1 => 'İzin', + 2 => 'İzinler', + + 'ASSIGN_NEW' => 'Yeni izin ata', + 'HOOK_CONDITION' => 'Kanca/Koşullar', + 'ID' => 'İzin Kimliği', + 'INFO_PAGE' => '{{name}} için izin bilgi sayfası', + 'MANAGE' => 'İzinleri yönet', + 'NOTE_READ_ONLY' => "Lütfen Dikkat izinler ''bir kodun parçası'' olarak kabul edilir ve arayüz aracılığıyla değiştirilemez. İzln eklemek, kaldırmak ya da değiştirmek için site bakımcıları bir veritabanı geçişi kullanmalıdır", + 'PAGE_DESCRIPTION' => 'Siteniz için izinlerin bir listesi. Düzenleme yapmak ve izinleri kaldırmak yönetim araçları temin eder.', + 'SUMMARY' => 'İzin Özeti', + 'UPDATE' => 'İzinlerin Güncellenmesi', + 'VIA_ROLES' => 'Roller ile izin alımı' ], - "ROLE" => [ - 1 => "Rol", - 2 => "Roller", - - "ASSIGN_NEW" => "Yeni rol ata", - "CREATE" => "Rol oluştur", - "CREATION_SUCCESSFUL" => "Rol oluşturma başarılı {{name}}", - "DELETE" => "Rolü sil", - "DELETE_CONFIRM" => "Rolü silmek istediğine emin misin {{name}}?", - "DELETE_DEFAULT" => "Rolü silemezsin {{name}} çünkü o kaydolmuş kullanıcılar için varsayılan bir rol.", - "DELETE_YES" => "Evet, rolü sil", - "DELETION_SUCCESSFUL" => "Rol başarıyla silindi{{name}}", - "EDIT" => "Rolü düzenle", - "HAS_USERS" => "Bunu yapamazsın çünkü hala bu rol ile bağlantılı kullanıcılar var{{name}}.", - "INFO_PAGE" => "{{name}} için rol bilgi sayfası", - "MANAGE" => "Rolleri yönet", - "NAME" => "Ad", - "NAME_EXPLAIN" => "Lütfen rol için bir ad giriniz", - "NAME_IN_USE" => "{{name}} adında bir rol zaten mevcut", - "PAGE_DESCRIPTION" => "Siteniz için rollerin bir listesi. Düzenlemek ve rolleri silmek için yönetim araçları sağlar.", - "PERMISSIONS_UPDATED" => "Rol için izinler güncellendi{{name}}", - "SUMMARY" => "Rol özeti", - "UPDATED" => "Rol için detaylar güncellendi{{name}}" + 'ROLE' => [ + 1 => 'Rol', + 2 => 'Roller', + + 'ASSIGN_NEW' => 'Yeni rol ata', + 'CREATE' => 'Rol oluştur', + 'CREATION_SUCCESSFUL' => 'Rol oluşturma başarılı {{name}}', + 'DELETE' => 'Rolü sil', + 'DELETE_CONFIRM' => 'Rolü silmek istediğine emin misin {{name}}?', + 'DELETE_DEFAULT' => 'Rolü silemezsin {{name}} çünkü o kaydolmuş kullanıcılar için varsayılan bir rol.', + 'DELETE_YES' => 'Evet, rolü sil', + 'DELETION_SUCCESSFUL' => 'Rol başarıyla silindi{{name}}', + 'EDIT' => 'Rolü düzenle', + 'HAS_USERS' => 'Bunu yapamazsın çünkü hala bu rol ile bağlantılı kullanıcılar var{{name}}.', + 'INFO_PAGE' => '{{name}} için rol bilgi sayfası', + 'MANAGE' => 'Rolleri yönet', + 'NAME' => 'Ad', + 'NAME_EXPLAIN' => 'Lütfen rol için bir ad giriniz', + 'NAME_IN_USE' => '{{name}} adında bir rol zaten mevcut', + 'PAGE_DESCRIPTION' => 'Siteniz için rollerin bir listesi. Düzenlemek ve rolleri silmek için yönetim araçları sağlar.', + 'PERMISSIONS_UPDATED' => 'Rol için izinler güncellendi{{name}}', + 'SUMMARY' => 'Rol özeti', + 'UPDATED' => 'Rol için detaylar güncellendi{{name}}' ], - "SYSTEM_INFO" => [ - "@TRANSLATION" => "Sistem bilgisi", - - "DB_NAME" => "Veritabanı adı", - "DB_VERSION" => "Veritabanı sürümü", - "DIRECTORY" => "Proje dizini", - "PHP_VERSION" => "PHP sürümü", - "SERVER" => "Web sunucu yazılımı", - "SPRINKLES" => "Yüklü serpintiler", - "UF_VERSION" => "UserFrosting sürümü", - "URL" => "Site kök url" + 'SYSTEM_INFO' => [ + '@TRANSLATION' => 'Sistem bilgisi', + + 'DB_NAME' => 'Veritabanı adı', + 'DB_VERSION' => 'Veritabanı sürümü', + 'DIRECTORY' => 'Proje dizini', + 'PHP_VERSION' => 'PHP sürümü', + 'SERVER' => 'Web sunucu yazılımı', + 'SPRINKLES' => 'Yüklü serpintiler', + 'UF_VERSION' => 'UserFrosting sürümü', + 'URL' => 'Site kök url' ], - "TOGGLE_COLUMNS" => "Sütünları değiştirme", + 'TOGGLE_COLUMNS' => 'Sütünları değiştirme', - "USER" => [ - 1 => "Kullanıcı", - 2 => "Kullanıcılar", + 'USER' => [ + 1 => 'Kullanıcı', + 2 => 'Kullanıcılar', - "ADMIN" => [ - "CHANGE_PASSWORD" => "Kullanıcı şifresini değiştir", - "SEND_PASSWORD_LINK" => "Kullanıcıya kendi şifresini seçebileceği bir bağlantı gönder", - "SET_PASSWORD" => "Kullanıcının şifresi olarak ayarla" + 'ADMIN' => [ + 'CHANGE_PASSWORD' => 'Kullanıcı şifresini değiştir', + 'SEND_PASSWORD_LINK' => 'Kullanıcıya kendi şifresini seçebileceği bir bağlantı gönder', + 'SET_PASSWORD' => 'Kullanıcının şifresi olarak ayarla' ], - "ACTIVATE" => "Aktif Kullanıcı", - "CREATE" => "Kullanıcı oluştur", - "CREATED" => "Kullanıcı {{user_name}} başarıyla oluşturuldu", - "DELETE" => "Kullanıcıyı sil", - "DELETE_CONFIRM" => "Kullanıcıyı silmek istediğinden emin misin?{{name}}?", - "DELETE_YES" => "Evet, kullanıcıyı sil", - "DELETED" => "Kullanıcı silindi", - "DISABLE" => "Kullanıcı devre dışı", - "EDIT" => "Kullanıcıyı düzenle", - "ENABLE" => "Kullanıcı etkin", - "INFO_PAGE" => "{{name}} için kullanıcı bilgisi", - "LATEST" => "Son Kullanıcılar", - "PAGE_DESCRIPTION" => "Siten için kullanıcıların listesi. Kullanıcı detaylarını düzenlemek, elle kullanıcıları aktifleştirmek, kullanıcıları etkinleştirme/devre dışı bırakma, ve daha fazlası için yönetimsel araçlar sağlar.", - "SUMMARY" => "Hesap özeti", - "VIEW_ALL" => "Tüm kullanıcıları göster", - "WITH_PERMISSION" => "Bu izni olan kullanıcılar" + 'ACTIVATE' => 'Aktif Kullanıcı', + 'CREATE' => 'Kullanıcı oluştur', + 'CREATED' => 'Kullanıcı {{user_name}} başarıyla oluşturuldu', + 'DELETE' => 'Kullanıcıyı sil', + 'DELETE_CONFIRM' => 'Kullanıcıyı silmek istediğinden emin misin?{{name}}?', + 'DELETE_YES' => 'Evet, kullanıcıyı sil', + 'DELETED' => 'Kullanıcı silindi', + 'DISABLE' => 'Kullanıcı devre dışı', + 'EDIT' => 'Kullanıcıyı düzenle', + 'ENABLE' => 'Kullanıcı etkin', + 'INFO_PAGE' => '{{name}} için kullanıcı bilgisi', + 'LATEST' => 'Son Kullanıcılar', + 'PAGE_DESCRIPTION' => 'Siten için kullanıcıların listesi. Kullanıcı detaylarını düzenlemek, elle kullanıcıları aktifleştirmek, kullanıcıları etkinleştirme/devre dışı bırakma, ve daha fazlası için yönetimsel araçlar sağlar.', + 'SUMMARY' => 'Hesap özeti', + 'VIEW_ALL' => 'Tüm kullanıcıları göster', + 'WITH_PERMISSION' => 'Bu izni olan kullanıcılar' ], - "X_USER" => [ - 0 => "Kullanıcı yok", - 1 => "{{plural}} kullanıcı", - 2 => "{{plural}} kullanıcılar" + 'X_USER' => [ + 0 => 'Kullanıcı yok', + 1 => '{{plural}} kullanıcı', + 2 => '{{plural}} kullanıcılar' ] ]; diff --git a/app/sprinkles/admin/locale/zh_CN/messages.php b/app/sprinkles/admin/locale/zh_CN/messages.php index 2adc8c88b..5030f2552 100644 --- a/app/sprinkles/admin/locale/zh_CN/messages.php +++ b/app/sprinkles/admin/locale/zh_CN/messages.php @@ -3,159 +3,160 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - * + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) + */ + +/** * Chinese message token translations for the 'admin' sprinkle. * - * @package userfrosting\i18n\zh_CN * @author @BruceGui (https://github.com/BruceGui) */ - return [ - "ACTIVITY" => [ - 1 => "活动", - 2 => "活动", + 'ACTIVITY' => [ + 1 => '活动', + 2 => '活动', - "LAST" => "最后活动", - "PAGE" => "用户活动列表", - "TIME" => "活动时间" + 'LAST' => '最后活动', + 'PAGE' => '用户活动列表', + 'TIME' => '活动时间' ], - "ADMIN" => [ - "PANEL" => "控制台" + 'ADMIN' => [ + 'PANEL' => '控制台' ], - "CACHE" => [ - "CLEAR" => "清除缓存", - "CLEAR_CONFIRM" => "你确定要清除此网站的缓存?", - "CLEAR_CONFIRM_YES" => "是的, 清除缓存", - "CLEARED" => "缓存成功清除 !" + 'CACHE' => [ + 'CLEAR' => '清除缓存', + 'CLEAR_CONFIRM' => '你确定要清除此网站的缓存?', + 'CLEAR_CONFIRM_YES' => '是的, 清除缓存', + 'CLEARED' => '缓存成功清除 !' ], - "DASHBOARD" => "仪表盘", - "DELETE_MASTER" => "你不能删除超级账户!", - "DELETION_SUCCESSFUL" => "用户 {{user_name}} 删除成功.", - "DETAILS_UPDATED" => "用户 {{user_name}} 更新成功", - "DISABLE_MASTER" => "你不能禁用超级用户!", - "DISABLE_SELF" => "不能禁用你自己的账户!", - "DISABLE_SUCCESSFUL" => "用户名{{user_name}} 成功禁用.", - - "ENABLE_SUCCESSFUL" => "用户名 {{user_name}} 启用成功.", - - "GROUP" => [ - 1 => "组", - 2 => "组", - - "CREATE" => "新建组", - "CREATION_SUCCESSFUL" => "成功创建组 {{name}}", - "DELETE" => "删除组", - "DELETE_CONFIRM" => "确定要删除组 {{name}}?", - "DELETE_DEFAULT" => "你无法删除组 {{name}} 因为这是新注册用户的默认组.", - "DELETE_YES" => "是, 删除组", - "DELETION_SUCCESSFUL" => "成功删除组{{name}}", - "EDIT" => "编辑组", - "ICON" => "组图标", - "ICON_EXPLAIN" => "组成员图标", - "INFO_PAGE" => "{{name}} 组的信息页", - "NAME" => "组名", - "NAME_EXPLAIN" => "请为组取一个名字", - "NOT_EMPTY" => "你不能这样做,因为还有用户在组 {{name}}.", - "PAGE_DESCRIPTION" => "网站组列表. 请管理编辑和删除组的工具.", - "SUMMARY" => "组简介", - "UPDATE" => "组{{name}}的信息已经更新" + 'DASHBOARD' => '仪表盘', + 'DELETE_MASTER' => '你不能删除超级账户!', + 'DELETION_SUCCESSFUL' => '用户 {{user_name}} 删除成功.', + 'DETAILS_UPDATED' => '用户 {{user_name}} 更新成功', + 'DISABLE_MASTER' => '你不能禁用超级用户!', + 'DISABLE_SELF' => '不能禁用你自己的账户!', + 'DISABLE_SUCCESSFUL' => '用户名{{user_name}} 成功禁用.', + + 'ENABLE_SUCCESSFUL' => '用户名 {{user_name}} 启用成功.', + + 'GROUP' => [ + 1 => '组', + 2 => '组', + + 'CREATE' => '新建组', + 'CREATION_SUCCESSFUL' => '成功创建组 {{name}}', + 'DELETE' => '删除组', + 'DELETE_CONFIRM' => '确定要删除组 {{name}}?', + 'DELETE_DEFAULT' => '你无法删除组 {{name}} 因为这是新注册用户的默认组.', + 'DELETE_YES' => '是, 删除组', + 'DELETION_SUCCESSFUL' => '成功删除组{{name}}', + 'EDIT' => '编辑组', + 'ICON' => '组图标', + 'ICON_EXPLAIN' => '组成员图标', + 'INFO_PAGE' => '{{name}} 组的信息页', + 'NAME' => '组名', + 'NAME_EXPLAIN' => '请为组取一个名字', + 'NOT_EMPTY' => '你不能这样做,因为还有用户在组 {{name}}.', + 'PAGE_DESCRIPTION' => '网站组列表. 请管理编辑和删除组的工具.', + 'SUMMARY' => '组简介', + 'UPDATE' => '组{{name}}的信息已经更新' ], - "MANUALLY_ACTIVATED" => "{{user_name}} 账户已手动激活", - "MASTER_ACCOUNT_EXISTS" => "超级账户已存在!", - "MIGRATION" => [ - "REQUIRED" => "数据库需要更新" + 'MANUALLY_ACTIVATED' => '{{user_name}} 账户已手动激活', + 'MASTER_ACCOUNT_EXISTS' => '超级账户已存在!', + 'MIGRATION' => [ + 'REQUIRED' => '数据库需要更新' ], - "PERMISSION" => [ - 1 => "权限", - 2 => "权限", - - "ASSIGN_NEW" => "分配新权限", - "HOOK_CONDITION" => "Hook/条件", - "ID" => "权限ID", - "INFO_PAGE" => "用户 '{{name}}' 的权限页", - "MANAGE" => "管理权限", - "NOTE_READ_ONLY" => "请注意: 权限是 \"part of the code\" 不能通过界面修改. 如果要添加、删除、编辑权限, 网站维护者需要 进行数据库迁移.", - "PAGE_DESCRIPTION" => "网站的权限列表. 提供编辑和删除权限的工具.", - "SUMMARY" => "权限概要", - "UPDATE" => "更新权限", - "VIA_ROLES" => "权限通过用户了" + 'PERMISSION' => [ + 1 => '权限', + 2 => '权限', + + 'ASSIGN_NEW' => '分配新权限', + 'HOOK_CONDITION' => 'Hook/条件', + 'ID' => '权限ID', + 'INFO_PAGE' => "用户 '{{name}}' 的权限页", + 'MANAGE' => '管理权限', + 'NOTE_READ_ONLY' => '请注意: 权限是 "part of the code" 不能通过界面修改. 如果要添加、删除、编辑权限, 网站维护者需要 进行数据库迁移.', + 'PAGE_DESCRIPTION' => '网站的权限列表. 提供编辑和删除权限的工具.', + 'SUMMARY' => '权限概要', + 'UPDATE' => '更新权限', + 'VIA_ROLES' => '权限通过用户了' ], - "ROLE" => [ - 1 => "角色", - 2 => "角色", - - "ASSIGN_NEW" => "分配角色", - "CREATE" => "创建角色", - "CREATION_SUCCESSFUL" => "成功创建角色 {{name}}", - "DELETE" => "删除角色", - "DELETE_CONFIRM" => "你确定要删除角色 {{name}}?", - "DELETE_DEFAULT" => "你无法删除角色{{name}} 因为这是新注册用户的默认角色.", - "DELETE_YES" => "是, 删除角色", - "DELETION_SUCCESSFUL" => "成功删除角色 {{name}}", - "EDIT" => "编辑角色", - "HAS_USERS" => "你不能这样做,因为还有用户拥有角色 {{name}}.", - "INFO_PAGE" => "{{name}}角色的权限页", - "MANAGE" => "管理角色", - "NAME" => "名字", - "NAME_EXPLAIN" => "请为角色输入名字", - "NAME_IN_USE" => "角色名 {{name}} 以存在", - "PAGE_DESCRIPTION" => "网站的角色列表. 请管理编辑和删除角色的工具.", - "PERMISSIONS_UPDATED" => "角色 {{name}} 的权限更新成功", - "SUMMARY" => "角色概要", - "UPDATED" => "角色 {{name}} 更新成功" + 'ROLE' => [ + 1 => '角色', + 2 => '角色', + + 'ASSIGN_NEW' => '分配角色', + 'CREATE' => '创建角色', + 'CREATION_SUCCESSFUL' => '成功创建角色 {{name}}', + 'DELETE' => '删除角色', + 'DELETE_CONFIRM' => '你确定要删除角色 {{name}}?', + 'DELETE_DEFAULT' => '你无法删除角色{{name}} 因为这是新注册用户的默认角色.', + 'DELETE_YES' => '是, 删除角色', + 'DELETION_SUCCESSFUL' => '成功删除角色 {{name}}', + 'EDIT' => '编辑角色', + 'HAS_USERS' => '你不能这样做,因为还有用户拥有角色 {{name}}.', + 'INFO_PAGE' => '{{name}}角色的权限页', + 'MANAGE' => '管理角色', + 'NAME' => '名字', + 'NAME_EXPLAIN' => '请为角色输入名字', + 'NAME_IN_USE' => '角色名 {{name}} 以存在', + 'PAGE_DESCRIPTION' => '网站的角色列表. 请管理编辑和删除角色的工具.', + 'PERMISSIONS_UPDATED' => '角色 {{name}} 的权限更新成功', + 'SUMMARY' => '角色概要', + 'UPDATED' => '角色 {{name}} 更新成功' ], - "SYSTEM_INFO" => [ - "@TRANSLATION" => "系统信息", - - "DB_NAME" => "数据库", - "DB_VERSION" => "数据库版本", - "DIRECTORY" => "工程目录", - "PHP_VERSION" => "PHP 版本", - "SERVER" => "网站服务器", - "SPRINKLES" => "加载的 sprinkles", - "UF_VERSION" => "UserFrosting 版本", - "URL" => "网站根目录" + 'SYSTEM_INFO' => [ + '@TRANSLATION' => '系统信息', + + 'DB_NAME' => '数据库', + 'DB_VERSION' => '数据库版本', + 'DIRECTORY' => '工程目录', + 'PHP_VERSION' => 'PHP 版本', + 'SERVER' => '网站服务器', + 'SPRINKLES' => '加载的 sprinkles', + 'UF_VERSION' => 'UserFrosting 版本', + 'URL' => '网站根目录' ], - "TOGGLE_COLUMNS" => "加载列", + 'TOGGLE_COLUMNS' => '加载列', - "USER" => [ - 1 => "用户", - 2 => "用户", + 'USER' => [ + 1 => '用户', + 2 => '用户', - "ADMIN" => [ - "CHANGE_PASSWORD" => "修改用户密码", - "SEND_PASSWORD_LINK" => "发送允许该用户修改密码的链接", - "SET_PASSWORD" => "设置用户的密码为" + 'ADMIN' => [ + 'CHANGE_PASSWORD' => '修改用户密码', + 'SEND_PASSWORD_LINK' => '发送允许该用户修改密码的链接', + 'SET_PASSWORD' => '设置用户的密码为' ], - "ACTIVATE" => "激活用户", - "CREATE" => "新建用户", - "CREATED" => "用户 {{user_name}} 新建成功", - "DELETE" => "删除用户", - "DELETE_CONFIRM" => "确定要删除用户 {{name}}?", - "DELETE_YES" => "是, 删除用户", - "DISABLE" => "禁用用户", - "EDIT" => "编辑用户", - "ENABLE" => "启用用户", - "INFO_PAGE" => "用户 {{name}} 的信息页", - "LATEST" => "最新用户", - "PAGE_DESCRIPTION" => "网站用户列表. 提供编辑用户信息, 手动激活用户, 启用/禁用 用户等工具.", - "SUMMARY" => "账户概要", - "VIEW_ALL" => "浏览所有用户", - "WITH_PERMISSION" => "有此权限的用户" + 'ACTIVATE' => '激活用户', + 'CREATE' => '新建用户', + 'CREATED' => '用户 {{user_name}} 新建成功', + 'DELETE' => '删除用户', + 'DELETE_CONFIRM' => '确定要删除用户 {{name}}?', + 'DELETE_YES' => '是, 删除用户', + 'DISABLE' => '禁用用户', + 'EDIT' => '编辑用户', + 'ENABLE' => '启用用户', + 'INFO_PAGE' => '用户 {{name}} 的信息页', + 'LATEST' => '最新用户', + 'PAGE_DESCRIPTION' => '网站用户列表. 提供编辑用户信息, 手动激活用户, 启用/禁用 用户等工具.', + 'SUMMARY' => '账户概要', + 'VIEW_ALL' => '浏览所有用户', + 'WITH_PERMISSION' => '有此权限的用户' ], - "X_USER" => [ - 0 => "没有用户", - 1 => "{{plural}} 用户", - 2 => "{{plural}} 用户" + 'X_USER' => [ + 0 => '没有用户', + 1 => '{{plural}} 用户', + 2 => '{{plural}} 用户' ] ]; diff --git a/app/sprinkles/admin/routes/activities.php b/app/sprinkles/admin/routes/activities.php index b32455320..a68646556 100644 --- a/app/sprinkles/admin/routes/activities.php +++ b/app/sprinkles/admin/routes/activities.php @@ -3,17 +3,20 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ +use UserFrosting\Sprinkle\Core\Util\NoCache; + /** * Routes for administrative activity monitoring. */ $app->group('/activities', function () { $this->get('', 'UserFrosting\Sprinkle\Admin\Controller\ActivityController:pageList') ->setName('uri_activities'); -})->add('authGuard'); +})->add('authGuard')->add(new NoCache()); $app->group('/api/activities', function () { $this->get('', 'UserFrosting\Sprinkle\Admin\Controller\ActivityController:getList'); -})->add('authGuard'); +})->add('authGuard')->add(new NoCache()); diff --git a/app/sprinkles/admin/routes/admin.php b/app/sprinkles/admin/routes/admin.php index 1a8c31a90..dd5baee99 100644 --- a/app/sprinkles/admin/routes/admin.php +++ b/app/sprinkles/admin/routes/admin.php @@ -3,21 +3,24 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ +use UserFrosting\Sprinkle\Core\Util\NoCache; + /** * Routes for administrative panel management. */ $app->group('/dashboard', function () { $this->get('', 'UserFrosting\Sprinkle\Admin\Controller\AdminController:pageDashboard') ->setName('dashboard'); -})->add('authGuard'); +})->add('authGuard')->add(new NoCache()); $app->group('/api/dashboard', function () { $this->post('/clear-cache', 'UserFrosting\Sprinkle\Admin\Controller\AdminController:clearCache'); -})->add('authGuard'); +})->add('authGuard')->add(new NoCache()); $app->group('/modals/dashboard', function () { $this->get('/clear-cache', 'UserFrosting\Sprinkle\Admin\Controller\AdminController:getModalConfirmClearCache'); -})->add('authGuard'); +})->add('authGuard')->add(new NoCache()); diff --git a/app/sprinkles/admin/routes/groups.php b/app/sprinkles/admin/routes/groups.php index e861960c2..a4841a467 100644 --- a/app/sprinkles/admin/routes/groups.php +++ b/app/sprinkles/admin/routes/groups.php @@ -3,9 +3,12 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ +use UserFrosting\Sprinkle\Core\Util\NoCache; + /** * Routes for administrative group management. */ @@ -14,7 +17,7 @@ ->setName('uri_groups'); $this->get('/g/{slug}', 'UserFrosting\Sprinkle\Admin\Controller\GroupController:pageInfo'); -})->add('authGuard'); +})->add('authGuard')->add(new NoCache()); $app->group('/api/groups', function () { $this->delete('/g/{slug}', 'UserFrosting\Sprinkle\Admin\Controller\GroupController:delete'); @@ -28,7 +31,7 @@ $this->post('', 'UserFrosting\Sprinkle\Admin\Controller\GroupController:create'); $this->put('/g/{slug}', 'UserFrosting\Sprinkle\Admin\Controller\GroupController:updateInfo'); -})->add('authGuard'); +})->add('authGuard')->add(new NoCache()); $app->group('/modals/groups', function () { $this->get('/confirm-delete', 'UserFrosting\Sprinkle\Admin\Controller\GroupController:getModalConfirmDelete'); @@ -36,4 +39,4 @@ $this->get('/create', 'UserFrosting\Sprinkle\Admin\Controller\GroupController:getModalCreate'); $this->get('/edit', 'UserFrosting\Sprinkle\Admin\Controller\GroupController:getModalEdit'); -})->add('authGuard'); +})->add('authGuard')->add(new NoCache()); diff --git a/app/sprinkles/admin/routes/permissions.php b/app/sprinkles/admin/routes/permissions.php index 4df04c8c6..9f6871beb 100644 --- a/app/sprinkles/admin/routes/permissions.php +++ b/app/sprinkles/admin/routes/permissions.php @@ -3,9 +3,12 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ +use UserFrosting\Sprinkle\Core\Util\NoCache; + /** * Routes for administrative permission management. */ @@ -14,7 +17,7 @@ ->setName('uri_permissions'); $this->get('/p/{id}', 'UserFrosting\Sprinkle\Admin\Controller\PermissionController:pageInfo'); -})->add('authGuard'); +})->add('authGuard')->add(new NoCache()); $app->group('/api/permissions', function () { $this->get('', 'UserFrosting\Sprinkle\Admin\Controller\PermissionController:getList'); @@ -22,4 +25,4 @@ $this->get('/p/{id}', 'UserFrosting\Sprinkle\Admin\Controller\PermissionController:getInfo'); $this->get('/p/{id}/users', 'UserFrosting\Sprinkle\Admin\Controller\PermissionController:getUsers'); -})->add('authGuard'); +})->add('authGuard')->add(new NoCache()); diff --git a/app/sprinkles/admin/routes/roles.php b/app/sprinkles/admin/routes/roles.php index 1de12e8a2..efcd21704 100644 --- a/app/sprinkles/admin/routes/roles.php +++ b/app/sprinkles/admin/routes/roles.php @@ -3,9 +3,12 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ +use UserFrosting\Sprinkle\Core\Util\NoCache; + /** * Routes for administrative role management. */ @@ -14,7 +17,7 @@ ->setName('uri_roles'); $this->get('/r/{slug}', 'UserFrosting\Sprinkle\Admin\Controller\RoleController:pageInfo'); -})->add('authGuard'); +})->add('authGuard')->add(new NoCache()); $app->group('/api/roles', function () { $this->delete('/r/{slug}', 'UserFrosting\Sprinkle\Admin\Controller\RoleController:delete'); @@ -32,7 +35,7 @@ $this->put('/r/{slug}', 'UserFrosting\Sprinkle\Admin\Controller\RoleController:updateInfo'); $this->put('/r/{slug}/{field}', 'UserFrosting\Sprinkle\Admin\Controller\RoleController:updateField'); -})->add('authGuard'); +})->add('authGuard')->add(new NoCache()); $app->group('/modals/roles', function () { $this->get('/confirm-delete', 'UserFrosting\Sprinkle\Admin\Controller\RoleController:getModalConfirmDelete'); @@ -42,4 +45,4 @@ $this->get('/edit', 'UserFrosting\Sprinkle\Admin\Controller\RoleController:getModalEdit'); $this->get('/permissions', 'UserFrosting\Sprinkle\Admin\Controller\RoleController:getModalEditPermissions'); -})->add('authGuard'); +})->add('authGuard')->add(new NoCache()); diff --git a/app/sprinkles/admin/routes/users.php b/app/sprinkles/admin/routes/users.php index f1b22431d..0bcdbabd6 100644 --- a/app/sprinkles/admin/routes/users.php +++ b/app/sprinkles/admin/routes/users.php @@ -3,9 +3,12 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ +use UserFrosting\Sprinkle\Core\Util\NoCache; + /** * Routes for administrative user management. */ @@ -14,7 +17,7 @@ ->setName('uri_users'); $this->get('/u/{user_name}', 'UserFrosting\Sprinkle\Admin\Controller\UserController:pageInfo'); -})->add('authGuard'); +})->add('authGuard')->add(new NoCache()); $app->group('/api/users', function () { $this->delete('/u/{user_name}', 'UserFrosting\Sprinkle\Admin\Controller\UserController:delete'); @@ -36,7 +39,7 @@ $this->put('/u/{user_name}', 'UserFrosting\Sprinkle\Admin\Controller\UserController:updateInfo'); $this->put('/u/{user_name}/{field}', 'UserFrosting\Sprinkle\Admin\Controller\UserController:updateField'); -})->add('authGuard'); +})->add('authGuard')->add(new NoCache()); $app->group('/modals/users', function () { $this->get('/confirm-delete', 'UserFrosting\Sprinkle\Admin\Controller\UserController:getModalConfirmDelete'); @@ -48,4 +51,4 @@ $this->get('/password', 'UserFrosting\Sprinkle\Admin\Controller\UserController:getModalEditPassword'); $this->get('/roles', 'UserFrosting\Sprinkle\Admin\Controller\UserController:getModalEditRoles'); -})->add('authGuard'); +})->add('authGuard')->add(new NoCache()); diff --git a/app/sprinkles/admin/src/Admin.php b/app/sprinkles/admin/src/Admin.php index 8a6dcc1d7..284d2f420 100644 --- a/app/sprinkles/admin/src/Admin.php +++ b/app/sprinkles/admin/src/Admin.php @@ -3,8 +3,10 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Admin; use UserFrosting\System\Sprinkle\Sprinkle; @@ -16,5 +18,4 @@ */ class Admin extends Sprinkle { - } diff --git a/app/sprinkles/admin/src/Controller/ActivityController.php b/app/sprinkles/admin/src/Controller/ActivityController.php index 2fbe0d97f..099889aba 100644 --- a/app/sprinkles/admin/src/Controller/ActivityController.php +++ b/app/sprinkles/admin/src/Controller/ActivityController.php @@ -3,19 +3,16 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Admin\Controller; -use Illuminate\Database\Capsule\Manager as Capsule; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; -use Slim\Exception\NotFoundException; use UserFrosting\Sprinkle\Core\Controller\SimpleController; -use UserFrosting\Sprinkle\Core\Facades\Debug; -use UserFrosting\Support\Exception\BadRequestException; use UserFrosting\Support\Exception\ForbiddenException; -use UserFrosting\Support\Exception\HttpException; /** * Controller class for activity-related requests. @@ -30,16 +27,19 @@ class ActivityController extends SimpleController * Generates a list of activities, optionally paginated, sorted and/or filtered. * This page requires authentication. * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args */ - public function getList($request, $response, $args) + public function getList(Request $request, Response $response, $args) { // GET parameters $params = $request->getQueryParams(); - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -47,7 +47,7 @@ public function getList($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; $sprunje = $classMapper->createInstance('activity_sprunje', $classMapper, $params); @@ -66,13 +66,16 @@ public function getList($request, $response, $args) * This page renders a table of user activities. * This page requires authentication. * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args */ - public function pageList($request, $response, $args) + public function pageList(Request $request, Response $response, $args) { - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page diff --git a/app/sprinkles/admin/src/Controller/AdminController.php b/app/sprinkles/admin/src/Controller/AdminController.php index da4da8aec..0af79336c 100644 --- a/app/sprinkles/admin/src/Controller/AdminController.php +++ b/app/sprinkles/admin/src/Controller/AdminController.php @@ -3,16 +3,16 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Admin\Controller; use Carbon\Carbon; +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; use UserFrosting\Sprinkle\Core\Controller\SimpleController; -use UserFrosting\Sprinkle\Account\Database\Models\Group; -use UserFrosting\Sprinkle\Account\Database\Models\User; -use UserFrosting\Sprinkle\Account\Database\Models\Role; -use UserFrosting\Sprinkle\Core\Database\Models\Version; use UserFrosting\Sprinkle\Core\Util\EnvironmentInfo; use UserFrosting\Support\Exception\ForbiddenException; @@ -25,17 +25,18 @@ */ class AdminController extends SimpleController { - /** * Renders the admin panel dashboard - * + * @param Request $request + * @param Response $response + * @param array $args */ - public function pageDashboard($request, $response, $args) + public function pageDashboard(Request $request, Response $response, $args) { - //** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ + //** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -43,7 +44,7 @@ public function pageDashboard($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // Probably a better way to do this @@ -54,13 +55,14 @@ public function pageDashboard($request, $response, $args) // Transform the `create_at` date in "x days ago" type of string $users->transform(function ($item, $key) { $item->registered = Carbon::parse($item->created_at)->diffForHumans(); + return $item; }); - /** @var Config $config */ + /** @var \UserFrosting\Support\Repository\Repository $config */ $config = $this->ci->config; - /** @var Config $config */ + /** @var \Illuminate\Cache\Repository $cache */ $cache = $this->ci->cache; // Get each sprinkle db version @@ -68,26 +70,26 @@ public function pageDashboard($request, $response, $args) return $this->ci->view->render($response, 'pages/dashboard.html.twig', [ 'counter' => [ - 'users' => $classMapper->staticMethod('user', 'count'), - 'roles' => $classMapper->staticMethod('role', 'count'), + 'users' => $classMapper->staticMethod('user', 'count'), + 'roles' => $classMapper->staticMethod('role', 'count'), 'groups' => $classMapper->staticMethod('group', 'count') ], 'info' => [ 'version' => [ - 'UF' => \UserFrosting\VERSION, - 'php' => phpversion(), + 'UF' => \UserFrosting\VERSION, + 'php' => phpversion(), 'database' => EnvironmentInfo::database() ], 'database' => [ 'name' => $config['db.default.database'] ], 'environment' => $this->ci->environment, - 'path' => [ + 'path' => [ 'project' => \UserFrosting\ROOT_DIR ] ], 'sprinkles' => $sprinkles, - 'users' => $users + 'users' => $users ]); } @@ -96,13 +98,16 @@ public function pageDashboard($request, $response, $args) * * This route requires authentication. * Request type: POST + * @param Request $request + * @param Response $response + * @param array $args */ - public function clearCache($request, $response, $args) + public function clearCache(Request $request, Response $response, $args) { - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -113,12 +118,12 @@ public function clearCache($request, $response, $args) // Flush cache $this->ci->cache->flush(); - /** @var MessageStream $ms */ + /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ $ms = $this->ci->alerts; $ms->addMessageTranslated('success', 'CACHE.CLEARED'); - return $response->withStatus(200); + return $response->withJson([], 200); } /** @@ -127,13 +132,16 @@ public function clearCache($request, $response, $args) * This does NOT render a complete page. Instead, it renders the HTML for the modal, which can be embedded in other pages. * This page requires authentication. * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args */ - public function getModalConfirmClearCache($request, $response, $args) + public function getModalConfirmClearCache(Request $request, Response $response, $args) { - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page diff --git a/app/sprinkles/admin/src/Controller/GroupController.php b/app/sprinkles/admin/src/Controller/GroupController.php index 7ca94b1b1..0e97b7df3 100644 --- a/app/sprinkles/admin/src/Controller/GroupController.php +++ b/app/sprinkles/admin/src/Controller/GroupController.php @@ -3,27 +3,24 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Admin\Controller; -use Carbon\Carbon; -use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Capsule\Manager as Capsule; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; -use Slim\Exception\NotFoundException; use UserFrosting\Fortress\RequestDataTransformer; use UserFrosting\Fortress\RequestSchema; use UserFrosting\Fortress\ServerSideValidator; use UserFrosting\Fortress\Adapter\JqueryValidationAdapter; use UserFrosting\Sprinkle\Account\Database\Models\Group; -use UserFrosting\Sprinkle\Account\Database\Models\User; use UserFrosting\Sprinkle\Core\Controller\SimpleController; -use UserFrosting\Sprinkle\Core\Facades\Debug; use UserFrosting\Support\Exception\BadRequestException; use UserFrosting\Support\Exception\ForbiddenException; -use UserFrosting\Support\Exception\HttpException; +use UserFrosting\Support\Exception\NotFoundException; /** * Controller class for group-related requests, including listing groups, CRUD for groups, etc. @@ -40,18 +37,23 @@ class GroupController extends SimpleController * 2. The user has permission to create a new group; * 3. The submitted data is valid. * This route requires authentication (and should generally be limited to admins or the root user). + * * Request type: POST * @see getModalCreateGroup + * @param Request $request + * @param Response $response + * @param array $args + * @throws ForbiddenException If user is not authozied to access page */ - public function create($request, $response, $args) + public function create(Request $request, Response $response, $args) { // Get POST parameters: name, slug, icon, description $params = $request->getParsedBody(); - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -59,7 +61,7 @@ public function create($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Sprinkle\Core\MessageStream $ms */ + /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ $ms = $this->ci->alerts; // Load the request schema @@ -78,7 +80,7 @@ public function create($request, $response, $args) $error = true; } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // Check if name or slug already exists @@ -93,15 +95,15 @@ public function create($request, $response, $args) } if ($error) { - return $response->withStatus(400); + return $response->withJson([], 400); } - /** @var UserFrosting\Config\Config $config */ + /** @var \UserFrosting\Support\Repository\Repository $config */ $config = $this->ci->config; // All checks passed! log events/activities and create group // Begin transaction - DB will be rolled back if an exception occurs - Capsule::transaction( function() use ($classMapper, $data, $ms, $config, $currentUser) { + Capsule::transaction(function () use ($classMapper, $data, $ms, $config, $currentUser) { // Create the group $group = $classMapper->createInstance('group', $data); @@ -110,14 +112,14 @@ public function create($request, $response, $args) // Create activity record $this->ci->userActivityLogger->info("User {$currentUser->user_name} created group {$group->name}.", [ - 'type' => 'group_create', + 'type' => 'group_create', 'user_id' => $currentUser->id ]); $ms->addMessageTranslated('success', 'GROUP.CREATION_SUCCESSFUL', $data); }); - return $response->withStatus(200); + return $response->withJson([], 200); } /** @@ -130,21 +132,28 @@ public function create($request, $response, $args) * 3. The group is empty (does not have any users); * 4. The submitted data is valid. * This route requires authentication (and should generally be limited to admins or the root user). + * * Request type: DELETE + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If group is not found + * @throws ForbiddenException If user is not authozied to access page + * @throws BadRequestException */ - public function delete($request, $response, $args) + public function delete(Request $request, Response $response, $args) { $group = $this->getGroupFromParams($args); // If the group doesn't exist, return 404 if (!$group) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -154,7 +163,7 @@ public function delete($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Config\Config $config */ + /** @var \UserFrosting\Support\Repository\Repository $config */ $config = $this->ci->config; // Check that we are not deleting the default group @@ -165,7 +174,7 @@ public function delete($request, $response, $args) throw $e; } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // Check if there are any users in this group @@ -179,39 +188,45 @@ public function delete($request, $response, $args) $groupName = $group->name; // Begin transaction - DB will be rolled back if an exception occurs - Capsule::transaction( function() use ($group, $groupName, $currentUser) { + Capsule::transaction(function () use ($group, $groupName, $currentUser) { $group->delete(); unset($group); // Create activity record $this->ci->userActivityLogger->info("User {$currentUser->user_name} deleted group {$groupName}.", [ - 'type' => 'group_delete', + 'type' => 'group_delete', 'user_id' => $currentUser->id ]); }); - /** @var UserFrosting\Sprinkle\Core\MessageStream $ms */ + /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ $ms = $this->ci->alerts; $ms->addMessageTranslated('success', 'GROUP.DELETION_SUCCESSFUL', [ 'name' => $groupName ]); - return $response->withStatus(200); + return $response->withJson([], 200); } /** * Returns info for a single group. * * This page requires authentication. + * * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws ForbiddenException If user is not authozied to access page + * @throws NotFoundException If group is not found */ - public function getInfo($request, $response, $args) + public function getInfo(Request $request, Response $response, $args) { - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -221,14 +236,14 @@ public function getInfo($request, $response, $args) $slug = $args['slug']; - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; $group = $classMapper->staticMethod('group', 'where', 'slug', $slug)->first(); // If the group doesn't exist, return 404 if (!$group) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } // Get group @@ -244,17 +259,22 @@ public function getInfo($request, $response, $args) * * Generates a list of groups, optionally paginated, sorted and/or filtered. * This page requires authentication. + * * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws ForbiddenException If user is not authozied to access page */ - public function getList($request, $response, $args) + public function getList(Request $request, Response $response, $args) { // GET parameters $params = $request->getQueryParams(); - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -262,7 +282,7 @@ public function getList($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; $sprunje = $classMapper->createInstance('group_sprunje', $classMapper, $params); @@ -272,7 +292,17 @@ public function getList($request, $response, $args) return $sprunje->toResponse($response); } - public function getModalConfirmDelete($request, $response, $args) + /** + * Get deletetion confirmation modal + * + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If group is not found + * @throws ForbiddenException If user is not authozied to access page + * @throws BadRequestException + */ + public function getModalConfirmDelete(Request $request, Response $response, $args) { // GET parameters $params = $request->getQueryParams(); @@ -281,13 +311,13 @@ public function getModalConfirmDelete($request, $response, $args) // If the group no longer exists, forward to main group listing page if (!$group) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -297,7 +327,7 @@ public function getModalConfirmDelete($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // Check if there are any users in this group @@ -310,7 +340,7 @@ public function getModalConfirmDelete($request, $response, $args) return $this->ci->view->render($response, 'modals/confirm-delete-group.html.twig', [ 'group' => $group, - 'form' => [ + 'form' => [ 'action' => "api/groups/g/{$group->slug}", ] ]); @@ -321,20 +351,25 @@ public function getModalConfirmDelete($request, $response, $args) * * This does NOT render a complete page. Instead, it renders the HTML for the modal, which can be embedded in other pages. * This page requires authentication. + * * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws ForbiddenException If user is not authozied to access page */ - public function getModalCreate($request, $response, $args) + public function getModalCreate(Request $request, Response $response, $args) { // GET parameters $params = $request->getQueryParams(); - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; - /** @var UserFrosting\I18n\MessageTranslator $translator */ + /** @var \UserFrosting\I18n\MessageTranslator $translator */ $translator = $this->ci->translator; // Access-controlled page @@ -342,7 +377,7 @@ public function getModalCreate($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // Create a dummy group to prepopulate fields @@ -352,7 +387,7 @@ public function getModalCreate($request, $response, $args) $fieldNames = ['name', 'slug', 'icon', 'description']; $fields = [ - 'hidden' => [], + 'hidden' => [], 'disabled' => [] ]; @@ -362,10 +397,10 @@ public function getModalCreate($request, $response, $args) return $this->ci->view->render($response, 'modals/group.html.twig', [ 'group' => $group, - 'form' => [ - 'action' => 'api/groups', - 'method' => 'POST', - 'fields' => $fields, + 'form' => [ + 'action' => 'api/groups', + 'method' => 'POST', + 'fields' => $fields, 'submit_text' => $translator->translate('CREATE') ], 'page' => [ @@ -379,9 +414,15 @@ public function getModalCreate($request, $response, $args) * * This does NOT render a complete page. Instead, it renders the HTML for the modal, which can be embedded in other pages. * This page requires authentication. + * * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If group is not found + * @throws ForbiddenException If user is not authozied to access page */ - public function getModalEdit($request, $response, $args) + public function getModalEdit(Request $request, Response $response, $args) { // GET parameters $params = $request->getQueryParams(); @@ -390,19 +431,19 @@ public function getModalEdit($request, $response, $args) // If the group doesn't exist, return 404 if (!$group) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; - /** @var UserFrosting\I18n\MessageTranslator $translator */ + /** @var \UserFrosting\I18n\MessageTranslator $translator */ $translator = $this->ci->translator; // Access-controlled resource - check that currentUser has permission to edit basic fields "name", "slug", "icon", "description" for this group @@ -416,7 +457,7 @@ public function getModalEdit($request, $response, $args) // Generate form $fields = [ - 'hidden' => [], + 'hidden' => [], 'disabled' => [] ]; @@ -426,10 +467,10 @@ public function getModalEdit($request, $response, $args) return $this->ci->view->render($response, 'modals/group.html.twig', [ 'group' => $group, - 'form' => [ - 'action' => "api/groups/g/{$group->slug}", - 'method' => 'PUT', - 'fields' => $fields, + 'form' => [ + 'action' => "api/groups/g/{$group->slug}", + 'method' => 'PUT', + 'fields' => $fields, 'submit_text' => $translator->translate('UPDATE') ], 'page' => [ @@ -438,22 +479,31 @@ public function getModalEdit($request, $response, $args) ]); } - public function getUsers($request, $response, $args) + /** + * Users List API + * + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If group is not found + * @throws ForbiddenException If user is not authozied to access page + */ + public function getUsers(Request $request, Response $response, $args) { $group = $this->getGroupFromParams($args); // If the group no longer exists, forward to main group listing page if (!$group) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } // GET parameters $params = $request->getQueryParams(); - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -464,7 +514,7 @@ public function getUsers($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; $sprunje = $classMapper->createInstance('user_sprunje', $classMapper, $params); @@ -484,22 +534,26 @@ public function getUsers($request, $response, $args) * It checks each field individually, showing only those that you have permission to view. * This will also try to show buttons for deleting, and editing the group. * This page requires authentication. + * * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws ForbiddenException If user is not authozied to access page */ - public function pageInfo($request, $response, $args) + public function pageInfo(Request $request, Response $response, $args) { $group = $this->getGroupFromParams($args); // If the group no longer exists, forward to main group listing page if (!$group) { - $redirectPage = $this->ci->router->pathFor('uri_groups'); - return $response->withRedirect($redirectPage, 404); + throw new NotFoundException(); } - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -545,9 +599,10 @@ public function pageInfo($request, $response, $args) } return $this->ci->view->render($response, 'pages/group.html.twig', [ - 'group' => $group, + 'group' => $group, 'fields' => $fields, - 'tools' => $editButtons + 'tools' => $editButtons, + 'delete_redirect' => $this->ci->router->pathFor('uri_groups') ]); } @@ -557,14 +612,19 @@ public function pageInfo($request, $response, $args) * This page renders a table of groups, with dropdown menus for admin actions for each group. * Actions typically include: edit group, delete group. * This page requires authentication. + * * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws ForbiddenException If user is not authozied to access page */ - public function pageList($request, $response, $args) + public function pageList(Request $request, Response $response, $args) { - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -583,25 +643,31 @@ public function pageList($request, $response, $args) * 2. The user has the necessary permissions to update the posted field(s); * 3. The submitted data is valid. * This route requires authentication (and should generally be limited to admins or the root user). + * * Request type: PUT * @see getModalGroupEdit + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If group is not found + * @throws ForbiddenException If user is not authozied to access page */ - public function updateInfo($request, $response, $args) + public function updateInfo(Request $request, Response $response, $args) { // Get the group based on slug in URL $group = $this->getGroupFromParams($args); if (!$group) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } - /** @var UserFrosting\Config\Config $config */ + /** @var \UserFrosting\Support\Repository\Repository $config */ $config = $this->ci->config; // Get PUT parameters: (name, slug, icon, description) $params = $request->getParsedBody(); - /** @var UserFrosting\Sprinkle\Core\MessageStream $ms */ + /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ $ms = $this->ci->alerts; // Load the request schema @@ -626,10 +692,10 @@ public function updateInfo($request, $response, $args) $fieldNames[] = $name; } - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled resource - check that currentUser has permission to edit submitted fields for this group @@ -640,7 +706,7 @@ public function updateInfo($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // Check if name or slug already exists @@ -663,11 +729,11 @@ public function updateInfo($request, $response, $args) } if ($error) { - return $response->withStatus(400); + return $response->withJson([], 400); } // Begin transaction - DB will be rolled back if an exception occurs - Capsule::transaction( function() use ($data, $group, $currentUser) { + Capsule::transaction(function () use ($data, $group, $currentUser) { // Update the group and generate success messages foreach ($data as $name => $value) { if ($value != $group->$name) { @@ -679,7 +745,7 @@ public function updateInfo($request, $response, $args) // Create activity record $this->ci->userActivityLogger->info("User {$currentUser->user_name} updated details for group {$group->name}.", [ - 'type' => 'group_update_info', + 'type' => 'group_update_info', 'user_id' => $currentUser->id ]); }); @@ -688,9 +754,16 @@ public function updateInfo($request, $response, $args) 'name' => $group->name ]); - return $response->withStatus(200); + return $response->withJson([], 200); } + /** + * Get group from params + * + * @param array $params + * @throws BadRequestException + * @return Group + */ protected function getGroupFromParams($params) { // Load the request schema @@ -706,14 +779,14 @@ protected function getGroupFromParams($params) // TODO: encapsulate the communication of error messages from ServerSideValidator to the BadRequestException $e = new BadRequestException(); foreach ($validator->errors() as $idx => $field) { - foreach($field as $eidx => $error) { + foreach ($field as $eidx => $error) { $e->addUserMessage($error); } } throw $e; } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // Get the group diff --git a/app/sprinkles/admin/src/Controller/PermissionController.php b/app/sprinkles/admin/src/Controller/PermissionController.php index 660e296c9..402442ef6 100644 --- a/app/sprinkles/admin/src/Controller/PermissionController.php +++ b/app/sprinkles/admin/src/Controller/PermissionController.php @@ -3,27 +3,17 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Admin\Controller; -use Carbon\Carbon; -use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\Capsule\Manager as Capsule; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; -use Slim\Exception\NotFoundException; -use UserFrosting\Fortress\RequestDataTransformer; -use UserFrosting\Fortress\RequestSchema; -use UserFrosting\Fortress\ServerSideValidator; -use UserFrosting\Fortress\Adapter\JqueryValidationAdapter; -use UserFrosting\Sprinkle\Account\Database\Models\Permission; -use UserFrosting\Sprinkle\Account\Database\Models\Role; use UserFrosting\Sprinkle\Core\Controller\SimpleController; -use UserFrosting\Sprinkle\Core\Facades\Debug; -use UserFrosting\Support\Exception\BadRequestException; use UserFrosting\Support\Exception\ForbiddenException; -use UserFrosting\Support\Exception\HttpException; +use UserFrosting\Support\Exception\NotFoundException; /** * Controller class for permission-related requests, including listing permissions, CRUD for permissions, etc. @@ -36,14 +26,20 @@ class PermissionController extends SimpleController * Returns info for a single permission. * * This page requires authentication. + * * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws ForbiddenException If user is not authozied to access page + * @throws NotFoundException If permission is not found */ - public function getInfo($request, $response, $args) + public function getInfo(Request $request, Response $response, $args) { - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -53,14 +49,14 @@ public function getInfo($request, $response, $args) $permissionId = $args['id']; - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; $permission = $classMapper->staticMethod('permission', 'find', $permissionId); // If the permission doesn't exist, return 404 if (!$permission) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } // Get permission @@ -76,17 +72,22 @@ public function getInfo($request, $response, $args) * * Generates a list of permissions, optionally paginated, sorted and/or filtered. * This page requires authentication. + * * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws ForbiddenException If user is not authozied to access page */ - public function getList($request, $response, $args) + public function getList(Request $request, Response $response, $args) { // GET parameters $params = $request->getQueryParams(); - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -94,7 +95,7 @@ public function getList($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; $sprunje = $classMapper->createInstance('permission_sprunje', $classMapper, $params); @@ -109,17 +110,22 @@ public function getList($request, $response, $args) * * Generates a list of users, optionally paginated, sorted and/or filtered. * This page requires authentication. + * * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws ForbiddenException If user is not authozied to access page */ - public function getUsers($request, $response, $args) + public function getUsers(Request $request, Response $response, $args) { // GET parameters $params = $request->getQueryParams(); - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -127,18 +133,16 @@ public function getUsers($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; $params['permission_id'] = $args['id']; $sprunje = $classMapper->createInstance('permission_user_sprunje', $classMapper, $params); - $response = $sprunje->toResponse($response); - // Be careful how you consume this data - it has not been escaped and contains untrusted user-supplied content. // For example, if you plan to insert it into an HTML DOM, you must escape it on the client side (or use client-side templating). - return $response; + return $sprunje->toResponse($response); } /** @@ -148,14 +152,20 @@ public function getUsers($request, $response, $args) * Note that permissions cannot be modified through the interface. This is because * permissions are tighly coupled to the code and should only be modified by developers. * This page requires authentication. + * * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws ForbiddenException If user is not authozied to access page + * @throws NotFoundException If permission is not found */ - public function pageInfo($request, $response, $args) + public function pageInfo(Request $request, Response $response, $args) { - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -165,14 +175,14 @@ public function pageInfo($request, $response, $args) $permissionId = $args['id']; - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; $permission = $classMapper->staticMethod('permission', 'find', $permissionId); // If the permission doesn't exist, return 404 if (!$permission) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } return $this->ci->view->render($response, 'pages/permission.html.twig', [ @@ -186,14 +196,19 @@ public function pageInfo($request, $response, $args) * This page renders a table of permissions, with dropdown menus for admin actions for each permission. * Actions typically include: edit permission, delete permission. * This page requires authentication. + * * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws ForbiddenException If user is not authozied to access page */ - public function pageList($request, $response, $args) + public function pageList(Request $request, Response $response, $args) { - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page diff --git a/app/sprinkles/admin/src/Controller/RoleController.php b/app/sprinkles/admin/src/Controller/RoleController.php index ab86c88be..3232e266c 100644 --- a/app/sprinkles/admin/src/Controller/RoleController.php +++ b/app/sprinkles/admin/src/Controller/RoleController.php @@ -3,27 +3,24 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Admin\Controller; -use Carbon\Carbon; -use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Capsule\Manager as Capsule; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; -use Slim\Exception\NotFoundException; use UserFrosting\Fortress\RequestDataTransformer; use UserFrosting\Fortress\RequestSchema; use UserFrosting\Fortress\ServerSideValidator; use UserFrosting\Fortress\Adapter\JqueryValidationAdapter; use UserFrosting\Sprinkle\Account\Database\Models\Role; -use UserFrosting\Sprinkle\Account\Database\Models\User; use UserFrosting\Sprinkle\Core\Controller\SimpleController; -use UserFrosting\Sprinkle\Core\Facades\Debug; use UserFrosting\Support\Exception\BadRequestException; use UserFrosting\Support\Exception\ForbiddenException; -use UserFrosting\Support\Exception\HttpException; +use UserFrosting\Support\Exception\NotFoundException; /** * Controller class for role-related requests, including listing roles, CRUD for roles, etc. @@ -40,18 +37,23 @@ class RoleController extends SimpleController * 2. The user has permission to create a new role; * 3. The submitted data is valid. * This route requires authentication (and should generally be limited to admins or the root user). + * * Request type: POST * @see getModalCreateRole + * @param Request $request + * @param Response $response + * @param array $args + * @throws ForbiddenException If user is not authozied to access page */ - public function create($request, $response, $args) + public function create(Request $request, Response $response, $args) { // Get POST parameters: name, slug, description $params = $request->getParsedBody(); - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -59,7 +61,7 @@ public function create($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Sprinkle\Core\MessageStream $ms */ + /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ $ms = $this->ci->alerts; // Load the request schema @@ -78,7 +80,7 @@ public function create($request, $response, $args) $error = true; } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // Check if name or slug already exists @@ -93,15 +95,15 @@ public function create($request, $response, $args) } if ($error) { - return $response->withStatus(400); + return $response->withJson([], 400); } - /** @var UserFrosting\Config\Config $config */ + /** @var \UserFrosting\Support\Repository\Repository $config */ $config = $this->ci->config; // All checks passed! log events/activities and create role // Begin transaction - DB will be rolled back if an exception occurs - Capsule::transaction( function() use ($classMapper, $data, $ms, $config, $currentUser) { + Capsule::transaction(function () use ($classMapper, $data, $ms, $config, $currentUser) { // Create the role $role = $classMapper->createInstance('role', $data); @@ -110,14 +112,14 @@ public function create($request, $response, $args) // Create activity record $this->ci->userActivityLogger->info("User {$currentUser->user_name} created role {$role->name}.", [ - 'type' => 'role_create', + 'type' => 'role_create', 'user_id' => $currentUser->id ]); $ms->addMessageTranslated('success', 'ROLE.CREATION_SUCCESSFUL', $data); }); - return $response->withStatus(200); + return $response->withJson([], 200); } /** @@ -130,21 +132,28 @@ public function create($request, $response, $args) * 3. The role does not have any associated users; * 4. The submitted data is valid. * This route requires authentication (and should generally be limited to admins or the root user). + * * Request type: DELETE + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If role is not found + * @throws ForbiddenException If user is not authozied to access page + * @throws BadRequestException */ - public function delete($request, $response, $args) + public function delete(Request $request, Response $response, $args) { $role = $this->getRoleFromParams($args); // If the role doesn't exist, return 404 if (!$role) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -154,7 +163,7 @@ public function delete($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // Check that we are not deleting a default role @@ -178,39 +187,45 @@ public function delete($request, $response, $args) $roleName = $role->name; // Begin transaction - DB will be rolled back if an exception occurs - Capsule::transaction( function() use ($role, $roleName, $currentUser) { + Capsule::transaction(function () use ($role, $roleName, $currentUser) { $role->delete(); unset($role); // Create activity record $this->ci->userActivityLogger->info("User {$currentUser->user_name} deleted role {$roleName}.", [ - 'type' => 'role_delete', + 'type' => 'role_delete', 'user_id' => $currentUser->id ]); }); - /** @var UserFrosting\Sprinkle\Core\MessageStream $ms */ + /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ $ms = $this->ci->alerts; $ms->addMessageTranslated('success', 'ROLE.DELETION_SUCCESSFUL', [ 'name' => $roleName ]); - return $response->withStatus(200); + return $response->withJson([], 200); } /** * Returns info for a single role, along with associated permissions. * * This page requires authentication. + * * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws ForbiddenException If user is not authozied to access page + * @throws NotFoundException If role is not found */ - public function getInfo($request, $response, $args) + public function getInfo(Request $request, Response $response, $args) { - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -220,14 +235,14 @@ public function getInfo($request, $response, $args) $slug = $args['slug']; - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; $role = $classMapper->staticMethod('role', 'where', 'slug', $slug)->first(); // If the role doesn't exist, return 404 if (!$role) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } // Get role @@ -243,17 +258,22 @@ public function getInfo($request, $response, $args) * * Generates a list of roles, optionally paginated, sorted and/or filtered. * This page requires authentication. + * * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws ForbiddenException If user is not authozied to access page */ - public function getList($request, $response, $args) + public function getList(Request $request, Response $response, $args) { // GET parameters $params = $request->getQueryParams(); - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -261,7 +281,7 @@ public function getList($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; $sprunje = $classMapper->createInstance('role_sprunje', $classMapper, $params); @@ -271,7 +291,17 @@ public function getList($request, $response, $args) return $sprunje->toResponse($response); } - public function getModalConfirmDelete($request, $response, $args) + /** + * Display deletion confirmation modal + * + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If role is not found + * @throws ForbiddenException If user is not authozied to access page + * @throws BadRequestException + */ + public function getModalConfirmDelete(Request $request, Response $response, $args) { // GET parameters $params = $request->getQueryParams(); @@ -280,13 +310,13 @@ public function getModalConfirmDelete($request, $response, $args) // If the role no longer exists, forward to main role listing page if (!$role) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -296,7 +326,7 @@ public function getModalConfirmDelete($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // Check that we are not deleting a default role @@ -330,20 +360,25 @@ public function getModalConfirmDelete($request, $response, $args) * * This does NOT render a complete page. Instead, it renders the HTML for the modal, which can be embedded in other pages. * This page requires authentication. + * * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws ForbiddenException If user is not authozied to access page */ - public function getModalCreate($request, $response, $args) + public function getModalCreate(Request $request, Response $response, $args) { // GET parameters $params = $request->getQueryParams(); - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; - /** @var UserFrosting\I18n\MessageTranslator $translator */ + /** @var \UserFrosting\I18n\MessageTranslator $translator */ $translator = $this->ci->translator; // Access-controlled page @@ -351,7 +386,7 @@ public function getModalCreate($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // Create a dummy role to prepopulate fields @@ -359,7 +394,7 @@ public function getModalCreate($request, $response, $args) $fieldNames = ['name', 'slug', 'description']; $fields = [ - 'hidden' => [], + 'hidden' => [], 'disabled' => [] ]; @@ -370,9 +405,9 @@ public function getModalCreate($request, $response, $args) return $this->ci->view->render($response, 'modals/role.html.twig', [ 'role' => $role, 'form' => [ - 'action' => 'api/roles', - 'method' => 'POST', - 'fields' => $fields, + 'action' => 'api/roles', + 'method' => 'POST', + 'fields' => $fields, 'submit_text' => $translator->translate('CREATE') ], 'page' => [ @@ -386,9 +421,15 @@ public function getModalCreate($request, $response, $args) * * This does NOT render a complete page. Instead, it renders the HTML for the modal, which can be embedded in other pages. * This page requires authentication. + * * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If role is not found + * @throws ForbiddenException If user is not authozied to access page */ - public function getModalEdit($request, $response, $args) + public function getModalEdit(Request $request, Response $response, $args) { // GET parameters $params = $request->getQueryParams(); @@ -397,19 +438,19 @@ public function getModalEdit($request, $response, $args) // If the role doesn't exist, return 404 if (!$role) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; - /** @var UserFrosting\I18n\MessageTranslator $translator */ + /** @var \UserFrosting\I18n\MessageTranslator $translator */ $translator = $this->ci->translator; // Access-controlled resource - check that currentUser has permission to edit basic fields "name", "slug", "description" for this role @@ -423,7 +464,7 @@ public function getModalEdit($request, $response, $args) // Generate form $fields = [ - 'hidden' => [], + 'hidden' => [], 'disabled' => [] ]; @@ -434,9 +475,9 @@ public function getModalEdit($request, $response, $args) return $this->ci->view->render($response, 'modals/role.html.twig', [ 'role' => $role, 'form' => [ - 'action' => "api/roles/r/{$role->slug}", - 'method' => 'PUT', - 'fields' => $fields, + 'action' => "api/roles/r/{$role->slug}", + 'method' => 'PUT', + 'fields' => $fields, 'submit_text' => $translator->translate('UPDATE') ], 'page' => [ @@ -450,9 +491,15 @@ public function getModalEdit($request, $response, $args) * * This does NOT render a complete page. Instead, it renders the HTML for the form, which can be embedded in other pages. * This page requires authentication. + * * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If role is not found + * @throws ForbiddenException If user is not authozied to access page */ - public function getModalEditPermissions($request, $response, $args) + public function getModalEditPermissions(Request $request, Response $response, $args) { // GET parameters $params = $request->getQueryParams(); @@ -461,13 +508,13 @@ public function getModalEditPermissions($request, $response, $args) // If the role doesn't exist, return 404 if (!$role) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled resource - check that currentUser has permission to edit "permissions" field for this role @@ -488,24 +535,30 @@ public function getModalEditPermissions($request, $response, $args) * * Generates a list of permissions, optionally paginated, sorted and/or filtered. * This page requires authentication. + * * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If role is not found + * @throws ForbiddenException If user is not authozied to access page */ - public function getPermissions($request, $response, $args) + public function getPermissions(Request $request, Response $response, $args) { $role = $this->getRoleFromParams($args); // If the role no longer exists, forward to main role listing page if (!$role) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } // GET parameters $params = $request->getQueryParams(); - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -516,7 +569,7 @@ public function getPermissions($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; $sprunje = $classMapper->createInstance('permission_sprunje', $classMapper, $params); @@ -533,27 +586,33 @@ public function getPermissions($request, $response, $args) * Returns users associated with a single role. * * This page requires authentication. + * * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If role is not found + * @throws ForbiddenException If user is not authozied to access page */ - public function getUsers($request, $response, $args) + public function getUsers(Request $request, Response $response, $args) { $role = $this->getRoleFromParams($args); // If the role doesn't exist, return 404 if (!$role) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // GET parameters $params = $request->getQueryParams(); - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -581,22 +640,26 @@ public function getUsers($request, $response, $args) * It checks each field individually, showing only those that you have permission to view. * This will also try to show buttons for deleting and editing the role. * This page requires authentication. + * * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws ForbiddenException If user is not authozied to access page */ - public function pageInfo($request, $response, $args) + public function pageInfo(Request $request, Response $response, $args) { $role = $this->getRoleFromParams($args); // If the role no longer exists, forward to main role listing page if (!$role) { - $redirectPage = $this->ci->router->pathFor('uri_roles'); - return $response->withRedirect($redirectPage, 404); + throw new NotFoundException(); } - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -642,9 +705,10 @@ public function pageInfo($request, $response, $args) } return $this->ci->view->render($response, 'pages/role.html.twig', [ - 'role' => $role, + 'role' => $role, 'fields' => $fields, - 'tools' => $editButtons + 'tools' => $editButtons, + 'delete_redirect' => $this->ci->router->pathFor('uri_roles') ]); } @@ -654,14 +718,19 @@ public function pageInfo($request, $response, $args) * This page renders a table of roles, with dropdown menus for admin actions for each role. * Actions typically include: edit role, delete role. * This page requires authentication. + * * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws ForbiddenException If user is not authozied to access page */ - public function pageList($request, $response, $args) + public function pageList(Request $request, Response $response, $args) { - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -680,25 +749,31 @@ public function pageList($request, $response, $args) * 2. The user has the necessary permissions to update the posted field(s); * 3. The submitted data is valid. * This route requires authentication (and should generally be limited to admins or the root user). + * * Request type: PUT * @see getModalRoleEdit + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If role is not found + * @throws ForbiddenException If user is not authozied to access page */ - public function updateInfo($request, $response, $args) + public function updateInfo(Request $request, Response $response, $args) { // Get the role based on slug in the URL $role = $this->getRoleFromParams($args); if (!$role) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } - /** @var UserFrosting\Config\Config $config */ + /** @var \UserFrosting\Support\Repository\Repository $config */ $config = $this->ci->config; // Get PUT parameters: (name, slug, description) $params = $request->getParsedBody(); - /** @var UserFrosting\I18n\MessageTranslator $translator */ + /** @var \UserFrosting\I18n\MessageTranslator $translator */ $ms = $this->ci->alerts; // Load the request schema @@ -723,10 +798,10 @@ public function updateInfo($request, $response, $args) $fieldNames[] = $name; } - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled resource - check that currentUser has permission to edit submitted fields for this role @@ -737,7 +812,7 @@ public function updateInfo($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // Check if name or slug already exists @@ -760,14 +835,14 @@ public function updateInfo($request, $response, $args) } if ($error) { - return $response->withStatus(400); + return $response->withJson([], 400); } // Begin transaction - DB will be rolled back if an exception occurs - Capsule::transaction( function() use ($data, $role, $currentUser) { + Capsule::transaction(function () use ($data, $role, $currentUser) { // Update the role and generate success messages foreach ($data as $name => $value) { - if ($value != $role->$name){ + if ($value != $role->$name) { $role->$name = $value; } } @@ -776,7 +851,7 @@ public function updateInfo($request, $response, $args) // Create activity record $this->ci->userActivityLogger->info("User {$currentUser->user_name} updated details for role {$role->name}.", [ - 'type' => 'role_update_info', + 'type' => 'role_update_info', 'user_id' => $currentUser->id ]); }); @@ -785,7 +860,7 @@ public function updateInfo($request, $response, $args) 'name' => $role->name ]); - return $response->withStatus(200); + return $response->withJson([], 200); } /** @@ -795,24 +870,31 @@ public function updateInfo($request, $response, $args) * 1. The logged-in user has the necessary permissions to update the putted field(s); * 2. The submitted data is valid. * This route requires authentication. + * * Request type: PUT + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If role is not found + * @throws ForbiddenException If user is not authozied to access page + * @throws BadRequestException */ - public function updateField($request, $response, $args) + public function updateField(Request $request, Response $response, $args) { // Get the username from the URL $role = $this->getRoleFromParams($args); if (!$role) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } // Get key->value pair from URL and request body $fieldName = $args['field']; - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled resource - check that currentUser has permission to edit the specified field for this user @@ -823,7 +905,7 @@ public function updateField($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Config\Config $config */ + /** @var \UserFrosting\Support\Repository\Repository $config */ $config = $this->ci->config; // Get PUT parameters: value @@ -852,7 +934,7 @@ public function updateField($request, $response, $args) // TODO: encapsulate the communication of error messages from ServerSideValidator to the BadRequestException $e = new BadRequestException(); foreach ($validator->errors() as $idx => $field) { - foreach($field as $eidx => $error) { + foreach ($field as $eidx => $error) { $e->addUserMessage($error); } } @@ -862,11 +944,11 @@ public function updateField($request, $response, $args) // Get validated and transformed value $fieldValue = $data[$fieldName]; - /** @var UserFrosting\I18n\MessageTranslator $translator */ + /** @var \UserFrosting\I18n\MessageTranslator $translator */ $ms = $this->ci->alerts; // Begin transaction - DB will be rolled back if an exception occurs - Capsule::transaction( function() use ($fieldName, $fieldValue, $role, $currentUser) { + Capsule::transaction(function () use ($fieldName, $fieldValue, $role, $currentUser) { if ($fieldName == 'permissions') { $newPermissions = collect($fieldValue)->pluck('permission_id')->all(); $role->permissions()->sync($newPermissions); @@ -877,7 +959,7 @@ public function updateField($request, $response, $args) // Create activity record $this->ci->userActivityLogger->info("User {$currentUser->user_name} updated property '$fieldName' for role {$role->name}.", [ - 'type' => 'role_update_field', + 'type' => 'role_update_field', 'user_id' => $currentUser->id ]); }); @@ -893,9 +975,16 @@ public function updateField($request, $response, $args) ]); } - return $response->withStatus(200); + return $response->withJson([], 200); } + /** + * Get role instance from params + * + * @param array $params + * @throws BadRequestException + * @return Role + */ protected function getRoleFromParams($params) { // Load the request schema @@ -911,14 +1000,14 @@ protected function getRoleFromParams($params) // TODO: encapsulate the communication of error messages from ServerSideValidator to the BadRequestException $e = new BadRequestException(); foreach ($validator->errors() as $idx => $field) { - foreach($field as $eidx => $error) { + foreach ($field as $eidx => $error) { $e->addUserMessage($error); } } throw $e; } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // Get the role diff --git a/app/sprinkles/admin/src/Controller/UserController.php b/app/sprinkles/admin/src/Controller/UserController.php index ff41009cd..d6293ec49 100644 --- a/app/sprinkles/admin/src/Controller/UserController.php +++ b/app/sprinkles/admin/src/Controller/UserController.php @@ -3,30 +3,28 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Admin\Controller; use Carbon\Carbon; -use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Capsule\Manager as Capsule; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; -use Slim\Exception\NotFoundException; use UserFrosting\Fortress\RequestDataTransformer; use UserFrosting\Fortress\RequestSchema; use UserFrosting\Fortress\ServerSideValidator; use UserFrosting\Fortress\Adapter\JqueryValidationAdapter; -use UserFrosting\Sprinkle\Account\Database\Models\Group; use UserFrosting\Sprinkle\Account\Database\Models\User; use UserFrosting\Sprinkle\Account\Facades\Password; use UserFrosting\Sprinkle\Core\Controller\SimpleController; -use UserFrosting\Sprinkle\Core\Facades\Debug; use UserFrosting\Sprinkle\Core\Mail\EmailRecipient; use UserFrosting\Sprinkle\Core\Mail\TwigMailMessage; use UserFrosting\Support\Exception\BadRequestException; use UserFrosting\Support\Exception\ForbiddenException; -use UserFrosting\Support\Exception\HttpException; +use UserFrosting\Support\Exception\NotFoundException; /** * Controller class for user-related requests, including listing users, CRUD for users, etc. @@ -43,18 +41,23 @@ class UserController extends SimpleController * 2. The logged-in user has the necessary permissions to update the posted field(s); * 3. The submitted data is valid. * This route requires authentication. + * * Request type: POST * @see getModalCreate + * @param Request $request + * @param Response $response + * @param array $args + * @throws ForbiddenException If user is not authozied to access page */ - public function create($request, $response, $args) + public function create(Request $request, Response $response, $args) { // Get POST parameters: user_name, first_name, last_name, email, locale, (group) $params = $request->getParsedBody(); - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -62,7 +65,7 @@ public function create($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Sprinkle\Core\MessageStream $ms */ + /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ $ms = $this->ci->alerts; // Load the request schema @@ -81,7 +84,7 @@ public function create($request, $response, $args) $error = true; } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // Check if username or email already exists @@ -96,10 +99,10 @@ public function create($request, $response, $args) } if ($error) { - return $response->withStatus(400); + return $response->withJson([], 400); } - /** @var UserFrosting\Config\Config $config */ + /** @var \UserFrosting\Support\Repository\Repository $config */ $config = $this->ci->config; // If currentUser does not have permission to set the group, but they try to set it to something other than their own group, @@ -123,7 +126,7 @@ public function create($request, $response, $args) // All checks passed! log events/activities, create user, and send verification email (if required) // Begin transaction - DB will be rolled back if an exception occurs - Capsule::transaction( function() use ($classMapper, $data, $ms, $config, $currentUser) { + Capsule::transaction(function () use ($classMapper, $data, $ms, $config, $currentUser) { // Create the user $user = $classMapper->createInstance('user', $data); @@ -132,7 +135,7 @@ public function create($request, $response, $args) // Create activity record $this->ci->userActivityLogger->info("User {$currentUser->user_name} created a new account for {$user->user_name}.", [ - 'type' => 'account_create', + 'type' => 'account_create', 'user_id' => $currentUser->id ]); @@ -153,9 +156,9 @@ public function create($request, $response, $args) $message->from($config['address_book.admin']) ->addEmailRecipient(new EmailRecipient($user->email, $user->full_name)) ->addParams([ - 'user' => $user, + 'user' => $user, 'create_password_expiration' => $config['password_reset.timeouts.create'] / 3600 . ' hours', - 'token' => $passwordRequest->getToken() + 'token' => $passwordRequest->getToken() ]); $this->ci->mailer->send($message); @@ -163,7 +166,7 @@ public function create($request, $response, $args) $ms->addMessageTranslated('success', 'USER.CREATED', $data); }); - return $response->withStatus(200); + return $response->withJson([], 200); } /** @@ -175,21 +178,27 @@ public function create($request, $response, $args) * 3. We're not trying to disable the master account; * 4. The submitted data is valid. * This route requires authentication. + * * Request type: POST + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If user is not found + * @throws ForbiddenException If user is not authozied to access page */ - public function createPasswordReset($request, $response, $args) + public function createPasswordReset(Request $request, Response $response, $args) { // Get the username from the URL $user = $this->getUserFromParams($args); if (!$user) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled resource - check that currentUser has permission to edit "password" for this user @@ -200,14 +209,14 @@ public function createPasswordReset($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Config\Config $config */ + /** @var \UserFrosting\Support\Repository\Repository $config */ $config = $this->ci->config; - /** @var UserFrosting\Sprinkle\Core\MessageStream $ms */ + /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ $ms = $this->ci->alerts; // Begin transaction - DB will be rolled back if an exception occurs - Capsule::transaction( function() use ($user, $config) { + Capsule::transaction(function () use ($user, $config) { // Create a password reset and shoot off an email $passwordReset = $this->ci->repoPasswordReset->create($user, $config['password_reset.timeouts.reset']); @@ -218,8 +227,8 @@ public function createPasswordReset($request, $response, $args) $message->from($config['address_book.admin']) ->addEmailRecipient(new EmailRecipient($user->email, $user->full_name)) ->addParams([ - 'user' => $user, - 'token' => $passwordReset->getToken(), + 'user' => $user, + 'token' => $passwordReset->getToken(), 'request_date' => Carbon::now()->format('Y-m-d H:i:s') ]); @@ -229,7 +238,8 @@ public function createPasswordReset($request, $response, $args) $ms->addMessageTranslated('success', 'PASSWORD.FORGET.REQUEST_SENT', [ 'email' => $user->email ]); - return $response->withStatus(200); + + return $response->withJson([], 200); } /** @@ -240,21 +250,28 @@ public function createPasswordReset($request, $response, $args) * 1. You are not trying to delete the master account; * 2. You have permission to delete the target user's account. * This route requires authentication (and should generally be limited to admins or the root user). + * * Request type: DELETE + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If user is not found + * @throws ForbiddenException If user is not authozied to access page + * @throws BadRequestException */ - public function delete($request, $response, $args) + public function delete(Request $request, Response $response, $args) { $user = $this->getUserFromParams($args); // If the user doesn't exist, return 404 if (!$user) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -264,7 +281,7 @@ public function delete($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Config\Config $config */ + /** @var \UserFrosting\Support\Repository\Repository $config */ $config = $this->ci->config; // Check that we are not deleting the master account @@ -278,25 +295,25 @@ public function delete($request, $response, $args) $userName = $user->user_name; // Begin transaction - DB will be rolled back if an exception occurs - Capsule::transaction( function() use ($user, $userName, $currentUser) { + Capsule::transaction(function () use ($user, $userName, $currentUser) { $user->delete(); unset($user); // Create activity record $this->ci->userActivityLogger->info("User {$currentUser->user_name} deleted the account for {$userName}.", [ - 'type' => 'account_delete', + 'type' => 'account_delete', 'user_id' => $currentUser->id ]); }); - /** @var UserFrosting\Sprinkle\Core\MessageStream $ms */ + /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ $ms = $this->ci->alerts; $ms->addMessageTranslated('success', 'DELETION_SUCCESSFUL', [ 'user_name' => $userName ]); - return $response->withStatus(200); + return $response->withJson([], 200); } /** @@ -304,26 +321,31 @@ public function delete($request, $response, $args) * * This page requires authentication. * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If user is not found + * @throws ForbiddenException If user is not authozied to access page */ - public function getActivities($request, $response, $args) + public function getActivities(Request $request, Response $response, $args) { $user = $this->getUserFromParams($args); // If the user doesn't exist, return 404 if (!$user) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // GET parameters $params = $request->getQueryParams(); - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -350,17 +372,22 @@ public function getActivities($request, $response, $args) * * This page requires authentication. * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If user is not found + * @throws ForbiddenException If user is not authozied to access page */ - public function getInfo($request, $response, $args) + public function getInfo(Request $request, Response $response, $args) { $user = $this->getUserFromParams($args); // If the user doesn't exist, return 404 if (!$user) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // Join user's most recent activity @@ -370,10 +397,10 @@ public function getInfo($request, $response, $args) ->with('lastActivity', 'group') ->first(); - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -396,16 +423,20 @@ public function getInfo($request, $response, $args) * Generates a list of users, optionally paginated, sorted and/or filtered. * This page requires authentication. * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws ForbiddenException If user is not authozied to access page */ - public function getList($request, $response, $args) + public function getList(Request $request, Response $response, $args) { // GET parameters $params = $request->getQueryParams(); - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -413,7 +444,7 @@ public function getList($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; $sprunje = $classMapper->createInstance('user_sprunje', $classMapper, $params); @@ -429,8 +460,14 @@ public function getList($request, $response, $args) * This does NOT render a complete page. Instead, it renders the HTML for the modal, which can be embedded in other pages. * This page requires authentication. * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If user is not found + * @throws ForbiddenException If user is not authozied to access page + * @throws BadRequestException */ - public function getModalConfirmDelete($request, $response, $args) + public function getModalConfirmDelete(Request $request, Response $response, $args) { // GET parameters $params = $request->getQueryParams(); @@ -439,13 +476,13 @@ public function getModalConfirmDelete($request, $response, $args) // If the user doesn't exist, return 404 if (!$user) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -455,7 +492,7 @@ public function getModalConfirmDelete($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Config\Config $config */ + /** @var \UserFrosting\Support\Repository\Repository $config */ $config = $this->ci->config; // Check that we are not deleting the master account @@ -480,21 +517,26 @@ public function getModalConfirmDelete($request, $response, $args) * This does NOT render a complete page. Instead, it renders the HTML for the modal, which can be embedded in other pages. * If the currently logged-in user has permission to modify user group membership, then the group toggle will be displayed. * Otherwise, the user will be added to the default group and receive the default roles automatically. + * * This page requires authentication. * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws ForbiddenException If user is not authozied to access page */ - public function getModalCreate($request, $response, $args) + public function getModalCreate(Request $request, Response $response, $args) { // GET parameters $params = $request->getQueryParams(); - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; - /** @var UserFrosting\I18n\MessageTranslator $translator */ + /** @var \UserFrosting\I18n\MessageTranslator $translator */ $translator = $this->ci->translator; // Access-controlled page @@ -502,16 +544,16 @@ public function getModalCreate($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; - /** @var UserFrosting\Config\Config $config */ + /** @var \UserFrosting\Support\Repository\Repository $config */ $config = $this->ci->config; // Determine form fields to hide/disable // TODO: come back to this when we finish implementing theming $fields = [ - 'hidden' => ['theme'], + 'hidden' => ['theme'], 'disabled' => [] ]; @@ -545,13 +587,13 @@ public function getModalCreate($request, $response, $args) $validator = new JqueryValidationAdapter($schema, $this->ci->translator); return $this->ci->view->render($response, 'modals/user.html.twig', [ - 'user' => $user, - 'groups' => $groups, + 'user' => $user, + 'groups' => $groups, 'locales' => $locales, - 'form' => [ - 'action' => 'api/users', - 'method' => 'POST', - 'fields' => $fields, + 'form' => [ + 'action' => 'api/users', + 'method' => 'POST', + 'fields' => $fields, 'submit_text' => $translator->translate('CREATE') ], 'page' => [ @@ -565,9 +607,15 @@ public function getModalCreate($request, $response, $args) * * This does NOT render a complete page. Instead, it renders the HTML for the modal, which can be embedded in other pages. * This page requires authentication. + * * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If user is not found + * @throws ForbiddenException If user is not authozied to access page */ - public function getModalEdit($request, $response, $args) + public function getModalEdit(Request $request, Response $response, $args) { // GET parameters $params = $request->getQueryParams(); @@ -576,10 +624,10 @@ public function getModalEdit($request, $response, $args) // If the user doesn't exist, return 404 if (!$user) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // Get the user to edit @@ -587,10 +635,10 @@ public function getModalEdit($request, $response, $args) ->with('group') ->first(); - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled resource - check that currentUser has permission to edit basic fields "name", "email", "locale" for this user @@ -605,7 +653,7 @@ public function getModalEdit($request, $response, $args) // Get a list of all groups $groups = $classMapper->staticMethod('group', 'all'); - /** @var UserFrosting\Config\Config $config */ + /** @var \UserFrosting\Support\Repository\Repository $config */ $config = $this->ci->config; // Get a list of all locales @@ -613,7 +661,7 @@ public function getModalEdit($request, $response, $args) // Generate form $fields = [ - 'hidden' => ['theme'], + 'hidden' => ['theme'], 'disabled' => ['user_name'] ]; @@ -632,13 +680,13 @@ public function getModalEdit($request, $response, $args) $translator = $this->ci->translator; return $this->ci->view->render($response, 'modals/user.html.twig', [ - 'user' => $user, - 'groups' => $groups, + 'user' => $user, + 'groups' => $groups, 'locales' => $locales, - 'form' => [ - 'action' => "api/users/u/{$user->user_name}", - 'method' => 'PUT', - 'fields' => $fields, + 'form' => [ + 'action' => "api/users/u/{$user->user_name}", + 'method' => 'PUT', + 'fields' => $fields, 'submit_text' => $translator->translate('UPDATE') ], 'page' => [ @@ -652,9 +700,15 @@ public function getModalEdit($request, $response, $args) * * This does NOT render a complete page. Instead, it renders the HTML for the form, which can be embedded in other pages. * This page requires authentication. + * * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If user is not found + * @throws ForbiddenException If user is not authozied to access page */ - public function getModalEditPassword($request, $response, $args) + public function getModalEditPassword(Request $request, Response $response, $args) { // GET parameters $params = $request->getQueryParams(); @@ -663,13 +717,13 @@ public function getModalEditPassword($request, $response, $args) // If the user doesn't exist, return 404 if (!$user) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled resource - check that currentUser has permission to edit "password" field for this user @@ -697,9 +751,15 @@ public function getModalEditPassword($request, $response, $args) * * This does NOT render a complete page. Instead, it renders the HTML for the form, which can be embedded in other pages. * This page requires authentication. + * * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If user is not found + * @throws ForbiddenException If user is not authozied to access page */ - public function getModalEditRoles($request, $response, $args) + public function getModalEditRoles(Request $request, Response $response, $args) { // GET parameters $params = $request->getQueryParams(); @@ -708,13 +768,13 @@ public function getModalEditRoles($request, $response, $args) // If the user doesn't exist, return 404 if (!$user) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled resource - check that currentUser has permission to edit "roles" field for this user @@ -736,23 +796,28 @@ public function getModalEditRoles($request, $response, $args) * Generates a list of permissions, optionally paginated, sorted and/or filtered. * This page requires authentication. * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If user is not found + * @throws ForbiddenException If user is not authozied to access page */ - public function getPermissions($request, $response, $args) + public function getPermissions(Request $request, Response $response, $args) { $user = $this->getUserFromParams($args); // If the user doesn't exist, return 404 if (!$user) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } // GET parameters $params = $request->getQueryParams(); - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -763,17 +828,15 @@ public function getPermissions($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; $params['user_id'] = $user->id; $sprunje = $classMapper->createInstance('user_permission_sprunje', $classMapper, $params); - $response = $sprunje->toResponse($response); - // Be careful how you consume this data - it has not been escaped and contains untrusted user-supplied content. // For example, if you plan to insert it into an HTML DOM, you must escape it on the client side (or use client-side templating). - return $response; + return $sprunje->toResponse($response); } /** @@ -781,26 +844,31 @@ public function getPermissions($request, $response, $args) * * This page requires authentication. * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If user is not found + * @throws ForbiddenException If user is not authozied to access page */ - public function getRoles($request, $response, $args) + public function getRoles(Request $request, Response $response, $args) { $user = $this->getUserFromParams($args); // If the user doesn't exist, return 404 if (!$user) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // GET parameters $params = $request->getQueryParams(); - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -827,23 +895,27 @@ public function getRoles($request, $response, $args) * This checks that the currently logged-in user has permission to view the requested user's info. * It checks each field individually, showing only those that you have permission to view. * This will also try to show buttons for activating, disabling/enabling, deleting, and editing the user. + * * This page requires authentication. * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws ForbiddenException If user is not authozied to access page */ - public function pageInfo($request, $response, $args) + public function pageInfo(Request $request, Response $response, $args) { $user = $this->getUserFromParams($args); // If the user no longer exists, forward to main user listing page if (!$user) { - $usersPage = $this->ci->router->pathFor('uri_users'); - return $response->withRedirect($usersPage, 404); + throw new NotFoundException(); } - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -853,7 +925,7 @@ public function pageInfo($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Config\Config $config */ + /** @var \UserFrosting\Support\Repository\Repository $config */ $config = $this->ci->config; // Get a list of all locales @@ -944,11 +1016,12 @@ public function pageInfo($request, $response, $args) } return $this->ci->view->render($response, 'pages/user.html.twig', [ - 'user' => $user, + 'user' => $user, 'locales' => $locales, - 'fields' => $fields, - 'tools' => $editButtons, - 'widgets' => $widgets + 'fields' => $fields, + 'tools' => $editButtons, + 'widgets' => $widgets, + 'delete_redirect' => $this->ci->router->pathFor('uri_users') ]); } @@ -957,15 +1030,20 @@ public function pageInfo($request, $response, $args) * * This page renders a table of users, with dropdown menus for admin actions for each user. * Actions typically include: edit user details, activate user, enable/disable user, delete user. + * * This page requires authentication. * Request type: GET + * @param Request $request + * @param Response $response + * @param array $args + * @throws ForbiddenException If user is not authozied to access page */ - public function pageList($request, $response, $args) + public function pageList(Request $request, Response $response, $args) { - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page @@ -983,25 +1061,31 @@ public function pageList($request, $response, $args) * 1. The target user's new email address, if specified, is not already in use; * 2. The logged-in user has the necessary permissions to update the putted field(s); * 3. The submitted data is valid. + * * This route requires authentication. * Request type: PUT + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If user is not found + * @throws ForbiddenException If user is not authozied to access page */ - public function updateInfo($request, $response, $args) + public function updateInfo(Request $request, Response $response, $args) { // Get the username from the URL $user = $this->getUserFromParams($args); if (!$user) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } - /** @var UserFrosting\Config\Config $config */ + /** @var \UserFrosting\Support\Repository\Repository $config */ $config = $this->ci->config; // Get PUT parameters $params = $request->getParsedBody(); - /** @var UserFrosting\Sprinkle\Core\MessageStream $ms */ + /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ $ms = $this->ci->alerts; // Load the request schema @@ -1032,10 +1116,10 @@ public function updateInfo($request, $response, $args) } } - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled resource - check that currentUser has permission to edit submitted fields for this user @@ -1054,7 +1138,7 @@ public function updateInfo($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // Check if email already exists @@ -1068,11 +1152,11 @@ public function updateInfo($request, $response, $args) } if ($error) { - return $response->withStatus(400); + return $response->withJson([], 400); } // Begin transaction - DB will be rolled back if an exception occurs - Capsule::transaction( function() use ($data, $user, $currentUser) { + Capsule::transaction(function () use ($data, $user, $currentUser) { // Update the user and generate success messages foreach ($data as $name => $value) { if ($value != $user->$name) { @@ -1084,7 +1168,7 @@ public function updateInfo($request, $response, $args) // Create activity record $this->ci->userActivityLogger->info("User {$currentUser->user_name} updated basic account info for user {$user->user_name}.", [ - 'type' => 'account_update_info', + 'type' => 'account_update_info', 'user_id' => $currentUser->id ]); }); @@ -1092,7 +1176,8 @@ public function updateInfo($request, $response, $args) $ms->addMessageTranslated('success', 'DETAILS_UPDATED', [ 'user_name' => $user->user_name ]); - return $response->withStatus(200); + + return $response->withJson([], 200); } /** @@ -1103,25 +1188,32 @@ public function updateInfo($request, $response, $args) * 1. The logged-in user has the necessary permissions to update the putted field(s); * 2. We're not trying to disable the master account; * 3. The submitted data is valid. + * * This route requires authentication. * Request type: PUT + * @param Request $request + * @param Response $response + * @param array $args + * @throws NotFoundException If user is not found + * @throws ForbiddenException If user is not authozied to access page + * @throws BadRequestException */ - public function updateField($request, $response, $args) + public function updateField(Request $request, Response $response, $args) { // Get the username from the URL $user = $this->getUserFromParams($args); if (!$user) { - throw new NotFoundException($request, $response); + throw new NotFoundException(); } // Get key->value pair from URL and request body $fieldName = $args['field']; - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; - /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ + /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled resource - check that currentUser has permission to edit the specified field for this user @@ -1132,7 +1224,7 @@ public function updateField($request, $response, $args) throw new ForbiddenException(); } - /** @var UserFrosting\Config\Config $config */ + /** @var \UserFrosting\Support\Repository\Repository $config */ $config = $this->ci->config; // Only the master account can edit the master account! @@ -1168,7 +1260,7 @@ public function updateField($request, $response, $args) // TODO: encapsulate the communication of error messages from ServerSideValidator to the BadRequestException $e = new BadRequestException(); foreach ($validator->errors() as $idx => $field) { - foreach($field as $eidx => $error) { + foreach ($field as $eidx => $error) { $e->addUserMessage($error); } } @@ -1178,7 +1270,7 @@ public function updateField($request, $response, $args) // Get validated and transformed value $fieldValue = $data[$fieldName]; - /** @var UserFrosting\Sprinkle\Core\MessageStream $ms */ + /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ $ms = $this->ci->alerts; // Special checks and transformations for certain fields @@ -1204,7 +1296,7 @@ public function updateField($request, $response, $args) } // Begin transaction - DB will be rolled back if an exception occurs - Capsule::transaction( function() use ($fieldName, $fieldValue, $user, $currentUser) { + Capsule::transaction(function () use ($fieldName, $fieldValue, $user, $currentUser) { if ($fieldName == 'roles') { $newRoles = collect($fieldValue)->pluck('role_id')->all(); $user->roles()->sync($newRoles); @@ -1215,7 +1307,7 @@ public function updateField($request, $response, $args) // Create activity record $this->ci->userActivityLogger->info("User {$currentUser->user_name} updated property '$fieldName' for user {$user->user_name}.", [ - 'type' => 'account_update_field', + 'type' => 'account_update_field', 'user_id' => $currentUser->id ]); }); @@ -1241,9 +1333,16 @@ public function updateField($request, $response, $args) ]); } - return $response->withStatus(200); + return $response->withJson([], 200); } + /** + * Get User instance from params + * + * @param array $params + * @throws BadRequestException + * @return User + */ protected function getUserFromParams($params) { // Load the request schema @@ -1259,14 +1358,14 @@ protected function getUserFromParams($params) // TODO: encapsulate the communication of error messages from ServerSideValidator to the BadRequestException $e = new BadRequestException(); foreach ($validator->errors() as $idx => $field) { - foreach($field as $eidx => $error) { + foreach ($field as $eidx => $error) { $e->addUserMessage($error); } } throw $e; } - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // Get the user to delete diff --git a/app/sprinkles/admin/src/ServicesProvider/ServicesProvider.php b/app/sprinkles/admin/src/ServicesProvider/ServicesProvider.php index 061d90c51..37411feb1 100644 --- a/app/sprinkles/admin/src/ServicesProvider/ServicesProvider.php +++ b/app/sprinkles/admin/src/ServicesProvider/ServicesProvider.php @@ -3,15 +3,15 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Admin\ServicesProvider; +use Interop\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; -use UserFrosting\Sprinkle\Account\Authenticate\Authenticator; -use UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager; -use UserFrosting\Sprinkle\Core\Facades\Debug; /** * Registers services for the admin sprinkle. @@ -23,14 +23,16 @@ class ServicesProvider /** * Register UserFrosting's admin services. * - * @param Container $container A DI container implementing ArrayAccess and container-interop. + * @param ContainerInterface $container A DI container implementing ArrayAccess and container-interop. */ - public function register($container) + public function register(ContainerInterface $container) { /** * Extend the 'classMapper' service to register sprunje classes. * * Mappings added: 'activity_sprunje', 'group_sprunje', 'permission_sprunje', 'role_sprunje', 'user_sprunje' + * + * @return \UserFrosting\Sprinkle\Core\Util\ClassMapper */ $container->extend('classMapper', function ($classMapper, $c) { $classMapper->setClassMapping('activity_sprunje', 'UserFrosting\Sprinkle\Admin\Sprunje\ActivitySprunje'); @@ -40,6 +42,7 @@ public function register($container) $classMapper->setClassMapping('role_sprunje', 'UserFrosting\Sprinkle\Admin\Sprunje\RoleSprunje'); $classMapper->setClassMapping('user_sprunje', 'UserFrosting\Sprinkle\Admin\Sprunje\UserSprunje'); $classMapper->setClassMapping('user_permission_sprunje', 'UserFrosting\Sprinkle\Admin\Sprunje\UserPermissionSprunje'); + return $classMapper; }); @@ -47,26 +50,28 @@ public function register($container) * Returns a callback that handles setting the `UF-Redirect` header after a successful login. * * Overrides the service definition in the account Sprinkle. + * + * @return callable */ $container['redirect.onLogin'] = function ($c) { /** * This method is invoked when a user completes the login process. * * Returns a callback that handles setting the `UF-Redirect` header after a successful login. - * @param \Psr\Http\Message\ServerRequestInterface $request - * @param \Psr\Http\Message\ResponseInterface $response - * @param array $args + * @param \Psr\Http\Message\ServerRequestInterface $request + * @param \Psr\Http\Message\ResponseInterface $response + * @param array $args * @return \Psr\Http\Message\ResponseInterface */ return function (Request $request, Response $response, array $args) use ($c) { // Backwards compatibility for the deprecated determineRedirectOnLogin service if ($c->has('determineRedirectOnLogin')) { $determineRedirectOnLogin = $c->determineRedirectOnLogin; - + return $determineRedirectOnLogin($response)->withStatus(200); } - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ + /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ $authorizer = $c->authorizer; $currentUser = $c->authenticator->user(); diff --git a/app/sprinkles/admin/src/Sprunje/ActivitySprunje.php b/app/sprinkles/admin/src/Sprunje/ActivitySprunje.php index da4f0e3f5..5af3e8cfe 100644 --- a/app/sprinkles/admin/src/Sprunje/ActivitySprunje.php +++ b/app/sprinkles/admin/src/Sprunje/ActivitySprunje.php @@ -3,12 +3,13 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Admin\Sprunje; -use Illuminate\Database\Capsule\Manager as Capsule; -use UserFrosting\Sprinkle\Core\Facades\Debug; +use Illuminate\Database\Schema\Builder; use UserFrosting\Sprinkle\Core\Sprunje\Sprunje; /** @@ -47,9 +48,9 @@ protected function baseQuery() /** * Filter LIKE the user info. * - * @param Builder $query - * @param mixed $value - * @return $this + * @param Builder $query + * @param mixed $value + * @return self */ protected function filterUser($query, $value) { @@ -62,19 +63,21 @@ protected function filterUser($query, $value) ->orLike('users.email', $value); } }); + return $this; } /** * Sort based on user last name. * - * @param Builder $query - * @param string $direction - * @return $this + * @param Builder $query + * @param string $direction + * @return self */ protected function sortUser($query, $direction) { $query->orderBy('users.last_name', $direction); + return $this; } } diff --git a/app/sprinkles/admin/src/Sprunje/GroupSprunje.php b/app/sprinkles/admin/src/Sprunje/GroupSprunje.php index 7c7569124..ec4809aea 100644 --- a/app/sprinkles/admin/src/Sprunje/GroupSprunje.php +++ b/app/sprinkles/admin/src/Sprunje/GroupSprunje.php @@ -3,12 +3,12 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Admin\Sprunje; -use Illuminate\Database\Capsule\Manager as Capsule; -use UserFrosting\Sprinkle\Core\Facades\Debug; use UserFrosting\Sprinkle\Core\Sprunje\Sprunje; /** @@ -33,7 +33,7 @@ class GroupSprunje extends Sprunje ]; /** - * {@inheritDoc} + * {@inheritdoc} */ protected function baseQuery() { diff --git a/app/sprinkles/admin/src/Sprunje/PermissionSprunje.php b/app/sprinkles/admin/src/Sprunje/PermissionSprunje.php index c1803f11b..011c86710 100644 --- a/app/sprinkles/admin/src/Sprunje/PermissionSprunje.php +++ b/app/sprinkles/admin/src/Sprunje/PermissionSprunje.php @@ -3,12 +3,13 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Admin\Sprunje; -use Illuminate\Database\Capsule\Manager as Capsule; -use UserFrosting\Sprinkle\Core\Facades\Debug; +use Illuminate\Database\Schema\Builder; use UserFrosting\Sprinkle\Core\Sprunje\Sprunje; /** @@ -38,7 +39,7 @@ class PermissionSprunje extends Sprunje ]; /** - * {@inheritDoc} + * {@inheritdoc} */ protected function baseQuery() { @@ -48,9 +49,9 @@ protected function baseQuery() /** * Filter LIKE the slug, conditions, or description. * - * @param Builder $query - * @param mixed $value - * @return $this + * @param Builder $query + * @param mixed $value + * @return self */ protected function filterInfo($query, $value) { @@ -60,9 +61,9 @@ protected function filterInfo($query, $value) /** * Filter LIKE the slug, conditions, or description. * - * @param Builder $query - * @param mixed $value - * @return $this + * @param Builder $query + * @param mixed $value + * @return self */ protected function filterProperties($query, $value) { @@ -75,19 +76,21 @@ protected function filterProperties($query, $value) ->orLike('description', $value); } }); + return $this; } /** * Sort based on slug. * - * @param Builder $query - * @param string $direction - * @return $this + * @param Builder $query + * @param string $direction + * @return self */ protected function sortProperties($query, $direction) { $query->orderBy('slug', $direction); + return $this; } } diff --git a/app/sprinkles/admin/src/Sprunje/PermissionUserSprunje.php b/app/sprinkles/admin/src/Sprunje/PermissionUserSprunje.php index 242681d03..6da396c2d 100644 --- a/app/sprinkles/admin/src/Sprunje/PermissionUserSprunje.php +++ b/app/sprinkles/admin/src/Sprunje/PermissionUserSprunje.php @@ -3,12 +3,12 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Admin\Sprunje; -use Illuminate\Database\Capsule\Manager as Capsule; -use UserFrosting\Sprinkle\Core\Facades\Debug; use UserFrosting\Support\Exception\BadRequestException; use UserFrosting\Support\Exception\NotFoundException; @@ -24,7 +24,7 @@ class PermissionUserSprunje extends UserSprunje protected $name = 'permission_users'; /** - * {@inheritDoc} + * {@inheritdoc} */ protected function baseQuery() { @@ -37,7 +37,7 @@ protected function baseQuery() // If the permission doesn't exist, return 404 if (!$permission) { - throw new NotFoundException; + throw new NotFoundException(); } // Get permission users diff --git a/app/sprinkles/admin/src/Sprunje/RoleSprunje.php b/app/sprinkles/admin/src/Sprunje/RoleSprunje.php index c5e0f8b34..cdce42f8f 100644 --- a/app/sprinkles/admin/src/Sprunje/RoleSprunje.php +++ b/app/sprinkles/admin/src/Sprunje/RoleSprunje.php @@ -3,12 +3,13 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Admin\Sprunje; -use Illuminate\Database\Capsule\Manager as Capsule; -use UserFrosting\Sprinkle\Core\Facades\Debug; +use Illuminate\Database\Schema\Builder; use UserFrosting\Sprinkle\Core\Sprunje\Sprunje; /** @@ -38,7 +39,7 @@ class RoleSprunje extends Sprunje ]; /** - * {@inheritDoc} + * {@inheritdoc} */ protected function baseQuery() { @@ -48,9 +49,9 @@ protected function baseQuery() /** * Filter LIKE name OR description. * - * @param Builder $query - * @param mixed $value - * @return $this + * @param Builder $query + * @param mixed $value + * @return self */ protected function filterInfo($query, $value) { @@ -62,6 +63,7 @@ protected function filterInfo($query, $value) ->orLike('description', $value); } }); + return $this; } } diff --git a/app/sprinkles/admin/src/Sprunje/UserPermissionSprunje.php b/app/sprinkles/admin/src/Sprunje/UserPermissionSprunje.php index 6142e7413..c1268fb50 100644 --- a/app/sprinkles/admin/src/Sprunje/UserPermissionSprunje.php +++ b/app/sprinkles/admin/src/Sprunje/UserPermissionSprunje.php @@ -3,12 +3,12 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Admin\Sprunje; -use Illuminate\Database\Capsule\Manager as Capsule; -use UserFrosting\Sprinkle\Core\Facades\Debug; use UserFrosting\Support\Exception\BadRequestException; use UserFrosting\Support\Exception\NotFoundException; @@ -24,7 +24,7 @@ class UserPermissionSprunje extends PermissionSprunje protected $name = 'user_permissions'; /** - * {@inheritDoc} + * {@inheritdoc} */ protected function baseQuery() { @@ -37,7 +37,7 @@ protected function baseQuery() // If the user doesn't exist, return 404 if (!$user) { - throw new NotFoundException; + throw new NotFoundException(); } // Get user permissions diff --git a/app/sprinkles/admin/src/Sprunje/UserSprunje.php b/app/sprinkles/admin/src/Sprunje/UserSprunje.php index 12378f9ac..038247a2a 100644 --- a/app/sprinkles/admin/src/Sprunje/UserSprunje.php +++ b/app/sprinkles/admin/src/Sprunje/UserSprunje.php @@ -3,12 +3,13 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Sprinkle\Admin\Sprunje; -use Illuminate\Database\Capsule\Manager as Capsule; -use UserFrosting\Sprinkle\Core\Facades\Debug; +use Illuminate\Database\Schema\Builder; use UserFrosting\Sprinkle\Core\Facades\Translator; use UserFrosting\Sprinkle\Core\Sprunje\Sprunje; @@ -44,7 +45,7 @@ class UserSprunje extends Sprunje ]; /** - * {@inheritDoc} + * {@inheritdoc} */ protected function baseQuery() { @@ -57,9 +58,9 @@ protected function baseQuery() /** * Filter LIKE the last activity description. * - * @param Builder $query - * @param mixed $value - * @return $this + * @param Builder $query + * @param mixed $value + * @return self */ protected function filterLastActivity($query, $value) { @@ -70,15 +71,16 @@ protected function filterLastActivity($query, $value) $query->orLike('activities.description', $value); } }); + return $this; } /** * Filter LIKE the first name, last name, or email. * - * @param Builder $query - * @param mixed $value - * @return $this + * @param Builder $query + * @param mixed $value + * @return self */ protected function filterName($query, $value) { @@ -91,15 +93,16 @@ protected function filterName($query, $value) ->orLike('email', $value); } }); + return $this; } /** * Filter by status (active, disabled, unactivated) * - * @param Builder $query - * @param mixed $value - * @return $this + * @param Builder $query + * @param mixed $value + * @return self */ protected function filterStatus($query, $value) { @@ -118,6 +121,7 @@ protected function filterStatus($query, $value) } } }); + return $this; } @@ -131,15 +135,15 @@ protected function listStatus() return [ [ 'value' => 'active', - 'text' => Translator::translate('ACTIVE') + 'text' => Translator::translate('ACTIVE') ], [ 'value' => 'unactivated', - 'text' => Translator::translate('UNACTIVATED') + 'text' => Translator::translate('UNACTIVATED') ], [ 'value' => 'disabled', - 'text' => Translator::translate('DISABLED') + 'text' => Translator::translate('DISABLED') ] ]; } @@ -147,39 +151,42 @@ protected function listStatus() /** * Sort based on last activity time. * - * @param Builder $query - * @param string $direction - * @return $this + * @param Builder $query + * @param string $direction + * @return self */ protected function sortLastActivity($query, $direction) { $query->orderBy('activities.occurred_at', $direction); + return $this; } /** * Sort based on last name. * - * @param Builder $query - * @param string $direction - * @return $this + * @param Builder $query + * @param string $direction + * @return self */ protected function sortName($query, $direction) { $query->orderBy('last_name', $direction); + return $this; } /** * Sort active, unactivated, disabled * - * @param Builder $query - * @param string $direction - * @return $this + * @param Builder $query + * @param string $direction + * @return self */ protected function sortStatus($query, $direction) { $query->orderBy('flag_enabled', $direction)->orderBy('flag_verified', $direction); + return $this; } } diff --git a/app/sprinkles/admin/templates/pages/group.html.twig b/app/sprinkles/admin/templates/pages/group.html.twig index bf4d275b2..35bf233f0 100644 --- a/app/sprinkles/admin/templates/pages/group.html.twig +++ b/app/sprinkles/admin/templates/pages/group.html.twig @@ -88,11 +88,12 @@ + - + {{ assets.js() | raw }} {% endblock %} diff --git a/app/sprinkles/core/templates/pages/index.html.twig b/app/sprinkles/core/templates/pages/index.html.twig index fea1213bf..a0c0630a0 100755 --- a/app/sprinkles/core/templates/pages/index.html.twig +++ b/app/sprinkles/core/templates/pages/index.html.twig @@ -20,21 +20,21 @@
- +
diff --git a/app/sprinkles/core/templates/tables/table-paginated.html.twig b/app/sprinkles/core/templates/tables/table-paginated.html.twig index 5b9412052..cfe32e173 100644 --- a/app/sprinkles/core/templates/tables/table-paginated.html.twig +++ b/app/sprinkles/core/templates/tables/table-paginated.html.twig @@ -34,7 +34,7 @@ {% endblock %} {% block table_pager_controls %} -
+
{# this can be any element, including an input #} diff --git a/app/sprinkles/core/tests/ControllerTestCase.php b/app/sprinkles/core/tests/ControllerTestCase.php new file mode 100644 index 000000000..da67cdda3 --- /dev/null +++ b/app/sprinkles/core/tests/ControllerTestCase.php @@ -0,0 +1,56 @@ +setupTestDatabase(); + $this->refreshDatabase(); + } + + /** + * @param array $args Request arguments + * @return Request + */ + protected function getRequest($args = []) + { + $env = Environment::mock($args); + + return Request::createFromEnvironment($env); + } + + /** + * @return Response + */ + protected function getResponse() + { + return new Response(); + } +} diff --git a/app/sprinkles/core/tests/DatabaseTransactions.php b/app/sprinkles/core/tests/DatabaseTransactions.php new file mode 100644 index 000000000..668905e1c --- /dev/null +++ b/app/sprinkles/core/tests/DatabaseTransactions.php @@ -0,0 +1,48 @@ +ci->db; + + foreach ($this->connectionsToTransact() as $name) { + $database->connection($name)->beginTransaction(); + } + + $this->beforeApplicationDestroyed(function () use ($database) { + foreach ($this->connectionsToTransact() as $name) { + $database->connection($name)->rollBack(); + } + }); + } + + /** + * The database connections that should have transactions. + * + * @return array + */ + protected function connectionsToTransact() + { + return property_exists($this, 'connectionsToTransact') + ? $this->connectionsToTransact : [null]; + } +} diff --git a/app/sprinkles/core/tests/Integration/BakeryMigrateCommandTest.php b/app/sprinkles/core/tests/Integration/BakeryMigrateCommandTest.php new file mode 100644 index 000000000..79fb5d91b --- /dev/null +++ b/app/sprinkles/core/tests/Integration/BakeryMigrateCommandTest.php @@ -0,0 +1,105 @@ +shouldReceive('repositoryExists')->once()->andReturn(true); + $migrator->shouldReceive('getPendingMigrations')->once()->andReturn(['foo']); + $migrator->shouldReceive('run')->once()->with(['pretend' => false, 'step' => false])->andReturn([]); + $migrator->shouldReceive('getNotes'); + + // Run command + $commandTester = $this->runCommand($migrator, []); + } + + public function testMigrationRepositoryCreatedWhenNecessary() + { + $migrator = m::mock('UserFrosting\Sprinkle\Core\Database\Migrator\Migrator'); + $repository = m::mock('UserFrosting\Sprinkle\Core\Database\Migrator\DatabaseMigrationRepository'); + + $migrator->shouldReceive('repositoryExists')->once()->andReturn(false); + $migrator->shouldReceive('getRepository')->once()->andReturn($repository); + $migrator->shouldReceive('getPendingMigrations')->once()->andReturn(['foo']); + $migrator->shouldReceive('run')->once()->with(['pretend' => false, 'step' => false])->andReturn([]); + $migrator->shouldReceive('getNotes'); + + $repository->shouldReceive('createRepository')->once(); + + // Run command + $commandTester = $this->runCommand($migrator, []); + } + + public function testTheCommandMayBePretended() + { + // Setup migrator mock + $migrator = m::mock('UserFrosting\Sprinkle\Core\Database\Migrator\Migrator'); + $migrator->shouldReceive('repositoryExists')->once()->andReturn(true); + $migrator->shouldReceive('getPendingMigrations')->once()->andReturn(['foo']); + $migrator->shouldReceive('run')->once()->with(['pretend' => true, 'step' => false])->andReturn([]); + $migrator->shouldReceive('getNotes'); + + // Run command + $commandTester = $this->runCommand($migrator, ['--pretend' => true]); + } + + public function testStepMayBeSet() + { + // Setup migrator mock + $migrator = m::mock('UserFrosting\Sprinkle\Core\Database\Migrator\Migrator'); + $migrator->shouldReceive('repositoryExists')->once()->andReturn(true); + $migrator->shouldReceive('getPendingMigrations')->once()->andReturn(['foo']); + $migrator->shouldReceive('run')->once()->with(['pretend' => false, 'step' => true])->andReturn([]); + $migrator->shouldReceive('getNotes'); + + // Run command + $commandTester = $this->runCommand($migrator, ['--step' => true]); + } + + protected function runCommand($migrator, $input = []) + { + // Place the mock migrator inside the $ci + $ci = $this->ci; + $ci->migrator = $migrator; + + // Create the app, create the command, replace $ci and add the command to the app + $app = new Application(); + $command = new MigrateCommand(); + $command->setContainer($ci); + $app->add($command); + + // Add the command to the input to create the execute argument + $execute = array_merge([ + 'command' => $command->getName() + ], $input); + + // Execute command tester + $commandTester = new CommandTester($command); + $commandTester->execute($execute); + + return $commandTester; + } +} diff --git a/app/sprinkles/core/tests/Integration/BakeryMigrateRefreshCommandTest.php b/app/sprinkles/core/tests/Integration/BakeryMigrateRefreshCommandTest.php new file mode 100644 index 000000000..58e3cb8a0 --- /dev/null +++ b/app/sprinkles/core/tests/Integration/BakeryMigrateRefreshCommandTest.php @@ -0,0 +1,91 @@ +shouldReceive('repositoryExists')->once()->andReturn(true); + $migrator->shouldReceive('getRanMigrations')->once()->andReturn(['foo']); + $migrator->shouldReceive('rollback')->once()->with(['pretend' => false, 'steps' => 1])->andReturn(['foo']); + $migrator->shouldReceive('run')->once()->with(['pretend' => false, 'step' => false])->andReturn([]); + $migrator->shouldReceive('getNotes'); + + // Run command + $commandTester = $this->runCommand($migrator, []); + } + + public function testBasicCallWithNotthingToRollback() + { + // Setup migrator mock + $migrator = m::mock('UserFrosting\Sprinkle\Core\Database\Migrator\Migrator'); + $migrator->shouldReceive('repositoryExists')->once()->andReturn(true); + $migrator->shouldReceive('getRanMigrations')->once()->andReturn(['foo']); + $migrator->shouldReceive('rollback')->once()->with(['pretend' => false, 'steps' => 1])->andReturn([]); + $migrator->shouldNotReceive('run'); + $migrator->shouldReceive('getNotes'); + + // Run command + $commandTester = $this->runCommand($migrator, []); + } + + public function testStepsMayBeSet() + { + // Setup migrator mock + $migrator = m::mock('UserFrosting\Sprinkle\Core\Database\Migrator\Migrator'); + $migrator->shouldReceive('repositoryExists')->once()->andReturn(true); + $migrator->shouldReceive('getRanMigrations')->once()->andReturn(['foo']); + $migrator->shouldReceive('rollback')->once()->with(['pretend' => false, 'steps' => 3])->andReturn(['foo']); + $migrator->shouldReceive('run')->once()->with(['pretend' => false, 'step' => false])->andReturn([]); + $migrator->shouldReceive('getNotes'); + + // Run command + $commandTester = $this->runCommand($migrator, ['--steps' => 3]); + } + + protected function runCommand($migrator, $input = []) + { + // Place the mock migrator inside the $ci + $ci = $this->ci; + $ci->migrator = $migrator; + + // Create the app, create the command, replace $ci and add the command to the app + $app = new Application(); + $command = new MigrateRefreshCommand(); + $command->setContainer($ci); + $app->add($command); + + // Add the command to the input to create the execute argument + $execute = array_merge([ + 'command' => $command->getName() + ], $input); + + // Execute command tester + $commandTester = new CommandTester($command); + $commandTester->execute($execute); + + return $commandTester; + } +} diff --git a/app/sprinkles/core/tests/Integration/BakeryMigrateResetCommandTest.php b/app/sprinkles/core/tests/Integration/BakeryMigrateResetCommandTest.php new file mode 100644 index 000000000..7a995c319 --- /dev/null +++ b/app/sprinkles/core/tests/Integration/BakeryMigrateResetCommandTest.php @@ -0,0 +1,99 @@ +shouldReceive('deleteRepository')->andReturn(null); + + // Setup migrator mock + $migrator = m::mock('UserFrosting\Sprinkle\Core\Database\Migrator\Migrator'); + $migrator->shouldReceive('repositoryExists')->twice()->andReturn(true); + $migrator->shouldReceive('getRanMigrations')->once()->andReturn(['foo']); + $migrator->shouldReceive('reset')->once()->with(false)->andReturn(['foo']); + $migrator->shouldReceive('getNotes'); + $migrator->shouldReceive('getRepository')->once()->andReturn($repository); + + // Run command + $commandTester = $this->runCommand($migrator, []); + } + + public function testBasicCallWithNotthingToRollback() + { + // Setup repository mock + $repository = m::mock('UserFrosting\Sprinkle\Core\Database\Migrator\DatabaseMigrationRepository'); + $repository->shouldReceive('deleteRepository')->andReturn(null); + + // Setup migrator mock + $migrator = m::mock('UserFrosting\Sprinkle\Core\Database\Migrator\Migrator'); + $migrator->shouldReceive('repositoryExists')->twice()->andReturn(true); + $migrator->shouldReceive('getRanMigrations')->once()->andReturn(['foo']); + $migrator->shouldReceive('reset')->once()->with(false)->andReturn([]); + $migrator->shouldReceive('getNotes'); + $migrator->shouldReceive('getRepository')->once()->andReturn($repository); + + // Run command + $commandTester = $this->runCommand($migrator, []); + } + + public function testTheCommandMayBePretended() + { + // Setup migrator mock + $migrator = m::mock('UserFrosting\Sprinkle\Core\Database\Migrator\Migrator'); + $migrator->shouldReceive('repositoryExists')->once()->andReturn(true); + $migrator->shouldReceive('getRanMigrations')->once()->andReturn(['foo']); + $migrator->shouldReceive('reset')->once()->with(true)->andReturn(['foo']); + $migrator->shouldReceive('getNotes'); + $migrator->shouldNotReceive('getRepository'); + + // Run command + $commandTester = $this->runCommand($migrator, ['--pretend' => true]); + } + + protected function runCommand($migrator, $input = []) + { + // Place the mock migrator inside the $ci + $ci = $this->ci; + $ci->migrator = $migrator; + + // Create the app, create the command, replace $ci and add the command to the app + $app = new Application(); + $command = new MigrateResetCommand(); + $command->setContainer($ci); + $app->add($command); + + // Add the command to the input to create the execute argument + $execute = array_merge([ + 'command' => $command->getName() + ], $input); + + // Execute command tester + $commandTester = new CommandTester($command); + $commandTester->execute($execute); + + return $commandTester; + } +} diff --git a/app/sprinkles/core/tests/Integration/BakeryMigrateRollbackCommandTest.php b/app/sprinkles/core/tests/Integration/BakeryMigrateRollbackCommandTest.php new file mode 100644 index 000000000..6aba5a97d --- /dev/null +++ b/app/sprinkles/core/tests/Integration/BakeryMigrateRollbackCommandTest.php @@ -0,0 +1,105 @@ +shouldReceive('repositoryExists')->once()->andReturn(true); + $migrator->shouldReceive('getRanMigrations')->once()->andReturn(['foo']); + $migrator->shouldReceive('rollback')->once()->with(['pretend' => false, 'steps' => 1])->andReturn([]); + $migrator->shouldReceive('getNotes'); + + // Run command + $commandTester = $this->runCommand($migrator, []); + } + + public function testMigrationRepositoryCreatedWhenNecessary() + { + $migrator = m::mock('UserFrosting\Sprinkle\Core\Database\Migrator\Migrator'); + $repository = m::mock('UserFrosting\Sprinkle\Core\Database\Migrator\DatabaseMigrationRepository'); + + $migrator->shouldReceive('repositoryExists')->once()->andReturn(false); + $migrator->shouldReceive('getRanMigrations')->once()->andReturn(['foo']); + $migrator->shouldReceive('getRepository')->once()->andReturn($repository); + $migrator->shouldReceive('rollback')->once()->with(['pretend' => false, 'steps' => 1])->andReturn([]); + $migrator->shouldReceive('getNotes'); + + $repository->shouldReceive('createRepository')->once(); + + // Run command + $commandTester = $this->runCommand($migrator, []); + } + + public function testTheCommandMayBePretended() + { + // Setup migrator mock + $migrator = m::mock('UserFrosting\Sprinkle\Core\Database\Migrator\Migrator'); + $migrator->shouldReceive('repositoryExists')->once()->andReturn(true); + $migrator->shouldReceive('getRanMigrations')->once()->andReturn(['foo']); + $migrator->shouldReceive('rollback')->once()->with(['pretend' => true, 'steps' => 1])->andReturn([]); + $migrator->shouldReceive('getNotes'); + + // Run command + $commandTester = $this->runCommand($migrator, ['--pretend' => true]); + } + + public function testStepsMayBeSet() + { + // Setup migrator mock + $migrator = m::mock('UserFrosting\Sprinkle\Core\Database\Migrator\Migrator'); + $migrator->shouldReceive('repositoryExists')->once()->andReturn(true); + $migrator->shouldReceive('getRanMigrations')->once()->andReturn(['foo']); + $migrator->shouldReceive('rollback')->once()->with(['pretend' => false, 'steps' => 3])->andReturn([]); + $migrator->shouldReceive('getNotes'); + + // Run command + $commandTester = $this->runCommand($migrator, ['--steps' => 3]); + } + + protected function runCommand($migrator, $input = []) + { + // Place the mock migrator inside the $ci + $ci = $this->ci; + $ci->migrator = $migrator; + + // Create the app, create the command, replace $ci and add the command to the app + $app = new Application(); + $command = new MigrateRollbackCommand(); + $command->setContainer($ci); + $app->add($command); + + // Add the command to the input to create the execute argument + $execute = array_merge([ + 'command' => $command->getName() + ], $input); + + // Execute command tester + $commandTester = new CommandTester($command); + $commandTester->execute($execute); + + return $commandTester; + } +} diff --git a/app/sprinkles/core/tests/Integration/BakeryMigrateStatusCommandTest.php b/app/sprinkles/core/tests/Integration/BakeryMigrateStatusCommandTest.php new file mode 100644 index 000000000..da0a83177 --- /dev/null +++ b/app/sprinkles/core/tests/Integration/BakeryMigrateStatusCommandTest.php @@ -0,0 +1,105 @@ +getInstalledMigrationStub()->pluck('migration')->all(); + $pending = ['oof', 'rab']; + + // Set expectations + $migrator->shouldReceive('setConnection')->once()->with(null)->andReturn(null); + $migrator->shouldReceive('repositoryExists')->once()->andReturn(true); + $migrator->shouldReceive('getRepository')->once()->andReturn($repository); + $migrator->shouldReceive('getAvailableMigrations')->once()->andReturn($available); + $migrator->shouldReceive('getPendingMigrations')->once()->andReturn($pending); + + $repository->shouldReceive('getMigrations')->once()->andReturn($this->getInstalledMigrationStub()); + + // Run command + $commandTester = $this->runCommand($migrator, []); + } + + public function testDatabaseMayBeSet() + { + // Setup migrator mock + $migrator = m::mock('UserFrosting\Sprinkle\Core\Database\Migrator\Migrator'); + $repository = m::mock('UserFrosting\Sprinkle\Core\Database\Migrator\DatabaseMigrationRepository'); + + // Define dummy data + $available = ['foo', 'bar', 'oof', 'rab']; + $installed = $this->getInstalledMigrationStub()->pluck('migration')->all(); + $pending = ['oof', 'rab']; + + // Set expectations + $migrator->shouldReceive('setConnection')->once()->with('test')->andReturn(null); + $migrator->shouldReceive('repositoryExists')->once()->andReturn(true); + $migrator->shouldReceive('getRepository')->once()->andReturn($repository); + $migrator->shouldReceive('getAvailableMigrations')->once()->andReturn($available); + $migrator->shouldReceive('getPendingMigrations')->once()->andReturn($pending); + + $repository->shouldReceive('getMigrations')->once()->andReturn($this->getInstalledMigrationStub()); + + // Run command + $commandTester = $this->runCommand($migrator, ['--database' => 'test']); + } + + protected function runCommand($migrator, $input = []) + { + // Place the mock migrator inside the $ci + $ci = $this->ci; + $ci->migrator = $migrator; + + // Create the app, create the command, replace $ci and add the command to the app + $app = new Application(); + $command = new MigrateStatusCommand(); + $command->setContainer($ci); + $app->add($command); + + // Add the command to the input to create the execute argument + $execute = array_merge([ + 'command' => $command->getName() + ], $input); + + // Execute command tester + $commandTester = new CommandTester($command); + $commandTester->execute($execute); + + return $commandTester; + } + + protected function getInstalledMigrationStub() + { + return collect([ + (object) ['migration' => 'foo', 'batch' => 1, 'sprinkle' => 'foo'], + (object) ['migration' => 'bar', 'batch' => 2, 'sprinkle' => 'bar'] + ]); + } +} diff --git a/app/sprinkles/core/tests/Integration/CheckEnvironmentTest.php b/app/sprinkles/core/tests/Integration/CheckEnvironmentTest.php new file mode 100644 index 000000000..2d283e7b9 --- /dev/null +++ b/app/sprinkles/core/tests/Integration/CheckEnvironmentTest.php @@ -0,0 +1,24 @@ +assertInstanceOf(CheckEnvironment::class, $this->ci->checkEnvironment); + } +} diff --git a/app/sprinkles/core/tests/Integration/Controllers/CoreControllerTest.php b/app/sprinkles/core/tests/Integration/Controllers/CoreControllerTest.php new file mode 100644 index 000000000..f8dc0415c --- /dev/null +++ b/app/sprinkles/core/tests/Integration/Controllers/CoreControllerTest.php @@ -0,0 +1,89 @@ +ci); + $this->assertInstanceOf(CoreController::class, $controller); + + return $controller; + } + + /** + * @depends testControllerConstructor + * @param CoreController $controller + */ + public function testPageIndex(CoreController $controller) + { + $result = $controller->pageIndex($this->getRequest(), $this->getResponse(), []); + $this->assertSame($result->getStatusCode(), 200); + $this->assertTrue((bool) preg_match('/<\/html>/', (string) $result->getBody())); + } + + /** + * @depends testControllerConstructor + * @param CoreController $controller + */ + public function testJsonAlerts(CoreController $controller) + { + $result = $controller->jsonAlerts($this->getRequest(), $this->getResponse(), []); + $this->assertSame($result->getStatusCode(), 200); + $this->assertJson((string) $result->getBody()); + } + + /** + * @depends testControllerConstructor + * @expectedException \UserFrosting\Support\Exception\NotFoundException + * @param CoreController $controller + */ + public function testGetAsset_ExceptionNoUrl(CoreController $controller) + { + $controller->getAsset($this->getRequest(), $this->getResponse(), []); + } + + /** + * @depends testControllerConstructor + * @expectedException \UserFrosting\Support\Exception\NotFoundException + * @param CoreController $controller + */ + public function testGetAsset_ExceptionBadUrl(CoreController $controller) + { + $url = '/' . rand(0, 99999); + $controller->getAsset($this->getRequest(), $this->getResponse(), ['url' => $url]); + } + + /** + * @depends testControllerConstructor + * @depends testGetAsset_ExceptionNoUrl + * @depends testGetAsset_ExceptionBadUrl + * @param CoreController $controller + */ + public function testGetAsset(CoreController $controller) + { + $result = $controller->getAsset($this->getRequest(), $this->getResponse(), ['url' => '']); + $this->assertSame($result->getStatusCode(), 200); + $this->assertSame('', (string) $result->getBody()); + + $result = $controller->getAsset($this->getRequest(), $this->getResponse(), ['url' => 'userfrosting/js/uf-alerts.js']); + $this->assertSame($result->getStatusCode(), 200); + $this->assertNotEmpty((string) $result->getBody()); + } +} diff --git a/app/sprinkles/core/tests/Integration/DatabaseMigratorIntegrationTest.php b/app/sprinkles/core/tests/Integration/DatabaseMigratorIntegrationTest.php new file mode 100644 index 000000000..a68530d29 --- /dev/null +++ b/app/sprinkles/core/tests/Integration/DatabaseMigratorIntegrationTest.php @@ -0,0 +1,383 @@ +repository = new DatabaseMigrationRepository($this->ci->db, $this->migrationTable); + $this->locator = new MigrationLocatorStub($this->ci->locator); + + // Get the migrator instance and setup right connection + $this->migrator = new Migrator($this->ci->db, $this->repository, $this->locator); + $this->migrator->setConnection($this->connection); + + // Get schema Builder + $this->schema = $this->migrator->getSchemaBuilder(); + + if (!$this->repository->repositoryExists()) { + $this->repository->createRepository(); + } + } + + public function testMigrationRepositoryCreated() + { + $this->assertTrue($this->schema->hasTable($this->migrationTable)); + } + + public function testBasicMigration() + { + $ran = $this->migrator->run(); + + $this->assertTrue($this->schema->hasTable('users')); + $this->assertTrue($this->schema->hasTable('password_resets')); + + $this->assertEquals($this->locator->getMigrations(), $ran); + } + + public function testRepository() + { + $ran = $this->migrator->run(); + + // Theses assertions makes sure the repository and the migration returns the same format + // N.B.: getLast return the migrations in reverse order (last ran first) + $this->assertEquals($this->locator->getMigrations(), $ran); + $this->assertEquals(array_reverse($this->locator->getMigrations()), $this->repository->getLast()); + $this->assertEquals($this->locator->getMigrations(), $this->repository->getMigrationsList()); + } + + public function testMigrationsCanBeRolledBack() + { + // Run up + $this->migrator->run(); + $this->assertTrue($this->schema->hasTable('users')); + $this->assertTrue($this->schema->hasTable('password_resets')); + + $rolledBack = $this->migrator->rollback(); + $this->assertFalse($this->schema->hasTable('users')); + $this->assertFalse($this->schema->hasTable('password_resets')); + + // Make sure the data returned from migrator is accurate. + // N.B.: The order returned by the rollback method is ordered by which + // migration was rollbacked first (reversed from the order they where ran up) + $this->assertEquals(array_reverse($this->locator->getMigrations()), $rolledBack); + } + + public function testMigrationsCanBeReset() + { + // Run up + $this->migrator->run(); + $this->assertTrue($this->schema->hasTable('users')); + $this->assertTrue($this->schema->hasTable('password_resets')); + + $rolledBack = $this->migrator->reset(); + $this->assertFalse($this->schema->hasTable('users')); + $this->assertFalse($this->schema->hasTable('password_resets')); + + // Make sure the data returned from migrator is accurate. + $this->assertEquals(array_reverse($this->locator->getMigrations()), $rolledBack); + } + + public function testNoErrorIsThrownWhenNoOutstandingMigrationsExist() + { + $this->migrator->run(); + $this->assertTrue($this->schema->hasTable('users')); + $this->assertTrue($this->schema->hasTable('password_resets')); + $this->migrator->run(); + } + + public function testNoErrorIsThrownWhenNothingToRollback() + { + $this->migrator->run(); + $this->assertTrue($this->schema->hasTable('users')); + $this->assertTrue($this->schema->hasTable('password_resets')); + $this->migrator->rollback(); + $this->assertFalse($this->schema->hasTable('users')); + $this->assertFalse($this->schema->hasTable('password_resets')); + $this->migrator->rollback(); + } + + public function testPretendUp() + { + $result = $this->migrator->run(['pretend' => true]); + $notes = $this->migrator->getNotes(); + $this->assertFalse($this->schema->hasTable('users')); + $this->assertFalse($this->schema->hasTable('password_resets')); + $this->assertNotEquals([], $notes); + } + + public function testPretendRollback() + { + // Run up as usual + $result = $this->migrator->run(); + $this->assertTrue($this->schema->hasTable('users')); + $this->assertTrue($this->schema->hasTable('password_resets')); + + $rolledBack = $this->migrator->rollback(['pretend' => true]); + $this->assertTrue($this->schema->hasTable('users')); + $this->assertTrue($this->schema->hasTable('password_resets')); + $this->assertEquals(array_reverse($this->locator->getMigrations()), $rolledBack); + } + + public function testChangeRepositoryAndDeprecatedClass() + { + // Change the repository so we can test with the DeprecatedMigrationLocatorStub + $locator = new DeprecatedMigrationLocatorStub($this->ci->locator); + $this->migrator->setLocator($locator); + + // Run up. Should also run the seeder + $this->migrator->run(); + $this->assertTrue($this->schema->hasTable('deprecated_table')); + + // Make sure the seeder ran. + // Easiest way to do so it asking the seeder to change the table structure + $this->assertTrue($this->schema->hasColumn('deprecated_table', 'foo')); + + // Rollback + $this->migrator->rollback(); + $this->assertFalse($this->schema->hasTable('deprecated_table')); + } + + public function testWithInvalidClass() + { + // Change the repository so we can test with the InvalidMigrationLocatorStub + $locator = new InvalidMigrationLocatorStub($this->ci->locator); + $this->migrator->setLocator($locator); + + // Expect a `BadClassNameException` exception + $this->expectException(BadClassNameException::class); + + // Run up + $this->migrator->run(); + } + + public function testDependableMigrations() + { + // Change the repository so we can test with the DependableMigrationLocatorStub + $locator = new DependableMigrationLocatorStub($this->ci->locator); + $this->migrator->setLocator($locator); + + // Run up + $migrated = $this->migrator->run(); + $this->assertTrue($this->schema->hasTable('users')); + $this->assertTrue($this->schema->hasTable('password_resets')); + $this->assertTrue($this->schema->hasTable('flights')); + + // Note here the `two` migration has been placed at the bottom even if + // it was supposed to be migrated first from the order the locator + // returned them. This is because `two` migration depends on `one` migrations + // We only check the last one, we don't care about the order the first two are since they are not dependendent on eachother + $this->assertEquals('\\UserFrosting\\Tests\\Integration\\Migrations\\two\\CreateFlightsTable', $migrated[2]); + } + + public function testDependableMigrationsWithInstalled() + { + // Run the `one` migrations + $this->migrator->run(); + $this->assertTrue($this->schema->hasTable('users')); + $this->assertTrue($this->schema->hasTable('password_resets')); + + // Change the repository so we can run up the `two` migrations + $locator = new FlightsTableMigrationLocatorStub($this->ci->locator); + $this->migrator->setLocator($locator); + + // Run up again + $migrated = $this->migrator->run(); + $this->assertTrue($this->schema->hasTable('flights')); + + // Only the `CreateFlightsTable` migration should be ran + $this->assertEquals([ + '\\UserFrosting\\Tests\\Integration\\Migrations\\two\\CreateFlightsTable' + ], $migrated); + } + + public function testUnfulfillableMigrations() + { + // Change the repository so we can test with the DeprecatedStub + $locator = new UnfulfillableMigrationLocatorStub($this->ci->locator); + $this->migrator->setLocator($locator); + + // Should have an exception for unfulfilled migrations + $this->expectException(\Exception::class); + $migrated = $this->migrator->run(); + } + + public function testSpecificMigrationCanBeRollback() + { + // Change the repository so we can test with the DependableMigrationLocatorStub + $locator = new DependableMigrationLocatorStub($this->ci->locator); + $this->migrator->setLocator($locator); + + // Run up + $migrated = $this->migrator->run(); + $this->assertTrue($this->schema->hasTable('users')); + $this->assertTrue($this->schema->hasTable('password_resets')); + $this->assertTrue($this->schema->hasTable('flights')); + + // Rollback only the Flights table. Should work as no other depends on it + $migration = '\\UserFrosting\\Tests\\Integration\\Migrations\\two\\CreateFlightsTable'; + $rolledBack = $this->migrator->rollbackMigration($migration); + $this->assertCount(1, $rolledBack); + $this->assertEquals([$migration], $rolledBack); + + // Look at actual db for tables. Flight should be gone, but other still there + $this->assertTrue($this->schema->hasTable('users')); + $this->assertTrue($this->schema->hasTable('password_resets')); + $this->assertFalse($this->schema->hasTable('flights')); + } + + public function testSpecificMigrationRollbackWithDependencies() + { + // Change the repository so we can test with the DependableMigrationLocatorStub + $locator = new DependableMigrationLocatorStub($this->ci->locator); + $this->migrator->setLocator($locator); + + // Run up + $migrated = $this->migrator->run(); + $this->assertTrue($this->schema->hasTable('users')); + $this->assertTrue($this->schema->hasTable('password_resets')); + $this->assertTrue($this->schema->hasTable('flights')); + + // Rollback only the user table. Should fail as the flight table depends on it + $migration = '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable'; + $this->expectException(\Exception::class); + $rolledBack = $this->migrator->rollbackMigration($migration); + + // Look at actual db for tables. Should be no changes + $this->assertTrue($this->schema->hasTable('users')); + $this->assertTrue($this->schema->hasTable('password_resets')); + $this->assertTrue($this->schema->hasTable('flights')); + } +} + +class MigrationLocatorStub extends MigrationLocator +{ + public function getMigrations() + { + return [ + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreatePasswordResetsTable' + ]; + } +} + +class FlightsTableMigrationLocatorStub extends MigrationLocator +{ + public function getMigrations() + { + return [ + '\\UserFrosting\\Tests\\Integration\\Migrations\\two\\CreateFlightsTable' + ]; + } +} + +/** + * This stub contain migration which file doesn't exists + */ +class InvalidMigrationLocatorStub extends MigrationLocator +{ + public function getMigrations() + { + return [ + '\\UserFrosting\\Tests\\Integration\\Migrations\\Foo' + ]; + } +} + +/** + * This stub contain migration which order they need to be run is different + * than the order the file are returned because of dependencies management. + * The `two` migration should be run last since it depends on the other two + */ +class DependableMigrationLocatorStub extends MigrationLocator +{ + public function getMigrations() + { + return [ + '\\UserFrosting\\Tests\\Integration\\Migrations\\two\\CreateFlightsTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreatePasswordResetsTable' + ]; + } +} + +/** + * This stub contain migration which order they need to be run is different + * than the order the file are returned because of dependencies management + */ +class UnfulfillableMigrationLocatorStub extends MigrationLocator +{ + public function getMigrations() + { + return [ + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreatePasswordResetsTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\UnfulfillableTable' + ]; + } +} + +/** + * This stub contain migration which order they need to be run is different + * than the order the file are returned because of dependencies management + */ +class DeprecatedMigrationLocatorStub extends MigrationLocator +{ + public function getMigrations() + { + return [ + '\\UserFrosting\\Tests\\Integration\\Migrations\\DeprecatedClassTable' + ]; + } +} diff --git a/app/sprinkles/core/tests/Integration/DatabaseMigratorServiceTest.php b/app/sprinkles/core/tests/Integration/DatabaseMigratorServiceTest.php new file mode 100644 index 000000000..494d12d90 --- /dev/null +++ b/app/sprinkles/core/tests/Integration/DatabaseMigratorServiceTest.php @@ -0,0 +1,39 @@ +setupTestDatabase(); + } + + public function testMigratorService() + { + $this->assertInstanceOf('UserFrosting\Sprinkle\Core\Database\Migrator\Migrator', $this->ci->migrator); + } +} diff --git a/app/sprinkles/core/tests/Integration/DatabaseMigratorTest.php b/app/sprinkles/core/tests/Integration/DatabaseMigratorTest.php new file mode 100644 index 000000000..c0134ad7e --- /dev/null +++ b/app/sprinkles/core/tests/Integration/DatabaseMigratorTest.php @@ -0,0 +1,409 @@ +schema = m::mock(Builder::class); + $this->repository = m::mock(DatabaseMigrationRepository::class); + $this->locator = m::mock(MigrationLocator::class); + $capsule = m::mock(Capsule::class); + $this->connection = m::mock(Connection::class); + + // Set global expections for $capule and $connection + $capsule->shouldReceive('getConnection')->andReturn($this->connection); + $this->connection->shouldReceive('getSchemaBuilder')->andReturn($this->schema); + + // Setup the migrator instance + $this->migrator = new Migrator($capsule, $this->repository, $this->locator); + } + + /** + * Basic test to make sure the base method syntaxt is ok + */ + public function testMigratorUpWithNoMigrations() + { + // Locator will be asked to return the avaialble migrations + $this->locator->shouldReceive('getMigrations')->once()->andReturn([]); + + // Repository will be asked to return the ran migrations + $this->repository->shouldReceive('getMigrationsList')->once()->andReturn([]); + + $migrations = $this->migrator->run(); + $this->assertEmpty($migrations); + } + + /** + * Basic test where all avaialble migrations are pending and fulfillable + */ + public function testMigratorUpWithOnlyPendingMigrations() + { + // The migrations set + $testMigrations = [ + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreatePasswordResetsTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\two\\CreateFlightsTable' + ]; + + // When running up, Locator will return all 3 migration classes + $this->locator->shouldReceive('getMigrations')->andReturn($testMigrations); + + // Repository will be asked to return the ran migrations, the next batch number and will log 3 new migrations + $this->repository->shouldReceive('getMigrationsList')->andReturn([]); + $this->repository->shouldReceive('getNextBatchNumber')->andReturn(1); + $this->repository->shouldReceive('log')->times(3)->andReturn(null); + + // SchemaBuilder will create all 3 tables + $this->schema->shouldReceive('create')->times(3)->andReturn(null); + + // Connection will be asked for the SchemaGrammar + $grammar = m::mock(Grammar::class); + $this->connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); + $grammar->shouldReceive('supportsSchemaTransactions')->andReturn(false); + + // Run migrations up + $migrations = $this->migrator->run(); + + // All classes should have been migrated + $this->assertEquals($testMigrations, $migrations); + } + + /** + * Test where one of the avaialble migrations is already installed + */ + public function testMigratorUpWithOneInstalledMigrations() + { + // When running up, Locator will return all 3 migration classes + $this->locator->shouldReceive('getMigrations')->andReturn([ + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreatePasswordResetsTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\two\\CreateFlightsTable' + ]); + + // Repository will be asked to return the ran migrations (one), the next batch number and will log 2 new migrations + $this->repository->shouldReceive('getMigrationsList')->andReturn([ + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable' + ]); + $this->repository->shouldReceive('getNextBatchNumber')->andReturn(2); + $this->repository->shouldReceive('log')->times(2)->andReturn(null); + + // SchemaBuilder will only create 2 tables + $this->schema->shouldReceive('create')->times(2)->andReturn(null); + + // Connection will be asked for the SchemaGrammar + $grammar = m::mock(Grammar::class); + $this->connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); + $grammar->shouldReceive('supportsSchemaTransactions')->andReturn(false); + + // Run migrations up + $migrations = $this->migrator->run(); + + // The migration already ran shoudn't be in the pending ones + $this->assertEquals([ + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreatePasswordResetsTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\two\\CreateFlightsTable' + ], $migrations); + } + + /** + * Test where all avaialble migrations have been ran + */ + public function testMigratorUpWithNoPendingMigrations() + { + // The migrations set + $testMigrations = [ + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreatePasswordResetsTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\two\\CreateFlightsTable' + ]; + + // When running up, Locator will return all 3 migration classes + $this->locator->shouldReceive('getMigrations')->andReturn($testMigrations); + + // Repository will be asked to return the ran migrations (one), the next batch number and will log 2 new migrations + $this->repository->shouldReceive('getMigrationsList')->andReturn($testMigrations); + $this->repository->shouldNotReceive('getNextBatchNumber'); + $this->repository->shouldNotReceive('log'); + + // SchemaBuilder will only create 2 tables + $this->schema->shouldNotReceive('create'); + + // Run migrations up + $migrations = $this->migrator->run(); + + // The migration already ran shoudn't be in the pending ones + $this->assertEquals([], $migrations); + } + + /** + * Test where one of the available migrations is missing a dependency + */ + //!TODO + + /** + * Test rolling back where no migrations have been ran + */ + public function testMigratorRollbackWithNoInstalledMigrations() + { + // Repository will be asked to return the last batch of ran migrations + $this->repository->shouldReceive('getLast')->andReturn([]); + + // Run migrations up + $migrations = $this->migrator->rollback(); + + // The migration already ran shoudn't be in the pending ones + $this->assertEquals([], $migrations); + } + + /** + * Test rolling back all installed migrations + */ + public function testMigratorRollbackAllInstalledMigrations() + { + // The migrations set + $testMigrations = [ + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreatePasswordResetsTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\two\\CreateFlightsTable' + ]; + + // When running up, Locator will return all 3 migration classes + $this->locator->shouldReceive('getMigrations')->once()->andReturn($testMigrations); + + // Repository will be asked to return the ran migrations (one), the next batch number and will log 2 new migrations + $this->repository->shouldReceive('getLast')->once()->andReturn($testMigrations); + $this->repository->shouldReceive('getMigrationsList')->once()->andReturn($testMigrations); + $this->repository->shouldReceive('delete')->times(3)->andReturn([]); + + // SchemaBuilder will only create 2 tables + $this->schema->shouldReceive('dropIfExists')->times(3)->andReturn([]); + + // Connection will be asked for the SchemaGrammar + $grammar = m::mock(Grammar::class); + $this->connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); + $grammar->shouldReceive('supportsSchemaTransactions')->andReturn(false); + + // Run migrations up + $migrations = $this->migrator->rollback(); + + // The migration already ran shoudn't be in the pending ones + $this->assertEquals($testMigrations, $migrations); + } + + /** + * Test where one of the installed migration is not in the available migration classes + */ + public function testMigratorRollbackAllInstalledMigrationsWithOneMissing() + { + // Locator will only return one of the two installed migrations + $this->locator->shouldReceive('getMigrations')->once()->andReturn([ + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreatePasswordResetsTable' + ]); + + // Repository will be asked to return the ran migrations (two of them) + // and will only be asked to delete one + $installed = [ + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreatePasswordResetsTable' + ]; + $this->repository->shouldReceive('getLast')->once()->andReturn($installed); + $this->repository->shouldReceive('getMigrationsList')->once()->andReturn($installed); + $this->repository->shouldReceive('delete')->times(1)->andReturn([]); + + // SchemaBuilder will only drop one of the 2 tables + $this->schema->shouldReceive('dropIfExists')->times(1)->andReturn([]); + + // Connection will be asked for the SchemaGrammar + $grammar = m::mock(Grammar::class); + $this->connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); + $grammar->shouldReceive('supportsSchemaTransactions')->andReturn(false); + + // Rollback migrations + $migrations = $this->migrator->rollback(); + + // The migration not available from the locator shouldn't have been run dowm + $this->assertEquals([ + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreatePasswordResetsTable' + ], $migrations); + } + + /** + * Test a specific migration with no dependencies can be rollbacked + */ + public function testMigratorRollbackSpecific() + { + // The installed / available migrations + $testMigrations = [ + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreatePasswordResetsTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\two\\CreateFlightsTable' + ]; + + // Migration object for the one being deleted + $migration = '\\UserFrosting\\Tests\\Integration\\Migrations\\two\\CreateFlightsTable'; + $migrationObject = (object) [ + 'migration' => $migration + ]; + + // Locator will return all 3 migration classes as available + $this->locator->shouldReceive('getMigrations')->once()->andReturn($testMigrations); + + // Repository will be asked to return the ran migrations and delete one + $this->repository->shouldReceive('getMigration')->once()->andReturn($migrationObject); + $this->repository->shouldReceive('getMigrationsList')->once()->andReturn($testMigrations); + $this->repository->shouldReceive('delete')->times(1)->andReturn([]); + + // SchemaBuilder will delete 1 table + $this->schema->shouldReceive('dropIfExists')->times(1)->andReturn([]); + + // Connection will be asked for the SchemaGrammar + $grammar = m::mock(Grammar::class); + $this->connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); + $grammar->shouldReceive('supportsSchemaTransactions')->andReturn(false); + + // Rollback only the Flights table. Should work as no other depends on it + $rolledback = $this->migrator->rollbackMigration($migration); + + // The migration already ran shoudn't be in the pending ones + $this->assertEquals([$migration], $rolledback); + } + + /** + * Test a specific migration with some dependencies can be rollbacked + */ + public function testMigratorRollbackSpecificWithDependencies() + { + // The installed / available migrations + $testMigrations = [ + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreatePasswordResetsTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\two\\CreateFlightsTable' + ]; + + // Migration object for the one being deleted + $migration = '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable'; + $migrationObject = (object) [ + 'migration' => $migration + ]; + + // Locator will return all 3 migration classes as available + $this->locator->shouldReceive('getMigrations')->once()->andReturn($testMigrations); + + // Repository will be asked to return the ran migrations and delete one + $this->repository->shouldReceive('getMigration')->once()->andReturn($migrationObject); + $this->repository->shouldReceive('getMigrationsList')->once()->andReturn($testMigrations); + $this->repository->shouldNotReceive('delete'); + + // SchemaBuilder will delete 1 table + $this->schema->shouldNotReceive('dropIfExists'); + + // Connection will be asked for the SchemaGrammar + $grammar = m::mock(Grammar::class); + $this->connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); + $grammar->shouldReceive('supportsSchemaTransactions')->andReturn(false); + + // Rollback only the user table. Should fail as the flight table depends on it + $this->expectException(\Exception::class); + $rolledback = $this->migrator->rollbackMigration($migration); + } + + /** + * Test where one of the installed migration is not in the available migration classes + */ + public function testMigratorResetAllInstalledMigrations() + { + // The migrations set + $testMigrations = [ + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreatePasswordResetsTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\two\\CreateFlightsTable' + ]; + + // Locator will return all 3 migration classes + $this->locator->shouldReceive('getMigrations')->once()->andReturn($testMigrations); + + // Repository will be asked to return the ran migrations (all of them), + // then asked to delete all 3 of them + $this->repository->shouldReceive('getMigrationsList')->twice()->andReturn($testMigrations); + $this->repository->shouldReceive('delete')->times(3)->andReturn([]); + + // SchemaBuilder will drop all 3 tables + $this->schema->shouldReceive('dropIfExists')->times(3)->andReturn([]); + + // Connection will be asked for the SchemaGrammar + $grammar = m::mock(Grammar::class); + $this->connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); + $grammar->shouldReceive('supportsSchemaTransactions')->andReturn(false); + + // Reset mgirations + $migrations = $this->migrator->reset(); + + // All the migrations should have been rolledback + $this->assertEquals(array_reverse($testMigrations), $migrations); + } +} diff --git a/app/sprinkles/core/tests/Integration/DatabaseTests.php b/app/sprinkles/core/tests/Integration/DatabaseTests.php index 166f55eef..f7a44e67a 100644 --- a/app/sprinkles/core/tests/Integration/DatabaseTests.php +++ b/app/sprinkles/core/tests/Integration/DatabaseTests.php @@ -1,25 +1,28 @@ createSchema(); } + /** + * createSchema + */ protected function createSchema() { - $this->schema($this->schemaName)->create('users', function ($table) { + $this->schema($this->schemaName)->create('users', function (Blueprint $table) { $table->increments('id'); $table->string('name')->nullable(); }); // Users have multiple email addresses - $this->schema($this->schemaName)->create('emails', function ($table) { + $this->schema($this->schemaName)->create('emails', function (Blueprint $table) { $table->increments('id'); $table->integer('user_id'); $table->string('label'); @@ -48,7 +54,7 @@ protected function createSchema() }); // Users have multiple phones (polymorphic - other entities can have phones as well) - $this->schema($this->schemaName)->create('phones', function ($table) { + $this->schema($this->schemaName)->create('phones', function (Blueprint $table) { $table->increments('id'); $table->string('label'); $table->string('number', 20); @@ -56,45 +62,45 @@ protected function createSchema() }); // Users have multiple roles... (m:m) - $this->schema($this->schemaName)->create('role_users', function ($table) { + $this->schema($this->schemaName)->create('role_users', function (Blueprint $table) { $table->integer('user_id')->unsigned(); $table->integer('role_id')->unsigned(); }); - $this->schema($this->schemaName)->create('roles', function ($table) { + $this->schema($this->schemaName)->create('roles', function (Blueprint $table) { $table->increments('id'); $table->string('slug'); }); // And Roles have multiple permissions... (m:m) - $this->schema($this->schemaName)->create('permission_roles', function ($table) { + $this->schema($this->schemaName)->create('permission_roles', function (Blueprint $table) { $table->integer('permission_id')->unsigned(); $table->integer('role_id')->unsigned(); }); - $this->schema($this->schemaName)->create('permissions', function($table) { + $this->schema($this->schemaName)->create('permissions', function ($table) { $table->increments('id'); $table->string('slug'); }); // A user can be assigned to a specific task at a specific location - $this->schema($this->schemaName)->create('tasks', function($table) { + $this->schema($this->schemaName)->create('tasks', function ($table) { $table->increments('id'); $table->string('name'); }); - $this->schema($this->schemaName)->create('locations', function($table) { + $this->schema($this->schemaName)->create('locations', function ($table) { $table->increments('id'); $table->string('name'); }); - $this->schema($this->schemaName)->create('assignments', function($table) { + $this->schema($this->schemaName)->create('assignments', function ($table) { $table->integer('task_id')->unsigned(); $table->integer('location_id')->unsigned(); $table->morphs('assignable'); }); - $this->schema($this->schemaName)->create('jobs', function($table) { + $this->schema($this->schemaName)->create('jobs', function ($table) { $table->integer('user_id')->unsigned(); $table->integer('location_id')->unsigned(); $table->integer('role_id')->unsigned(); @@ -104,8 +110,6 @@ protected function createSchema() /** * Tear down the database schema. - * - * @return void */ public function tearDown() { @@ -122,6 +126,8 @@ public function tearDown() $this->schema($this->schemaName)->drop('jobs'); Relation::morphMap([], false); + + parent::tearDown(); } /** @@ -143,8 +149,8 @@ public function testOneToManyRelationship() $this->assertInstanceOf('Illuminate\Database\Eloquent\Collection', $emails); $this->assertEquals(2, $emails->count()); - $this->assertInstanceOf('UserFrosting\Tests\Integration\EloquentTestEmail', $emails[0]); - $this->assertInstanceOf('UserFrosting\Tests\Integration\EloquentTestEmail', $emails[1]); + $this->assertInstanceOf(EloquentTestEmail::class, $emails[0]); + $this->assertInstanceOf(EloquentTestEmail::class, $emails[1]); } /** @@ -160,7 +166,7 @@ public function testSyncOneToMany() 'email' => 'david@owlfancy.com' ]); $user->emails()->create([ - 'id' => 2, + 'id' => 2, 'label' => 'work', 'email' => 'david@attenboroughsreef.com' ]); @@ -168,7 +174,7 @@ public function testSyncOneToMany() // Delete `work`, update `primary`, and add `gmail` $user->emails()->sync([ [ - 'id' => 1, + 'id' => 1, 'email' => 'david@aol.com' ], [ @@ -181,16 +187,16 @@ public function testSyncOneToMany() $this->assertEquals([ [ - 'id' => 1, + 'id' => 1, 'user_id'=> 1, - 'label' => 'primary', - 'email' => 'david@aol.com' + 'label' => 'primary', + 'email' => 'david@aol.com' ], [ - 'id' => 3, + 'id' => 3, 'user_id' => 1, - 'label' => 'gmail', - 'email' => 'davidattenborough@gmail.com' + 'label' => 'gmail', + 'email' => 'davidattenborough@gmail.com' ] ], $emails); } @@ -203,24 +209,24 @@ public function testSyncMorphMany() $user = EloquentTestUser::create(['name' => 'David']); // Set up original phones $user->phones()->create([ - 'id' => 1, - 'label' => 'primary', + 'id' => 1, + 'label' => 'primary', 'number' => '5555551212' ]); $user->phones()->create([ - 'id' => 2, - 'label' => 'work', + 'id' => 2, + 'label' => 'work', 'number' => '2223334444' ]); // Delete `work`, update `primary`, and add `fax` $user->phones()->sync([ [ - 'id' => 1, + 'id' => 1, 'number' => '8883332222' ], [ - 'label' => 'fax', + 'label' => 'fax', 'number' => '5550005555' ] ]); @@ -229,22 +235,25 @@ public function testSyncMorphMany() $this->assertEquals([ [ - 'id' => 1, - 'phoneable_id'=> 1, - 'phoneable_type' => 'UserFrosting\Tests\Integration\EloquentTestUser', - 'label' => 'primary', - 'number' => '8883332222' + 'id' => 1, + 'phoneable_id' => 1, + 'phoneable_type' => EloquentTestUser::class, + 'label' => 'primary', + 'number' => '8883332222' ], [ - 'id' => 3, - 'phoneable_id'=> 1, - 'phoneable_type' => 'UserFrosting\Tests\Integration\EloquentTestUser', - 'label' => 'fax', - 'number' => '5550005555' + 'id' => 3, + 'phoneable_id' => 1, + 'phoneable_type' => EloquentTestUser::class, + 'label' => 'fax', + 'number' => '5550005555' ] ], $phones); } + /** + * testBelongsToManyUnique + */ public function testBelongsToManyUnique() { $user = EloquentTestUser::create(['name' => 'David']); @@ -255,16 +264,16 @@ public function testBelongsToManyUnique() $expectedRoles = [ [ - 'id' => 2, - 'slug' => 'soldier', + 'id' => 2, + 'slug' => 'soldier', 'pivot' => [ 'user_id' => 1, 'role_id' => 2 ] ], [ - 'id' => 3, - 'slug' => 'egg-layer', + 'id' => 3, + 'slug' => 'egg-layer', 'pivot' => [ 'user_id' => 1, 'role_id' => 3 @@ -280,6 +289,9 @@ public function testBelongsToManyUnique() $this->assertEquals($expectedRoles, $users->toArray()[0]['job_roles']); } + /** + * testMorphsToManyUnique + */ public function testMorphsToManyUnique() { $user = EloquentTestUser::create(['name' => 'David']); @@ -291,19 +303,19 @@ public function testMorphsToManyUnique() $expectedTasks = [ [ - 'id' => 2, - 'name' => 'Chopping', + 'id' => 2, + 'name' => 'Chopping', 'pivot' => [ 'assignable_id' => 1, - 'task_id' => 2 + 'task_id' => 2 ] ], [ - 'id' => 3, - 'name' => 'Baleing', + 'id' => 3, + 'name' => 'Baleing', 'pivot' => [ 'assignable_id' => 1, - 'task_id' => 3 + 'task_id' => 3 ] ] ]; @@ -316,6 +328,9 @@ public function testMorphsToManyUnique() $this->assertEquals($expectedTasks, $users->toArray()[0]['assignment_tasks']); } + /** + * testMorphsToManyUniqueWithTertiary + */ public function testMorphsToManyUniqueWithTertiary() { $user = EloquentTestUser::create(['name' => 'David']); @@ -327,45 +342,45 @@ public function testMorphsToManyUniqueWithTertiary() $expectedTasks = [ [ - 'id' => 2, - 'name' => 'Chopping', + 'id' => 2, + 'name' => 'Chopping', 'pivot' => [ 'assignable_id' => 1, - 'task_id' => 2 + 'task_id' => 2 ], 'locations' => [ [ - 'id' => 1, - 'name' => 'Hatchery', + 'id' => 1, + 'name' => 'Hatchery', 'pivot' => [ 'location_id' => 1, - 'task_id' => 2 + 'task_id' => 2 ], ], [ - 'id' => 2, - 'name' => 'Nexus', + 'id' => 2, + 'name' => 'Nexus', 'pivot' => [ 'location_id' => 2, - 'task_id' => 2 + 'task_id' => 2 ], ] ] ], [ - 'id' => 3, - 'name' => 'Baleing', + 'id' => 3, + 'name' => 'Baleing', 'pivot' => [ 'assignable_id' => 1, - 'task_id' => 3 + 'task_id' => 3 ], 'locations' => [ [ - 'id' => 2, - 'name' => 'Nexus', + 'id' => 2, + 'name' => 'Nexus', 'pivot' => [ 'location_id' => 2, - 'task_id' => 3 + 'task_id' => 3 ], ] ] @@ -380,6 +395,9 @@ public function testMorphsToManyUniqueWithTertiary() $this->assertEquals($expectedTasks, $users->toArray()[0]['tasks']); } + /** + * testBelongsToManyUniqueWithTertiary + */ public function testBelongsToManyUniqueWithTertiary() { $user = EloquentTestUser::create(['name' => 'David']); @@ -390,48 +408,48 @@ public function testBelongsToManyUniqueWithTertiary() $expectedJobs = [ [ - 'id' => 2, - 'slug' => 'soldier', + 'id' => 2, + 'slug' => 'soldier', 'pivot' => [ 'user_id' => 1, 'role_id' => 2 ], 'locations' => [ [ - 'id' => 1, - 'name' => 'Hatchery', + 'id' => 1, + 'name' => 'Hatchery', 'pivot' => [ - 'title' => 'Grunt', + 'title' => 'Grunt', 'location_id' => 1, - 'role_id' => 2 + 'role_id' => 2 ] ], [ - 'id' => 2, - 'name' => 'Nexus', + 'id' => 2, + 'name' => 'Nexus', 'pivot' => [ - 'title' => 'Sergeant', + 'title' => 'Sergeant', 'location_id' => 2, - 'role_id' => 2 + 'role_id' => 2 ] ] ] ], [ - 'id' => 3, - 'slug' => 'egg-layer', + 'id' => 3, + 'slug' => 'egg-layer', 'pivot' => [ 'user_id' => 1, 'role_id' => 3 ], 'locations' => [ [ - 'id' => 2, - 'name' => 'Nexus', + 'id' => 2, + 'name' => 'Nexus', 'pivot' => [ - 'title' => 'Queen', + 'title' => 'Queen', 'location_id' => 2, - 'role_id' => 3 + 'role_id' => 3 ] ] ] @@ -449,6 +467,9 @@ public function testBelongsToManyUniqueWithTertiary() $this->assertEquals($expectedJobs, $users->toArray()[0]['jobs']); } + /** + * testBelongsToManyUniqueWithTertiaryEagerLoad + */ public function testBelongsToManyUniqueWithTertiaryEagerLoad() { $user1 = EloquentTestUser::create(['name' => 'David']); @@ -462,49 +483,49 @@ public function testBelongsToManyUniqueWithTertiaryEagerLoad() $this->assertEquals([ [ - 'id' => 1, + 'id' => 1, 'name' => 'David', 'jobs' => [ [ - 'id' => 2, - 'slug' => 'soldier', + 'id' => 2, + 'slug' => 'soldier', 'pivot' => [ 'user_id' => 1, 'role_id' => 2 ], 'locations' => [ [ - 'id' => 1, - 'name' => 'Hatchery', + 'id' => 1, + 'name' => 'Hatchery', 'pivot' => [ 'location_id' => 1, - 'role_id' => 2 + 'role_id' => 2 ] ], [ - 'id' => 2, - 'name' => 'Nexus', + 'id' => 2, + 'name' => 'Nexus', 'pivot' => [ 'location_id' => 2, - 'role_id' => 2 + 'role_id' => 2 ] ] ] ], [ - 'id' => 3, - 'slug' => 'egg-layer', + 'id' => 3, + 'slug' => 'egg-layer', 'pivot' => [ 'user_id' => 1, 'role_id' => 3 ], 'locations' => [ [ - 'id' => 2, - 'name' => 'Nexus', + 'id' => 2, + 'name' => 'Nexus', 'pivot' => [ 'location_id' => 2, - 'role_id' => 3 + 'role_id' => 3 ] ] ] @@ -512,23 +533,23 @@ public function testBelongsToManyUniqueWithTertiaryEagerLoad() ] ], [ - 'id' => 2, + 'id' => 2, 'name' => 'Alex', 'jobs' => [ [ - 'id' => 3, - 'slug' => 'egg-layer', + 'id' => 3, + 'slug' => 'egg-layer', 'pivot' => [ 'user_id' => 2, 'role_id' => 3 ], 'locations' => [ [ - 'id' => 1, - 'name' => 'Hatchery', + 'id' => 1, + 'name' => 'Hatchery', 'pivot' => [ 'location_id' => 1, - 'role_id' => 3 + 'role_id' => 3 ] ], ] @@ -547,31 +568,31 @@ public function testBelongsToManyThrough() $user = EloquentTestUser::create(['name' => 'David']); - $user->roles()->attach([1,2]); + $user->roles()->attach([1, 2]); // Test retrieval of via models as well $this->assertEquals([ [ - 'id' => 1, - 'slug' => 'uri_harvest', + 'id' => 1, + 'slug' => 'uri_harvest', 'pivot' => [ - 'user_id' => 1, + 'user_id' => 1, 'permission_id' => 1 ] ], [ - 'id' => 2, - 'slug' => 'uri_spit_acid', + 'id' => 2, + 'slug' => 'uri_spit_acid', 'pivot' => [ - 'user_id' => 1, + 'user_id' => 1, 'permission_id' => 2 ] ], [ - 'id' => 3, - 'slug' => 'uri_slash', + 'id' => 3, + 'slug' => 'uri_slash', 'pivot' => [ - 'user_id' => 1, + 'user_id' => 1, 'permission_id' => 3 ] ] @@ -581,7 +602,7 @@ public function testBelongsToManyThrough() $this->assertEquals(3, $user->permissions()->count()); $user2 = EloquentTestUser::create(['name' => 'Alex']); - $user2->roles()->attach([2,3]); + $user2->roles()->attach([2, 3]); // Test eager load $users = EloquentTestUser::with('permissions')->get(); @@ -589,30 +610,30 @@ public function testBelongsToManyThrough() $this->assertEquals([ [ - 'id' => 2, - 'slug' => 'uri_spit_acid', + 'id' => 2, + 'slug' => 'uri_spit_acid', 'pivot' => [ - 'user_id' => 2, + 'user_id' => 2, 'permission_id' => 2 ] ], [ - 'id' => 3, - 'slug' => 'uri_slash', + 'id' => 3, + 'slug' => 'uri_slash', 'pivot' => [ - 'user_id' => 2, + 'user_id' => 2, 'permission_id' => 3 ] ], [ - 'id' => 4, - 'slug' => 'uri_royal_jelly', + 'id' => 4, + 'slug' => 'uri_royal_jelly', 'pivot' => [ - 'user_id' => 2, + 'user_id' => 2, 'permission_id' => 4 ] ] - ],$usersWithPermissions[1]['permissions']); + ], $usersWithPermissions[1]['permissions']); // Test counting related models (withCount) $users = EloquentTestUser::withCount('permissions')->get(); @@ -633,24 +654,24 @@ public function testBelongsToManyThroughPaginated() $user = EloquentTestUser::create(['name' => 'David']); - $user->roles()->attach([1,2]); + $user->roles()->attach([1, 2]); $paginatedPermissions = $user->permissions()->take(2)->offset(1); $this->assertEquals([ [ - 'id' => 2, - 'slug' => 'uri_spit_acid', + 'id' => 2, + 'slug' => 'uri_spit_acid', 'pivot' => [ - 'user_id' => 1, + 'user_id' => 1, 'permission_id' => 2 ] ], [ - 'id' => 3, - 'slug' => 'uri_slash', + 'id' => 3, + 'slug' => 'uri_slash', 'pivot' => [ - 'user_id' => 1, + 'user_id' => 1, 'permission_id' => 3 ] ] @@ -668,7 +689,7 @@ public function testBelongsToManyThroughPaginatedWithOrderByAggregateColumn() $this->generateRolesWithPermissions(); $user = EloquentTestUser::create(['name' => 'David']); - $user->roles()->attach([1,2]); + $user->roles()->attach([1, 2]); // If the paginated query is being ordered correctly by including the `roles_count` computed column, // Then `uri_spit_acid` should appear first. If not, then the results will not be ordered and the `uri_harvest` @@ -677,11 +698,11 @@ public function testBelongsToManyThroughPaginatedWithOrderByAggregateColumn() $this->assertEquals([ [ - 'id' => 2, - 'slug' => 'uri_spit_acid', + 'id' => 2, + 'slug' => 'uri_spit_acid', 'roles_count' => 3, - 'pivot' => [ - 'user_id' => 1, + 'pivot' => [ + 'user_id' => 1, 'permission_id' => 2 ] ] @@ -698,13 +719,13 @@ public function testBelongsToManyThroughWithVia() $user = EloquentTestUser::create(['name' => 'David']); - $user->roles()->attach([1,2]); + $user->roles()->attach([1, 2]); // Test retrieval of via models as well $this->assertBelongsToManyThroughForDavid($user->permissions()->withVia('roles_via')->get()->toArray()); $user2 = EloquentTestUser::create(['name' => 'Alex']); - $user2->roles()->attach([2,3]); + $user2->roles()->attach([2, 3]); // Test eager loading $users = EloquentTestUser::with(['permissions' => function ($query) { @@ -720,6 +741,9 @@ public function testBelongsToManyThroughWithVia() $this->assertBelongsToManyThroughForAlex($usersWithPermissions[1]['permissions']); } + /** + * testQueryExclude + */ public function testQueryExclude() { $this->generateRoles(); @@ -732,14 +756,16 @@ public function testQueryExclude() ], $job->toArray()); } - + /** + * testQueryExcludeOnJoinedTable + */ public function testQueryExcludeOnJoinedTable() { $this->generateRolesWithPermissions(); $user = EloquentTestUser::create(['name' => 'David']); - $user->roles()->attach([1,2]); + $user->roles()->attach([1, 2]); $users = EloquentTestUser::with(['permissions' => function ($query) { $query->exclude('slug'); @@ -747,27 +773,27 @@ public function testQueryExcludeOnJoinedTable() $this->assertEquals([ [ - 'id' => 1, - 'name' => 'David', + 'id' => 1, + 'name' => 'David', 'permissions' => [ [ - 'id' => 1, + 'id' => 1, 'pivot' => [ - 'user_id' => 1, + 'user_id' => 1, 'permission_id' => 1 ] ], [ - 'id' => 2, + 'id' => 2, 'pivot' => [ - 'user_id' => 1, + 'user_id' => 1, 'permission_id' => 2 ] ], [ - 'id' => 3, + 'id' => 3, 'pivot' => [ - 'user_id' => 1, + 'user_id' => 1, 'permission_id' => 3 ] ] @@ -776,13 +802,16 @@ public function testQueryExcludeOnJoinedTable() ], $users->toArray()); } + /** + * testQueryExcludeUseQualifiedNamesOnJoinedTable + */ public function testQueryExcludeUseQualifiedNamesOnJoinedTable() { $this->generateRolesWithPermissions(); $user = EloquentTestUser::create(['name' => 'David']); - $user->roles()->attach([1,2]); + $user->roles()->attach([1, 2]); $users = EloquentTestUser::with(['roles' => function ($query) { $query->addSelect('roles.*', 'jobs.*')->leftJoin('jobs', 'jobs.role_id', '=', 'roles.id') @@ -791,11 +820,11 @@ public function testQueryExcludeUseQualifiedNamesOnJoinedTable() $this->assertEquals([ [ - 'id' => 1, - 'name' => 'David', + 'id' => 1, + 'name' => 'David', 'roles' => [ [ - 'id' => 1, + 'id' => 1, 'title' => null, 'pivot' => [ 'user_id' => 1, @@ -803,7 +832,7 @@ public function testQueryExcludeUseQualifiedNamesOnJoinedTable() ] ], [ - 'id' => 2, + 'id' => 2, 'title' => null, 'pivot' => [ 'user_id' => 1, @@ -815,6 +844,9 @@ public function testQueryExcludeUseQualifiedNamesOnJoinedTable() ], $users->toArray()); } + /** + * testQueryExcludeWildcard + */ public function testQueryExcludeWildcard() { $this->generateRoles(); @@ -845,6 +877,7 @@ public function testQueryExcludeWildcard() /** * Get a database connection instance. * + * @param string $connection [description] * @return \Illuminate\Database\Connection */ protected function connection($connection = 'test_integration') @@ -855,6 +888,7 @@ protected function connection($connection = 'test_integration') /** * Get a schema builder instance. * + * @param string $connection * @return \Illuminate\Database\Schema\Builder */ protected function schema($connection = 'test_integration') @@ -862,69 +896,81 @@ protected function schema($connection = 'test_integration') return $this->connection($connection)->getSchemaBuilder(); } + /** + * generateRoles + */ protected function generateRoles() { return [ EloquentTestRole::create([ - 'id' => 1, + 'id' => 1, 'slug' => 'forager' ]), EloquentTestRole::create([ - 'id' => 2, + 'id' => 2, 'slug' => 'soldier' ]), EloquentTestRole::create([ - 'id' => 3, + 'id' => 3, 'slug' => 'egg-layer' ]) ]; } + /** + * generatePermissions + */ protected function generatePermissions() { return [ EloquentTestPermission::create([ - 'id' => 1, + 'id' => 1, 'slug' => 'uri_harvest' ]), EloquentTestPermission::create([ - 'id' => 2, + 'id' => 2, 'slug' => 'uri_spit_acid' ]), EloquentTestPermission::create([ - 'id' => 3, + 'id' => 3, 'slug' => 'uri_slash' ]), EloquentTestPermission::create([ - 'id' => 4, + 'id' => 4, 'slug' => 'uri_royal_jelly' ]) ]; } + /** + * generateRolesWithPermissions + */ protected function generateRolesWithPermissions() { $roles = $this->generateRoles(); $this->generatePermissions(); - $roles[0]->permissions()->attach([1,2]); + $roles[0]->permissions()->attach([1, 2]); // We purposefully want a permission that belongs to more than one role - $roles[1]->permissions()->attach([2,3]); - $roles[2]->permissions()->attach([2,4]); + $roles[1]->permissions()->attach([2, 3]); + $roles[2]->permissions()->attach([2, 4]); return $roles; } + /** + * generateJobs + */ protected function generateJobs() { - /** + /* * Sample data | user_id | role_id | location_id | @@ -937,103 +983,116 @@ protected function generateJobs() return [ EloquentTestJob::create([ - 'role_id' => 2, + 'role_id' => 2, 'location_id' => 1, - 'user_id' => 1, - 'title' => 'Grunt' + 'user_id' => 1, + 'title' => 'Grunt' ]), EloquentTestJob::create([ - 'role_id' => 2, + 'role_id' => 2, 'location_id' => 2, - 'user_id' => 1, - 'title' => 'Sergeant' + 'user_id' => 1, + 'title' => 'Sergeant' ]), EloquentTestJob::create([ - 'role_id' => 3, + 'role_id' => 3, 'location_id' => 2, - 'user_id' => 1, - 'title' => 'Queen' + 'user_id' => 1, + 'title' => 'Queen' ]), EloquentTestJob::create([ - 'role_id' => 3, + 'role_id' => 3, 'location_id' => 1, - 'user_id' => 2, - 'title' => 'Demi-queen' + 'user_id' => 2, + 'title' => 'Demi-queen' ]) ]; } + /** + * generateLocations + */ protected function generateLocations() { return [ EloquentTestLocation::create([ - 'id' => 1, + 'id' => 1, 'name' => 'Hatchery' ]), EloquentTestLocation::create([ - 'id' => 2, + 'id' => 2, 'name' => 'Nexus' ]) ]; } + /** + * generateTasks + */ protected function generateTasks() { return [ EloquentTestTask::create([ - 'id' => 1, + 'id' => 1, 'name' => 'Digging' ]), EloquentTestTask::create([ - 'id' => 2, + 'id' => 2, 'name' => 'Chopping' ]), EloquentTestTask::create([ - 'id' => 3, + 'id' => 3, 'name' => 'Baleing' ]) ]; } + /** + * generateAssignments + */ protected function generateAssignments() { return [ EloquentTestAssignment::create([ - 'task_id' => 2, - 'location_id' => 1, - 'assignable_id' => 1, - 'assignable_type' => 'UserFrosting\Tests\Integration\EloquentTestUser' + 'task_id' => 2, + 'location_id' => 1, + 'assignable_id' => 1, + 'assignable_type' => EloquentTestUser::class ]), EloquentTestAssignment::create([ - 'task_id' => 2, - 'location_id' => 2, - 'assignable_id' => 1, - 'assignable_type' => 'UserFrosting\Tests\Integration\EloquentTestUser' + 'task_id' => 2, + 'location_id' => 2, + 'assignable_id' => 1, + 'assignable_type' => EloquentTestUser::class ]), EloquentTestAssignment::create([ - 'task_id' => 3, - 'location_id' => 2, - 'assignable_id' => 1, - 'assignable_type' => 'UserFrosting\Tests\Integration\EloquentTestUser' + 'task_id' => 3, + 'location_id' => 2, + 'assignable_id' => 1, + 'assignable_type' => EloquentTestUser::class ]), EloquentTestAssignment::create([ - 'task_id' => 3, - 'location_id' => 3, - 'assignable_id' => 1, + 'task_id' => 3, + 'location_id' => 3, + 'assignable_id' => 1, 'assignable_type' => 'UserFrosting\Tests\Integration\EloquentTestNonExistant' ]), EloquentTestAssignment::create([ - 'task_id' => 3, - 'location_id' => 1, - 'assignable_id' => 2, - 'assignable_type' => 'UserFrosting\Tests\Integration\EloquentTestUser' + 'task_id' => 3, + 'location_id' => 1, + 'assignable_id' => 2, + 'assignable_type' => EloquentTestUser::class ]) ]; } + /** + * assertBelongsToManyThroughForDavid + * @param array $permissions + */ protected function assertBelongsToManyThroughForDavid($permissions) { // User should have effective permissions uri_harvest, uri_spit_acid, and uri_slash. @@ -1041,46 +1100,50 @@ protected function assertBelongsToManyThroughForDavid($permissions) $this->assertEquals('uri_harvest', $permissions[0]['slug']); $this->assertEquals([ [ - 'id' => 1, - 'slug' => 'forager', + 'id' => 1, + 'slug' => 'forager', 'pivot' => [ 'permission_id' => 1, - 'role_id' => 1 + 'role_id' => 1 ] ] ], $permissions[0]['roles_via']); $this->assertEquals('uri_spit_acid', $permissions[1]['slug']); $this->assertEquals([ [ - 'id' => 1, - 'slug' => 'forager', + 'id' => 1, + 'slug' => 'forager', 'pivot' => [ 'permission_id' => 2, - 'role_id' => 1 + 'role_id' => 1 ] ], [ - 'id' => 2, - 'slug' => 'soldier', + 'id' => 2, + 'slug' => 'soldier', 'pivot' => [ 'permission_id' => 2, - 'role_id' => 2 + 'role_id' => 2 ] ] ], $permissions[1]['roles_via']); $this->assertEquals('uri_slash', $permissions[2]['slug']); $this->assertEquals([ [ - 'id' => 2, - 'slug' => 'soldier', + 'id' => 2, + 'slug' => 'soldier', 'pivot' => [ 'permission_id' => 3, - 'role_id' => 2 + 'role_id' => 2 ] ] ], $permissions[2]['roles_via']); } + /** + * assertBelongsToManyThroughForAlex + * @param array $permissions + */ protected function assertBelongsToManyThroughForAlex($permissions) { // User should have effective permissions uri_spit_acid, uri_slash, and uri_royal_jelly. @@ -1088,41 +1151,41 @@ protected function assertBelongsToManyThroughForAlex($permissions) $this->assertEquals('uri_spit_acid', $permissions[0]['slug']); $this->assertEquals([ [ - 'id' => 2, - 'slug' => 'soldier', + 'id' => 2, + 'slug' => 'soldier', 'pivot' => [ 'permission_id' => 2, - 'role_id' => 2 + 'role_id' => 2 ] ], [ - 'id' => 3, - 'slug' => 'egg-layer', + 'id' => 3, + 'slug' => 'egg-layer', 'pivot' => [ 'permission_id' => 2, - 'role_id' => 3 + 'role_id' => 3 ] ] ], $permissions[0]['roles_via']); $this->assertEquals('uri_slash', $permissions[1]['slug']); $this->assertEquals([ [ - 'id' => 2, - 'slug' => 'soldier', + 'id' => 2, + 'slug' => 'soldier', 'pivot' => [ 'permission_id' => 3, - 'role_id' => 2 + 'role_id' => 2 ] ] ], $permissions[1]['roles_via']); $this->assertEquals('uri_royal_jelly', $permissions[2]['slug']); $this->assertEquals([ [ - 'id' => 3, - 'slug' => 'egg-layer', + 'id' => 3, + 'slug' => 'egg-layer', 'pivot' => [ 'permission_id' => 4, - 'role_id' => 3 + 'role_id' => 3 ] ] ], $permissions[2]['roles_via']); @@ -1144,12 +1207,12 @@ class EloquentTestUser extends EloquentTestModel public function emails() { - return $this->hasMany('UserFrosting\Tests\Integration\EloquentTestEmail', 'user_id'); + return $this->hasMany(EloquentTestEmail::class, 'user_id'); } public function phones() { - return $this->morphMany('UserFrosting\Tests\Integration\EloquentTestPhone', 'phoneable'); + return $this->morphMany(EloquentTestPhone::class, 'phoneable'); } /** @@ -1159,7 +1222,7 @@ public function phones() */ public function roles() { - return $this->belongsToMany('UserFrosting\Tests\Integration\EloquentTestRole', 'role_users', 'user_id', 'role_id'); + return $this->belongsToMany(EloquentTestRole::class, 'role_users', 'user_id', 'role_id'); } /** @@ -1170,8 +1233,8 @@ public function roles() public function permissions() { return $this->belongsToManyThrough( - 'UserFrosting\Tests\Integration\EloquentTestPermission', - 'UserFrosting\Tests\Integration\EloquentTestRole', + EloquentTestPermission::class, + EloquentTestRole::class, 'role_users', 'user_id', 'role_id', @@ -1187,7 +1250,7 @@ public function permissions() public function assignmentTasks() { $relation = $this->morphToManyUnique( - 'UserFrosting\Tests\Integration\EloquentTestTask', + EloquentTestTask::class, 'assignable', 'assignments', null, @@ -1203,7 +1266,7 @@ public function assignmentTasks() public function tasks() { $relation = $this->morphToManyUnique( - 'UserFrosting\Tests\Integration\EloquentTestTask', + EloquentTestTask::class, 'assignable', 'assignments', null, @@ -1219,7 +1282,7 @@ public function tasks() public function jobRoles() { $relation = $this->belongsToManyUnique( - 'UserFrosting\Tests\Integration\EloquentTestRole', + EloquentTestRole::class, 'jobs', 'user_id', 'role_id' @@ -1251,7 +1314,7 @@ class EloquentTestEmail extends EloquentTestModel public function user() { - return $this->belongsTo('UserFrosting\Tests\Integration\EloquentTestUser', 'user_id'); + return $this->belongsTo(EloquentTestUser::class, 'user_id'); } } @@ -1276,7 +1339,7 @@ class EloquentTestRole extends EloquentTestModel */ public function permissions() { - return $this->belongsToMany('UserFrosting\Tests\Integration\EloquentTestPermission', 'permission_roles', 'role_id', 'permission_id'); + return $this->belongsToMany(EloquentTestPermission::class, 'permission_roles', 'role_id', 'permission_id'); } } @@ -1290,7 +1353,7 @@ class EloquentTestPermission extends EloquentTestModel */ public function roles() { - return $this->belongsToMany('UserFrosting\Tests\Integration\EloquentTestRole', 'permission_roles', 'permission_id', 'role_id'); + return $this->belongsToMany(EloquentTestRole::class, 'permission_roles', 'permission_id', 'role_id'); } } @@ -1326,7 +1389,7 @@ class EloquentTestAssignment extends EloquentTestModel */ public function users() { - return $this->morphedByMany('UserFrosting\Tests\Integration\EloquentTestUser', 'assignable'); + return $this->morphedByMany(EloquentTestUser::class, 'assignable'); } } @@ -1340,6 +1403,6 @@ class EloquentTestJob extends EloquentTestModel */ public function role() { - return $this->belongsTo('UserFrosting\Tests\Integration\EloquentTestRole', 'role_id'); + return $this->belongsTo(EloquentTestRole::class, 'role_id'); } } diff --git a/app/sprinkles/core/tests/Integration/Error/Handler/ExceptionHandlerTest.php b/app/sprinkles/core/tests/Integration/Error/Handler/ExceptionHandlerTest.php new file mode 100644 index 000000000..4d9ca9df5 --- /dev/null +++ b/app/sprinkles/core/tests/Integration/Error/Handler/ExceptionHandlerTest.php @@ -0,0 +1,58 @@ +ci, $this->getRequest(), $this->getResponse(), $this->getException(), false); + $this->assertInstanceOf(ExceptionHandler::class, $handler); + + return $handler; + } + + /** + * @return ServerRequestInterface + */ + private function getRequest() + { + return $this->getMockBuilder(ServerRequestInterface::class) + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * @return ResponseInterface + */ + private function getResponse() + { + return $this->getMockBuilder(ResponseInterface::class) + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * @return RuntimeException + */ + private function getException() + { + return new RuntimeException('This is my exception'); + } +} diff --git a/app/sprinkles/core/tests/Integration/Error/Renderer/WhoopsRendererTest.php b/app/sprinkles/core/tests/Integration/Error/Renderer/WhoopsRendererTest.php index 89533c887..4b819f345 100644 --- a/app/sprinkles/core/tests/Integration/Error/Renderer/WhoopsRendererTest.php +++ b/app/sprinkles/core/tests/Integration/Error/Renderer/WhoopsRendererTest.php @@ -1,4 +1,11 @@ getException('My first exception'); + $exception2 = $this->getException('My second exception', 0, $exception1); + $exception3 = $this->getException('And the third one', 0, $exception2); + + $inspector = new Inspector($exception3); + + $previousExceptions = $inspector->getPreviousExceptionMessages(); + + $this->assertEquals($exception2->getMessage(), $previousExceptions[0]); + $this->assertEquals($exception1->getMessage(), $previousExceptions[1]); + } + + /** + * @depends testGetPreviousExceptionMessages + */ public function testRenderWhoopsPage() { $request = $this->getMockBuilder(ServerRequestInterface::class) @@ -27,7 +55,18 @@ public function testRenderWhoopsPage() $whoopsRenderer->handleUnconditionally(true); $renderBody = $whoopsRenderer->render(); - $this->assertTrue(!!preg_match('/RuntimeException: This is my exception in file /', $renderBody)); - $this->assertTrue(!!preg_match('/This is my exception<\/span>/', $renderBody)); + $this->assertTrue((bool) preg_match('/RuntimeException: This is my exception in file /', $renderBody)); + $this->assertTrue((bool) preg_match('/This is my exception<\/span>/', $renderBody)); + } + + /** + * @param string $message + * @param int $code + * @param \Exception $previous + * @return \Exception + */ + protected function getException($message = null, $code = 0, $previous = null) + { + return new \Exception($message, $code, $previous); } -} \ No newline at end of file +} diff --git a/app/sprinkles/core/tests/Integration/MigrationDependencyAnalyserTest.php b/app/sprinkles/core/tests/Integration/MigrationDependencyAnalyserTest.php new file mode 100644 index 000000000..5a72ab04d --- /dev/null +++ b/app/sprinkles/core/tests/Integration/MigrationDependencyAnalyserTest.php @@ -0,0 +1,83 @@ +assertEquals($migrations, $analyser->getFulfillable()); + $this->assertEquals([], $analyser->getUnfulfillable()); + } + + public function testAnalyserWithInvalidClass() + { + $migrations = [ + '\\UserFrosting\\Tests\\Integration\\Migrations\\Foo' + ]; + + $analyser = new MigrationDependencyAnalyser($migrations, []); + + $this->expectException(BadClassNameException::class); + $analyser->analyse(); + } + + public function testAnalyserWithReordered() + { + $analyser = new MigrationDependencyAnalyser([ + '\\UserFrosting\\Tests\\Integration\\Migrations\\two\\CreateFlightsTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreatePasswordResetsTable' + ], []); + + $this->assertEquals([], $analyser->getUnfulfillable()); + $this->assertEquals([ + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreatePasswordResetsTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\two\\CreateFlightsTable' + ], $analyser->getFulfillable()); + } + + public function testAnalyserWithUnfulfillable() + { + $migrations = [ + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreatePasswordResetsTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\UnfulfillableTable' + ]; + + $analyser = new MigrationDependencyAnalyser($migrations, []); + + $this->assertEquals([ + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreatePasswordResetsTable' + ], $analyser->getFulfillable()); + + $this->assertEquals([ + '\\UserFrosting\\Tests\\Integration\\Migrations\\UnfulfillableTable' => '\UserFrosting\Tests\Integration\Migrations\NonExistingMigration' + ], $analyser->getUnfulfillable()); + } +} diff --git a/app/sprinkles/core/tests/Integration/MigrationLocatorTest.php b/app/sprinkles/core/tests/Integration/MigrationLocatorTest.php new file mode 100644 index 000000000..1ccab2b06 --- /dev/null +++ b/app/sprinkles/core/tests/Integration/MigrationLocatorTest.php @@ -0,0 +1,132 @@ +shouldReceive('getPath')->andReturn('src/Database/Migrations'); + + // Setup mock locations + $resourceCoreLocation = m::mock(ResourceLocation::class); + $resourceCoreLocation->shouldReceive('getName')->andReturn('Core'); + $resourceCoreLocation->shouldReceive('getPath')->andReturn('app/sprinkles/Core'); + $resourceAccountLocation = m::mock(ResourceLocation::class); + $resourceAccountLocation->shouldReceive('getName')->andReturn('account'); + $resourceAccountLocation->shouldReceive('getPath')->andReturn('app/sprinkles/Account'); + + // When `MigrationLocator` will ask the resource locator to `listResources`, we simulate returning no Resources + $resourceLocator->shouldReceive('listResources')->once()->andReturn([]); + $locator = new MigrationLocator($resourceLocator); + $results = $locator->getMigrations(); + + // Test results match expectations + $this->assertCount(0, $results); + $this->assertEquals([], $results); + } + + /** + * Make sure migrations can be returned for all sprinkles + */ + public function testGetMigrations() + { + // Setup mock locator + $resourceLocator = m::mock(ResourceLocator::class); + + // Setup mock stream + $resourceStream = m::mock(ResourceStream::class); + $resourceStream->shouldReceive('getPath')->andReturn('src/Database/Migrations'); + + // Setup mock locations + $resourceCoreLocation = m::mock(ResourceLocation::class); + $resourceCoreLocation->shouldReceive('getName')->andReturn('Core'); + $resourceCoreLocation->shouldReceive('getPath')->andReturn('app/sprinkles/Core'); + + $resourceAccountLocation = m::mock(ResourceLocation::class); + $resourceAccountLocation->shouldReceive('getName')->andReturn('account'); + $resourceAccountLocation->shouldReceive('getPath')->andReturn('app/sprinkles/Account'); + + // When `MigrationLocator` will ask the resource locator to `listResources`, we simulate returning Resources + $resourceLocator->shouldReceive('listResources')->once()->andReturn([ + new Resource($resourceStream, $resourceCoreLocation, 'one/CreateUsersTable.php'), + new Resource($resourceStream, $resourceCoreLocation, 'one/CreatePasswordResetsTable.php'), + new Resource($resourceStream, $resourceCoreLocation, 'two/CreateFlightsTable.php'), + new Resource($resourceStream, $resourceCoreLocation, 'CreateMainTable.php'), + new Resource($resourceStream, $resourceAccountLocation, 'one/CreateUsersTable.php'), + new Resource($resourceStream, $resourceAccountLocation, 'one/CreatePasswordResetsTable.php'), + new Resource($resourceStream, $resourceAccountLocation, 'two/CreateFlightsTable.php'), + new Resource($resourceStream, $resourceAccountLocation, 'CreateMainTable.php') + ]); + + // Create a new MigrationLocator instance with our simulated ResourceLocation + $locator = new MigrationLocator($resourceLocator); + $results = $locator->getMigrations(); + + // The `getMigration` method should return this + $expected = [ + '\\UserFrosting\\Sprinkle\\Core\\Database\\Migrations\\one\\CreateUsersTable', + '\\UserFrosting\\Sprinkle\\Core\\Database\\Migrations\\one\\CreatePasswordResetsTable', + '\\UserFrosting\\Sprinkle\\Core\\Database\\Migrations\\two\\CreateFlightsTable', + '\\UserFrosting\\Sprinkle\\Core\\Database\\Migrations\\CreateMainTable', + '\\UserFrosting\\Sprinkle\\Account\\Database\\Migrations\\one\\CreateUsersTable', + '\\UserFrosting\\Sprinkle\\Account\\Database\\Migrations\\one\\CreatePasswordResetsTable', + '\\UserFrosting\\Sprinkle\\Account\\Database\\Migrations\\two\\CreateFlightsTable', + '\\UserFrosting\\Sprinkle\\Account\\Database\\Migrations\\CreateMainTable' + ]; + + // Test results match expectations + $this->assertCount(8, $results); + $this->assertEquals($expected, $results); + + return $locator; + } + + /** + * Test MigratonLocator against the real thing, no Mockery + */ + public function testActualInstance() + { + // Get sprinkle manager and make sure `core` is returned + $this->assertContains('core', $this->ci->sprinkleManager->getSprinkleNames()); + + // Create a new MigrationLocator instance with our real SprinkleManager and filesystem + // and ask to find core sprinkle migration files + $locator = new MigrationLocator($this->ci->locator); + $results = $locator->getMigrations(); + + // We'll need to convert the array returned by `getMigrations` to a + // collection to make it easier to search + $this->assertInternalType('array', $results); + $this->assertContains('\UserFrosting\Sprinkle\Core\Database\Migrations\v400\SessionsTable', $results); + } +} diff --git a/app/sprinkles/core/tests/Integration/MigrationRepositoryTest.php b/app/sprinkles/core/tests/Integration/MigrationRepositoryTest.php new file mode 100644 index 000000000..a16a95cbc --- /dev/null +++ b/app/sprinkles/core/tests/Integration/MigrationRepositoryTest.php @@ -0,0 +1,141 @@ +repository = new DatabaseMigrationRepository($capsule, 'migrations'); + + // Set global expections for $capule and $connection + // Repository -> capsule -> connection -> Schema + // When repository call `getConnection`, it will receive the connection mock + // When repository call `getSchemaBuilder`, it will receive the schema builder mock + $capsule->shouldReceive('getConnection')->andReturn($connection); + $connection->shouldReceive('getSchemaBuilder')->andReturn($schemaBuilder); + } + + public function testGetRanMigrationsListMigrationsByPackage() + { + $query = m::mock('stdClass'); + $sortedQuery = m::mock('stdClass'); + + // Set expectations for the Connection mock + $this->repository->getConnection()->shouldReceive('table')->once()->with('migrations')->andReturn($query); + + // When getRan is called, the $query should be orderedBy batch, then migration and pluck. + $query->shouldReceive('orderBy')->once()->with('id', 'asc')->andReturn($sortedQuery); + $sortedQuery->shouldReceive('get')->once()->andReturn(new Collection([['migration' => 'bar']])); + + $this->assertEquals(['bar'], $this->repository->getMigrationsList()); + } + + public function testGetLastBatchNumberReturnsMaxBatch() + { + $query = m::mock('stdClass'); + + // Set expectations for the Connection mock + $this->repository->getConnection()->shouldReceive('table')->once()->with('migrations')->andReturn($query); + + // Table query will need to tell what is the max batch number + $query->shouldReceive('max')->once()->andReturn(1); + + $this->assertEquals(1, $this->repository->getLastBatchNumber()); + } + + public function testGetLastMigrationsGetsAllMigrationsWithTheLatestBatchNumber() + { + $query = m::mock('stdClass'); + + // Set expectations for the Connection mock. Table will be get twice + // (once for max batch, once to get the migration list) + $this->repository->getConnection()->shouldReceive('table')->twice()->with('migrations')->andReturn($query); + + // Table query will need to tell what is the max batch number + $query->shouldReceive('max')->once()->with('batch')->andReturn(1); + + // Table should be asked to search for batch 1, orderBy migration and return the foo migration + $query->shouldReceive('where')->once()->with('batch', 1)->andReturn($query); + $query->shouldReceive('orderBy')->once()->with('id', 'desc')->andReturn($query); + $query->shouldReceive('get')->once()->andReturn(new Collection([['migration' => 'foo']])); + + $this->assertEquals(['foo'], $this->repository->getLast()); + } + + public function testLogMethodInsertsRecordIntoMigrationTable() + { + $query = m::mock('stdClass'); + + // Set expectations for the Connection mock + $this->repository->getConnection()->shouldReceive('table')->once()->with('migrations')->andReturn($query); + + // When log is called, the table query should receive the insert statement + $query->shouldReceive('insert')->once()->with(['migration' => 'bar', 'batch' => 1, 'sprinkle' => '']); + + $this->repository->log('bar', 1); + } + + public function testDeleteMethodRemovesAMigrationFromTheTable() + { + $query = m::mock('stdClass'); + + // Set expectations for the Connection mock + $this->repository->getConnection()->shouldReceive('table')->once()->with('migrations')->andReturn($query); + + // When delete is called, a where and delete operation should be performed on the table + $query->shouldReceive('where')->once()->with('migration', 'foo')->andReturn($query); + $query->shouldReceive('delete')->once(); + + $this->repository->delete('foo'); + } + + public function testGetNextBatchNumberReturnsLastBatchNumberPlusOne() + { + $query = m::mock('stdClass'); + + // Set expectations for the Connection mock + $this->repository->getConnection()->shouldReceive('table')->once()->with('migrations')->andReturn($query); + + // Table query will need to tell what is the max batch number + $query->shouldReceive('max')->once()->andReturn(2); + + $this->assertEquals(3, $this->repository->getNextBatchNumber()); + } + + public function testCreateRepositoryCreatesProperDatabaseTable() + { + // Setup expectations for SchemaBuilder. When asked to create the repository, the schema should reeceive the create command + $this->repository->getSchemaBuilder()->shouldReceive('create')->once()->with('migrations', m::type('Closure')); + $this->repository->createRepository(); + } +} diff --git a/app/sprinkles/core/tests/Integration/Migrations/DeprecatedClassTable.php b/app/sprinkles/core/tests/Integration/Migrations/DeprecatedClassTable.php new file mode 100644 index 000000000..4aee13c71 --- /dev/null +++ b/app/sprinkles/core/tests/Integration/Migrations/DeprecatedClassTable.php @@ -0,0 +1,46 @@ +schema->create('deprecated_table', function (Blueprint $table) { + $table->string('email')->index(); + $table->string('token')->index(); + $table->timestamp('created_at'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + $this->schema->dropIfExists('deprecated_table'); + } + + /** + * Seed the database. + */ + public function seed() + { + $this->schema->table('deprecated_table', function (Blueprint $table) { + $table->string('foo')->nullable(); + }); + } +} diff --git a/app/sprinkles/core/tests/Integration/Migrations/UnfulfillableTable.php b/app/sprinkles/core/tests/Integration/Migrations/UnfulfillableTable.php new file mode 100644 index 000000000..a389f5187 --- /dev/null +++ b/app/sprinkles/core/tests/Integration/Migrations/UnfulfillableTable.php @@ -0,0 +1,45 @@ +schema->create('unfulfillable', function (Blueprint $table) { + $table->increments('id'); + $table->string('name'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + $this->schema->dropIfExists('unfulfillable'); + } +} diff --git a/app/sprinkles/core/tests/Integration/Migrations/one/CreatePasswordResetsTable.php b/app/sprinkles/core/tests/Integration/Migrations/one/CreatePasswordResetsTable.php new file mode 100644 index 000000000..0e43db518 --- /dev/null +++ b/app/sprinkles/core/tests/Integration/Migrations/one/CreatePasswordResetsTable.php @@ -0,0 +1,41 @@ +schema->create('password_resets', function (Blueprint $table) { + $table->string('email')->index(); + $table->string('token')->index(); + $table->timestamp('created_at'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + $this->schema->dropIfExists('password_resets'); + } +} diff --git a/app/sprinkles/core/tests/Integration/Migrations/one/CreateUsersTable.php b/app/sprinkles/core/tests/Integration/Migrations/one/CreateUsersTable.php new file mode 100644 index 000000000..688b5c8a1 --- /dev/null +++ b/app/sprinkles/core/tests/Integration/Migrations/one/CreateUsersTable.php @@ -0,0 +1,46 @@ +schema->create('users', function (Blueprint $table) { + $table->increments('id'); + $table->string('name'); + $table->string('email')->unique(); + $table->string('password'); + $table->rememberToken(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + $this->schema->dropIfExists('users'); + } +} diff --git a/app/sprinkles/core/tests/Integration/Migrations/two/CreateFlightsTable.php b/app/sprinkles/core/tests/Integration/Migrations/two/CreateFlightsTable.php new file mode 100644 index 000000000..2c8fe5f31 --- /dev/null +++ b/app/sprinkles/core/tests/Integration/Migrations/two/CreateFlightsTable.php @@ -0,0 +1,43 @@ +schema->create('flights', function (Blueprint $table) { + $table->increments('id'); + $table->string('name'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + $this->schema->dropIfExists('flights'); + } +} diff --git a/app/sprinkles/core/tests/Integration/Seeder/SeederTests.php b/app/sprinkles/core/tests/Integration/Seeder/SeederTests.php new file mode 100644 index 000000000..f565b8612 --- /dev/null +++ b/app/sprinkles/core/tests/Integration/Seeder/SeederTests.php @@ -0,0 +1,180 @@ +fakeCi = new Container(); + + // Register services stub + $serviceProvider = new ServicesProviderStub(); + $serviceProvider->register($this->fakeCi); + } + + public function tearDown() + { + parent::tearDown(); + m::close(); + } + + /** + * @return Seeder + */ + public function testSeeder() + { + $seeder = new Seeder($this->fakeCi); + $this->assertInstanceOf(Seeder::class, $seeder); + + return $seeder; + } + + /** + * @param Seeder $seeder + * @depends testSeeder + */ + public function testgetSeeds(Seeder $seeder) + { + $seeds = $seeder->getSeeds(); + $this->assertInternalType('array', $seeds); + $this->assertCount(3, $seeds); + $this->assertEquals([ + [ + 'name' => 'Seed1', + 'class' => '\\UserFrosting\\Sprinkle\\Core\\Database\\Seeds\\Seed1', + 'sprinkle' => 'Core' + ], + [ + 'name' => 'Seed2', + 'class' => '\\UserFrosting\\Sprinkle\\Core\\Database\\Seeds\\Seed2', + 'sprinkle' => 'Core' + ], + [ + 'name' => 'Test/Seed', + 'class' => '\\UserFrosting\\Sprinkle\\Core\\Database\\Seeds\\Test\\Seed', + 'sprinkle' => 'Core' + ] + ], $seeds); + } + + /** + * @param Seeder $seeder + * @depends testSeeder + */ + public function testGetSeed(Seeder $seeder) + { + $seed = $seeder->getSeed('Seed1'); + $this->assertInternalType('array', $seed); + $this->assertEquals([ + 'name' => 'Seed1', + 'class' => '\\UserFrosting\\Sprinkle\\Core\\Database\\Seeds\\Seed1', + 'sprinkle' => 'Core' + ], $seed); + } + + /** + * @param Seeder $seeder + * @depends testSeeder + * @expectedException \Exception + */ + public function testUnfoundGetSeed(Seeder $seeder) + { + $seed = $seeder->getSeed('FakeSeed'); + } + + /** + * @param Seeder $seeder + * @depends testSeeder + */ + public function testGetSeedClass(Seeder $seeder) + { + $seed = $seeder->getSeedClass('Seed1'); + $this->assertInstanceOf(SeedInterface::class, $seed); + } + + /** + * @param Seeder $seeder + * @depends testSeeder + * @expectedException \Exception + */ + public function testGetSeedClassNotSeedInterface(Seeder $seeder) + { + // This class is not an instance of SeedInterface + $seeder->getSeedClass('Seed2'); + } + + /** + * @param Seeder $seeder + * @depends testSeeder + * @expectedException \Exception + */ + public function testGetSeedClassException(Seeder $seeder) + { + // The namespace in this class is wrong + $seeder->getSeedClass('Test/Seed'); + } + + /** + * @param Seeder $seeder + * @depends testSeeder + */ + public function testExecuteSeed(Seeder $seeder) + { + // Get a fake seed + $seed = m::mock('\UserFrosting\Sprinkle\Core\Database\Seeder\BaseSeed'); + $seed->shouldReceive('run'); + + $seeder->executeSeed($seed); + } +} + +/** + * ServicesProviderStub + */ +class ServicesProviderStub +{ + /** + * @param ContainerInterface $container A DI container implementing ArrayAccess and container-interop. + */ + public function register(ContainerInterface $container) + { + /** + * @return \UserFrosting\UniformResourceLocator\ResourceLocator + */ + $container['locator'] = function ($c) { + $locator = new ResourceLocator(\UserFrosting\SPRINKLES_DIR); + $locator->registerStream('seeds', '', 'Seeder/Seeds/'); + $locator->registerLocation('Core', 'core/tests/Integration/'); + + return $locator; + }; + } +} diff --git a/app/sprinkles/core/tests/Integration/Seeder/Seeds/Seed1.php b/app/sprinkles/core/tests/Integration/Seeder/Seeds/Seed1.php new file mode 100644 index 000000000..d6923599e --- /dev/null +++ b/app/sprinkles/core/tests/Integration/Seeder/Seeds/Seed1.php @@ -0,0 +1,25 @@ +usingInMemoryDatabase() + ? $this->refreshInMemoryDatabase() + : $this->refreshTestDatabase(); + } + + /** + * Determine if an in-memory database is being used. + * + * @return bool + */ + protected function usingInMemoryDatabase() + { + $connection = $this->ci->db->getConnection(); + + return $connection->getDatabaseName() == ':memory:'; + } + + /** + * Refresh the in-memory database. + */ + protected function refreshInMemoryDatabase() + { + $this->ci->migrator->run(); + } + + /** + * Refresh a conventional test database. + */ + protected function refreshTestDatabase() + { + if (!self::$migrated) { + + // Refresh the Database. Rollback all migrations and start over + $this->ci->migrator->reset(); + $this->ci->migrator->run(); + + self::$migrated = true; + } + + $this->beginDatabaseTransaction(); + } + + /** + * Handle database transactions on the specified connections. + */ + protected function beginDatabaseTransaction() + { + $database = $this->ci->db; + + foreach ($this->connectionsToTransact() as $name) { + $database->connection($name)->beginTransaction(); + } + + $this->beforeApplicationDestroyed(function () use ($database) { + foreach ($this->connectionsToTransact() as $name) { + $database->connection($name)->rollBack(); + } + }); + } + + /** + * The database connections that should have transactions. + * + * @return array + */ + protected function connectionsToTransact() + { + return property_exists($this, 'connectionsToTransact') + ? $this->connectionsToTransact : [null]; + } +} diff --git a/app/sprinkles/core/tests/TestDatabase.php b/app/sprinkles/core/tests/TestDatabase.php new file mode 100644 index 000000000..0029f519a --- /dev/null +++ b/app/sprinkles/core/tests/TestDatabase.php @@ -0,0 +1,27 @@ +ci->config['testing.dbConnection']; + $this->ci->db->getDatabaseManager()->setDefaultConnection($connection); + } +} diff --git a/app/sprinkles/core/tests/Unit/BelongsToManyThroughTest.php b/app/sprinkles/core/tests/Unit/BelongsToManyThroughTest.php index d4fd4f831..22e2ad203 100644 --- a/app/sprinkles/core/tests/Unit/BelongsToManyThroughTest.php +++ b/app/sprinkles/core/tests/Unit/BelongsToManyThroughTest.php @@ -3,34 +3,32 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ -namespace UserFrosting\Tests\Unit; -use Illuminate\Database\Capsule\Manager as DB; +namespace UserFrosting\Sprinkle\Core\Tests\Unit; + use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Mockery as m; -use ReflectionClass; use UserFrosting\Tests\TestCase; -use UserFrosting\Tests\DatabaseTransactions; use UserFrosting\Sprinkle\Core\Database\Builder as QueryBuilder; use UserFrosting\Sprinkle\Core\Database\Models\Model; use UserFrosting\Sprinkle\Core\Database\Relations\BelongsToManyThrough; /** * Tests the BelongsToManyThrough relation. - * - * @extends TestCase */ class BelongsToManyThroughTest extends TestCase { public function tearDown() { + parent::tearDown(); m::close(); } - function testPaginatedQuery() + public function testPaginatedQuery() { // Creates a real BelongsToManyThrough object $relation = $this->getRelation(); @@ -51,11 +49,11 @@ function testPaginatedQuery() // Mock the collection generated by the constrained query $collection = m::mock('Illuminate\Database\Eloquent\Collection'); $collection->shouldReceive('pluck')->once()->with('id')->andReturn($collection); - $collection->shouldReceive('toArray')->once()->andReturn([1,2]); + $collection->shouldReceive('toArray')->once()->andReturn([1, 2]); $builder->shouldReceive('get')->once()->andReturn($collection); // Test the final modification to the original unpaginated query - $builder->shouldReceive('whereIn')->once()->with('users.id', [1,2])->andReturnSelf(); + $builder->shouldReceive('whereIn')->once()->with('users.id', [1, 2])->andReturnSelf(); $paginatedQuery = $relation->getPaginatedQuery($builder, 2, 1); } @@ -92,7 +90,13 @@ protected function getRelation() // Now we set up the relationship with the related model. return new BelongsToManyThrough( - $builder, $related, $intermediateRelationship, 'role_users', 'role_id', 'user_id', 'relation_name' + $builder, + $related, + $intermediateRelationship, + 'role_users', + 'role_id', + 'user_id', + 'relation_name' ); } } diff --git a/app/sprinkles/core/tests/Unit/DatabaseSyncableTest.php b/app/sprinkles/core/tests/Unit/DatabaseSyncableTest.php index 188b01246..cf363f876 100644 --- a/app/sprinkles/core/tests/Unit/DatabaseSyncableTest.php +++ b/app/sprinkles/core/tests/Unit/DatabaseSyncableTest.php @@ -3,18 +3,17 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ -namespace UserFrosting\Tests\Unit; + +namespace UserFrosting\Sprinkle\Core\Tests\Unit; use stdClass; use Mockery as m; -use ReflectionClass; use PHPUnit\Framework\TestCase; use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\Collection; -use Illuminate\Support\Collection as BaseCollection; -use Illuminate\Database\Eloquent\ModelNotFoundException; +use Illuminate\Support\Collection; use UserFrosting\Sprinkle\Core\Database\Relations\HasManySyncable; @@ -22,6 +21,7 @@ class DatabaseSyncableTest extends TestCase { public function tearDown() { + parent::tearDown(); m::close(); } @@ -36,9 +36,9 @@ public function testSyncMethod($list) $relation->getRelated()->shouldReceive('getKeyName')->once()->andReturn('id'); // Simulate fetching of current relationships (1,2,3) - $query = m::mock('stdClass'); + $query = m::mock(stdClass::class); $relation->shouldReceive('newQuery')->once()->andReturn($query); - $query->shouldReceive('pluck')->once()->with('id')->andReturn(new BaseCollection([1, 2, 3])); + $query->shouldReceive('pluck')->once()->with('id')->andReturn(new Collection([1, 2, 3])); // withoutGlobalScopes will get called exactly 3 times $relation->getRelated()->shouldReceive('withoutGlobalScopes')->times(3)->andReturn($query); @@ -47,7 +47,7 @@ public function testSyncMethod($list) $query->shouldReceive('whereIn')->once()->with('id', [1])->andReturn($query); $query->shouldReceive('delete')->once()->andReturn($query); - // Test updates to existing items in relationship (2,3) + // Test updates to existing items in relationship (2,3) $query->shouldReceive('where')->once()->with('id', 2)->andReturn($query); $query->shouldReceive('update')->once()->with(['id' => 2, 'species' => 'Tyto'])->andReturn($query); $query->shouldReceive('where')->once()->with('id', 3)->andReturn($query); @@ -58,8 +58,8 @@ public function testSyncMethod($list) 'id' => 'x' ]); $model->shouldReceive('getAttribute')->with('id')->andReturn('x'); - - $this->assertEquals(['created' => ['x'], 'deleted' => [1], 'updated' => [2,3]], $relation->sync($list)); + + $this->assertEquals(['created' => ['x'], 'deleted' => [1], 'updated' => [2, 3]], $relation->sync($list)); } /** @@ -76,6 +76,7 @@ protected function getRelation() $parent->shouldReceive('getAttribute')->with('id')->andReturn(1); $parent->shouldReceive('getCreatedAtColumn')->andReturn('created_at'); $parent->shouldReceive('getUpdatedAtColumn')->andReturn('updated_at'); + return new HasManySyncable($builder, $parent, 'table.foreign_key', 'id'); } @@ -87,11 +88,11 @@ public function syncMethodHasManyListProvider() // First argument [ [ - 'id' => 2, + 'id' => 2, 'species' => 'Tyto' ], [ - 'id' => 3, + 'id' => 3, 'species' => 'Megascops' ], [ @@ -107,6 +108,7 @@ protected function expectNewModel($relation, $attributes = null) { $relation->getRelated()->shouldReceive('newInstance')->once()->with($attributes)->andReturn($model = m::mock(Model::class)); $model->shouldReceive('setAttribute')->with('foreign_key', 1)->andReturn($model); + return $model; } @@ -114,6 +116,7 @@ protected function expectCreatedModel($relation, $attributes) { $model = $this->expectNewModel($relation, $attributes); $model->shouldReceive('save')->andReturn($model); + return $model; } } diff --git a/app/sprinkles/core/tests/Unit/FilesystemTest.php b/app/sprinkles/core/tests/Unit/FilesystemTest.php new file mode 100644 index 000000000..8e6212466 --- /dev/null +++ b/app/sprinkles/core/tests/Unit/FilesystemTest.php @@ -0,0 +1,151 @@ +testDir = $this->ci->config["filesystems.disks.{$this->testDisk}.root"]; + } + + /** + * Test the service and FilesystemManager + */ + public function testService() + { + // Force this test to use the testing disk + $this->ci->config['filesystems.default'] = $this->testDisk; + + // Filesystem service will return an instance of FilesystemManger + $filesystem = $this->ci->filesystem; + $this->assertInstanceOf(FilesystemManager::class, $filesystem); + + // Main aspect of our FilesystemManager is to adapt our config structure + // to Laravel class we'll make sure here the forced config actually works + $this->assertEquals($this->testDisk, $filesystem->getDefaultDriver()); + + // The disk won't return a Manager, but an Adapter. + $disk = $filesystem->disk($this->testDisk); + $this->assertInstanceOf(FilesystemAdapter::class, $disk); + + return $disk; + } + + /** + * @depends testService + */ + public function testFacade() + { + $this->assertInstanceOf(FilesystemAdapter::class, Storage::disk($this->testDisk)); + } + + /** + * @param FilesystemAdapter $files + * @depends testService + */ + public function testAdapter(FilesystemAdapter $files) + { + // Test basic "put" + $this->assertTrue($files->put('file.txt', 'Something inside')); + $this->assertStringEqualsFile($this->testDir . '/file.txt', 'Something inside'); + + // Test "exist" & "get" + // We'll assume Laravel test covers the rest ;) + $this->assertTrue($files->exists('file.txt')); + $this->assertEquals('Something inside', $files->get('file.txt')); + + // We'll delete the test file now + $this->assertTrue($files->delete('file.txt')); + $this->assertFileNotExists($this->testDir.'/file.txt'); + } + + /** + * @param FilesystemAdapter $files + * @depends testService + * NOTE : The `download` method was introduced in Laravel 5.5. + * We'll need to enable this once we can upgrade to newer version of Laravel + */ + /*public function testDownload(FilesystemAdapter $files) + { + // We'll test the file download feature + $response = $files->download('file.txt', 'hello.txt'); + $this->assertInstanceOf(StreamedResponse::class, $response); + // $this->assertEquals('attachment; filename="hello.txt"', $response->headers->get('content-disposition')); + }*/ + + /** + * @param FilesystemAdapter $files + * @depends testService + */ + public function testUrl(FilesystemAdapter $files) + { + // Test the URL + $this->assertTrue($files->put('file.txt', 'Blah!')); + $url = $files->url('file.txt'); + $this->assertEquals('files/testing/file.txt', $url); + $this->assertTrue($files->delete('file.txt')); + $this->assertFileNotExists($this->testDir.'/file.txt'); + } + + /** + * Test to make sure we can still add custom adapter + */ + public function testNonExistingAdapter() + { + $filesystemManager = $this->ci->filesystem; + + // InvalidArgumentException + $this->expectException('InvalidArgumentException'); + $filesystemManager->disk('testingDriver'); + } + + /** + * @depends testNonExistingAdapter + */ + public function testAddingAdapter() + { + $filesystemManager = $this->ci->filesystem; + + $filesystemManager->extend('localTest', function ($configService, $config) { + return new Filesystem(new LocalAdapter($config['root'])); + }); + + $disk = $filesystemManager->disk('testingDriver'); + $this->assertInstanceOf(FilesystemAdapter::class, $disk); + + // Make sure the path was set correctly + $this->assertEquals(\UserFrosting\STORAGE_DIR . \UserFrosting\DS . 'testingDriver' . \UserFrosting\DS, $disk->path('')); + } +} diff --git a/app/sprinkles/core/tests/Unit/RouterTest.php b/app/sprinkles/core/tests/Unit/RouterTest.php new file mode 100644 index 000000000..534e5dfb1 --- /dev/null +++ b/app/sprinkles/core/tests/Unit/RouterTest.php @@ -0,0 +1,32 @@ +ci->router; + + // Get all routes. We should have more than 0 in a default install + $routes = $router->getRoutes(); + $this->assertNotCount(0, $routes); + + // Try to get a path + $path = $router->pathFor('index'); + $this->assertEquals('/', $path); + } +} diff --git a/app/sprinkles/core/tests/Unit/SprunjeTest.php b/app/sprinkles/core/tests/Unit/SprunjeTest.php index fa193196e..7346f1eee 100644 --- a/app/sprinkles/core/tests/Unit/SprunjeTest.php +++ b/app/sprinkles/core/tests/Unit/SprunjeTest.php @@ -3,14 +3,14 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ -namespace UserFrosting\Tests\Unit; -use Illuminate\Database\Capsule\Manager as DB; +namespace UserFrosting\Sprinkle\Core\Tests\Unit; + use Mockery as m; use UserFrosting\Tests\TestCase; -use UserFrosting\Tests\DatabaseTransactions; use UserFrosting\Sprinkle\Core\Database\Builder as Builder; use UserFrosting\Sprinkle\Core\Database\Models\Model; use UserFrosting\Sprinkle\Core\Sprunje\Sprunje; @@ -19,17 +19,16 @@ /** * SprunjeTest class. * Tests a basic Sprunje. - * - * @extends TestCase */ class SprunjeTest extends TestCase { public function tearDown() { + parent::tearDown(); m::close(); } - function testSprunjeApplyFiltersDefault() + public function testSprunjeApplyFiltersDefault() { $sprunje = new SprunjeStub([ 'filters' => [ @@ -51,7 +50,7 @@ function testSprunjeApplyFiltersDefault() $sprunje->applyFilters($builder); } - function testSprunjeApplySortsDefault() + public function testSprunjeApplySortsDefault() { $sprunje = new SprunjeStub([ 'sorts' => [ @@ -63,7 +62,6 @@ function testSprunjeApplySortsDefault() $builder->shouldReceive('orderBy')->once()->with('species', 'asc'); $sprunje->applySorts($builder); } - } class SprunjeStub extends Sprunje @@ -97,4 +95,3 @@ class SprunjeTestModelStub extends Model { protected $table = 'table'; } - diff --git a/app/sprinkles/core/tests/Unit/TestDatabaseTraitTest.php b/app/sprinkles/core/tests/Unit/TestDatabaseTraitTest.php new file mode 100644 index 000000000..0b6f69fbe --- /dev/null +++ b/app/sprinkles/core/tests/Unit/TestDatabaseTraitTest.php @@ -0,0 +1,40 @@ +setupTestDatabase(); + } + + /** + * Test the TestDatabase traits works + */ + public function testTrait() + { + // Use the testing db for this test + $connection = $this->ci->db->getConnection(); + $this->assertEquals($this->ci->config['testing.dbConnection'], $connection->getName()); + } +} diff --git a/app/storage/.gitkeep b/app/storage/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/app/system/Bakery/Bakery.php b/app/system/Bakery/Bakery.php index 8be84809c..68d5dda1f 100644 --- a/app/system/Bakery/Bakery.php +++ b/app/system/Bakery/Bakery.php @@ -3,13 +3,19 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\System\Bakery; +use Illuminate\Support\Str; use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Output\ConsoleOutput; use UserFrosting\System\UserFrosting; -use Illuminate\Support\Str; +use UserFrosting\UniformResourceLocator\Resource; +use UserFrosting\UniformResourceLocator\ResourceLocator; /** * Base class for UserFrosting Bakery CLI tools. @@ -19,40 +25,52 @@ class Bakery { /** - * @var $app Symfony\Component\Console\Application + * @var \Symfony\Component\Console\Application $app */ protected $app; /** - * @var ContainerInterface The global container object, which holds all your services. + * @var \Interop\Container\ContainerInterface The global container object, which holds all your services. */ protected $ci; + /** + * @var string $scheme The resource locator scheme + */ + protected $scheme = 'bakery://'; + /** * Constructor */ public function __construct() { + // Create Symfony Console App + $this->app = new Application('UserFrosting Bakery', \UserFrosting\VERSION); + // Check for Sprinkles schema file $sprinklesFile = @file_get_contents(\UserFrosting\SPRINKLES_SCHEMA_FILE); if ($sprinklesFile === false) { - $sprinklesFile = $this->setupBaseSprinkleList(); + try { + $sprinklesFile = $this->setupBaseSprinkleList(); + } catch (\Exception $e) { + $this->app->renderException($e, new ConsoleOutput()); + exit(1); + } } - // Create Symfony Console App - $this->app = new Application("UserFrosting Bakery", \UserFrosting\VERSION); - // Setup the sprinkles - $uf = new UserFrosting(); - - // Set argument as false, we are using the CLI - $uf->setupSprinkles(false); + $uf = new UserFrosting(true); // Get the container $this->ci = $uf->getContainer(); // Add each commands to the Console App - $this->loadCommands(); + try { + $this->loadCommands(); + } catch (\Exception $e) { + $this->app->renderException($e, new ConsoleOutput()); + exit(1); + } } /** @@ -68,86 +86,81 @@ public function run() */ protected function loadCommands() { - // Get base Bakery command - $commands = $this->getBakeryCommands(); + /** + * @var ResourceLocator $locator + */ + $locator = $this->ci->locator; - // Get the sprinkles commands - $sprinkles = $this->ci->sprinkleManager->getSprinkleNames(); - foreach ($sprinkles as $sprinkle) { - $commands = $commands->merge($this->getSprinkleCommands($sprinkle)); - } + // Get Bakery command resources + $commandResources = $locator->listResources($this->scheme, false, false); // Add commands to the App - $commands->each(function($command) { + foreach ($commandResources as $commandResource) { + + // Translate the resource to a class. Skip if class not found + if (!$command = $this->getResourceClass($commandResource)) { + continue; + } + + // Get command instance $instance = new $command(); + + // Class must be an instance of symfony command + if (!$instance instanceof Command) { + continue; + } + + // Add command to the Console app $instance->setContainer($this->ci); $this->app->add($instance); - }); + } } /** - * Return the list of available commands for a specific sprinkle - * Sprinkles commands should be located in `src/Bakery/` + * Transform a Bakery Command Resource into a classpath + * + * @param \UserFrosting\UniformResourceLocator\Resource $file The command resource + * @return string The command class path */ - protected function getSprinkleCommands($sprinkle) + protected function getResourceClass(Resource $file) { - // Find all the migration files - $path = $this->commandDirectoryPath($sprinkle); - $files = glob($path . "*.php"); - $commands = collect($files); - - // Transform the path into a class names - $commands->transform(function ($file) use ($sprinkle, $path) { - $className = basename($file, '.php'); - $sprinkleName = Str::studly($sprinkle); - $className = "\\UserFrosting\\Sprinkle\\".$sprinkleName."\\Bakery\\".$className; - return $className; - }); - - return $commands; - } + // Process sprinkle and system commands + if (!is_null($location = $file->getLocation())) { + + // Format the sprinkle name for the namespace + $sprinkleName = $file->getLocation()->getName(); + $sprinkleName = Str::studly($sprinkleName); + $classPath = "\\UserFrosting\\Sprinkle\\$sprinkleName\\Bakery\\{$this->getClassNameFromFile($file)}"; + } else { + $classPath = "\\UserFrosting\\System\\Bakery\\Command\\{$this->getClassNameFromFile($file)}"; + } - /** - * Return the list of available commands in system/Bakery/Command/ - */ - protected function getBakeryCommands() - { - // Find all the migration files - $files = glob(\UserFrosting\APP_DIR . "/system/Bakery/Command/" . "*.php"); - $commands = collect($files); - - // Transform the path into a class names - $commands->transform(function ($file) { - $className = basename($file, '.php'); - $className = "\\UserFrosting\\System\\Bakery\\Command\\".$className; - return $className; - }); - - return $commands; + // Make sure class exist + if (!class_exists($classPath)) { + return false; + } + + return $classPath; } /** - * Returns the path of the Migration directory. + * Return the classname from the file instance * - * @access protected - * @param mixed $sprinkleName - * @return void + * @param \UserFrosting\UniformResourceLocator\Resource $file The command resource + * @return string */ - protected function commandDirectoryPath($sprinkleName) + protected function getClassNameFromFile(Resource $file) { - return \UserFrosting\SPRINKLES_DIR . - \UserFrosting\DS . - $sprinkleName . - \UserFrosting\DS . - \UserFrosting\SRC_DIR_NAME . - "/Bakery/"; + $basePath = str_replace($file->getBasename(), '', $file->getBasePath()); + $className = str_replace('/', '\\', $basePath) . $file->getFilename(); + + return $className; } /** - * Write the base Sprinkles schema file if it doesn't exist. + * Write the base `sprinkles.json` file if none exist. * - * @access protected - * @return void + * @return string The sprinkle model file */ protected function setupBaseSprinkleList() { @@ -155,8 +168,7 @@ protected function setupBaseSprinkleList() $destination = \UserFrosting\SPRINKLES_SCHEMA_FILE; $sprinklesModelFile = @file_get_contents($model); if ($sprinklesModelFile === false) { - $this->io->error("File `$sprinklesModelFile` not found. Please create '$destination' manually and try again."); - exit(1); + throw new \Exception("File `$model` not found. Please create '$destination' manually and try again."); } file_put_contents($destination, $sprinklesModelFile); diff --git a/app/system/Bakery/BaseCommand.php b/app/system/Bakery/BaseCommand.php index 1a59141b4..f150d47fc 100644 --- a/app/system/Bakery/BaseCommand.php +++ b/app/system/Bakery/BaseCommand.php @@ -3,16 +3,15 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\System\Bakery; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Style\SymfonyStyle; use Interop\Container\ContainerInterface; @@ -24,35 +23,45 @@ abstract class BaseCommand extends Command { /** - * @var @Symfony\Component\Console\Style\SymfonyStyle - * See http://symfony.com/doc/current/console/style.html + * @var \Symfony\Component\Console\Style\SymfonyStyle + * See http://symfony.com/doc/current/console/style.html */ protected $io; /** - * @var string Path to the project root folder - */ - protected $projectRoot; - - /** - * @var ContainerInterface $ci The global container object, which holds all of the UserFrosting services. + * @var ContainerInterface $ci The global container object, which holds all of UserFrosting services. */ protected $ci; /** - * {@inheritDoc} + * {@inheritdoc} */ protected function initialize(InputInterface $input, OutputInterface $output) { $this->io = new SymfonyStyle($input, $output); - $this->projectRoot = \UserFrosting\ROOT_DIR; } /** * Setup the global container object + * + * @param ContainerInterface $ci */ public function setContainer(ContainerInterface $ci) { $this->ci = $ci; } + + /** + * Return if the app is in production mode + * + * @return bool True/False if the app is in production mode + */ + protected function isProduction() + { + // N.B.: Need to touch the config service first to load dotenv values + $config = $this->ci->config; + $mode = getenv('UF_MODE') ?: ''; + + return $mode == 'production'; + } } diff --git a/app/system/Bakery/Command/BuildAssets.php b/app/system/Bakery/Command/BuildAssets.php deleted file mode 100644 index 055fa4378..000000000 --- a/app/system/Bakery/Command/BuildAssets.php +++ /dev/null @@ -1,180 +0,0 @@ -setName("build-assets") - ->setDescription("Build the assets using node and npm") - ->setHelp("The build directory contains the scripts and configuration files required to download Javascript, CSS, and other assets used by UserFrosting. This command will install Gulp, Bower, and several other required npm packages locally. With npm set up with all of its required packages, it can be use it to automatically download and install the assets in the correct directories. For more info, see https://learn.userfrosting.com/basics/installation") - ->addOption("compile", "c", InputOption::VALUE_NONE, "Compile the assets and asset bundles for production environment") - ->addOption("force", "f", InputOption::VALUE_NONE, "Force assets compilation by deleting cached data and installed assets before proceeding"); - } - - /** - * {@inheritDoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) - { - // Display header, - $this->io->title("UserFrosting's Assets Builder"); - - // Set $path - $this->buildPath = $this->projectRoot . \UserFrosting\DS . \UserFrosting\BUILD_DIR_NAME; - - // Delete cached data is requested - if ($input->getOption('force')) { - $this->clean(); - } - - // Perform tasks - $this->npmInstall(); - $this->assetsInstall(); - - // Compile if requested - if ($input->getOption('compile') || $this->isProduction()) { - $this->buildAssets(); - } - - // Test the result - $this->checkAssets(); - - // If all went well and there's no fatal errors, we are successful - $this->io->success("Assets install looks successful"); - } - - /** - * Install npm package - * - * @access protected - * @return void - */ - protected function npmInstall() - { - $this->io->section("Installing npm dependencies"); - $this->io->writeln("> npm install"); - - // Temporarily change the working directory so we can install npm dependencies - $wd = getcwd(); - chdir($this->buildPath); - passthru("npm install"); - chdir($wd); - } - - /** - * Perform UF Assets installation - * - * @access protected - * @return void - */ - protected function assetsInstall() - { - $this->io->section("Installing assets bundles"); - $this->io->writeln("> npm run uf-assets-install"); - passthru("npm run uf-assets-install --prefix " . $this->buildPath); - } - - /** - * Build the production bundle. - * - * @access protected - * @return void - */ - protected function buildAssets() - { - $this->io->section("Building assets for production"); - - $this->io->writeln("> npm run uf-bundle-build"); - passthru("npm run uf-bundle-build --prefix " . $this->buildPath); - - $this->io->writeln("> npm run uf-bundle"); - passthru("npm run uf-bundle --prefix " . $this->buildPath); - - $this->io->writeln("> npm run uf-bundle-clean"); - passthru("npm run uf-bundle-clean --prefix " . $this->buildPath); - } - - /** - * Check that the assets where installed in the core sprinkles - * - * @access protected - * @return void - */ - protected function checkAssets() - { - $this->io->section("Testing assets installation"); - - // Get path and vendor files - $vendorPath = \UserFrosting\SPRINKLES_DIR . "/core/assets/vendor/*"; - $coreVendorFiles = glob($vendorPath); - - if (!$coreVendorFiles){ - $this->io->error("Assets installation seems to have failed. Directory `$vendorPath` is empty, but it shouldn't be. Check the above log for any errors."); - exit(1); - } - - // Check that `bundle.result.json` is present in production mode - $config = $this->ci->config; - $resultFile = \UserFrosting\ROOT_DIR . \UserFrosting\DS . \UserFrosting\BUILD_DIR_NAME . \UserFrosting\DS . $config['assets.compiled.schema']; - if ($this->isProduction() && !file_exists($resultFile)) { - $this->io->error("Assets building seems to have failed. File `$resultFile` not found. This file is required for production envrionement. Check the above log for any errors."); - exit(1); - } - } - - /** - * Run the `uf-clean` command to delete installed assets, delete compiled - * bundle config file and delete compiled assets - * - * @access protected - * @return void - */ - protected function clean() - { - $this->io->section("Cleaning cached data"); - $this->io->writeln("> npm run uf-clean"); - passthru("npm run uf-clean --prefix " . $this->buildPath); - } - - /** - * Return if the app is in production mode - * - * @access protected - * @return bool - */ - protected function isProduction() - { - // N.B.: Need to touch the config service first to load dotenv values - $config = $this->ci->config; - $mode = getenv("UF_MODE") ?: ''; - - return ($mode == "production"); - } -} \ No newline at end of file diff --git a/app/system/Bakery/Command/Debug.php b/app/system/Bakery/Command/Debug.php deleted file mode 100644 index 4e8a3e45b..000000000 --- a/app/system/Bakery/Command/Debug.php +++ /dev/null @@ -1,185 +0,0 @@ -setName("debug") - ->setDescription("Test the UserFrosting installation and setup the database") - ->setHelp("This command is used to check if the various dependencies of UserFrosting are met and display useful debugging information. \nIf any error occurs, check out the online documentation for more info about that error. \nThis command also provide the necessary tools to setup the database credentials"); - } - - /** - * {@inheritDoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) - { - // Display header, - $this->io->title("UserFrosting"); - $this->io->writeln("UserFrosing version : " . \UserFrosting\VERSION); - $this->io->writeln("OS Name : " . php_uname('s')); - $this->io->writeln("Project Root : {$this->projectRoot}"); - - // Need to touch the config service first to load dotenv values - $config = $this->ci->config; - $this->io->writeln("Environment mode : " . getenv("UF_MODE")); - - // Perform tasks - $this->checkPhpVersion(); - $this->checkNodeVersion(); - $this->checkNpmVersion(); - $this->listSprinkles(); - $this->showConfig(); - $this->checkDatabase(); - - // If all went well and there's no fatal errors, we are ready to bake - $this->io->success("Ready to bake !"); - } - - /** - * Check the minimum version of php. - * This is done by composer itself, but we do it again for good mesure - * - * @access public - * @return void - */ - protected function checkPhpVersion() - { - $this->io->writeln("PHP Version : " . phpversion()); - if (version_compare(phpversion(), \UserFrosting\PHP_MIN_VERSION, '<')) { - $this->io->error("UserFrosting requires php version ".\UserFrosting\PHP_MIN_VERSION." or above. You'll need to update you PHP version before you can continue."); - exit(1); - } - } - - /** - * Check the minimum version requirement of Node installed - * - * @access public - * @return void - */ - protected function checkNodeVersion() - { - $npmVersion = trim(exec('node -v')); - $this->io->writeln("Node Version : $npmVersion"); - - if (version_compare($npmVersion, 'v4', '<')) { - $this->io->error("UserFrosting requires Node version 4.x or above. Check the documentation for more details."); - exit(1); - } - } - - /** - * Check the minimum version requirement for Npm - * - * @access public - * @return void - */ - protected function checkNpmVersion() - { - $npmVersion = trim(exec('npm -v')); - $this->io->writeln("NPM Version : $npmVersion"); - - if (version_compare($npmVersion, '3', '<')) { - $this->io->error("UserFrosting requires npm version 3.x or above. Check the documentation for more details."); - exit(1); - } - } - - /** - * List all sprinkles defined in the Sprinkles schema file, - * making sure this file exist at the same time - * - * @access protected - * @return void - */ - protected function listSprinkles() - { - // Check for Sprinkles schema file - $path = \UserFrosting\SPRINKLES_SCHEMA_FILE; - $sprinklesFile = @file_get_contents($path); - if ($sprinklesFile === false) { - $this->io->error("The file `$path` not found."); - } - - // List installed sprinkles - $sprinkles = json_decode($sprinklesFile)->base; - $this->io->section("Loaded sprinkles"); - $this->io->listing($sprinkles); - - // Throw fatal error if the `core` sprinkle is missing - if (!in_array("core", $sprinkles)) { - $this->io->error("The `core` sprinkle is missing from the 'sprinkles.json' file."); - exit(1); - } - } - - /** - * Check the database connexion and setup the `.env` file if we can't - * connect and there's no one found. - * - * @access protected - * @return void - */ - protected function checkDatabase() - { - $this->io->section("Testing database connection..."); - - try { - $this->testDB(); - $this->io->writeln("Database connection successful"); - return; - } catch (\Exception $e) { - $error = $e->getMessage(); - $this->io->error($error); - exit(1); - } - } - - /** - * Display database config as for debug purposes - * - * @access protected - * @return void - */ - protected function showConfig() - { - // Get config - $config = $this->ci->config; - - // Display database info - $this->io->section("Database config"); - $this->io->writeln([ - "DRIVER : " . $config['db.default.driver'], - "HOST : " . $config['db.default.host'], - "PORT : " . $config['db.default.port'], - "DATABASE : " . $config['db.default.database'], - "USERNAME : " . $config['db.default.username'], - "PASSWORD : " . ($config['db.default.password'] ? "*********" : "") - ]); - } -} diff --git a/app/system/Bakery/Command/Migrate.php b/app/system/Bakery/Command/Migrate.php deleted file mode 100644 index c0a5b4f3a..000000000 --- a/app/system/Bakery/Command/Migrate.php +++ /dev/null @@ -1,48 +0,0 @@ -setName("migrate") - ->setDescription("Perform database migration") - ->setHelp("This command runs all the pending database migrations.") - ->addOption('pretend', 'p', InputOption::VALUE_NONE, 'Run migrations in "dry run" mode'); - } - - /** - * {@inheritDoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) - { - $this->io->title("UserFrosting's Migrator"); - - $pretend = $input->getOption('pretend'); - - $migrator = new Migrator($this->io, $this->ci); - $migrator->runUp($pretend); - } -} \ No newline at end of file diff --git a/app/system/Bakery/Command/MigrateRefresh.php b/app/system/Bakery/Command/MigrateRefresh.php deleted file mode 100644 index 3d18c41fe..000000000 --- a/app/system/Bakery/Command/MigrateRefresh.php +++ /dev/null @@ -1,52 +0,0 @@ -setName("migrate:refresh") - ->setDescription("Rollback the last migration operation and run it up again") - ->addOption('steps', 's', InputOption::VALUE_REQUIRED, 'Number of steps to rollback', 1) - ->addOption('sprinkle', null, InputOption::VALUE_REQUIRED, 'The sprinkle to rollback', "") - ->addOption('pretend', 'p', InputOption::VALUE_NONE, 'Run migrations in "dry run" mode'); - } - - /** - * {@inheritDoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) - { - $this->io->title("Migration refresh"); - - $step = $input->getOption('steps'); - $sprinkle = $input->getOption('sprinkle'); - $pretend = $input->getOption('pretend'); - - $migrator = new Migrator($this->io, $this->ci); - $migrator->runDown($step, $sprinkle, $pretend); - $migrator->runUp($pretend); - } -} \ No newline at end of file diff --git a/app/system/Bakery/Command/MigrateReset.php b/app/system/Bakery/Command/MigrateReset.php deleted file mode 100644 index 9e38cbb02..000000000 --- a/app/system/Bakery/Command/MigrateReset.php +++ /dev/null @@ -1,49 +0,0 @@ -setName("migrate:reset") - ->setDescription("Reset the whole database to an empty state") - ->addOption('sprinkle', null, InputOption::VALUE_REQUIRED, 'The sprinkle to rollback', "") - ->addOption('pretend', 'p', InputOption::VALUE_NONE, 'Run migrations in "dry run" mode'); - } - - /** - * {@inheritDoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) - { - $this->io->title("Migration reset"); - - $sprinkle = $input->getOption('sprinkle'); - $pretend = $input->getOption('pretend'); - - $migrator = new Migrator($this->io, $this->ci); - $migrator->runDown(-1, $sprinkle, $pretend); - } -} \ No newline at end of file diff --git a/app/system/Bakery/Command/MigrateRollback.php b/app/system/Bakery/Command/MigrateRollback.php deleted file mode 100644 index 916f5eef8..000000000 --- a/app/system/Bakery/Command/MigrateRollback.php +++ /dev/null @@ -1,51 +0,0 @@ -setName("migrate:rollback") - ->setDescription("Rollback last database migration") - ->addOption('steps', 's', InputOption::VALUE_REQUIRED, 'Number of steps to rollback', 1) - ->addOption('sprinkle', null, InputOption::VALUE_REQUIRED, 'The sprinkle to rollback', "") - ->addOption('pretend', 'p', InputOption::VALUE_NONE, 'Run migrations in "dry run" mode'); - } - - /** - * {@inheritDoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) - { - $this->io->title("Migration rollback"); - - $step = $input->getOption('steps'); - $sprinkle = $input->getOption('sprinkle'); - $pretend = $input->getOption('pretend'); - - $migrator = new Migrator($this->io, $this->ci); - $migrator->runDown($step, $sprinkle, $pretend); - } -} \ No newline at end of file diff --git a/app/system/Bakery/Command/RouteListCommand.php b/app/system/Bakery/Command/RouteListCommand.php deleted file mode 100644 index cec58516b..000000000 --- a/app/system/Bakery/Command/RouteListCommand.php +++ /dev/null @@ -1,70 +0,0 @@ -setName("route:list") - ->setDescription("Dumps all routes."); - } - - /** - * {@inheritDoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) - { - global $app; - - $this->ci->settings = $this->ci->config['settings']; - $this->app = new App($this->ci); - $app = $this->app; - - $this->io->title("Registered Routes"); - $routePaths = array_reverse($this->ci->locator->findResources('routes://', true, true)); - $routeCount=0; - foreach ($routePaths as $path) { - $routeFiles = glob($path . '/*.php'); - foreach ($routeFiles as $routeFile) { - $routeCount += 1; - //$this->io->writeln("$routeFile"); - require_once $routeFile; - } - } - - $allRoutes = $app->getContainer()->get('router')->getRoutes(); - foreach ($allRoutes as $route) { - $p = $route->getPattern(); - foreach ($route->getMethods() as $m) - $this->io->writeln($m.' '.$p); - } - - $this->io->success("Route list generated from {$routeCount} files!"); - } -} diff --git a/app/system/Bakery/Command/Test.php b/app/system/Bakery/Command/Test.php deleted file mode 100644 index 553fdddc7..000000000 --- a/app/system/Bakery/Command/Test.php +++ /dev/null @@ -1,56 +0,0 @@ -setName("test") - ->setDescription("Run tests") - ->setHelp("Run php unit tests"); - } - - /** - * {@inheritDoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) - { - $this->io->title("UserFrosting's Tester"); - - // Get command - $command = \UserFrosting\VENDOR_DIR . "/bin/phpunit --colors=always"; - if ($output->isVerbose() || $output->isVeryVerbose()) { - $command .= " -v"; - } - - // Execute - $this->io->writeln("> $command"); - passthru($command); - } -} \ No newline at end of file diff --git a/app/system/Bakery/Migration.php b/app/system/Bakery/Migration.php index e6c6ae0b3..bc73c3c91 100644 --- a/app/system/Bakery/Migration.php +++ b/app/system/Bakery/Migration.php @@ -3,62 +3,38 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\System\Bakery; use Illuminate\Database\Schema\Builder; +use UserFrosting\Sprinkle\Core\Database\Migration as NewMigration; use Symfony\Component\Console\Style\SymfonyStyle; +use UserFrosting\Sprinkle\Core\Facades\Config; +use UserFrosting\Sprinkle\Core\Facades\Debug; /** * Abstract Migration class. * - * @abstract + * @deprecated since 4.2.0 Use `UserFrosting\Sprinkle\Core\Database\Migration` instead * @author Alex Weissman (https://alexanderweissman.com) */ -abstract class Migration +abstract class Migration extends NewMigration { /** - * @var Illuminate\Database\Schema\Builder $schema - */ - protected $schema; - - /** - * @var @Composer\IO\IOInterface - */ - protected $io; - - /** - * List of dependencies for this migration. - * Should return an array of class required to be run before this migration - */ - public $dependencies = []; - - /** - * __construct function. + * Constructor * - * @access public - * @param Illuminate\Database\Schema\Builder $schema - * @return void + * @param Builder|null $schema The schema builder + * @param SymfonyStyle|null $io The SymfonyStyle instance */ - public function __construct(Builder $schema, SymfonyStyle $io) + public function __construct(Builder $schema = null, SymfonyStyle $io = null) { - $this->schema = $schema; - $this->io = $io; - } - - /** - * Method to apply changes to the database - */ - public function up() {} - - /** - * Method to revert changes applied by the `up` method - */ - public function down() {} + if (Config::get('debug.deprecation')) { + Debug::warning("`UserFrosting\System\Bakery\Migration` has been deprecated and will be removed in future versions. Please have your `" . static::class . "` migration extend the base `UserFrosting\Sprinkle\Core\Database\Migration` class instead."); + } - /** - * Method to seed new information to the database - */ - public function seed() {} + parent::__construct($schema); + } } diff --git a/app/system/Bakery/Migrator.php b/app/system/Bakery/Migrator.php deleted file mode 100644 index 611f73f50..000000000 --- a/app/system/Bakery/Migrator.php +++ /dev/null @@ -1,584 +0,0 @@ -io = $io; - $this->ci = $ci; - - // Start by testing the DB connexion, just in case - try { - $this->io->writeln("Testing database connection", OutputInterface::VERBOSITY_VERBOSE); - $this->testDB(); - $this->io->writeln("Ok", OutputInterface::VERBOSITY_VERBOSE); - } catch (\Exception $e) { - $this->io->error($e->getMessage()); - exit(1); - } - - // Get schema required to run the table blueprints - $this->schema = Capsule::schema(); - - // Make sure the setup table exist - $this->setupVersionTable(); - } - - /** - * Run all the migrations available - * - * @access public - * @param bool $pretend (default: false) - * @return void - */ - public function runUp($pretend = false) - { - // Get installed migrations and pluck by class name. We only need this for now - $migrations = Migrations::get(); - $this->installed = $migrations->pluck('migration'); - - $this->io->writeln("\nInstalled migrations:", OutputInterface::VERBOSITY_VERBOSE); - $this->io->writeln($this->installed->toArray(), OutputInterface::VERBOSITY_VERBOSE); - - // Get pending migrations - $this->io->section("Fetching available migrations"); - $this->pending = $this->getPendingMigrations(); - - // If there's no pending migration, don't need to go further - if ($this->pending->isEmpty()) { - $this->io->success("Nothing to migrate !"); - return; - } - - // Resolve the dependencies - $this->resolveDependencies(); - - // If there are any unfulfillable migration, we can't continue - if (!$this->unfulfillable->isEmpty()) { - - $msg = "\nSome migrations dependencies can't be met. Check those migrations for unmet dependencies and try again:"; - - foreach ($this->unfulfillable as $migration) { - $msg .= "\n{$migration->className} depends on \n - "; - $msg .= implode("\n - ", $migration->dependencies); - $msg .= "\n"; - } - - $this->io->error($msg); - exit(1); - } - - // Ready to run ! - $this->io->section("Running migrations"); - - if ($pretend) { - $this->io->note("Running migration in pretend mode"); - } - - // We have a list of fulfillable migration, we run them up! - foreach ($this->fulfillable as $migration) { - $this->io->write("\n> Migrating {$migration->className}..."); - - if ($pretend) { - $this->io->newLine(); - $this->pretendToRun($migration, 'up'); - } else { - $migration->up(); - $migration->seed(); - $this->log($migration); - $this->io->writeln(" Done!"); - } - } - - // If all went well and there's no fatal errors, we are ready to bake - $this->io->success("Migration successful !"); - } - - /** - * Rollback the last btach of migrations. - * - * @access public - * @param int $step (default: 1). Number of batch we will be going back. -1 revert all migrations - * @param string $sprinkle (default: "") Limit rollback to a specific sprinkle - * @param bool $pretend (default: false) - * @return void - */ - public function runDown($step = 1, $sprinkle = "", $pretend = false) - { - // Can't go furhter down than 1 step - if ($step <= 0 && $step != -1) { - throw new \InvalidArgumentException("Step can't be 0 or less"); - } - - // Get last batch number - $batch = $this->getNextBatchNumber(); - - // Calculate the number of steps back we need to take - if ($step == -1) { - $stepsBack = 1; - $this->io->warning("Rolling back all migrations"); - } else { - $stepsBack = max($batch - $step, 1); - $this->io->note("Rolling back $step steps to batch $stepsBack", OutputInterface::VERBOSITY_VERBOSE); - } - - // Get installed migrations - $migrations = Migrations::orderBy("id", "desc")->where('batch', '>=', $stepsBack); - - // Add the sprinkle requirement too - if ($sprinkle != "") { - $this->io->note("Rolling back sprinkle `$sprinkle`", OutputInterface::VERBOSITY_VERBOSE); - $migrations->where('sprinkle', $sprinkle); - } - - // Run query - $migrations = $migrations->get(); - - // If there's nothing to rollback, stop here - if ($migrations->isEmpty()) { - $this->io->writeln("Nothing to rollback"); - exit(1); - } - - // Get pending migrations - $this->io->writeln("Migration to rollback:"); - $this->io->listing($migrations->pluck('migration')->toArray()); - - // Ask confirmation to continue. - if (!$pretend && !$this->io->confirm("Continue?", false)) { - exit(1); - } - - if ($pretend) { - $this->io->note("Rolling back in pretend mode"); - } - - // Loop again to run down each migration - foreach ($migrations as $migration) { - - // Check if those migration class are available - if (!class_exists($migration->migration)) { - $this->io->warning("Migration class {$migration->migration} doesn't exist."); - continue; - } - - $this->io->write("> Rolling back {$migration->migration}..."); - $migrationClass = $migration->migration; - $instance = new $migrationClass($this->schema, $this->io); - - if ($pretend) { - $this->io->newLine(); - $this->pretendToRun($instance, 'down'); - } else { - $instance->down(); - $migration->delete(); - $this->io->writeln(" Done!"); - } - - $this->io->newLine(); - } - - // If all went well and there's no fatal errors, we are ready to bake - $this->io->success("Rollback successful !"); - } - - /** - * Pretend to run migration class. - * - * @access protected - * @param mixed $migration - * @param string $method up/down - */ - protected function pretendToRun($migration, $method) - { - foreach ($this->getQueries($migration, $method) as $query) { - $this->io->writeln($query['query'], OutputInterface::VERBOSITY_VERBOSE); - } - } - - /** - * Return all of the queries that would be run for a migration. - * - * @access protected - * @param mixed $migration - * @param string $method up/down - * @return void - */ - protected function getQueries($migration, $method) - { - $db = $this->schema->getConnection(); - - return $db->pretend(function () use ($migration, $method) { - if (method_exists($migration, $method)) { - $migration->{$method}(); - } - }); - } - - /** - * Get pending migrations by looking at all the migration files - * and finding the one not yet runed by compairing with the ran migrations - * - * @access protected - * @return void - */ - protected function getPendingMigrations() - { - $pending = collect([]); - - // Get the sprinkle list - $sprinkles = $this->ci->sprinkleManager->getSprinkleNames(); - - // Loop all the sprinkles to find their pending migrations - foreach ($sprinkles as $sprinkle) { - - $this->io->writeln("> Fetching from `$sprinkle`"); - - // We get all the migrations. This will return them as a colleciton of class names - $migrations = $this->getMigrations($sprinkle); - - // We filter the available migration by removing the one that have already been run - // This reject the class name found in the installed collection - $migrations = $migrations->reject(function ($value, $key) { - return $this->installed->contains($value); - }); - - // Load each class - foreach ($migrations as $migrationClass) { - - // Make sure the class exist - if (!class_exists($migrationClass)) { - throw new BadClassNameException("Unable to find the migration class '$migrationClass'." ); - } - - // Load the migration class - $migration = new $migrationClass($this->schema, $this->io); - - //Set the sprinkle - $migration->sprinkle = $sprinkle; - - // Also set the class name. We could find it using ::class, but this - // will make it easier to manipulate the collection - $migration->className = $migrationClass; - - // Add it to the pending list - $pending->push($migration); - } - } - - // Get pending migrations - $pendingArray = ($pending->pluck('className')->toArray()) ?: ""; - $this->io->writeln("\nPending migrations:", OutputInterface::VERBOSITY_VERBOSE); - $this->io->writeln($pendingArray, OutputInterface::VERBOSITY_VERBOSE); - - return $pending; - } - - /** - * Get the list of migrations avaiables in the filesystem. - * Return a list of resolved className - * - * @access public - * @param string $sprinkleName - * @return void - */ - public function getMigrations($sprinkle) - { - // Find all the migration files - $path = $this->migrationDirectoryPath($sprinkle); - $files = glob($path . "*/*.php"); - - // Transform the array in a collection - $migrations = collect($files); - - // We transform the path into a migration object - $migrations->transform(function ($file) use ($sprinkle, $path) { - // Deconstruct the path - $migration = str_replace($path, "", $file); - $className = basename($file, '.php'); - $sprinkleName = Str::studly($sprinkle); - $version = str_replace("/$className.php", "", $migration); - - // Reconstruct the classname - $className = "\\UserFrosting\\Sprinkle\\".$sprinkleName."\\Database\\Migrations\\".$version."\\".$className; - - return $className; - }); - - return $migrations; - } - - /** - * Resolve all the dependencies for all the pending migrations - * This function fills in the `fullfillable` and `unfulfillable` list - * - * @access protected - * @return void - */ - protected function resolveDependencies() - { - $this->io->writeln("\nResolving migrations dependencies...", OutputInterface::VERBOSITY_VERBOSE); - - // Reset fulfillable/unfulfillable lists - $this->fulfillable = collect([]); - $this->unfulfillable = collect([]); - - // Loop pending and check for dependencies - foreach ($this->pending as $migration) { - $this->validateClassDependencies($migration); - } - - $fulfillable = ($this->fulfillable->pluck('className')->toArray()) ?: ""; - $this->io->writeln("\nFulfillable migrations:", OutputInterface::VERBOSITY_VERBOSE); - $this->io->writeln($fulfillable, OutputInterface::VERBOSITY_VERBOSE); - - $unfulfillable = ($this->unfulfillable->pluck('className')->toArray()) ?: ""; - $this->io->writeln("\nUnfulfillable migrations:", OutputInterface::VERBOSITY_VERBOSE); - $this->io->writeln($unfulfillable, OutputInterface::VERBOSITY_VERBOSE); - } - - /** - * Check if a migration dependencies are met. - * To test if a migration is fulfillable, the class must : - * Already been installed OR exist and have all it's dependencies met - * - * @access protected - * @param $migration - * @return bool true/false if all conditions are met - */ - protected function validateClassDependencies($migration) - { - $this->io->writeln("> Checking dependencies for {$migration->className}", OutputInterface::VERBOSITY_VERBOSE); - - // If it's already marked as fulfillable, it's fulfillable - // Return true directly (it's already marked) - if ($this->fulfillable->contains($migration)) { - return true; - } - - // If it's already marked as unfulfillable, it's unfulfillable - // Return false directly (it's already marked) - if ($this->unfulfillable->contains($migration)) { - return false; - } - - // If it's already run, it's fulfillable - // Mark it as such for next time it comes up in this loop - if ($this->installed->contains($migration->className)) { - return $this->markAsFulfillable($migration); - } - - // Loop dependencies. If one is not fulfillable, then this one is not either - foreach ($migration->dependencies as $dependencyClass) { - - // The dependency might already be installed. Check that first - if ($this->installed->contains($dependencyClass)) { - continue; - } - - // Try to find it in the `pending` list. Cant' find it? Then it's not fulfillable - $dependency = $this->pending->where('className', $dependencyClass)->first(); - - // Check migration dependencies of this one right now - // If ti's not fullfillable, then this one isn't either - if (!$dependency || !$this->validateClassDependencies($dependency)) { - return $this->markAsUnfulfillable($migration); - } - } - - // If no dependencies returned false, it's fulfillable - return $this->markAsFulfillable($migration); - } - - /** - * Mark a dependency as fulfillable. - * Removes it from the pending list and add it to the fulfillable list - * - * @access protected - * @param $migration - * @return true - */ - protected function markAsFulfillable($migration) - { - $this->fulfillable->push($migration); - return true; - } - - /** - * Mark a dependency as unfulfillable. - * Removes it from the pending list and add it to the unfulfillable list - * - * @access protected - * @param $migration - * @return false - */ - protected function markAsUnfulfillable($migration) - { - $this->unfulfillable->push($migration); - return false; - } - - /** - * Log that a migration was run. - * - * @access public - * @param mixed $migration - * @return void - */ - protected function log($migration) - { - // Get the next batch number if not defined - if (!$this->batch) { - $this->batch = $this->getNextBatchNumber(); - } - - $log = new Migrations([ - 'sprinkle' => $migration->sprinkle, - 'migration' => $migration->className, - 'batch' => $this->batch - ]); - $log->save(); - } - - /** - * Return the next batch number from the db. - * Batch number is used to group together migration run in the same operation - * - * @access public - * @return int the next batch number - */ - public function getNextBatchNumber() - { - $batch = Migrations::max('batch'); - return ($batch) ? $batch + 1 : 1; - } - - /** - * Create the migration history table if needed. - * Also check if the tables requires migrations - * We run the migration file manually for this one - * - * @access public - * @return void - */ - protected function setupVersionTable() - { - // Check if the `migrations` table exist. Create it manually otherwise - if (!$this->schema->hasColumn($this->table, 'id')) { - $this->io->section("Creating the `{$this->table}` table"); - - $migration = new \UserFrosting\System\Database\Migrations\v410\MigrationTable($this->schema, $this->io); - $migration->up(); - - $this->io->success("Table `{$this->table}` created"); - } - } - - /** - * Returns the path of the Migration directory. - * - * @access protected - * @param mixed $sprinkleName - * @return void - */ - protected function migrationDirectoryPath($sprinkleName) - { - $path = \UserFrosting\SPRINKLES_DIR . - \UserFrosting\DS . - $sprinkleName . - \UserFrosting\DS . - \UserFrosting\SRC_DIR_NAME . - "/Database/Migrations/"; - - return $path; - } -} diff --git a/app/system/Database/Migrations/v410/MigrationTable.php b/app/system/Database/Migrations/v410/MigrationTable.php deleted file mode 100644 index fb833dfa8..000000000 --- a/app/system/Database/Migrations/v410/MigrationTable.php +++ /dev/null @@ -1,59 +0,0 @@ -schema->create('migrations', function (Blueprint $table) { - $table->increments('id'); - $table->string('sprinkle'); - $table->string('migration'); - $table->integer('batch'); - $table->timestamps(); - - $table->engine = 'InnoDB'; - $table->collation = 'utf8_unicode_ci'; - $table->charset = 'utf8'; - }); - - // Drop the old `version` table if found - if ($this->schema->hasTable('version')) { - $this->schema->drop('version'); - } - } - - /** - * {@inheritDoc} - */ - public function down() - { - $this->schema->drop('migrations'); - } -} diff --git a/app/system/Database/Model/Migrations.php b/app/system/Database/Model/Migrations.php deleted file mode 100644 index 6a0942e49..000000000 --- a/app/system/Database/Model/Migrations.php +++ /dev/null @@ -1,55 +0,0 @@ -where('sprinkle', $sprinkleName); - } -} diff --git a/app/system/Facade.php b/app/system/Facade.php index 0d1ad8276..4fd04d2e8 100644 --- a/app/system/Facade.php +++ b/app/system/Facade.php @@ -3,12 +3,14 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\System; -use Illuminate\Support\Facades\Mockery; -use Illuminate\Support\Facades\Mockery\MockInterface; +use Mockery; +use Mockery\MockInterface; use Interop\Container\ContainerInterface; use RuntimeException; @@ -26,7 +28,7 @@ abstract class Facade /** * The Pimple container being facaded. * - * @var \Interop\Container\ContainerInterface + * @var ContainerInterface */ protected static $container; @@ -40,8 +42,7 @@ abstract class Facade /** * Hotswap the underlying service instance behind the facade. * - * @param mixed $instance - * @return void + * @param mixed $instance */ public static function swap($instance) { @@ -57,7 +58,6 @@ public static function swap($instance) /** * Initiate a mock expectation on the facade. * - * @param mixed * @return \Mockery\Expectation */ public static function shouldReceive() @@ -76,7 +76,7 @@ public static function shouldReceive() /** * Create a fresh mock instance for the given class. * - * @param string $name + * @param string $name * @return \Mockery\Expectation */ protected static function createFreshMockInstance($name) @@ -95,7 +95,7 @@ protected static function createFreshMockInstance($name) /** * Create a fresh mock instance for the given class. * - * @param string $name + * @param string $name * @return \Mockery\Expectation */ protected static function createMockByName($name) @@ -142,8 +142,6 @@ public static function getFacadeRoot() /** * Get the registered name of the component. * - * @return string - * * @throws \RuntimeException */ protected static function getFacadeAccessor() @@ -154,7 +152,7 @@ protected static function getFacadeAccessor() /** * Resolve the facade root instance from the container. * - * @param string|object $name + * @param string|object $name * @return mixed */ protected static function resolveFacadeInstance($name) @@ -173,8 +171,7 @@ protected static function resolveFacadeInstance($name) /** * Clear a resolved facade instance. * - * @param string $name - * @return void + * @param string $name */ public static function clearResolvedInstance($name) { @@ -183,8 +180,6 @@ public static function clearResolvedInstance($name) /** * Clear all of the resolved instances. - * - * @return void */ public static function clearResolvedInstances() { @@ -194,7 +189,7 @@ public static function clearResolvedInstances() /** * Get the container instance behind the facade. * - * @return \Interop\Container\ContainerInterface + * @return ContainerInterface */ public static function getFacadeContainer() { @@ -204,10 +199,9 @@ public static function getFacadeContainer() /** * Set the container instance. * - * @param \Interop\Container\ContainerInterface $container - * @return void + * @param ContainerInterface $container */ - public static function setFacadeContainer($container) + public static function setFacadeContainer(ContainerInterface $container) { static::$container = $container; } @@ -215,17 +209,16 @@ public static function setFacadeContainer($container) /** * Handle dynamic, static calls to the object. * - * @param string $method - * @param array $args - * @return mixed - * + * @param string $method + * @param array $args * @throws \RuntimeException + * @return mixed */ public static function __callStatic($method, $args) { $instance = static::getFacadeRoot(); - if (! $instance) { + if (!$instance) { throw new RuntimeException('A facade root has not been set.'); } diff --git a/app/system/ServicesProvider.php b/app/system/ServicesProvider.php index 6286bc0c7..8a8e331f1 100644 --- a/app/system/ServicesProvider.php +++ b/app/system/ServicesProvider.php @@ -3,16 +3,16 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\System; use Interop\Container\ContainerInterface; use RocketTheme\Toolbox\Event\EventDispatcher; -use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator; -use RocketTheme\Toolbox\StreamWrapper\ReadOnlyStream; -use RocketTheme\Toolbox\StreamWrapper\StreamBuilder; use UserFrosting\System\Sprinkle\SprinkleManager; +use UserFrosting\UniformResourceLocator\ResourceLocator; /** * UserFrosting system services provider. @@ -31,6 +31,8 @@ public function register(ContainerInterface $container) { /** * Set up the event dispatcher, required by Sprinkles to hook into the UF lifecycle. + * + * @return \RocketTheme\Toolbox\Event\EventDispatcher */ $container['eventDispatcher'] = function ($c) { return new EventDispatcher(); @@ -40,65 +42,27 @@ public function register(ContainerInterface $container) * Path/file locator service. * * Register custom streams for the application, and add paths for app-level streams. + * + * @return \UserFrosting\UniformResourceLocator\ResourceLocator */ $container['locator'] = function ($c) { + $locator = new ResourceLocator(\UserFrosting\ROOT_DIR); - $locator = new UniformResourceLocator(\UserFrosting\ROOT_DIR); - - $locator->addPath('build', '', \UserFrosting\BUILD_DIR_NAME); - $locator->addPath('log', '', \UserFrosting\APP_DIR_NAME . '/' . \UserFrosting\LOG_DIR_NAME); - $locator->addPath('cache', '', \UserFrosting\APP_DIR_NAME . '/' . \UserFrosting\CACHE_DIR_NAME); - $locator->addPath('session', '', \UserFrosting\APP_DIR_NAME . '/' . \UserFrosting\SESSION_DIR_NAME); - - // Use locator to initialize streams - ReadOnlyStream::setLocator($locator); - - // Fire up StreamBuilder - $c->streamBuilder; + // Register streams + $locator->registerStream('bakery', '', \UserFrosting\BAKERY_SYSTEM_DIR, true); + $locator->registerStream('bakery', '', \UserFrosting\BAKERY_DIR); + $locator->registerStream('sprinkles', '', ''); return $locator; }; - /** - * StreamBuilder, to fire up our custom StreamWrapper defined in the locator service. - */ - $container['streamBuilder'] = function ($c) { - - $streams = [ - 'build' => '\\RocketTheme\\Toolbox\\StreamWrapper\\Stream', - 'log' => '\\RocketTheme\\Toolbox\\StreamWrapper\\Stream', - 'cache' => '\\RocketTheme\\Toolbox\\StreamWrapper\\Stream', - 'session' => '\\RocketTheme\\Toolbox\\StreamWrapper\\Stream', - 'sprinkles' => '\\RocketTheme\\Toolbox\\StreamWrapper\\ReadOnlyStream', - 'assets' => '\\RocketTheme\\Toolbox\\StreamWrapper\\ReadOnlyStream', - 'schema' => '\\RocketTheme\\Toolbox\\StreamWrapper\\ReadOnlyStream', - 'templates' => '\\RocketTheme\\Toolbox\\StreamWrapper\\ReadOnlyStream', - 'extra' => '\\RocketTheme\\Toolbox\\StreamWrapper\\ReadOnlyStream', - 'locale' => '\\RocketTheme\\Toolbox\\StreamWrapper\\ReadOnlyStream', - 'config' => '\\RocketTheme\\Toolbox\\StreamWrapper\\ReadOnlyStream', - 'routes' => '\\RocketTheme\\Toolbox\\StreamWrapper\\ReadOnlyStream', - 'factories' => '\\RocketTheme\\Toolbox\\StreamWrapper\\ReadOnlyStream' - ]; - - // Before registering them, we need to unregister any that where previously registered. - // This will cause error when two scripts are run in succession from the CLI - foreach ($streams as $scheme => $handler) { - if (in_array($scheme, stream_get_wrappers())) { - stream_wrapper_unregister($scheme); - } - } - - $sb = new StreamBuilder($streams); - - return $sb; - }; - /** * Set up sprinkle manager service. + * + * @return \UserFrosting\System\Sprinkle\SprinkleManager */ $container['sprinkleManager'] = function ($c) { - $sprinkleManager = new SprinkleManager($c); - return $sprinkleManager; + return new SprinkleManager($c); }; } } diff --git a/app/system/SlimAppEvent.php b/app/system/SlimAppEvent.php index f1217a53f..65ca965f2 100644 --- a/app/system/SlimAppEvent.php +++ b/app/system/SlimAppEvent.php @@ -3,8 +3,10 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\System; use RocketTheme\Toolbox\Event\Event; @@ -15,6 +17,9 @@ */ class SlimAppEvent extends Event { + /** + * @var App + */ protected $app; public function __construct(App $app) @@ -22,6 +27,9 @@ public function __construct(App $app) $this->app = $app; } + /** + * @return App + */ public function getApp() { return $this->app; diff --git a/app/system/Sprinkle/Sprinkle.php b/app/system/Sprinkle/Sprinkle.php index 47070251f..5f2b3e264 100644 --- a/app/system/Sprinkle/Sprinkle.php +++ b/app/system/Sprinkle/Sprinkle.php @@ -3,13 +3,14 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\System\Sprinkle; use Interop\Container\ContainerInterface; use RocketTheme\Toolbox\Event\EventSubscriberInterface; -use Slim\App; /** * Sprinkle class diff --git a/app/system/Sprinkle/SprinkleManager.php b/app/system/Sprinkle/SprinkleManager.php index c206cea94..f1479d699 100644 --- a/app/system/Sprinkle/SprinkleManager.php +++ b/app/system/Sprinkle/SprinkleManager.php @@ -3,15 +3,16 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\System\Sprinkle; use Illuminate\Support\Str; use Interop\Container\ContainerInterface; -use RocketTheme\Toolbox\Event\EventDispatcher; -use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator; use UserFrosting\Support\Exception\FileNotFoundException; +use UserFrosting\Support\Exception\JsonException; /** * Sprinkle manager class. @@ -33,14 +34,9 @@ class SprinkleManager protected $sprinkles = []; /** - * @var string The full absolute base path to the sprinkles directory. + * @var string Path to the sprinkles directory. Will be used to register the location with the ResourceLocator */ - protected $sprinklesPath; - - /** - * @var string[] Keeps track of a mapping from resource stream names to relative paths. - */ - protected $resourcePaths; + protected $sprinklesPath = \UserFrosting\SPRINKLES_DIR . \UserFrosting\DS; /** * Create a new SprinkleManager object. @@ -50,60 +46,87 @@ class SprinkleManager public function __construct(ContainerInterface $ci) { $this->ci = $ci; - $this->sprinklesPath = \UserFrosting\APP_DIR_NAME . \UserFrosting\DS . \UserFrosting\SPRINKLES_DIR_NAME . \UserFrosting\DS; + } - $this->resourcePaths = [ - 'assets' => \UserFrosting\DS . \UserFrosting\ASSET_DIR_NAME, - 'config' => \UserFrosting\DS . \UserFrosting\CONFIG_DIR_NAME, - 'extra' => \UserFrosting\DS . \UserFrosting\EXTRA_DIR_NAME, - 'factories' => \UserFrosting\DS . \UserFrosting\FACTORY_DIR_NAME, - 'locale' => \UserFrosting\DS . \UserFrosting\LOCALE_DIR_NAME, - 'routes' => \UserFrosting\DS . \UserFrosting\ROUTE_DIR_NAME, - 'schema' => \UserFrosting\DS . \UserFrosting\SCHEMA_DIR_NAME, - 'sprinkles' => '', - 'templates' => \UserFrosting\DS . \UserFrosting\TEMPLATE_DIR_NAME - ]; + /** + * Register resource streams for all base sprinkles. + * For each sprinkle, register its resources and then run its initializer + */ + public function addResources() + { + foreach ($this->sprinkles as $sprinkleName => $sprinkle) { + $this->addSprinkleResources($sprinkleName); + } } /** - * Adds the relative path for a specified resource type in a Sprinkle to the resource's stream. - * - * @param string $resourceName + * Register a sprinkle as a locator location * @param string $sprinkleName - * @return string|bool The full path to specified resource for the specified Sprinkle (if found). */ - public function addResource($resourceName, $sprinkleName) + public function addSprinkleResources($sprinkleName) { - $resourcePath = $this->resourcePaths[$resourceName]; - $fullPath = $this->sprinklesPath . $sprinkleName . $resourcePath; + /** @var \UserFrosting\UniformResourceLocator\ResourceLocator $locator */ + $locator = $this->ci->locator; + $locator->registerLocation($sprinkleName, $this->getSprinklePath($sprinkleName)); + } - $this->ci->locator->addPath($resourceName, '', $fullPath); + /** + * Returns sprinkle base path from name + * + * @param string $sprinkleName + * @return string + */ + public function getSprinklePath($sprinkleName) + { + // Get Sprinkle and make sure it exist + $sprinkle = $this->getSprinkle($sprinkleName); + if (!$sprinkle) { + throw new FileNotFoundException("Sprinkle `$sprinkleName` doesn't exist."); + } - return $this->ci->locator->findResource("$resourceName://", true, false); + // Get path and make sure it exist + $path = $this->getSprinklesPath() . $sprinkle; + if (!file_exists($path)) { + throw new FileNotFoundException("Sprinkle `$sprinkleName` should be found at `$path`, but that directory doesn't exist."); + } - /* This would allow a stream to subnavigate to a specific sprinkle (e.g. "templates://core/") - Not sure if we need this. - $locator->addPath('templates', '$name', $sprinklesDirFragment . '/' . \UserFrosting\TEMPLATE_DIR_NAME); - */ + return $path; } /** - * Register resource streams for all base sprinkles. + * Returns the sprinkle class + * + * @param string $sprinkleName + * @return string */ - public function addResources() + protected function getSprinkleClass($sprinkleName) { - // For each sprinkle, register its resources and then run its initializer - foreach ($this->sprinkles as $sprinkleName => $sprinkle) { - $this->addResource('config', $sprinkleName); - $this->addResource('assets', $sprinkleName); - $this->addResource('extra', $sprinkleName); - $this->addResource('factories', $sprinkleName); - $this->addResource('locale', $sprinkleName); - $this->addResource('routes', $sprinkleName); - $this->addResource('schema', $sprinkleName); - $this->addResource('sprinkles', $sprinkleName); - $this->addResource('templates', $sprinkleName); - } + $className = Str::studly($sprinkleName); + + return $this->getSprinkleClassNamespace($sprinkleName) . "\\$className"; + } + + /** + * Returns the claculated sprinkle namespace + * @param string $sprinkleName + * @return string The Sprinkle Namespace + */ + public function getSprinkleClassNamespace($sprinkleName) + { + $className = Str::studly($sprinkleName); + + return "UserFrosting\\Sprinkle\\$className"; + } + + /** + * Returns the sprinkle service provider class + * + * @param string $sprinkleName + * @return string + */ + protected function getSprinkleDefaultServiceProvider($sprinkleName) + { + return $this->getSprinkleClassNamespace($sprinkleName) . '\\ServicesProvider\\ServicesProvider'; } /** @@ -111,19 +134,20 @@ public function addResources() * * Creates an object of a subclass of UserFrosting\System\Sprinkle\Sprinkle if defined for the sprinkle (converting to StudlyCase). * Otherwise, returns null. - * @param $name The name of the Sprinkle to initialize. + * @param string $sprinkleName The name of the Sprinkle to initialize. + * @return mixed Sprinkle class instance or null if no such class exist */ - public function bootSprinkle($name) + public function bootSprinkle($sprinkleName) { - $className = Str::studly($name); - $fullClassName = "\\UserFrosting\\Sprinkle\\$className\\$className"; + $fullClassName = $this->getSprinkleClass($sprinkleName); // Check that class exists. If not, set to null if (class_exists($fullClassName)) { $sprinkle = new $fullClassName($this->ci); + return $sprinkle; } else { - return null; + return; } } @@ -151,9 +175,9 @@ public function getSprinkles() * Initialize a list of Sprinkles, instantiating their boot classes (if they exist), * and subscribing them to the event dispatcher. * - * @param string[] $baseSprinkleNames + * @param string[] $sprinkleNames */ - public function init($sprinkleNames) + public function init(array $sprinkleNames) { foreach ($sprinkleNames as $sprinkleName) { $sprinkle = $this->bootSprinkle($sprinkleName); @@ -182,13 +206,30 @@ public function initFromSchema($schemaPath) * Return if a Sprinkle is available * Can be used by other Sprinkles to test if their dependencies are met * - * @param $name The name of the Sprinkle + * @param string $sprinkleName The name of the Sprinkle + * @return bool */ - public function isAvailable($name) + public function isAvailable($sprinkleName) { - return in_array($name, $this->getSprinkleNames()); + return (bool) $this->getSprinkle($sprinkleName); } + /** + * Find sprinkle value from the sprinkles.json + * + * @param string $sprinkleName + * @return string|false Return sprinkle name or false if sprinkle not found + */ + public function getSprinkle($sprinkleName) + { + $mathches = preg_grep("/^$sprinkleName$/i", $this->getSprinkleNames()); + + if (count($mathches) <= 0) { + return false; + } + + return array_values($mathches)[0]; + } /** * Interate through the list of loaded Sprinkles, and invoke their ServiceProvider classes. @@ -202,11 +243,12 @@ public function registerAllServices() /** * Register services for a specified Sprinkle. + * + * @param string $sprinkleName */ - public function registerServices($name) + public function registerServices($sprinkleName) { - $className = Str::studly($name); - $fullClassName = "\\UserFrosting\\Sprinkle\\$className\\ServicesProvider\\ServicesProvider"; + $fullClassName = $this->getSprinkleDefaultServiceProvider($sprinkleName); // Check that class exists, and register services if (class_exists($fullClassName)) { @@ -216,10 +258,33 @@ public function registerServices($name) } } + /** + * Returns sprinklePath parameter + * + * @return string + */ + public function getSprinklesPath() + { + return $this->sprinklesPath; + } + + /** + * Sets sprinklePath parameter + * + * @param string $sprinklesPath + * @return static + */ + public function setSprinklesPath($sprinklesPath) + { + $this->sprinklesPath = $sprinklesPath; + + return $this; + } + /** * Load list of Sprinkles from a JSON schema file (e.g. 'sprinkles.json'). * - * @param string $schemaPath + * @param string $schemaPath * @return string[] */ protected function loadSchema($schemaPath) @@ -227,10 +292,16 @@ protected function loadSchema($schemaPath) $sprinklesFile = @file_get_contents($schemaPath); if ($sprinklesFile === false) { - $errorMessage = "Error: Unable to determine Sprinkle load order. File '$schemaPath' not found or unable to read. Please create a 'sprinkles.json' file and try again."; + $errorMessage = "Error: Unable to determine Sprinkle load order. File '$schemaPath' not found or unable to read. Please create a 'sprinkles.json' file and try again."; throw new FileNotFoundException($errorMessage); } - return json_decode($sprinklesFile); + // Make sure sprinkle contains valid json + if (!$data = json_decode($sprinklesFile)) { + $errorMessage = "Error: Unable to determine Sprinkle load order. File '$schemaPath' doesn't contain valid json : " . json_last_error_msg(); + throw new JsonException($errorMessage); + } + + return $data; } } diff --git a/app/system/UserFrosting.php b/app/system/UserFrosting.php index 4f569ec4b..b784d6fe1 100644 --- a/app/system/UserFrosting.php +++ b/app/system/UserFrosting.php @@ -3,8 +3,10 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\System; use RocketTheme\Toolbox\Event\EventDispatcher; @@ -12,8 +14,10 @@ use Slim\App; use Slim\Container; use UserFrosting\Support\Exception\FileNotFoundException; -use UserFrosting\System\Facade; +/** + * UserFrosting Main Class + */ class UserFrosting { /** @@ -26,24 +30,36 @@ class UserFrosting */ protected $app; + /** + * @var bool Is the app in CLI mode + */ + protected $isCli; + /** * Create the UserFrosting application instance. + * + * @param bool $cli Is the app in CLI mode. Set to false if setting up in an HTTP/web environment, true if setting up for CLI scripts. */ - public function __construct() + public function __construct($cli = false) { // First, we create our DI container - $this->ci = new Container; + $this->ci = new Container(); + + // Assign vars + $this->isCli = $cli; // Set up facade reference to container. Facade::setFacadeContainer($this->ci); + + // Setup UF App + $this->setupApp(); } /** * Fires an event with optional parameters. * - * @param string $eventName - * @param Event $event - * + * @param string $eventName + * @param Event|null $event * @return Event */ public function fireEvent($eventName, Event $event = null) @@ -57,7 +73,7 @@ public function fireEvent($eventName, Event $event = null) /** * Return the underlying Slim App instance, if available. * - * @return Slim\App + * @return App */ public function getApp() { @@ -67,65 +83,25 @@ public function getApp() /** * Return the DI container. * - * @return Slim\Container + * @return Container */ public function getContainer() { return $this->ci; } - /** - * Include all defined routes in route stream. - * - * Include them in reverse order to allow higher priority routes to override lower priority. - */ - public function loadRoutes() - { - // Since routes aren't encapsulated in a class yet, we need this workaround :( - global $app; - $app = $this->app; - - $routePaths = array_reverse($this->ci->locator->findResources('routes://', true, true)); - foreach ($routePaths as $path) { - $routeFiles = glob($path . '/*.php'); - foreach ($routeFiles as $routeFile) { - require_once $routeFile; - } - } - } - /** * Initialize the application. Set up Sprinkles and the Slim app, define routes, register global middleware, and run Slim. */ public function run() { - $this->setupSprinkles(); - - // Set the configuration settings for Slim in the 'settings' service - $this->ci->settings = $this->ci->config['settings']; - - // Next, we'll instantiate the Slim application. Note that the application is required for the SprinkleManager to set up routes. - $this->app = new App($this->ci); - - $slimAppEvent = new SlimAppEvent($this->app); - - $this->fireEvent('onAppInitialize', $slimAppEvent); - - // Set up all routes - $this->loadRoutes(); - - // Add global middleware - $this->fireEvent('onAddGlobalMiddleware', $slimAppEvent); - $this->app->run(); } /** - * Register system services, load all sprinkles, and add their resources and services. - * - * @param bool $isWeb Set to true if setting up in an HTTP/web environment, false if setting up for CLI scripts. + * Register system services, load all sprinkles, and add their resources and services */ - public function setupSprinkles($isWeb = true) + protected function setupSprinkles() { // Register system services $serviceProvider = new ServicesProvider(); @@ -137,7 +113,7 @@ public function setupSprinkles($isWeb = true) try { $sprinkleManager->initFromSchema(\UserFrosting\SPRINKLES_SCHEMA_FILE); } catch (FileNotFoundException $e) { - if ($isWeb) { + if (!$this->isCli) { $this->renderSprinkleErrorPage($e->getMessage()); } else { $this->renderSprinkleErrorCli($e->getMessage()); @@ -155,21 +131,45 @@ public function setupSprinkles($isWeb = true) $this->fireEvent('onSprinklesRegisterServices'); } + /** + * Setup UserFrosting App, load sprinkles, load routes, etc. + */ + protected function setupApp() + { + // Setup sprinkles + $this->setupSprinkles(); + + // Set the configuration settings for Slim in the 'settings' service + $this->ci->settings = $this->ci->config['settings']; + + // Next, we'll instantiate the Slim application. Note that the application is required for the SprinkleManager to set up routes. + $this->app = new App($this->ci); + + $slimAppEvent = new SlimAppEvent($this->app); + + $this->fireEvent('onAppInitialize', $slimAppEvent); + + // Add global middleware + $this->fireEvent('onAddGlobalMiddleware', $slimAppEvent); + } + /** * Render a basic error page for problems with loading Sprinkles. + * + * @param string $errorMessage Message to display [Default ""] */ - protected function renderSprinkleErrorPage($errorMessage = "") + protected function renderSprinkleErrorPage($errorMessage = '') { ob_clean(); - $title = "UserFrosting Application Error"; - $errorMessage = "Unable to start site. Contact owner.

" . - "Version: UserFrosting ".\UserFrosting\VERSION."
" . + $title = 'UserFrosting Application Error'; + $errorMessage = 'Unable to start site. Contact owner.

' . + 'Version: UserFrosting '.\UserFrosting\VERSION.'
' . $errorMessage; $output = sprintf( "" . - "%s

%s

%s", + '%s

%s

%s', $title, $title, $errorMessage @@ -179,8 +179,10 @@ protected function renderSprinkleErrorPage($errorMessage = "") /** * Render a CLI error message for problems with loading Sprinkles. + * + * @param string $errorMessage Message to display [Default ""] */ - protected function renderSprinkleErrorCli($errorMessage = "") + protected function renderSprinkleErrorCli($errorMessage = '') { exit($errorMessage . PHP_EOL); } diff --git a/app/tests/DatabaseTransactions.php b/app/tests/DatabaseTransactions.php index ed2225bef..74844837b 100644 --- a/app/tests/DatabaseTransactions.php +++ b/app/tests/DatabaseTransactions.php @@ -3,8 +3,10 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Tests; /** @@ -17,12 +19,10 @@ trait DatabaseTransactions { /** * Handle database transactions on the specified connections. - * - * @return void */ public function beginDatabaseTransaction() { - $database = $this->ci['db']; + $database = $this->ci->db; foreach ($this->connectionsToTransact() as $name) { $database->connection($name)->beginTransaction(); @@ -45,4 +45,4 @@ protected function connectionsToTransact() return property_exists($this, 'connectionsToTransact') ? $this->connectionsToTransact : [null]; } -} \ No newline at end of file +} diff --git a/app/tests/TestCase.php b/app/tests/TestCase.php index 1115abee4..2ff5ca74b 100644 --- a/app/tests/TestCase.php +++ b/app/tests/TestCase.php @@ -3,13 +3,15 @@ * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ + namespace UserFrosting\Tests; -use Slim\App; use PHPUnit\Framework\TestCase as BaseTestCase; use UserFrosting\System\UserFrosting; +use UserFrosting\Sprinkle\Core\Facades\Debug; /** * Class to handle Test @@ -18,13 +20,6 @@ */ class TestCase extends BaseTestCase { - /** - * The Slim application instance. - * - * @var \Slim\App - */ - protected $app; - /** * The global container object, which holds all your services. * @@ -55,12 +50,10 @@ class TestCase extends BaseTestCase /** * Setup the test environment. - * - * @return void */ protected function setUp() { - if (!$this->app) { + if (!$this->ci) { $this->refreshApplication(); } @@ -76,25 +69,20 @@ protected function setUp() /** * Boot the testing helper traits. * - * @return void + * @deprecated */ protected function setUpTraits() { $uses = array_flip(class_uses_recursive(static::class)); - /*if (isset($uses[DatabaseMigrations::class])) { - $this->runDatabaseMigrations(); - }*/ - if (isset($uses[DatabaseTransactions::class])) { + Debug::warning("`UserFrosting\Tests\DatabaseTransactions` has been deprecated and will be removed in future versions. Please use `UserFrosting\Sprinkle\Core\Tests\DatabaseTransactions` class instead."); $this->beginDatabaseTransaction(); } } /** * Refresh the application instance. - * - * @return void */ protected function refreshApplication() { @@ -104,32 +92,23 @@ protected function refreshApplication() // no way to override environment vars that have already been set. putenv('UF_MODE=testing'); - // Setup the sprinkles - $uf = new UserFrosting(); - - // Set argument as false, we are using the CLI - $uf->setupSprinkles(false); + // Setup the base UF app + $uf = new UserFrosting(true); // Get the container $this->ci = $uf->getContainer(); - - // Next, we'll instantiate the application. Note that the application is required for the SprinkleManager to set up routes. - $this->app = new App($this->ci); } /** * Clean up the testing environment before the next test. - * - * @return void */ protected function tearDown() { - if ($this->app) { + if ($this->ci) { foreach ($this->beforeApplicationDestroyedCallbacks as $callback) { call_user_func($callback); } - $this->app = null; $this->ci = null; } @@ -142,8 +121,7 @@ protected function tearDown() /** * Register a callback to be run after the application is created. * - * @param callable $callback - * @return void + * @param callable $callback */ public function afterApplicationCreated(callable $callback) { @@ -157,10 +135,11 @@ public function afterApplicationCreated(callable $callback) /** * Asserts that collections are equivalent. * - * @param array $expected - * @param array $actual - * @param string $message - * @throws PHPUnit_Framework_AssertionFailedError + * @param array $expected + * @param array $actual + * @param string $key [description] + * @param string $message [description] + * @throws \PHPUnit_Framework_AssertionFailedError */ public static function assertCollectionsSame($expected, $actual, $key = 'id', $message = '') { @@ -187,11 +166,27 @@ public static function assertCollectionsSame($expected, $actual, $key = 'id', $m } } + /** + * Call protected/private method of a class. + * + * @param object &$object Instantiated object that we will run method on. + * @param string $methodName Method name to call + * @param array $parameters Array of parameters to pass into method. + * @return mixed Method return. + */ + public function invokeMethod(&$object, $methodName, array $parameters = []) + { + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + + return $method->invokeArgs($object, $parameters); + } + /** * Register a callback to be run before the application is destroyed. * - * @param callable $callback - * @return void + * @param callable $callback */ protected function beforeApplicationDestroyed(callable $callback) { @@ -205,8 +200,8 @@ protected function beforeApplicationDestroyed(callable $callback) /** * Cast an item to an array if it has a toArray() method. * - * @param $item Collection|mixed[]|mixed - * @return mixed|mixed[] + * @param object $item + * @return mixed */ protected static function castToComparable($item) { @@ -215,6 +210,8 @@ protected static function castToComparable($item) /** * Remove all relations on a collection of models. + * + * @param array $models */ protected static function ignoreRelations($models) { @@ -223,14 +220,20 @@ protected static function ignoreRelations($models) } } + /** + * cloneObjectArray + * + * @param array $original + * @return array + */ protected function cloneObjectArray($original) { $cloned = []; - + foreach ($original as $k => $v) { $cloned[$k] = clone $v; } return $cloned; } -} \ No newline at end of file +} diff --git a/app/tests/Unit/ExampleTest.php b/app/tests/Unit/ExampleTest.php index 4ac3e840f..69d4c75fc 100644 --- a/app/tests/Unit/ExampleTest.php +++ b/app/tests/Unit/ExampleTest.php @@ -1,19 +1,23 @@ assertTrue(true); } -} \ No newline at end of file +} diff --git a/app/tests/Unit/SprinkleManagerTest.php b/app/tests/Unit/SprinkleManagerTest.php new file mode 100644 index 000000000..d8953f70f --- /dev/null +++ b/app/tests/Unit/SprinkleManagerTest.php @@ -0,0 +1,255 @@ +fakeCi = m::mock(ContainerInterface::class); + $this->fakeCi->eventDispatcher = new eventDispatcherStub(); + $this->fakeCi->locator = new ResourceLocatorStub(); + + // Setup test sprinkle mock class so it can be found by `class_exist` + m::mock('UserFrosting\Sprinkle\Test\Test'); + } + + public function tearDown() + { + parent::tearDown(); + m::close(); + } + + /** + * @return SprinkleManager + */ + public function testConstructor() + { + $sprinkleManager = new SprinkleManager($this->fakeCi); + $this->assertInstanceOf(SprinkleManager::class, $sprinkleManager); + + return $sprinkleManager; + } + + /** + * @depends testConstructor + * @param SprinkleManager $sprinkleManager + */ + public function testGetSetSprinklesPath(SprinkleManager $sprinkleManager) + { + $sprinkleManager->setSprinklesPath('/foo'); + $this->assertSame('/foo', $sprinkleManager->getSprinklesPath()); + } + + /** + * @depends testConstructor + * @param SprinkleManager $sprinkleManager + * @return SprinkleManager + */ + public function testInitFromSchema(SprinkleManager $sprinkleManager) + { + $sprinkleManager->initFromSchema(__DIR__ . '/data/sprinkles.json'); + + return $sprinkleManager; + } + + /** + * @depends testConstructor + * @expectedException \UserFrosting\Support\Exception\FileNotFoundException + */ + public function testLoadSprinkleWithNonExistingFile() + { + $sprinkleManager = new SprinkleManager($this->fakeCi); + $sprinkleManager->initFromSchema('foo.json'); + } + + /** + * @depends testInitFromSchema + * @param SprinkleManager $sprinkleManager + */ + public function testGetSprinkles(SprinkleManager $sprinkleManager) + { + $sprinkles = $sprinkleManager->getSprinkles(); + $this->assertEquals([ + 'foo' => null, + 'bar' => null, + 'test' => new \UserFrosting\Sprinkle\Test\Test() + ], $sprinkles); + } + + /** + * @depends testInitFromSchema + * @param SprinkleManager $sprinkleManager + */ + public function testGetSprinkleNames(SprinkleManager $sprinkleManager) + { + $sprinkles = $sprinkleManager->getSprinkleNames(); + $this->assertSame(['foo', 'bar', 'test'], $sprinkles); + } + + /** + * @depends testInitFromSchema + * @depends testGetSprinkleNames + * @param string $sprinkleName + * @param bool $isAvailable + * @param SprinkleManager $sprinkleManager + * @testWith ["bar", true] + * ["test", true] + * ["foo", true] + * ["fOo", true] + * ["foO", true] + * ["Foo", true] + * ["FOO", true] + * ["fo", false] + * ["o", false] + * ["oo", false] + * ["f0o", false] + * ["foofoo", false] + * ["1foo1", false] + * ["barfoo", false] + * ["blah", false] + */ + public function testIsAvailable($sprinkleName, $isAvailable, SprinkleManager $sprinkleManager) + { + $this->assertSame($isAvailable, $sprinkleManager->isAvailable($sprinkleName)); + } + + /** + * @depends testInitFromSchema + * @param string $sprinkleName + * @param SprinkleManager $sprinkleManager + * @testWith ["foo"] + * ["bar"] + * ["test"] + */ + public function testGetSprinklePath($sprinkleName, SprinkleManager $sprinkleManager) + { + $basePath = 'app/tests/Unit/data/'; + $sprinkleManager->setSprinklesPath($basePath); + $this->assertSame($basePath . $sprinkleName, $sprinkleManager->getSprinklePath($sprinkleName)); + } + + /** + * @depends testInitFromSchema + * @depends testGetSprinklePath + * @expectedException \UserFrosting\Support\Exception\FileNotFoundException + * @param SprinkleManager $sprinkleManager + */ + public function testGetSprinklePathWherePathDoesntExist(SprinkleManager $sprinkleManager) + { + $basePath = 'app/tests/Unit/foo/'; + $sprinkleManager->setSprinklesPath($basePath); + $sprinkleManager->getSprinklePath('foo'); + } + + /** + * @depends testInitFromSchema + * @depends testGetSprinklePath + * @expectedException \UserFrosting\Support\Exception\FileNotFoundException + * @param SprinkleManager $sprinkleManager + */ + public function testGetSprinklePathWhereSprinkleDoesntExist(SprinkleManager $sprinkleManager) + { + $sprinkleManager->getSprinklePath('blah'); + } + + /** + * @depends testInitFromSchema + * @param SprinkleManager $sprinkleManager + */ + public function testRegisterAllServices(SprinkleManager $sprinkleManager) + { + // Set Expectations for test sprinkle ServiceProvider + // @see https://stackoverflow.com/a/13390001/445757 + $this->getMockBuilder('nonexistant') + ->setMockClassName('foo') + ->setMethods(['register']) + ->getMock(); + class_alias('foo', 'UserFrosting\Sprinkle\Test\ServicesProvider\ServicesProvider'); + + $sprinkleManager->registerAllServices(); + } + + /** + * @depends testInitFromSchema + * @depends testGetSprinklePath + * @param SprinkleManager $sprinkleManager + */ + public function testAddResources(SprinkleManager $sprinkleManager) + { + $basePath = 'app/tests/Unit/data/'; + $sprinkleManager->setSprinklesPath($basePath); + $sprinkleManager->addResources(); + } + + /** + * This will work, as long as it contains valid json + * + * @depends testConstructor + * @depends testGetSprinkles + */ + public function testLoadSprinkleWithTxtFile() + { + $sprinkleManager = new SprinkleManager($this->fakeCi); + $sprinkleManager->initFromSchema(__DIR__ . '/data/sprinkles.txt'); + $this->assertCount(3, $sprinkleManager->getSprinkles()); + } + + /** + * @depends testConstructor + * @expectedException \UserFrosting\Support\Exception\JsonException + */ + public function testLoadSprinkleWithBadJson() + { + $sprinkleManager = new SprinkleManager($this->fakeCi); + $sprinkleManager->initFromSchema(__DIR__ . '/data/sprinkles-bad.json'); + } + + /** + * @depends testConstructor + * @depends testIsAvailable + */ + public function testLoadSprinkleWithDuplicateSprinkles() + { + $sprinkleManager = new SprinkleManager($this->fakeCi); + $sprinkleManager->initFromSchema(__DIR__ . '/data/sprinkles-duplicate.json'); + $this->assertEquals([ + 'foo' => null, + 'FOO' => null, + 'bar' => null, + ], $sprinkleManager->getSprinkles()); + + $this->assertTrue($sprinkleManager->isAvailable('Foo')); + } +} + +class eventDispatcherStub +{ + public function addSubscriber() + { + } +} + +class ResourceLocatorStub +{ + public function registerLocation() + { + } +} diff --git a/app/tests/Unit/data/bar/.gitkeep b/app/tests/Unit/data/bar/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/app/tests/Unit/data/foo/.gitkeep b/app/tests/Unit/data/foo/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/app/tests/Unit/data/sprinkles-bad.json b/app/tests/Unit/data/sprinkles-bad.json new file mode 100644 index 000000000..adbc379bc --- /dev/null +++ b/app/tests/Unit/data/sprinkles-bad.json @@ -0,0 +1,7 @@ +{ + "base": [ + "foo" + "bar" + "test" + ] +} diff --git a/app/tests/Unit/data/sprinkles-duplicate.json b/app/tests/Unit/data/sprinkles-duplicate.json new file mode 100644 index 000000000..d8be11781 --- /dev/null +++ b/app/tests/Unit/data/sprinkles-duplicate.json @@ -0,0 +1,11 @@ +{ + "require": { + + }, + "base": [ + "foo", + "FOO", + "bar", + "bar" + ] +} diff --git a/app/tests/Unit/data/sprinkles.json b/app/tests/Unit/data/sprinkles.json new file mode 100644 index 000000000..8a1ffa64b --- /dev/null +++ b/app/tests/Unit/data/sprinkles.json @@ -0,0 +1,10 @@ +{ + "require": { + + }, + "base": [ + "foo", + "bar", + "test" + ] +} diff --git a/app/tests/Unit/data/sprinkles.txt b/app/tests/Unit/data/sprinkles.txt new file mode 100644 index 000000000..8a1ffa64b --- /dev/null +++ b/app/tests/Unit/data/sprinkles.txt @@ -0,0 +1,10 @@ +{ + "require": { + + }, + "base": [ + "foo", + "bar", + "test" + ] +} diff --git a/app/tests/Unit/data/test/.gitkeep b/app/tests/Unit/data/test/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/bakery b/bakery index 4d0348d12..9098d244c 100644 --- a/bakery +++ b/bakery @@ -10,10 +10,11 @@ * \___/|___/\___|_| \_| |_| \___/|___/\__|_|_| |_|\__, | * __/ | * |___/ - * http://www.userfrosting.com + * UserFrosting (http://www.userfrosting.com) * * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + * @copyright Copyright (c) 2019 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License) */ /** diff --git a/build/.npmrc b/build/.npmrc new file mode 100644 index 000000000..43c97e719 --- /dev/null +++ b/build/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/build/before_install.sh b/build/before_install.sh index 37d752434..b37ce4f7b 100644 --- a/build/before_install.sh +++ b/build/before_install.sh @@ -16,7 +16,7 @@ if [ "$DB" == "mysql" ] ; then echo "Setting up mysql ..." mysql -u root -e "CREATE DATABASE userfrosting;" mysql -u root -e "GRANT ALL ON userfrosting.* TO 'travis'@'localhost';" - printf "UF_MODE=\"dev\"\nDB_DRIVER=\"mysql\"\nDB_HOST=\"localhost\"\nDB_PORT=\"3306\"\nDB_NAME=\"userfrosting\"\nDB_USER=\"travis\"\nDB_PASSWORD=\"\"\n" > app/.env + printf "UF_MODE=\"debug\"\nDB_DRIVER=\"mysql\"\nDB_HOST=\"localhost\"\nDB_PORT=\"3306\"\nDB_NAME=\"userfrosting\"\nDB_USER=\"travis\"\nDB_PASSWORD=\"\"\nTEST_DB=\"default\"\n" > app/.env fi # @@ -26,7 +26,7 @@ if [ "$DB" == "pgsql" ] ; then echo "Setting up pgsql ..." psql -c "CREATE DATABASE userfrosting;" -U postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE userfrosting TO postgres;" -U postgres - printf "UF_MODE=\"dev\"\nDB_DRIVER=\"pgsql\"\nDB_HOST=\"localhost\"\nDB_PORT=\"5432\"\nDB_NAME=\"userfrosting\"\nDB_USER=\"postgres\"\nDB_PASSWORD=\"\"\n" > app/.env + printf "UF_MODE=\"debug\"\nDB_DRIVER=\"pgsql\"\nDB_HOST=\"localhost\"\nDB_PORT=\"5432\"\nDB_NAME=\"userfrosting\"\nDB_USER=\"postgres\"\nDB_PASSWORD=\"\"\nTEST_DB=\"default\"\n" > app/.env fi # @@ -35,5 +35,5 @@ fi if [ "$DB" == "sqlite" ] ; then echo "Setting up sqlite ..." touch userfrosting.db - printf "UF_MODE=\"dev\"\nDB_DRIVER=\"sqlite\"\nDB_NAME=\"userfrosting.db\"\n" > app/.env + printf "UF_MODE=\"debug\"\nDB_DRIVER=\"sqlite\"\nDB_NAME=\"userfrosting.db\"\nTEST_DB=\"default\"\n" > app/.env fi diff --git a/build/gulpfile.esm.js b/build/gulpfile.esm.js new file mode 100644 index 000000000..dbfb37cbc --- /dev/null +++ b/build/gulpfile.esm.js @@ -0,0 +1,409 @@ +import browserifyDependencies from "@userfrosting/browserify-dependencies"; +import Bundler, { MergeRawConfigs, ValidateRawConfig } from "@userfrosting/gulp-bundle-assets"; +import { bower as mergeBowerDeps, npm as mergeNpmDeps } from "@userfrosting/merge-package-dependencies"; +import childProcess, { exec as _exec } from "child_process"; +import { sync as deleteSync } from "del"; +import { config as envConfig } from "dotenv"; +import { existsSync, readFileSync, writeFileSync } from "fs"; +import gulp from "gulp"; +import minifyCss from "gulp-clean-css"; +import concatJs from "gulp-concat"; +import concatCss from "gulp-concat-css"; +import prune from "gulp-prune"; +import rev from "gulp-rev"; +import minifyJs from "gulp-uglify-es"; +import { info } from "gulplog"; +import { resolve as resolvePath } from "path"; +import stripAnsi from "strip-ansi"; +import { promisify } from "util"; + +// Promisify exec +const exec = promisify(_exec); + +// Path constants +const rootDir = ".."; +const sprinklesDir = `${rootDir}/app/sprinkles/`; +const sprinklesSchemaPath = `${rootDir}/app/sprinkles.json`; +const publicAssetsDir = `${rootDir}/public/assets/`; +const legacyVendorAssetsGlob = `${rootDir}/sprinkles/*/assets/vendor/**`; +const sprinkleBundleFile = "asset-bundles.json"; +const vendorAssetsDir = `${rootDir}/app/assets/`; +const logFile = `${rootDir}/app/logs/build.log`; + +// Load environment variables +envConfig({ path: "../app/.env" }); + +// Set up logging + +// Write starting command to log +writeFileSync(logFile, "\n\n" + process.argv.join(" ") + "\n\n", { + flag: 'a' +}); + +// Verbosity +const debug = (process.env.UF_MODE === "debug"); + +// Catch stdout and write to build log +const write = process.stdout.write; +const w = (...args) => { + process.stdout.write = write; + process.stdout.write(...args); + + writeFileSync(logFile, stripAnsi(args[0]), { + flag: 'a' + }); + + process.stdout.write = w; +}; +process.stdout.write = w; + +/** + * Prints to stdout with newline when UF_MODE is dev. + * @param {string} message Message to log. If source specified, must be string. + * @param {string} source Message source. + */ +function Logger(message, source) { + const messageLines = message.split("\n"); + messageLines.forEach(msg => { + if (source) + info(`${source}: ${msg}`); + else + info(msg); + }); +} + +/** + * Runs the provided command and captures output. + * @param {string} cmd Command to execute. + * @param {childProcess.ExecOptions} options Options to pass to `exec`. + */ +async function RunCommand(cmd, options) { + Logger(`Running command "${cmd}"`, "CMD"); + + try { + const result = await exec(cmd, options); + if (result.stdout) Logger(result.stdout, `CMD> ${cmd}`); + if (result.stderr) Logger(result.stderr, `CMD> ${cmd}`); + } catch (e) { + if (e.stdout) Logger(e.stdout, `CMD> ${cmd}`); + if (e.stderr) Logger(e.stderr, `CMD> ${cmd}`); + Logger(`Command "${cmd}" has completed with an error`, "CMD"); + throw e; + } + + Logger(`Command "${cmd}" has completed successfully`, "CMD"); +} + +// Load sprinkles +let sprinkles; +try { + sprinkles = JSON.parse(readFileSync(sprinklesSchemaPath)).base; +} +catch (error) { + Logger(sprinklesSchemaPath + " could not be loaded, does it exist?"); + throw error; +} + +/** + * Installs vendor assets. Mapped to npm script "uf-assets-install". + */ +export async function assetsInstall() { + // Clean up any legacy assets + if (deleteSync(legacyVendorAssetsGlob, { force: true })) + Logger("Legacy frontend vendor assets were deleted. Frontend vendor assets are now installed to 'app/assets'."); + + // See if there are any npm packages + // TODO Would be better to read in file here then hand it off since we can avoid redundant `existsSync` calls + const npmPaths = []; + for (const sprinkle of sprinkles) { + const path = sprinklesDir + sprinkle + "/package.json"; + if (existsSync(path)) npmPaths.push(path); + } + + if (npmPaths.length > 0) { + // Install npm dependencies + Logger("Installing vendor assets with NPM...") + + // Remove package.json and package-lock.json (if it happens to exist) + deleteSync(vendorAssetsDir + "package.json", { force: true }); + deleteSync(vendorAssetsDir + "package-lock.json", { force: true }); + + // Generate package.json + const npmTemplate = { + // Private makes sure it isn't published, and cuts out a lot of unnecessary fields. + private: true + }; + Logger("Collating dependencies..."); + const pkg = mergeNpmDeps(npmTemplate, npmPaths, vendorAssetsDir, true); + Logger("Dependency collation complete."); + + Logger("Using npm from PATH variable"); + + // Remove any existing unneeded dependencies + Logger("Removing extraneous dependencies"); + await RunCommand("npm prune", { + cwd: vendorAssetsDir + }); + + // Perform installation + Logger("Installing dependencies"); + await RunCommand("npm install", { + cwd: vendorAssetsDir + }); + + // Conduct audit + Logger("Running audit"); + try { + await RunCommand("npm audit", { + cwd: vendorAssetsDir + }); + } + catch { + Logger("There appear to be some vulerabilities within your dependencies. Updating is recommended."); + } + + // Browserify dependencies + Logger("Running browserify against npm dependencies with a compatible main entrypoint"); + deleteSync(vendorAssetsDir + "browser_modules/", { force: true }); + await browserifyDependencies({ + dependencies: Object.keys(pkg.dependencies), + inputDir: vendorAssetsDir + "node_modules/", + outputDir: vendorAssetsDir + "browser_modules/" + }) + } + else { + // Delete npm artefacts + deleteSync([ + vendorAssetsDir + "package.json", + vendorAssetsDir + "node_modules/", + vendorAssetsDir + "package-lock.json", + vendorAssetsDir + "browser_modules/" + ], { force: true }); + } + + // See if there are any Bower packages + // TODO Would be better to read in file here then hand it off since we can avoid redundant `existsSync` calls + const bowerPaths = []; + for (const sprinkle of sprinkles) { + const path = sprinklesDir + sprinkle + "/bower.json"; + if (existsSync(path)) { + // TODO: We should really have a link to docs in the message + console.warn(`DEPRECATED: Detected bower.json in ${sprinkle} Sprinkle. Support for bower (bower.json) will be removed in the future, please use npm/yarn (package.json) instead.`); + bowerPaths.push(path); + } + } + + if (bowerPaths.length > 0) { + // Install bower dependencies + Logger("Installing vendor assets with Bower...") + + // TODO I think we might be able to get away with removing this + deleteSync(vendorAssetsDir + "bower.json", { force: true }); + + // Generate package.json + const bowerTemplate = { + name: "uf-vendor-assets" + }; + Logger("Collating dependencies..."); + mergeBowerDeps(bowerTemplate, bowerPaths, vendorAssetsDir, true); + Logger("Dependency collation complete."); + + // Perform installation + Logger("Installed dependencies"); + // --allow-root stops bower from complaining about being in 'sudo' in various situations + await RunCommand("bower install -q --allow-root", { + cwd: vendorAssetsDir + }); + + + // Prune any unnecessary dependencies + Logger("Removing extraneous dependencies"); + // --allow-root stops bower from complaining about being in 'sudo' in various situations + await RunCommand("bower prune -q --allow-root", { + cwd: vendorAssetsDir + }); + } + else { + // Remove bower artefacts + deleteSync([ + vendorAssetsDir + "bower.json", + vendorAssetsDir + "bower_components/" + ], { force: true }); + } +}; + +/** + * Compiles frontend assets. Mapped to npm script "uf-bundle". + */ +export function bundle() { + // Build sources list + const sources = []; + for (const sprinkle of sprinkles) { + sources.push(sprinklesDir + sprinkle + "/assets/**"); + } + sources.push(vendorAssetsDir + "node_modules/**"); + sources.push(vendorAssetsDir + "browser_modules/**"); + sources.push(vendorAssetsDir + "bower_components/**"); + + // Create bundle stream factories object + const bundleBuilder = { + Scripts: (src, name) => { + return src + .pipe(concatJs(name + ".js")) + .pipe(minifyJs()) + .pipe(rev()); + }, + Styles: (src, name) => { + return src + .pipe(concatCss(name + ".css")) + .pipe(minifyCss()) + .pipe(rev()); + } + }; + + // Load up bundle configurations + const rawConfigs = []; + for (const sprinkle of sprinkles) { + Logger("Looking for asset bundles in sprinkle " + sprinkle); + + // Try to read file + let fileContent; + try { + fileContent = readFileSync(sprinklesDir + sprinkle + "/" + sprinkleBundleFile); + Logger(`Read '${sprinkleBundleFile}'.`, sprinkle); + } + catch (error) { + Logger(`No '${sprinkleBundleFile}' detected, or can't be read.`, sprinkle); + continue; + } + + // Validate (JSON and content) + let rawConfig; + try { + rawConfig = JSON.parse(fileContent); + ValidateRawConfig(rawConfig); + rawConfigs.push(rawConfig); + Logger("Asset bundles validated and loaded.", sprinkle); + } + catch (error) { + Logger("Asset bundle is invalid.", sprinkle); + throw error; + } + } + + // Merge bundles + Logger("Merging asset bundles..."); + const rawConfig = MergeRawConfigs(rawConfigs); + + // Set up virtual path rules + rawConfig.VirtualPathRules = [ + ["../app/assets/node_modules", "./assets/vendor"], + ["../app/assets/bower_components", "./assets/vendor"]]; + for (const sprinkle of sprinkles) { + rawConfig.VirtualPathRules.push([ + sprinklesDir + sprinkle + "/assets", "./assets" + ]); + } + + // Set base path for bundle resources to align with virtual paths + rawConfig.BundlesVirtualBasePath = "./assets/"; + + /** + * Bundle results callback. + * @param {Map 0 || debug) { + Logger(message, "gulp-bundle-assets"); + } + } + rawConfig.Logger = LoggerAdapter; + + // Open stream + Logger("Starting bundle process proper..."); + return gulp.src(sources, { sourcemaps: true }) + .pipe(new Bundler(rawConfig, bundleBuilder, bundleResults)) + .pipe(prune(publicAssetsDir)) + .pipe(gulp.dest(publicAssetsDir, { sourcemaps: "." })); +}; + +/** + * Run all frontend tasks. + */ +export const frontend = gulp.series(assetsInstall, bundle); + +/** + * Clean vendor and public asset folders. + * @param {() => {}} done Used to mark task completion. + */ +export function clean(done) { + try { + Logger("Cleaning vendor assets..."); + deleteSync(vendorAssetsDir, { force: true }); + Logger("Finished cleaning vendor assets."); + + Logger("Cleaning public assets..."); + deleteSync(publicAssetsDir, { force: true }) + Logger("Finsihed cleaning public assets."); + + done(); + } + catch (error) { + done(error); + } +}; diff --git a/build/gulpfile.js b/build/gulpfile.js deleted file mode 100755 index 72cd7646a..000000000 --- a/build/gulpfile.js +++ /dev/null @@ -1,183 +0,0 @@ -/* To build bundles... - 1. npm run uf-bundle-build - 2. npm run uf-bundle - 3. npm run uf-bundle-clean - - To get frontend vendor packages via bower - 1. npm run uf-assets-install - - To clean frontend vendor assets - 1. npm run uf-assets-clean -*/ -"use strict"; -const gulp = require('gulp'); -const del = require('del'); -const fs = require('fs'); -const shell = require('shelljs'); -const plugins = require('gulp-load-plugins')(); - -const sprinklesDir = '../app/sprinkles/'; - -const sprinklesSchemaPath = '../app/sprinkles.json'; - -// The Sprinkle load order from sprinkles.json -const sprinkles = ['core'].concat(require(`${sprinklesSchemaPath}`)['base']); - -// The directory where the bundle task should place compiled assets. -// The names of assets in bundle.result.json will be specified relative to this path. -const publicAssetsDir = '../public/assets/'; - -// name of the bundle file -const bundleFile = 'asset-bundles.json'; - -// Compiled bundle config file -const bundleConfigFile = `./${bundleFile}`; - -// Destination directory for vendor assets -const vendorAssetsDir = './assets/vendor'; - -/** - * Vendor asset task - */ - -// Gulp task to install vendor packages via bower -gulp.task('bower-install', () => { - "use strict"; - shell.cd(`${sprinklesDir}`); - sprinkles.forEach((sprinkle) => { - if (fs.existsSync(`${sprinkle}/bower.json`)) { - console.log(`bower.json found in '${sprinkle}' Sprinkle, installing assets.`); - shell.cd(`${sprinkle}`); - shell.exec(`bower install --config.directory=${vendorAssetsDir}`); - shell.cd(`../`); - } - }); - - return true; -}); - -/** - * Bundling tasks - */ - -// Executes bundleing tasks according to bundle.config.json files in each Sprinkle, as per Sprinkle load order. -// Respects bundle collision rules. -gulp.task('bundle-build', () => { - "use strict"; - let copy = require('recursive-copy'); - let merge = require('merge-array-object'); - let cleanup = (e) => { - "use strict"; - // Delete temporary directory if exists - fs.rmdirSync("./temp"); - // Delete created bundle.config.json file - fs.unlinkSync(bundleConfigFile); - // Propagate error - throw e; - }; - let config = { - bundle: {}, - copy: [] - }; - sprinkles.forEach((sprinkle) => { - "use strict"; - let location = `${sprinklesDir}/${sprinkle}/`; - if (fs.existsSync(`${location}${bundleFile}`)) { - let currentConfig = require(`${location}${bundleFile}`); - // Add bundles to config, respecting collision rules. - for (let bundleName in currentConfig.bundle) { - // If bundle already defined, handle as per collision rules. - if (bundleName in config.bundle) { - let onCollision = 'replace'; - try { - onCollision = (typeof currentConfig.bundle[bundleName].options.sprinkle.onCollision !== 'undefined' ? currentConfig.bundle[bundleName].options.sprinkle.onCollision : 'replace'); - } - catch (e) { - - } - switch (onCollision) { - case 'replace': - config.bundle[bundleName] = currentConfig.bundle[bundleName]; - break; - case 'merge': - // If using this collision rule, keep in mind any bundling options will also be merged. - // Inspect the produced 'bundle.config.json' file in the 'build' folder to ensure options are correct. - config.bundle[bundleName] = merge(currentConfig.bundle[bundleName], config.bundle[bundleName]); - break; - case 'ignore': - // Do nothing. This simply exists to prevent falling through to error catchment. - break; - case 'error': - cleanup(`The bundle '${bundleName}' in the Sprinkle '${sprinkle}' has been previously defined, and the bundle's 'onCollision' property is set to 'error'.`); - default: - cleanup(`Unexpected input '${onCollision}' for 'onCollision' for the bundle '${bundleName}' in the Sprinkle '${sprinkle}'.`); - } - } - // Otherwise, just add. - else { - config.bundle[bundleName] = currentConfig.bundle[bundleName]; - } - } - // Add/merge copy files to config - if ('copy' in currentConfig) { - config.copy = new Set(config.copy, currentConfig.copy); - } - } - }); - // Save bundle rules to bundle.config.json - fs.writeFileSync(bundleConfigFile, JSON.stringify(config)); - - // Copy all assets in order of Sprinkle load order. - let sprinkleAssets = [] - sprinkles.forEach((sprinkle) => { - "use strict"; - sprinkleAssets.push(`../app/sprinkles/${sprinkle}/assets/**/*`); - }); - // Gulp v4 src respects order, until it is released, use this alternative. - return plugins.srcOrderedGlobs(sprinkleAssets) - .pipe(plugins.copy('../public/assets/', {prefix: 5}));// And gulp.dest doesn't give us the control needed. -}); - -// Execute gulp-bundle-assets -gulp.task('bundle', () => { - "use strict"; - return gulp.src(bundleConfigFile) - .pipe(plugins.ufBundleAssets({ - base: publicAssetsDir - })) - .pipe(plugins.ufBundleAssets.results({ - dest: './' - })) - .pipe(gulp.dest(publicAssetsDir)); -}); - -/** - * Clean up tasks - */ - -gulp.task('public-clean', () => { - "use strict"; - return del(publicAssetsDir, { force: true }); -}); - -// Clean up temporary bundling files -gulp.task('bundle-clean', () => { - "use strict"; - return del(bundleConfigFile, { force: true }); -}); - -// Deletes assets fetched by bower-install -gulp.task('bower-clean', () => { - "use strict"; - return del(`${sprinklesDir}/*/assets/vendor`, { force: true }); -}); - -// Deletes all generated, or acquired files. -gulp.task('clean', () => { - "use strict"; - del(publicAssetsDir, { force: true}); - del(bundleConfigFile, { force: true }); - del(`${sprinklesDir}/*/assets/vendor`, { force: true }); - - return true; -}) \ No newline at end of file diff --git a/build/package.json b/build/package.json index d9dc74a6c..f78c96ce6 100755 --- a/build/package.json +++ b/build/package.json @@ -1,33 +1,32 @@ { - "name": "@userfrosting/userfrosting", - "version": "4.1.0", - "description": "Build tool for UF 4.1", - "main": "index.js", - "repository": { - "type": "git", - "url": "https://github.com/userfrosting/userfrosting" - }, - "author": "Alex Weissman", - "license": "MIT", - "devDependencies": { - "gulp": "^3.9.1", - "gulp-uf-bundle-assets": "2.28.0", - "gulp-load-plugins": "^1.4.0", - "merge-array-object": "^1.0.3", - "recursive-copy": "^2.0.5", - "del": "^2.2.2", - "shelljs": "^0.7.6", - "bower": "^1.8.0", - "gulp-src-ordered-globs": "^1.0.3", - "gulp-copy": "^1.0.0" + "private": true, + "dependencies": { + "@userfrosting/browserify-dependencies": "^1.0.0", + "@userfrosting/gulp-bundle-assets": "^3.0.0", + "@userfrosting/merge-package-dependencies": "^1.2.1", + "bower": "^1.8.8", + "del": "^4.0.0", + "dotenv": "^6.2.0", + "esm": "^3.2.16", + "gulp": "^4.0.0", + "gulp-clean-css": "^4.0.0", + "gulp-concat": "^2.6.1", + "gulp-concat-css": "^3.1.0", + "gulp-prune": "^0.2.0", + "gulp-rev": "^9.0.0", + "gulp-uglify-es": "^1.0.4", + "gulplog": "^1.0.0", + "strip-ansi": "^5.1.0" }, "scripts": { - "uf-bundle-build": "gulp bundle-build", - "uf-bundle": "gulp bundle", - "uf-bundle-clean": "gulp bundle-clean", - "uf-assets-install": "gulp bower-install", - "uf-assets-clean": "gulp bower-clean", - "uf-public-clean": "gulp public-clean", - "uf-clean": "gulp clean" - } + "uf-bundle": "./node_modules/.bin/gulp bundle", + "uf-assets-install": "./node_modules/.bin/gulp assetsInstall", + "uf-frontend": "./node_modules/.bin/gulp frontend", + "uf-clean": "./node_modules/.bin/gulp clean" + }, + "engines": { + "node": ">=10.12.0", + "npm": ">=6.0.0" + }, + "engineStrict": true } diff --git a/composer.json b/composer.json index 28c3fe160..8a618c63e 100755 --- a/composer.json +++ b/composer.json @@ -18,10 +18,12 @@ "php": ">=5.6", "ext-gd": "*", "composer/installers": "^1.4.0", + "userfrosting/uniformresourcelocator": "~4.2.3", "symfony/console": "^3.3", - "wikimedia/composer-merge-plugin": "dev-master" + "wikimedia/composer-merge-plugin": "^1.4.0" }, "require-dev": { + "friendsofphp/php-cs-fixer": "^2.13", "phpunit/phpunit": "^5.7", "mockery/mockery": "1.0.0-alpha1", "league/factory-muffin": "^3.0", diff --git a/docker-compose-testdb.yml b/docker-compose-testdb.yml new file mode 100644 index 000000000..7813b06b3 --- /dev/null +++ b/docker-compose-testdb.yml @@ -0,0 +1,95 @@ +version: "3.2" +services: + php: + restart: unless-stopped + tty: true + build: + context: ./docker/php + environment: + - DB_DRIVER=mysql + - DB_HOST=ufmysql + - DB_PORT=3306 + - DB_NAME=userfrosting + - DB_USER=docker + - DB_PASSWORD=secret + - DB_TEST_DRIVER=mysql + - DB_TEST_HOST=mysqltest + - DB_TEST_PORT=3306 + - DB_TEST_NAME=userfrosting_test + - DB_TEST_USER=docker + - DB_TEST_PASSWORD=secret + - TEST_DB=test_ufdb + volumes: + - .:/app + - ./docker/php/custom.ini:/usr/local/etc/php/conf.d/custom.ini + networks: + - backend + + nginx: + restart: unless-stopped + tty: true + ports: + - "8591:80" + - "8592:443" + build: + context: ./docker/nginx + volumes: + - .:/app + depends_on: + - php + - ufmysql + - ufmysqltest + networks: + - frontend + - backend + + ufmysql: + image: mysql:5.7 + networks: + - backend + environment: + - MYSQL_DATABASE=userfrosting + - MYSQL_ROOT_PASSWORD=secret + - MYSQL_USER=docker + - MYSQL_PASSWORD=secret + ports: + - 8593:3306 + volumes: + - userfrosting-db:/var/lib/mysql + + ufmysqltest: + image: mysql:5.7 + networks: + - backend + environment: + - MYSQL_DATABASE=userfrosting_test + - MYSQL_ROOT_PASSWORD=secret + - MYSQL_USER=docker + - MYSQL_PASSWORD=secret + ports: + - 8594:3306 + volumes: + - userfrosting-test-db:/var/lib/mysql + + composer: + image: "composer" + volumes: + - .:/app + working_dir: /app + + node: + build: + context: ./docker/node + volumes: + - .:/app + working_dir: /app/build + +volumes: + userfrosting-db: + driver: local + userfrosting-test-db: + driver: local + +networks: + frontend: + backend: \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 932a1c0f6..6ff08f6f5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,56 +1,71 @@ -version: '2' +version: "3.2" services: php: + restart: unless-stopped + tty: true build: context: ./docker/php environment: - DB_DRIVER=mysql - - DB_HOST=mysql + - DB_HOST=ufmysql - DB_PORT=3306 - DB_NAME=userfrosting - - DB_USER=userfrosting - - DB_PASSWORD=password + - DB_USER=docker + - DB_PASSWORD=secret volumes: - .:/app - links: - - mysql + - ./docker/php/custom.ini:/usr/local/etc/php/conf.d/custom.ini + networks: + - backend + nginx: + restart: unless-stopped + tty: true + ports: + - "8591:80" + - "8592:443" build: context: ./docker/nginx - ports: - - 8570:80 volumes: - .:/app - links: + depends_on: - php - mysql: + - ufmysql + networks: + - frontend + - backend + + ufmysql: image: mysql:5.7 + networks: + - backend environment: - MYSQL_DATABASE=userfrosting - - MYSQL_ROOT_PASSWORD=root - - MYSQL_USER=userfrosting - - MYSQL_PASSWORD=password + - MYSQL_ROOT_PASSWORD=secret + - MYSQL_USER=docker + - MYSQL_PASSWORD=secret ports: - - 8571:3306 + - 8593:3306 volumes: - userfrosting-db:/var/lib/mysql composer: - image: composer/composer - volumes_from: - - php + image: "composer" + volumes: + - .:/app working_dir: /app - command: -V node: - image: node:alpine build: context: ./docker/node - volumes_from: - - php + volumes: + - .:/app working_dir: /app/build - command: npm run uf-assets-install volumes: userfrosting-db: + driver: local +networks: + frontend: + backend: diff --git a/docker/README.md b/docker/README.md index e97cfac06..7ce433f49 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,48 +1,56 @@ # Docker Development Environment +>This is also documented at [UserFrosting Learn](https://learn.userfrosting.com/installation/environment/docker). + First, install [Docker Compose](https://docs.docker.com/compose/install/). Second, initialize a new UserFrosting project: -1. Clone the repository `git clone https://github.com/userfrosting/UserFrosting.git .` and change into that directory `cd userfrosting` -1. Run `cp app/sprinkles.example.json app/sprinkles.json` or upload your own (also upload your sprinkles if you have some) -2. Run `sudo chown -R 33 app/{logs,cache,sessions}` (Changes the user to the www-data user of the image, more information [here](https://serversforhackers.com/c/dckr-file-permissions) ) -2. Run `sudo docker-compose run composer install` to install all composer modules. -3. Run `sudo docker-compose run node npm install` to install all npm modules. + +1. Copy `app/sprinkles.example.json` to `app/sprinkles.json` +2. Run `chmod 777 app/{logs,cache,sessions}` to fix file permissions for web server. (NOTE: File + permissions should be properly secured in a production environment!) +3. Run `docker-compose run composer install --ignore-platform-reqs --no-scripts` to install all composer modules. (https://hub.docker.com/_/composer) Sometimes dependencies or Composer scripts require the availability of certain PHP extensions. You can work around this as follows: Pass the `--ignore-platform-reqs and --no-scripts` flags to install or update +4. Run `docker-compose run node npm install` to install all npm modules. +5. Run `docker-compose run composer update --ignore-platform-reqs --no-scripts` to install remaining composer modules +6. Run `docker-compose run node npm run uf-assets-install` to install all frontend vendor assets. Now you can start up the entire Nginx + PHP + MySQL stack using docker with: - $ sudo docker-compose up -d + $ docker-compose up -d + +the `-d` flag will launch this in the background so you can continue to use the terminal window. On the first run you need to init the database (your container name may be different depending on the name of your root directory): + + $ docker exec -it -u www-data userfrosting_php_1 sh -c 'php bakery migrate' -On the first run you need to init the database (Be sure to execute this in the same directory, `${PWD##*/}` is a statement to get your current working directorys name. Docker uses it to name your container): +You also need to setup the first admin user (again, your container name may be different depending on the name of your root directory): - $ sudo docker exec -it -u www-data ${PWD##*/}_php_1 bash -c 'php bakery migrate' - -You also need to setup the first admin user (again, `${PWD##*/}` is a statement to get your current working directorys name): + $ docker exec -it -u www-data userfrosting_php_1 sh -c 'php bakery create-admin' - $ sudo docker exec -it -u www-data ${PWD##*/}_php_1 bash -c 'php bakery create-admin' +Now visit `http://localhost:8591/` to see your UserFrosting homepage! -Now visit http://localhost:8570/ to see your UserFrosting homepage! +**Paste these into a bash file and execute it!** -**This is not (yet) meant for production!!** +``` +chmod 777 app/{logs,cache,sessions} +docker-compose build --force-rm --no-cache +docker-compose run composer install --ignore-platform-reqs --no-scripts +docker-compose run node npm install +docker-compose run composer update --ignore-platform-reqs --no-scripts +docker-compose run node npm run uf-assets-install +docker-compose up -d +echo -n "Enter Docker Container Name --> " +read docker_container +docker exec -it -u www-data $docker_container sh -c 'php bakery migrate' +docker exec -it -u www-data $docker_container sh -c 'php bakery create-admin' +``` + +**This is not (yet) meant for production!** You may be tempted to run with this in production but this setup has not been security-hardened. For example: -- Database is exposed on port 8571 so you can access MySQL using your favorite client at localhost:8571. However, +- Database is exposed on port 8593 so you can access MySQL using your favorite client at localhost:8593. However, the way Docker exposes this actually bypasses common firewalls like `ufw` so this should not be exposed in production. - Database credentials are hard-coded so obviously not secure. - File permissions may be more open than necessary. +- HTTPS not implemented fully - It just hasn't been thoroughly tested in the capacity of being a production system. - -## Updating your code -As you might guessed you will have to run - - $ sudo docker exec -it -u www-data userfrosting_php_1 bash -c 'php bakery migrate' - -again if you want to migrate tables. -You can change `php bakery migrate` to other `bakery` commands as well. -Be aware that the userfrosting container doesn't know about npm! -Similary for composer: - - $ sudo docker-compose run composer update - -See the [Docker](https://docs.docker.com/engine) and [Docker-compose documentation](https://docs.docker.com/compose/) for more details. diff --git a/docker/nginx/Dockerfile b/docker/nginx/Dockerfile index 7d2fbc3cc..be54539ba 100644 --- a/docker/nginx/Dockerfile +++ b/docker/nginx/Dockerfile @@ -1,2 +1,3 @@ FROM nginx:alpine -COPY default.conf /etc/nginx/conf.d/default.conf +RUN rm /etc/nginx/conf.d/default.conf +COPY default.conf /etc/nginx/conf.d/default.conf \ No newline at end of file diff --git a/docker/nginx/default.conf b/docker/nginx/default.conf index 8ae7bb820..5241476a0 100644 --- a/docker/nginx/default.conf +++ b/docker/nginx/default.conf @@ -25,4 +25,5 @@ server { index index.php; try_files $uri /index.php?$query_string; } + client_max_body_size 100M; } diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile index c5b8a9eb2..951928abb 100644 --- a/docker/php/Dockerfile +++ b/docker/php/Dockerfile @@ -1,11 +1,17 @@ -FROM php:7.2-fpm -RUN apt-get update && apt-get install -y \ - libfreetype6-dev \ - libjpeg62-turbo-dev \ - libpng-dev \ - && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \ +FROM php:7.2-fpm-alpine + +# Update and install packages +RUN apk update +RUN apk add \ + freetype-dev \ + libjpeg-turbo-dev \ + libpng-dev \ + zip + +# Install and configure PHP extensions +RUN docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \ && docker-php-ext-install -j$(nproc) gd \ - && docker-php-ext-install -j$(nproc) pdo pdo_mysql -RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer + && docker-php-ext-install -j$(nproc) pdo pdo_mysql \ + && docker-php-ext-install -j$(nproc) zip -WORKDIR /app +WORKDIR /app \ No newline at end of file diff --git a/docker/php/custom.ini b/docker/php/custom.ini new file mode 100644 index 000000000..9de5b8415 --- /dev/null +++ b/docker/php/custom.ini @@ -0,0 +1,6 @@ +file_uploads = On +memory_limit = 64M +upload_max_filesize = 64M +post_max_size = 64M +max_execution_time = 600 +memory_limit=512M \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml index 8945a7192..044b4fe72 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,15 +7,24 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" + stderr="true" stopOnFailure="false"> app/tests/Unit app/sprinkles/*/tests/Unit + + app/sprinkles/*/tests/Integration - \ No newline at end of file + + + app/sprinkles/*/src/ + app/system/ + + + diff --git a/public/index.php b/public/index.php index 5cbc048a9..95b6f339b 100755 --- a/public/index.php +++ b/public/index.php @@ -1,10 +1,10 @@ Note: destroying the vagrant server will remove all traces of the VM from your computer, reclaiming any disk space used by it. However, it also means the next time you vagrant up, you will be creating a brand new VM with a fresh install of UserFrosting and a new database. + +## Customizing the UserFrosting configuration + +By default, UserFrosting is pre-configured to install with a MySQL database. You can, however, switch to PostegreSQL or SQLite3 by editing the `install-config.yml` file in the vagrant directory. The next time you run `vagrant up` (or `vagrant provision`) it will be installed under the new configuration. + +If you prefer to access UserFrosting from the more friendly URL `http://userfrosting.test` then you must update your computer's hosts file. This file is typically located at `/etc/hosts` for Mac/Linux or `C:\Windows\System32\drivers\etc\hosts` for Windows. Open this file and add the following line to it, at the very bottom, and save. + +``` +192.168.10.10 userfrosting.test +``` + +## How it all works + +When you vagrant up, the Laravel/Homestead box is transparently loaded as a Virtual Machine on your computer (this may take several minutes the very first time while it downloads the VM image to your computer). Your local UserFrosting repository clone is mirrored/shared with the VM, so you can work on the UserFrosting code on your computer, and see the changes immediately when you browse to UserFrosting at the URL provided by the VM. + +This is very similar to traditional methods of working with a local WAMP/MAMP stack, except the webserver is now being provided by a VM of a Linux server. The advantages here are the exact same Linux server environment is being used by everybody who uses Vagrant with UserFrosting, so there will be consist behaviour unlike when everybody is developing on different versions of PHP, server configurations, etc. + +The environment is also "sandboxed" from your system. This means you don't need to worry about adjusting your own computer's internal PHP settings, setting up databases, or doing damage to your system or to UserFrosting. Other than the UserFrosting codebase, which lives on your computer, all execution is taking place within the VM and you can at any time, halt or destroy the VM and start a brand new one. + +There are some caveats, however. You can only run one vagrant VM for the UserFrosting repository. And of course, the database will be destroyed when you vagrant destroy. If the database is important, you should SSH into your vagrant VM and export/import the DB as needed using SSH commands. + +For example, to export/import a MySQL database (using UserFrosting's `store` directory): + +SSH into the VM + +```sh +$ vagrant ssh +``` + +Export MySQL: + +```sh +$ mysqldump -uhomestead -psecret UserFrosting > /home/vagrant/userfrosting/userfrosting.sql +``` + +Import MySQL: + +```sh +$ mysql -uhomestead -psecret UserFrosting < /home/vagrant/userfrosting/userfrosting.sql +``` + +--- + +## About the Laravel/Homestead box + +### Included Software + +* Ubuntu 16.04 +* Git +* PHP 7.1 +* HHVM +* Nginx +* MySQL +* Sqlite3 +* Postgres +* Composer +* Node (With PM2, Bower, Grunt, and Gulp) +* Redis +* Memcached +* Beanstalkd +* Blackfire Profiler + +### MySQL Access + +- Hostname: 127.0.0.1 +- Username: homestead +- Password: secret +- Database: UserFrosting +- Port: 3306 + +### PostgreSQL Access + +- Hostname: 127.0.0.1 +- Username: homestead +- Password: secret +- Database: UserFrosting +- Port: 5432 diff --git a/vagrant/after.sh b/vagrant/after.sh new file mode 100644 index 000000000..4be3d9800 --- /dev/null +++ b/vagrant/after.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +BASE_PATH="/home/vagrant/userfrosting" + +# Update nodejs +npm cache clean -f +npm install -g n +n -q lts + +# Ensure composer deps are installed +cd ${BASE_PATH} +composer install + +# Setup .env +echo 'UF_MODE=""' > app/.env +echo 'DB_DRIVER="mysql"' >> app/.env +echo 'DB_HOST="localhost"' >> app/.env +echo 'DB_PORT="3306"' >> app/.env +echo 'DB_NAME="UserFrosting"' >> app/.env +echo 'DB_USER="homestead"' >> app/.env +echo 'DB_PASSWORD="secret"' >> app/.env +echo 'SMTP_HOST="host.example.com"' >> app/.env +echo 'SMTP_USER="relay@example.com"' >> app/.env +echo 'SMTP_PASSWORD="password"' >> app/.env + +# Setup sprinkles.json +cp app/sprinkles.example.json app/sprinkles.json + +# Install UserFrosting +php bakery debug +php bakery migrate +php bakery create-admin --username="admin" --email="admin@userfrosting.test" --password="adminadmin12" --firstName="Admin" --lastName="istrator" +php bakery build-assets + +echo "\n\nUserFrosting is ready at http://192.168.10.10/" diff --git a/vagrant/bootstrap.yaml b/vagrant/bootstrap.yaml new file mode 100644 index 000000000..dd44db7b9 --- /dev/null +++ b/vagrant/bootstrap.yaml @@ -0,0 +1,23 @@ +--- +ip: "192.168.10.10" +memory: 2048 +cpus: 1 +hostname: userfrosting +name: userfrosting +provider: virtualbox + +authorize: ~/.ssh/id_rsa.pub + +keys: + - ~/.ssh/id_rsa + +folders: + - map: "." + to: "/home/vagrant/userfrosting" + +sites: + - map: userfrosting.test + to: "/home/vagrant/userfrosting/public" + +databases: + - UserFrosting