diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 46a180c97..6f2bd114a 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -58,7 +58,7 @@ Contains breaking changes that will need to wait for the next version to be inte When ready, changes should be merged into both **master** and **hotfix**. #### `feature-*` -New features that introduce some breaking changes or incomplete code should be committed in a separate `feature-{name}` branch. +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). @@ -117,11 +117,13 @@ Additionally, the `learn` repository can have `dev-*` for learn specific feature ## Building the API documentation -To build the API documentation, install [ApiGen](http://www.apigen.org/) globally and then run: +To build the API documentation, install [phpDocumentor](https://www.phpdoc.org) globally and then run from the UserFrosting root : -`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"` +``` +phpdoc +``` -from inside your dev directory. +The resulting documentation will be available in `api/`. ## Automatically fixing coding style with PHP-CS-Fixer @@ -135,6 +137,6 @@ app/vendor/bin/php-cs-fixer fix ## Useful tools -If you are using **Atom**, be sure to checkout theses useful packages : +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 04a87a6e2..74d1d746a 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,9 @@ vagrant/Homestead/ .phpunit.result.cache .php_cs.cache +# Ignore api doc +api/ + # Igore npm lockfile build/package-lock.json build/package.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index f6ee99f69..c3c52ffdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ 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.4.1] + +### Fixed +- Fixed issue where incompatible NPM packages would be browserified, resulting in install failures. +- Replaced deprecated Twig class. +- Fixed issue when compiling assets for production ([#1078]). +- Migration dependencies should work with and without leading `\` ([#1023]) +- Throttler don't count successful logins ([#1073]) + ## [v4.4.0] ### Changed Requirements @@ -921,6 +930,7 @@ See [http://learn.userfrosting.com/upgrading/40-to-41](Upgrading 4.0.x to 4.1.x [#1017]: https://github.com/userfrosting/UserFrosting/issues/1017 [#1018]: https://github.com/userfrosting/UserFrosting/issues/1018 [#1019]: https://github.com/userfrosting/UserFrosting/issues/1019 +[#1023]: https://github.com/userfrosting/UserFrosting/issues/1023 [#1027]: https://github.com/userfrosting/UserFrosting/issues/1027 [#1028]: https://github.com/userfrosting/UserFrosting/issues/1028 [#1030]: https://github.com/userfrosting/UserFrosting/issues/1030 @@ -938,6 +948,8 @@ See [http://learn.userfrosting.com/upgrading/40-to-41](Upgrading 4.0.x to 4.1.x [#1057]: https://github.com/userfrosting/UserFrosting/issues/1057 [#1061]: https://github.com/userfrosting/UserFrosting/issues/1061 [#1062]: https://github.com/userfrosting/UserFrosting/issues/1062 +[#1073]: https://github.com/userfrosting/UserFrosting/issues/1073 +[#1078]: https://github.com/userfrosting/UserFrosting/issues/1078 [v4.2.0]: https://github.com/userfrosting/UserFrosting/compare/v4.1.22...v4.2.0 [v4.2.1]: https://github.com/userfrosting/UserFrosting/compare/v4.2.0...v.4.2.1 diff --git a/app/defines.php b/app/defines.php index 52cd5c95d..1329ec459 100755 --- a/app/defines.php +++ b/app/defines.php @@ -11,7 +11,7 @@ namespace UserFrosting; // Some standard defines -define('UserFrosting\VERSION', '4.4.0'); +define('UserFrosting\VERSION', '4.4.1'); define('UserFrosting\DS', '/'); define('UserFrosting\PHP_MIN_VERSION', '7.1'); define('UserFrosting\PHP_RECOMMENDED_VERSION', '7.3'); diff --git a/app/sprinkles/account/src/Controller/AccountController.php b/app/sprinkles/account/src/Controller/AccountController.php index ec2b82db6..92b7b3757 100644 --- a/app/sprinkles/account/src/Controller/AccountController.php +++ b/app/sprinkles/account/src/Controller/AccountController.php @@ -396,13 +396,11 @@ public function login(Request $request, Response $response, $args) return $response->withJson([], 429); } - // Log throttleable event - $throttler->logEvent('sign_in_attempt', $throttleData); - // If credential is an email address, but email login is not enabled, raise an error. - // Note that we do this after logging throttle event, so this error counts towards throttling limit. + // Note that this error counts towards the throttling limit. if ($isEmail && !$config['site.login.enable_email']) { $ms->addMessageTranslated('danger', 'USER_OR_PASS_INVALID'); + $throttler->logEvent('sign_in_attempt', $throttleData); return $response->withJson([], 403); } @@ -411,7 +409,14 @@ public function login(Request $request, Response $response, $args) /** @var \UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */ $authenticator = $this->ci->authenticator; - $currentUser = $authenticator->attempt(($isEmail ? 'email' : 'user_name'), $userIdentifier, $data['password'], $data['rememberme']); + try { + $currentUser = $authenticator->attempt(($isEmail ? 'email' : 'user_name'), $userIdentifier, $data['password'], $data['rememberme']); + } catch (\Exception $e) { + // only let unsuccessful logins count toward the throttling limit + $throttler->logEvent('sign_in_attempt', $throttleData); + + throw $e; + } $ms->addMessageTranslated('success', 'WELCOME', $currentUser->export()); diff --git a/app/sprinkles/account/src/Database/Models/Activity.php b/app/sprinkles/account/src/Database/Models/Activity.php index f84a3fc3f..a3845803c 100644 --- a/app/sprinkles/account/src/Database/Models/Activity.php +++ b/app/sprinkles/account/src/Database/Models/Activity.php @@ -21,11 +21,11 @@ * * @author Alex Weissman (https://alexanderweissman.com) * - * @property string ip_address - * @property int user_id - * @property string type - * @property datetime occurred_at - * @property string description + * @property string $ip_address + * @property int $user_id + * @property string $type + * @property datetime $occurred_at + * @property string $description */ class Activity extends Model { diff --git a/app/sprinkles/account/src/Database/Models/Group.php b/app/sprinkles/account/src/Database/Models/Group.php index 2e3080dd5..5bf788fb0 100644 --- a/app/sprinkles/account/src/Database/Models/Group.php +++ b/app/sprinkles/account/src/Database/Models/Group.php @@ -21,10 +21,10 @@ * * @see http://www.userfrosting.com/tutorials/lesson-3-data-model/ * - * @property string slug - * @property string name - * @property string description - * @property string icon + * @property string $slug + * @property string $name + * @property string $description + * @property string $icon */ class Group extends Model { diff --git a/app/sprinkles/account/src/Database/Models/PasswordReset.php b/app/sprinkles/account/src/Database/Models/PasswordReset.php index 6501fb333..4fe8f3ef1 100644 --- a/app/sprinkles/account/src/Database/Models/PasswordReset.php +++ b/app/sprinkles/account/src/Database/Models/PasswordReset.php @@ -19,11 +19,11 @@ * * @author Alex Weissman (https://alexanderweissman.com) * - * @property int user_id - * @property hash token - * @property bool completed - * @property datetime expires_at - * @property datetime completed_at + * @property int $user_id + * @property hash $token + * @property bool $completed + * @property datetime $expires_at + * @property datetime $completed_at */ class PasswordReset extends Model { diff --git a/app/sprinkles/account/src/Database/Models/Permission.php b/app/sprinkles/account/src/Database/Models/Permission.php index 5ad442a1a..c576a61f5 100644 --- a/app/sprinkles/account/src/Database/Models/Permission.php +++ b/app/sprinkles/account/src/Database/Models/Permission.php @@ -20,10 +20,10 @@ * * @author Alex Weissman (https://alexanderweissman.com) * - * @property string slug - * @property string name - * @property string conditions - * @property string description + * @property string $slug + * @property string $name + * @property string $conditions + * @property string $description */ class Permission extends Model { diff --git a/app/sprinkles/account/src/Database/Models/Persistence.php b/app/sprinkles/account/src/Database/Models/Persistence.php index 72dc978b1..ad41a15dd 100644 --- a/app/sprinkles/account/src/Database/Models/Persistence.php +++ b/app/sprinkles/account/src/Database/Models/Persistence.php @@ -20,10 +20,10 @@ * * @author Louis Charette * - * @property string user_id - * @property string token - * @property string persistent_token - * @property string expires_at + * @property string $user_id + * @property string $token + * @property string $persistent_token + * @property string $expires_at */ class Persistence extends Model { diff --git a/app/sprinkles/account/src/Database/Models/Role.php b/app/sprinkles/account/src/Database/Models/Role.php index 40c8a18ae..13d39fcfc 100644 --- a/app/sprinkles/account/src/Database/Models/Role.php +++ b/app/sprinkles/account/src/Database/Models/Role.php @@ -20,9 +20,9 @@ * * @author Alex Weissman (https://alexanderweissman.com) * - * @property string slug - * @property string name - * @property string description + * @property string $slug + * @property string $name + * @property string $description */ class Role extends Model { diff --git a/app/sprinkles/account/src/Database/Models/User.php b/app/sprinkles/account/src/Database/Models/User.php index b0ab88976..c5eba70eb 100644 --- a/app/sprinkles/account/src/Database/Models/User.php +++ b/app/sprinkles/account/src/Database/Models/User.php @@ -25,21 +25,21 @@ * * @author Alex Weissman (https://alexanderweissman.com) * - * @property int id - * @property string user_name - * @property string first_name - * @property string last_name - * @property string email - * @property string locale - * @property string theme - * @property int group_id - * @property bool flag_verified - * @property bool flag_enabled - * @property int last_activity_id - * @property timestamp created_at - * @property timestamp updated_at - * @property string password - * @property timestamp deleted_at + * @property int $id + * @property string $user_name + * @property string $first_name + * @property string $last_name + * @property string $email + * @property string $locale + * @property string $theme + * @property int $group_id + * @property bool $flag_verified + * @property bool $flag_enabled + * @property int $last_activity_id + * @property timestamp $created_at + * @property timestamp $updated_at + * @property string $password + * @property timestamp $deleted_at */ class User extends Model implements UserInterface { diff --git a/app/sprinkles/account/src/Database/Models/Verification.php b/app/sprinkles/account/src/Database/Models/Verification.php index 5d982c855..998f908a9 100644 --- a/app/sprinkles/account/src/Database/Models/Verification.php +++ b/app/sprinkles/account/src/Database/Models/Verification.php @@ -19,11 +19,11 @@ * * @author Alex Weissman (https://alexanderweissman.com) * - * @property int user_id - * @property hash token - * @property bool completed - * @property datetime expires_at - * @property datetime completed_at + * @property int $user_id + * @property hash $token + * @property bool $completed + * @property datetime $expires_at + * @property datetime $completed_at */ class Verification extends Model { diff --git a/app/sprinkles/account/src/Twig/AccountExtension.php b/app/sprinkles/account/src/Twig/AccountExtension.php index bc06a3666..a76c09d59 100644 --- a/app/sprinkles/account/src/Twig/AccountExtension.php +++ b/app/sprinkles/account/src/Twig/AccountExtension.php @@ -11,6 +11,9 @@ namespace UserFrosting\Sprinkle\Account\Twig; use Psr\Container\ContainerInterface; +use Twig\Extension\AbstractExtension; +use Twig\Extension\GlobalsInterface; +use Twig\TwigFunction; use UserFrosting\Support\Repository\Repository as Config; /** @@ -18,7 +21,7 @@ * * @author Alex Weissman (https://alexanderweissman.com) */ -class AccountExtension extends \Twig_Extension implements \Twig_Extension_GlobalsInterface +class AccountExtension extends AbstractExtension implements GlobalsInterface { /** * @var ContainerInterface @@ -48,13 +51,13 @@ public function getFunctions() { return [ // Add Twig function for checking permissions during dynamic menu rendering - new \Twig_SimpleFunction('checkAccess', function ($slug, $params = []) { + new TwigFunction('checkAccess', function ($slug, $params = []) { $authorizer = $this->services->authorizer; $currentUser = $this->services->currentUser; return $authorizer->checkAccess($currentUser, $slug, $params); }), - new \Twig_SimpleFunction('checkAuthenticated', function () { + new TwigFunction('checkAuthenticated', function () { $authenticator = $this->services->authenticator; return $authenticator->check(); diff --git a/app/sprinkles/account/tests/Integration/Controller/AccountControllerTest.php b/app/sprinkles/account/tests/Integration/Controller/AccountControllerTest.php index 5fb9dba04..f5f6c6cad 100644 --- a/app/sprinkles/account/tests/Integration/Controller/AccountControllerTest.php +++ b/app/sprinkles/account/tests/Integration/Controller/AccountControllerTest.php @@ -11,6 +11,7 @@ namespace UserFrosting\Sprinkle\Account\Tests\Integration\Controller; use Mockery as m; +use UserFrosting\Sprinkle\Account\Authenticate\Exception; use UserFrosting\Sprinkle\Account\Controller\AccountController; use UserFrosting\Sprinkle\Account\Controller\Exception\SpammyRequestException; use UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface; @@ -618,6 +619,74 @@ public function testloginWithThrottler() $this->assertSame('danger', end($messages)['type']); } + /** + * @depends testControllerConstructor + */ + public function testloginThrottlerCountsFailedLogins() + { + // Create fake throttler + $throttler = m::mock(Throttler::class); + $throttler->shouldReceive('getDelay')->once()->with('sign_in_attempt', ['user_identifier' => 'foo'])->andReturn(0); + $throttler->shouldReceive('logEvent')->once()->with('sign_in_attempt', ['user_identifier' => 'foo']); + $this->ci->throttler = $throttler; + + // Recreate controller to use fake throttler + $controller = $this->getController(); + + // Set POST + $request = $this->getRequest()->withParsedBody([ + 'user_name' => 'foo', + 'password' => 'bar', + 'rememberme' => false, + ]); + + $this->expectException(Exception\InvalidCredentialsException::class); + + $controller->login($request, $this->getResponse(), []); + } + + /** + * @depends testControllerConstructor + */ + public function testloginThrottlerDoesntCountSuccessfulLogins() + { + // 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(); + + // Create fake throttler + $throttler = m::mock(Throttler::class); + $throttler->shouldReceive('getDelay')->once()->with('sign_in_attempt', ['user_identifier' => $testUser->email])->andReturn(0); + $throttler->shouldNotReceive('logEvent'); + $this->ci->throttler = $throttler; + + // Recreate controller to use fake throttler and test user + $controller = $this->getController(); + + // Set POST + $request = $this->getRequest()->withParsedBody([ + 'user_name' => $testUser->email, + 'password' => $unhashed, + 'rememberme' => false, + ]); + + $result = $controller->login($request, $this->getResponse(), []); + $this->assertInstanceOf(\Psr\Http\Message\ResponseInterface::class, $result); + // Can't assert the status code or data, as this can be overwrited by sprinkles + + // Test message + $ms = $this->ci->alerts; + $messages = $ms->getAndClearMessages(); + $this->assertSame('success', end($messages)['type']); + + // We have to logout the user to avoid problem + $this->logoutCurrentUser($testUser); + } + /** * @depends testControllerConstructor */ diff --git a/app/sprinkles/core/src/Database/Migrator/MigrationDependencyAnalyser.php b/app/sprinkles/core/src/Database/Migrator/MigrationDependencyAnalyser.php index a8c02403a..3b30dd7d5 100644 --- a/app/sprinkles/core/src/Database/Migrator/MigrationDependencyAnalyser.php +++ b/app/sprinkles/core/src/Database/Migrator/MigrationDependencyAnalyser.php @@ -59,14 +59,16 @@ class MigrationDependencyAnalyser */ public function __construct(array $pending = [], array $installed = []) { - $this->pending = collect($pending); - $this->installed = collect($installed); + $this->pending = collect($this->normalizeClasses($pending)); + $this->installed = collect($this->normalizeClasses($installed)); } /** * Analyse the dependencies. + * + * @return void */ - public function analyse() + public function analyse(): void { // Reset fulfillable/unfulfillable lists $this->analysed = false; @@ -94,7 +96,7 @@ public function analyse() * * @return bool True/False if the migration is fulfillable */ - protected function validateClassDependencies($migrationName) + protected function validateClassDependencies(string $migrationName): bool { // If it's already marked as fulfillable, it's fulfillable // Return true directly (it's already marked) @@ -141,7 +143,7 @@ protected function validateClassDependencies($migrationName) * * @return array */ - public function getFulfillable() + public function getFulfillable(): array { if (!$this->analysed) { $this->analyse(); @@ -155,7 +157,7 @@ public function getFulfillable() * * @return array */ - public function getUnfulfillable() + public function getUnfulfillable(): array { if (!$this->analysed) { $this->analyse(); @@ -171,7 +173,7 @@ public function getUnfulfillable() * * @return bool True, it's fulfillable */ - protected function markAsFulfillable($migration) + protected function markAsFulfillable(string $migration): bool { $this->fulfillable->push($migration); @@ -186,7 +188,7 @@ protected function markAsFulfillable($migration) * * @return bool False, it's not fullfillable */ - protected function markAsUnfulfillable($migration, $dependency) + protected function markAsUnfulfillable(string $migration, $dependency): bool { if (is_array($dependency)) { $dependency = implode(', ', $dependency); @@ -205,9 +207,8 @@ protected function markAsUnfulfillable($migration, $dependency) * * @return array The dependency list */ - protected function getMigrationDependencies($migration) + protected function getMigrationDependencies(string $migration): array { - // Make sure class exists if (!class_exists($migration)) { throw new BadClassNameException("Unable to find the migration class '$migration'. Run 'php bakery migrate:clean' to remove stale migrations."); @@ -218,16 +219,34 @@ protected function getMigrationDependencies($migration) // We can remove this one the non static property is removed $reflectionClass = new ReflectionClass($migration); if ($reflectionClass->hasProperty('dependencies') && $reflectionClass->getProperty('dependencies')->isStatic()) { - return $migration::$dependencies; + return $this->normalizeClasses($migration::$dependencies); } elseif (property_exists($migration, 'dependencies')) { if (Config::get('debug.deprecation')) { Debug::warning("`$migration` uses a non static `dependencies` property. Please change the `dependencies` property to a static property."); } $instance = new $migration(); - return $instance->dependencies; + return $this->normalizeClasses($instance->dependencies); } else { return []; } } + + /** + * Normalize class so all class starts with '/'. + * + * @param string[] $classes + * + * @return string[] + */ + protected function normalizeClasses(array $classes): array + { + return array_map(function (string $class) { + if ($class[0] !== '\\') { + return '\\' . $class; + } + + return $class; + }, $classes); + } } diff --git a/app/sprinkles/core/src/Database/Migrator/MigrationRollbackDependencyAnalyser.php b/app/sprinkles/core/src/Database/Migrator/MigrationRollbackDependencyAnalyser.php index f8be6c552..6d5bd50ab 100644 --- a/app/sprinkles/core/src/Database/Migrator/MigrationRollbackDependencyAnalyser.php +++ b/app/sprinkles/core/src/Database/Migrator/MigrationRollbackDependencyAnalyser.php @@ -20,18 +20,6 @@ */ class MigrationRollbackDependencyAnalyser extends MigrationDependencyAnalyser { - /** - * Constructor. - * - * @param array $installed The installed migrations - * @param array $rollback The migrations to rollback - */ - public function __construct(array $installed = [], array $rollback = []) - { - $this->pending = collect($installed); - $this->installed = collect($rollback); - } - /** * Received each installed migrations and determine if it depends on the * migrations we want to delete (rollback). It can if no other installed @@ -43,7 +31,7 @@ public function __construct(array $installed = [], array $rollback = []) * * @return bool True/False if the migration is fulfillable */ - protected function validateClassDependencies($migrationName) + protected function validateClassDependencies(string $migrationName): bool { // If it's already marked as fulfillable, it's fulfillable // Return true directly (it's already marked) diff --git a/app/sprinkles/core/src/Database/Models/Throttle.php b/app/sprinkles/core/src/Database/Models/Throttle.php index c6f0676a3..ddd48f869 100644 --- a/app/sprinkles/core/src/Database/Models/Throttle.php +++ b/app/sprinkles/core/src/Database/Models/Throttle.php @@ -17,9 +17,9 @@ * * @author Alex Weissman (https://alexanderweissman.com) * - * @property string type - * @property string ip - * @property string request_data + * @property string $type + * @property string $ip + * @property string $request_data */ class Throttle extends Model { diff --git a/app/sprinkles/core/src/Error/Handler/ExceptionHandler.php b/app/sprinkles/core/src/Error/Handler/ExceptionHandler.php index c9175197f..2bd13d6b6 100644 --- a/app/sprinkles/core/src/Error/Handler/ExceptionHandler.php +++ b/app/sprinkles/core/src/Error/Handler/ExceptionHandler.php @@ -13,6 +13,7 @@ use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Twig\Error\LoaderError; use UserFrosting\Sprinkle\Core\Error\Renderer\JsonRenderer; use UserFrosting\Sprinkle\Core\Error\Renderer\PlainTextRenderer; use UserFrosting\Sprinkle\Core\Error\Renderer\WhoopsRenderer; @@ -153,7 +154,7 @@ public function renderGenericResponse() try { $template = $this->ci->view->getEnvironment()->loadTemplate("pages/error/$httpCode.html.twig"); - } catch (\Twig_Error_Loader $e) { + } catch (LoaderError $e) { $template = $this->ci->view->getEnvironment()->loadTemplate('pages/abstract/error.html.twig'); } diff --git a/app/sprinkles/core/src/Mail/TwigMailMessage.php b/app/sprinkles/core/src/Mail/TwigMailMessage.php index 2d2c16e62..fbc2991b2 100644 --- a/app/sprinkles/core/src/Mail/TwigMailMessage.php +++ b/app/sprinkles/core/src/Mail/TwigMailMessage.php @@ -11,6 +11,7 @@ namespace UserFrosting\Sprinkle\Core\Mail; use Slim\Views\Twig; +use Twig\Template; /** * MailMessage Class. @@ -27,7 +28,7 @@ class TwigMailMessage extends MailMessage protected $params; /** - * @var \Twig_Template The Twig template object, to source the content for this message. + * @var Template The Twig template object, to source the content for this message. */ protected $template; @@ -91,9 +92,9 @@ public function renderBody($params = []) /** * Sets the Twig template object for this message. * - * @param \Twig_Template $template The Twig template object, to source the content for this message. + * @param Template $template The Twig template object, to source the content for this message. */ - public function setTemplate(\Twig_Template $template) + public function setTemplate(Template $template) { $this->template = $template; diff --git a/app/sprinkles/core/src/ServicesProvider/ServicesProvider.php b/app/sprinkles/core/src/ServicesProvider/ServicesProvider.php index 568ff7eb3..e9c61dd4a 100755 --- a/app/sprinkles/core/src/ServicesProvider/ServicesProvider.php +++ b/app/sprinkles/core/src/ServicesProvider/ServicesProvider.php @@ -28,6 +28,7 @@ use Psr\Container\ContainerInterface; use Slim\Views\Twig; use Slim\Views\TwigExtension; +use Twig\Extension\DebugExtension; use UserFrosting\Assets\AssetBundles\GulpBundleAssetsCompiledBundles as CompiledAssetBundles; use UserFrosting\Assets\AssetLoader; use UserFrosting\Assets\Assets; @@ -636,7 +637,7 @@ public function register(ContainerInterface $container) if ($c->config['debug.twig']) { $twig->enableDebug(); - $view->addExtension(new \Twig_Extension_Debug()); + $view->addExtension(new DebugExtension()); } // Register the Slim extension with Twig diff --git a/app/sprinkles/core/src/Twig/CoreExtension.php b/app/sprinkles/core/src/Twig/CoreExtension.php index 7aab92951..f00ff390f 100755 --- a/app/sprinkles/core/src/Twig/CoreExtension.php +++ b/app/sprinkles/core/src/Twig/CoreExtension.php @@ -11,6 +11,10 @@ namespace UserFrosting\Sprinkle\Core\Twig; use Psr\Container\ContainerInterface; +use Twig\Extension\AbstractExtension; +use Twig\Extension\GlobalsInterface; +use Twig\TwigFilter; +use Twig\TwigFunction; use UserFrosting\Assets\AssetsTemplatePlugin; use UserFrosting\Sprinkle\Core\Util\Util; @@ -19,7 +23,7 @@ * * @author Alex Weissman (https://alexanderweissman.com) */ -class CoreExtension extends \Twig_Extension implements \Twig_Extension_GlobalsInterface +class CoreExtension extends AbstractExtension implements GlobalsInterface { /** * @var ContainerInterface The global container object, which holds all your services. @@ -49,20 +53,20 @@ public function getName() /** * Adds Twig functions `getAlerts` and `translate`. * - * @return array[\Twig_SimpleFunction] + * @return array[TwigFunction] */ public function getFunctions() { return [ // Add Twig function for fetching alerts - new \Twig_SimpleFunction('getAlerts', function ($clear = true) { + new TwigFunction('getAlerts', function ($clear = true) { if ($clear) { return $this->services['alerts']->getAndClearMessages(); } else { return $this->services['alerts']->messages(); } }), - new \Twig_SimpleFunction('translate', function ($hook, $params = []) { + new TwigFunction('translate', function ($hook, $params = []) { return $this->services['translator']->translate($hook, $params); }, [ 'is_safe' => ['html'], @@ -73,7 +77,7 @@ public function getFunctions() /** * Adds Twig filters `unescape`. * - * @return array[\Twig_SimpleFilter] + * @return array[TwigFilter] */ public function getFilters() { @@ -84,10 +88,10 @@ public function getFilters() * @param string $num A unformatted phone number * @return string Returns the formatted phone number */ - new \Twig_SimpleFilter('phone', function ($num) { + new TwigFilter('phone', function ($num) { return Util::formatPhoneNumber($num); }), - new \Twig_SimpleFilter('unescape', function ($string) { + new TwigFilter('unescape', function ($string) { return html_entity_decode($string); }), ]; diff --git a/app/sprinkles/core/tests/Integration/Database/Migrator/DatabaseMigratorIntegrationTest.php b/app/sprinkles/core/tests/Integration/Database/Migrator/DatabaseMigratorIntegrationTest.php index 2ffefc201..db726c354 100644 --- a/app/sprinkles/core/tests/Integration/Database/Migrator/DatabaseMigratorIntegrationTest.php +++ b/app/sprinkles/core/tests/Integration/Database/Migrator/DatabaseMigratorIntegrationTest.php @@ -87,18 +87,26 @@ public function testBasicMigration() $this->assertTrue($this->schema->hasTable('users')); $this->assertTrue($this->schema->hasTable('password_resets')); - $this->assertEquals($this->locator->getMigrations(), $ran); + $this->assertEquals([ + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreatePasswordResetsTable', + ], $ran); } public function testRepository() { $ran = $this->migrator->run(); + $expected = [ + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreatePasswordResetsTable', + ]; + // 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()); + $this->assertEquals($expected, $ran); + $this->assertEquals(array_reverse($expected), $this->repository->getLast()); + $this->assertEquals($expected, $this->repository->getMigrationsList()); } public function testMigrationsCanBeRolledBack() @@ -168,10 +176,15 @@ public function testPretendRollback() $this->assertTrue($this->schema->hasTable('users')); $this->assertTrue($this->schema->hasTable('password_resets')); + $expected = [ + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreatePasswordResetsTable', + ]; + $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); + $this->assertEquals(array_reverse($expected), $rolledBack); } public function testChangeRepositoryAndDeprecatedClass() diff --git a/app/sprinkles/core/tests/Integration/Database/Migrator/MigrationDependencyAnalyserTest.php b/app/sprinkles/core/tests/Integration/Database/Migrator/MigrationDependencyAnalyserTest.php index 72b5362a0..6e4152a80 100644 --- a/app/sprinkles/core/tests/Integration/Database/Migrator/MigrationDependencyAnalyserTest.php +++ b/app/sprinkles/core/tests/Integration/Database/Migrator/MigrationDependencyAnalyserTest.php @@ -24,13 +24,18 @@ class MigrationDependencyAnalyserTest extends TestCase public function testAnalyser() { $migrations = [ + 'UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable', + '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreatePasswordResetsTable', + ]; + + $expected = [ '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable', '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreatePasswordResetsTable', ]; $analyser = new MigrationDependencyAnalyser($migrations, []); - $this->assertEquals($migrations, $analyser->getFulfillable()); + $this->assertEquals($expected, $analyser->getFulfillable()); $this->assertEquals([], $analyser->getUnfulfillable()); } @@ -50,7 +55,7 @@ public function testAnalyserWithReordered() { $analyser = new MigrationDependencyAnalyser([ '\\UserFrosting\\Tests\\Integration\\Migrations\\two\\CreateFlightsTable', - '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable', + 'UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable', '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreatePasswordResetsTable', ], []); @@ -65,7 +70,7 @@ public function testAnalyserWithReordered() public function testAnalyserWithUnfulfillable() { $migrations = [ - '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable', + 'UserFrosting\\Tests\\Integration\\Migrations\\one\\CreateUsersTable', '\\UserFrosting\\Tests\\Integration\\Migrations\\one\\CreatePasswordResetsTable', '\\UserFrosting\\Tests\\Integration\\Migrations\\UnfulfillableTable', ]; diff --git a/app/sprinkles/core/tests/Integration/Migrations/two/CreateFlightsTable.php b/app/sprinkles/core/tests/Integration/Migrations/two/CreateFlightsTable.php index 934fd0529..c4d6624ef 100644 --- a/app/sprinkles/core/tests/Integration/Migrations/two/CreateFlightsTable.php +++ b/app/sprinkles/core/tests/Integration/Migrations/two/CreateFlightsTable.php @@ -20,7 +20,7 @@ class CreateFlightsTable extends Migration */ public static $dependencies = [ '\UserFrosting\Tests\Integration\Migrations\one\CreateUsersTable', - '\UserFrosting\Tests\Integration\Migrations\one\CreatePasswordResetsTable', + 'UserFrosting\Tests\Integration\Migrations\one\CreatePasswordResetsTable', ]; /** diff --git a/build/package.json b/build/package.json index 6cbf7dd43..46f377e3f 100755 --- a/build/package.json +++ b/build/package.json @@ -1,8 +1,8 @@ { "private": true, "dependencies": { - "@userfrosting/browserify-dependencies": "^3.0.0", - "@userfrosting/gulp-bundle-assets": "^4.0.0", + "@userfrosting/browserify-dependencies": "^3.1.0", + "@userfrosting/gulp-bundle-assets": "^4.0.1", "@userfrosting/merge-package-dependencies": "^1.2.1", "@userfrosting/vinyl-fs-vpath": "^1.0.0", "bower": "^1.8.8", @@ -21,7 +21,7 @@ "strip-ansi": "^6.0.0" }, "scripts": { - "uf-bundle": "./node_modules/.bin/gulp bundle", + "uf-bundle": "./node_modules/.bin/gulp build", "uf-assets-install": "./node_modules/.bin/gulp assetsInstall", "uf-frontend": "./node_modules/.bin/gulp frontend", "uf-clean": "./node_modules/.bin/gulp clean" diff --git a/build/tasks/assets-install.js b/build/tasks/assets-install.js index da4fc16a8..092e103cc 100644 --- a/build/tasks/assets-install.js +++ b/build/tasks/assets-install.js @@ -95,6 +95,7 @@ export async function assetsInstall() { dependencies: Object.keys(pkg.dependencies), inputDir: vendorAssetsDir + "node_modules/", outputDir: vendorAssetsDir + "browser_modules/", + silentFailures: true, }); } else { diff --git a/phpdoc.dist.xml b/phpdoc.dist.xml new file mode 100644 index 000000000..42a843aa3 --- /dev/null +++ b/phpdoc.dist.xml @@ -0,0 +1,15 @@ + + + UserFrosting API + + api + + + api + + + app/system + app/sprinkles + app/vendor/userfrosting/*/src + + \ No newline at end of file