diff --git a/CHANGELOG-5.7.md b/CHANGELOG-5.7.md index 0dc30f8a6e97..0a3570f7f1f1 100644 --- a/CHANGELOG-5.7.md +++ b/CHANGELOG-5.7.md @@ -1,5 +1,28 @@ # Release Notes for 5.7.x +## [v5.7.13 (2018-11-07)](https://github.com/laravel/framework/compare/v5.7.12...v5.7.13) + +### Added +- Added ability to return an array of messages in a custom validation rule ([#26327](https://github.com/laravel/framework/pull/26327)) +- Added `whenEmpty`/ `whenNotEmpty` / `unlessEmpty` / `unlessNotEmpty` methods to `Collection` ([#26345](https://github.com/laravel/framework/pull/26345)) +- Added `Illuminate\Support\Collection::some` method ([#26376](https://github.com/laravel/framework/pull/26376), [8f7e647](https://github.com/laravel/framework/commit/8f7e647dcee5fe13d7fc33a1e0e1ce531ea9f49e)) +- Added `Illuminate\Cache\Repository::missing` method ([#26351](https://github.com/laravel/framework/pull/26351)) +- Added `Macroable` trait to `Illuminate\View\Factory` ([#26361](https://github.com/laravel/framework/pull/26361)) +- Added support for UNION aggregate queries ([#26365](https://github.com/laravel/framework/pull/26365)) + +### Changed +- Updated `AbstractPaginator::appends` to handle null ([#26326](https://github.com/laravel/framework/pull/26326)) +- Added "guzzlehttp/guzzle": "^6.3", to `composer.json` ([#26328](https://github.com/laravel/framework/pull/26328)) +- Showed exception message on 403 error page when message is available ([#26356](https://github.com/laravel/framework/pull/26356)) +- Don't run TransformsRequest twice on ?query= parameters ([#26366](https://github.com/laravel/framework/pull/26366)) +- Added missing logging options to slack log driver ([#26360](https://github.com/laravel/framework/pull/26360)) +- Use cascade when truncating table in PostgreSQL ([#26389](https://github.com/laravel/framework/pull/26389)) +- Allowed pass absolute parameter in has valid signature request macro ([#26397](https://github.com/laravel/framework/pull/26397)) + +### Changed realization +- Used `Request::validate` macro in Auth traits ([#26314](https://github.com/laravel/framework/pull/26314)) + + ## [v5.7.12 (2018-10-30)](https://github.com/laravel/framework/compare/v5.7.11...v5.7.12) ### Added diff --git a/src/Illuminate/Auth/Console/stubs/make/views/auth/login.stub b/src/Illuminate/Auth/Console/stubs/make/views/auth/login.stub index 47e3f53068ad..9ee63bfbadc7 100644 --- a/src/Illuminate/Auth/Console/stubs/make/views/auth/login.stub +++ b/src/Illuminate/Auth/Console/stubs/make/views/auth/login.stub @@ -57,9 +57,11 @@ {{ __('Login') }} - - {{ __('Forgot Your Password?') }} - + @if (Route::has('password.request')) + + {{ __('Forgot Your Password?') }} + + @endif diff --git a/src/Illuminate/Cache/Console/ClearCommand.php b/src/Illuminate/Cache/Console/ClearCommand.php index c70f459a2587..4feeb17d0797 100755 --- a/src/Illuminate/Cache/Console/ClearCommand.php +++ b/src/Illuminate/Cache/Console/ClearCommand.php @@ -60,7 +60,7 @@ public function __construct(CacheManager $cache, Filesystem $files) */ public function handle() { - $this->laravel['events']->fire( + $this->laravel['events']->dispatch( 'cache:clearing', [$this->argument('store'), $this->tags()] ); @@ -72,7 +72,7 @@ public function handle() return $this->error('Failed to clear cache. Make sure you have the appropriate permissions.'); } - $this->laravel['events']->fire( + $this->laravel['events']->dispatch( 'cache:cleared', [$this->argument('store'), $this->tags()] ); diff --git a/src/Illuminate/Cache/Repository.php b/src/Illuminate/Cache/Repository.php index 83fdbcab9249..03eb26f0f86a 100755 --- a/src/Illuminate/Cache/Repository.php +++ b/src/Illuminate/Cache/Repository.php @@ -70,6 +70,17 @@ public function has($key) return ! is_null($this->get($key)); } + /** + * Determine if an item doesn't exist in the cache. + * + * @param string $key + * @return bool + */ + public function missing($key) + { + return ! $this->has($key); + } + /** * Retrieve an item from the cache by key. * @@ -308,7 +319,7 @@ public function forever($key, $value) } /** - * Get an item from the cache, or store the default value. + * Get an item from the cache, or execute the given Closure and store the result. * * @param string $key * @param \DateTimeInterface|\DateInterval|float|int $minutes @@ -332,9 +343,9 @@ public function remember($key, $minutes, Closure $callback) } /** - * Get an item from the cache, or store the default value forever. + * Get an item from the cache, or execute the given Closure and store the result forever. * - * @param string $key + * @param string $key * @param \Closure $callback * @return mixed */ @@ -344,9 +355,9 @@ public function sear($key, Closure $callback) } /** - * Get an item from the cache, or store the default value forever. + * Get an item from the cache, or execute the given Closure and store the result forever. * - * @param string $key + * @param string $key * @param \Closure $callback * @return mixed */ diff --git a/src/Illuminate/Console/Application.php b/src/Illuminate/Console/Application.php index d57236582b19..f8b9ca4202ef 100755 --- a/src/Illuminate/Console/Application.php +++ b/src/Illuminate/Console/Application.php @@ -80,7 +80,7 @@ public function run(InputInterface $input = null, OutputInterface $output = null $input = $input ?: new ArgvInput ); - $this->events->fire( + $this->events->dispatch( new Events\CommandStarting( $commandName, $input, $output = $output ?: new ConsoleOutput ) @@ -88,7 +88,7 @@ public function run(InputInterface $input = null, OutputInterface $output = null $exitCode = parent::run($input, $output); - $this->events->fire( + $this->events->dispatch( new Events\CommandFinished($commandName, $input, $output, $exitCode) ); diff --git a/src/Illuminate/Contracts/Cache/Repository.php b/src/Illuminate/Contracts/Cache/Repository.php index 7820fbf7af89..054a97229574 100644 --- a/src/Illuminate/Contracts/Cache/Repository.php +++ b/src/Illuminate/Contracts/Cache/Repository.php @@ -81,7 +81,7 @@ public function decrement($key, $value = 1); public function forever($key, $value); /** - * Get an item from the cache, or store the default value. + * Get an item from the cache, or execute the given Closure and store the result. * * @param string $key * @param \DateTimeInterface|\DateInterval|float|int $minutes @@ -91,18 +91,18 @@ public function forever($key, $value); public function remember($key, $minutes, Closure $callback); /** - * Get an item from the cache, or store the default value forever. + * Get an item from the cache, or execute the given Closure and store the result forever. * - * @param string $key + * @param string $key * @param \Closure $callback * @return mixed */ public function sear($key, Closure $callback); /** - * Get an item from the cache, or store the default value forever. + * Get an item from the cache, or execute the given Closure and store the result forever. * - * @param string $key + * @param string $key * @param \Closure $callback * @return mixed */ diff --git a/src/Illuminate/Contracts/Validation/Rule.php b/src/Illuminate/Contracts/Validation/Rule.php index 709b620554c7..cc03777af784 100644 --- a/src/Illuminate/Contracts/Validation/Rule.php +++ b/src/Illuminate/Contracts/Validation/Rule.php @@ -16,7 +16,7 @@ public function passes($attribute, $value); /** * Get the validation error message. * - * @return string + * @return string|array */ public function message(); } diff --git a/src/Illuminate/Cookie/CookieJar.php b/src/Illuminate/Cookie/CookieJar.php index abf23fd87933..5d28510e3937 100755 --- a/src/Illuminate/Cookie/CookieJar.php +++ b/src/Illuminate/Cookie/CookieJar.php @@ -3,13 +3,14 @@ namespace Illuminate\Cookie; use Illuminate\Support\Arr; +use Illuminate\Support\Traits\Macroable; use Illuminate\Support\InteractsWithTime; use Symfony\Component\HttpFoundation\Cookie; use Illuminate\Contracts\Cookie\QueueingFactory as JarContract; class CookieJar implements JarContract { - use InteractsWithTime; + use InteractsWithTime, Macroable; /** * The default path (if specified). diff --git a/src/Illuminate/Database/Console/Migrations/TableGuesser.php b/src/Illuminate/Database/Console/Migrations/TableGuesser.php index edcd706ff2d3..82dfbddbbceb 100644 --- a/src/Illuminate/Database/Console/Migrations/TableGuesser.php +++ b/src/Illuminate/Database/Console/Migrations/TableGuesser.php @@ -4,6 +4,16 @@ class TableGuesser { + const CREATE_PATTERNS = [ + '/^create_(\w+)_table$/', + '/^create_(\w+)$/', + ]; + + const CHANGE_PATTERNS = [ + '/_(to|from|in)_(\w+)_table$/', + '/_(to|from|in)_(\w+)$/', + ]; + /** * Attempt to guess the table name and "creation" status of the given migration. * @@ -12,12 +22,16 @@ class TableGuesser */ public static function guess($migration) { - if (preg_match('/^create_(\w+)_table$/', $migration, $matches)) { - return [$matches[1], $create = true]; + foreach (self::CREATE_PATTERNS as $pattern) { + if (preg_match($pattern, $migration, $matches)) { + return [$matches[1], $create = true]; + } } - if (preg_match('/_(to|from|in)_(\w+)_table$/', $migration, $matches)) { - return [$matches[2], $create = false]; + foreach (self::CHANGE_PATTERNS as $pattern) { + if (preg_match($pattern, $migration, $matches)) { + return [$matches[2], $create = false]; + } } } } diff --git a/src/Illuminate/Database/Eloquent/Relations/BelongsTo.php b/src/Illuminate/Database/Eloquent/Relations/BelongsTo.php index 3259bdcd1499..9af168c1f1c9 100755 --- a/src/Illuminate/Database/Eloquent/Relations/BelongsTo.php +++ b/src/Illuminate/Database/Eloquent/Relations/BelongsTo.php @@ -111,7 +111,9 @@ public function addEagerConstraints(array $models) // our eagerly loading query so it returns the proper models from execution. $key = $this->related->getTable().'.'.$this->ownerKey; - $this->query->whereIn($key, $this->getEagerModelKeys($models)); + $whereIn = $this->whereInMethod($this->related, $this->ownerKey); + + $this->query->{$whereIn}($key, $this->getEagerModelKeys($models)); } /** diff --git a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php index d1a1242df69f..5e6f327f9a36 100755 --- a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php @@ -209,7 +209,12 @@ protected function addWhereConstraints() */ public function addEagerConstraints(array $models) { - $this->query->whereIn($this->getQualifiedForeignPivotKeyName(), $this->getKeys($models, $this->parentKey)); + $whereIn = $this->whereInMethod($this->parent, $this->parentKey); + + $this->query->{$whereIn}( + $this->getQualifiedForeignPivotKeyName(), + $this->getKeys($models, $this->parentKey) + ); } /** diff --git a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php index 4ac8a3713e0c..2b69ca3694b4 100644 --- a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php @@ -146,7 +146,9 @@ public function throughParentSoftDeletes() */ public function addEagerConstraints(array $models) { - $this->query->whereIn( + $whereIn = $this->whereInMethod($this->farParent, $this->localKey); + + $this->query->{$whereIn}( $this->getQualifiedFirstKeyName(), $this->getKeys($models, $this->localKey) ); } diff --git a/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php b/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php index 64e16c2d31fe..57295e625e93 100755 --- a/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php @@ -81,7 +81,9 @@ public function addConstraints() */ public function addEagerConstraints(array $models) { - $this->query->whereIn( + $whereIn = $this->whereInMethod($this->parent, $this->localKey); + + $this->query->{$whereIn}( $this->foreignKey, $this->getKeys($models, $this->localKey) ); } diff --git a/src/Illuminate/Database/Eloquent/Relations/Relation.php b/src/Illuminate/Database/Eloquent/Relations/Relation.php index 7c563c80b05c..671d21127a50 100755 --- a/src/Illuminate/Database/Eloquent/Relations/Relation.php +++ b/src/Illuminate/Database/Eloquent/Relations/Relation.php @@ -307,6 +307,21 @@ public function relatedUpdatedAt() return $this->related->getUpdatedAtColumn(); } + /** + * Get the name of the "where in" method for eager loading. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @param string $key + * @return string + */ + protected function whereInMethod(Model $model, $key) + { + return $model->getKeyName() === last(explode('.', $key)) + && in_array($model->getKeyType(), ['int', 'integer']) + ? 'whereInRaw' + : 'whereIn'; + } + /** * Set or get the morph map for polymorphic relations. * diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index bd66993decbb..4d6e8a6917ac 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -948,6 +948,30 @@ protected function whereInExistingQuery($column, $query, $boolean, $not) return $this; } + /** + * Add a "where in raw" clause to the query. + * + * @param string $column + * @param array $values + * @param string $boolean + * @param bool $not + * @return $this + */ + public function whereInRaw($column, array $values, $boolean = 'and', $not = false) + { + $type = $not ? 'NotInRaw' : 'InRaw'; + + if ($values instanceof Arrayable) { + $values = $values->toArray(); + } + + $values = array_map('intval', $values); + + $this->wheres[] = compact('type', 'column', 'values', 'boolean'); + + return $this; + } + /** * Add a "where null" clause to the query. * @@ -2106,8 +2130,10 @@ public function getCountForPagination($columns = ['*']) */ protected function runPaginationCountQuery($columns = ['*']) { - return $this->cloneWithout(['columns', 'orders', 'limit', 'offset']) - ->cloneWithoutBindings(['select', 'order']) + $without = $this->unions ? ['orders', 'limit', 'offset'] : ['columns', 'orders', 'limit', 'offset']; + + return $this->cloneWithout($without) + ->cloneWithoutBindings($this->unions ? ['order'] : ['select', 'order']) ->setAggregate('count', $this->withoutSelectAliases($columns)) ->get()->all(); } @@ -2420,8 +2446,8 @@ public function average($column) */ public function aggregate($function, $columns = ['*']) { - $results = $this->cloneWithout(['columns']) - ->cloneWithoutBindings(['select']) + $results = $this->cloneWithout($this->unions ? [] : ['columns']) + ->cloneWithoutBindings($this->unions ? [] : ['select']) ->setAggregate($function, $columns) ->get($columns); diff --git a/src/Illuminate/Database/Query/Grammars/Grammar.php b/src/Illuminate/Database/Query/Grammars/Grammar.php index 29b08be0619f..3534a8a02748 100755 --- a/src/Illuminate/Database/Query/Grammars/Grammar.php +++ b/src/Illuminate/Database/Query/Grammars/Grammar.php @@ -46,6 +46,10 @@ class Grammar extends BaseGrammar */ public function compileSelect(Builder $query) { + if ($query->unions && $query->aggregate) { + return $this->compileUnionAggregate($query); + } + // If the query does not have any columns set, we'll set the columns to the // * character to just get all of the columns from the database. Then we // can build the query and concatenate all the pieces together as one. @@ -297,6 +301,24 @@ protected function whereNotInSub(Builder $query, $where) return $this->wrap($where['column']).' not in ('.$this->compileSelect($where['query']).')'; } + /** + * Compile a "where in raw" clause. + * + * For safety, this method is only used with integer values as whereInRaw utilizes "intval". + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereInRaw(Builder $query, $where) + { + if (! empty($where['values'])) { + return $this->wrap($where['column']).' in ('.implode(', ', $where['values']).')'; + } + + return '0 = 1'; + } + /** * Compile a "where null" clause. * @@ -735,6 +757,21 @@ protected function compileUnion(array $union) return $conjunction.$union['query']->toSql(); } + /** + * Compile a union aggregate query into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + protected function compileUnionAggregate(Builder $query) + { + $sql = $this->compileAggregate($query, $query->aggregate); + + $query->aggregate = null; + + return $sql.' from ('.$this->compileSelect($query).') as '.$this->wrapTable('temp_table'); + } + /** * Compile an exists statement into SQL. * diff --git a/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php b/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php index 9bc33286a4b6..50af2276f3a5 100755 --- a/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php +++ b/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php @@ -43,6 +43,10 @@ class MySqlGrammar extends Grammar */ public function compileSelect(Builder $query) { + if ($query->unions && $query->aggregate) { + return $this->compileUnionAggregate($query); + } + $sql = parent::compileSelect($query); if ($query->unions) { diff --git a/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php index d9350ef7bca0..be5b882d6ac6 100755 --- a/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php @@ -360,7 +360,7 @@ protected function compileDeleteWithJoins($query, $table) */ public function compileTruncate(Builder $query) { - return ['truncate '.$this->wrapTable($query->from).' restart identity' => []]; + return ['truncate '.$this->wrapTable($query->from).' restart identity cascade' => []]; } /** diff --git a/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php index 2d8e101ab6ec..43ddec98443d 100755 --- a/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php +++ b/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php @@ -46,6 +46,10 @@ class SQLiteGrammar extends Grammar */ public function compileSelect(Builder $query) { + if ($query->unions && $query->aggregate) { + return $this->compileUnionAggregate($query); + } + $sql = parent::compileSelect($query); if ($query->unions) { diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index c1973acda573..7d3c086a4752 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -29,7 +29,7 @@ class Application extends Container implements ApplicationContract, HttpKernelIn * * @var string */ - const VERSION = '5.7.12'; + const VERSION = '5.7.13'; /** * The base path for the Laravel installation. @@ -201,11 +201,11 @@ public function bootstrapWith(array $bootstrappers) $this->hasBeenBootstrapped = true; foreach ($bootstrappers as $bootstrapper) { - $this['events']->fire('bootstrapping: '.$bootstrapper, [$this]); + $this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]); $this->make($bootstrapper)->bootstrap($this); - $this['events']->fire('bootstrapped: '.$bootstrapper, [$this]); + $this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]); } } diff --git a/src/Illuminate/Foundation/Console/PresetCommand.php b/src/Illuminate/Foundation/Console/PresetCommand.php index a63082ea16fe..9c7e6c1a886a 100644 --- a/src/Illuminate/Foundation/Console/PresetCommand.php +++ b/src/Illuminate/Foundation/Console/PresetCommand.php @@ -27,6 +27,8 @@ class PresetCommand extends Command * Execute the console command. * * @return void + * + * @throws \InvalidArgumentException */ public function handle() { diff --git a/src/Illuminate/Foundation/Exceptions/views/401.blade.php b/src/Illuminate/Foundation/Exceptions/views/401.blade.php index 6da906216272..10f05b88c3e0 100644 --- a/src/Illuminate/Foundation/Exceptions/views/401.blade.php +++ b/src/Illuminate/Foundation/Exceptions/views/401.blade.php @@ -4,7 +4,7 @@ @section('title', __('Unauthorized')) @section('image') -
+
@endsection diff --git a/src/Illuminate/Foundation/Exceptions/views/403.blade.php b/src/Illuminate/Foundation/Exceptions/views/403.blade.php index 8f526c6eebc9..199953a23993 100644 --- a/src/Illuminate/Foundation/Exceptions/views/403.blade.php +++ b/src/Illuminate/Foundation/Exceptions/views/403.blade.php @@ -4,8 +4,8 @@ @section('title', __('Forbidden')) @section('image') -
+
@endsection -@section('message', __('Sorry, you are forbidden from accessing this page.')) +@section('message', __($exception->getMessage() ?: 'Sorry, you are forbidden from accessing this page.')) diff --git a/src/Illuminate/Foundation/Exceptions/views/404.blade.php b/src/Illuminate/Foundation/Exceptions/views/404.blade.php index 0398a98e0057..d2bae51f806f 100644 --- a/src/Illuminate/Foundation/Exceptions/views/404.blade.php +++ b/src/Illuminate/Foundation/Exceptions/views/404.blade.php @@ -4,7 +4,7 @@ @section('title', __('Page Not Found')) @section('image') -
+
@endsection diff --git a/src/Illuminate/Foundation/Exceptions/views/419.blade.php b/src/Illuminate/Foundation/Exceptions/views/419.blade.php index 1671bffb49df..1b00819f0b7c 100644 --- a/src/Illuminate/Foundation/Exceptions/views/419.blade.php +++ b/src/Illuminate/Foundation/Exceptions/views/419.blade.php @@ -4,7 +4,7 @@ @section('title', __('Page Expired')) @section('image') -
+
@endsection diff --git a/src/Illuminate/Foundation/Exceptions/views/429.blade.php b/src/Illuminate/Foundation/Exceptions/views/429.blade.php index a9f3caa53a81..2747654acf19 100644 --- a/src/Illuminate/Foundation/Exceptions/views/429.blade.php +++ b/src/Illuminate/Foundation/Exceptions/views/429.blade.php @@ -4,7 +4,7 @@ @section('title', __('Too Many Requests')) @section('image') -
+
@endsection diff --git a/src/Illuminate/Foundation/Exceptions/views/500.blade.php b/src/Illuminate/Foundation/Exceptions/views/500.blade.php index 8c5e2d5d6891..8868cf822bea 100644 --- a/src/Illuminate/Foundation/Exceptions/views/500.blade.php +++ b/src/Illuminate/Foundation/Exceptions/views/500.blade.php @@ -4,7 +4,7 @@ @section('title', __('Error')) @section('image') -
+
@endsection diff --git a/src/Illuminate/Foundation/Exceptions/views/503.blade.php b/src/Illuminate/Foundation/Exceptions/views/503.blade.php index 8272e0e98ef0..d75ae7e7e473 100644 --- a/src/Illuminate/Foundation/Exceptions/views/503.blade.php +++ b/src/Illuminate/Foundation/Exceptions/views/503.blade.php @@ -4,7 +4,7 @@ @section('title', __('Service Unavailable')) @section('image') -
+
@endsection diff --git a/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php b/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php index 4c1c4920ac8e..9c30fdcccf1e 100644 --- a/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php +++ b/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php @@ -43,7 +43,7 @@ protected function clean($request) if ($request->isJson()) { $this->cleanParameterBag($request->json()); - } else { + } elseif ($request->request !== $request->query) { $this->cleanParameterBag($request->request); } } diff --git a/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php b/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php index b12c1799aed7..ce31986a51e9 100644 --- a/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php +++ b/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php @@ -49,8 +49,8 @@ public function registerRequestValidation() */ public function registerRequestSignatureValidation() { - Request::macro('hasValidSignature', function () { - return URL::hasValidSignature($this); + Request::macro('hasValidSignature', function ($absolute = true) { + return URL::hasValidSignature($this, $absolute); }); } } diff --git a/src/Illuminate/Foundation/Testing/Concerns/MocksApplicationServices.php b/src/Illuminate/Foundation/Testing/Concerns/MocksApplicationServices.php index 1ad4be4d8ed0..a6e51d493e95 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/MocksApplicationServices.php +++ b/src/Illuminate/Foundation/Testing/Concerns/MocksApplicationServices.php @@ -175,7 +175,7 @@ protected function doesntExpectJobs($jobs) */ protected function withoutJobs() { - $mock = Mockery::mock(BusDispatcherContract::class); + $mock = Mockery::mock(BusDispatcherContract::class)->shouldIgnoreMissing(); $mock->shouldReceive('dispatch', 'dispatchNow')->andReturnUsing(function ($dispatched) { $this->dispatchedJobs[] = $dispatched; diff --git a/src/Illuminate/Foundation/helpers.php b/src/Illuminate/Foundation/helpers.php index 1b200624a8ab..123df898f20d 100644 --- a/src/Illuminate/Foundation/helpers.php +++ b/src/Illuminate/Foundation/helpers.php @@ -609,10 +609,14 @@ function mix($path, $manifestDirectory = '') $manifest = $manifests[$manifestPath]; if (! isset($manifest[$path])) { - report(new Exception("Unable to locate Mix file: {$path}.")); + $exception = new Exception("Unable to locate Mix file: {$path}."); if (! app('config')->get('app.debug')) { + report($exception); + return $path; + } else { + throw $exception; } } diff --git a/src/Illuminate/Log/LogManager.php b/src/Illuminate/Log/LogManager.php index ac5942b22bb4..bd3718810279 100644 --- a/src/Illuminate/Log/LogManager.php +++ b/src/Illuminate/Log/LogManager.php @@ -270,8 +270,10 @@ protected function createSlackDriver(array $config) $config['emoji'] ?? ':boom:', $config['short'] ?? false, $config['context'] ?? true, - $this->level($config) - )), + $this->level($config), + $config['bubble'] ?? true, + $config['exclude_fields'] ?? [] + ), $config), ]); } diff --git a/src/Illuminate/Queue/Jobs/RedisJob.php b/src/Illuminate/Queue/Jobs/RedisJob.php index d7059fd9753a..34e4fea0aef7 100644 --- a/src/Illuminate/Queue/Jobs/RedisJob.php +++ b/src/Illuminate/Queue/Jobs/RedisJob.php @@ -120,7 +120,7 @@ public function getJobId() /** * Get the underlying Redis factory implementation. * - * @return \Illuminate\Contracts\Redis\Factory + * @return \Illuminate\Queue\RedisQueue */ public function getRedisQueue() { diff --git a/src/Illuminate/Routing/Router.php b/src/Illuminate/Routing/Router.php index c3f08953405b..83dabfe4973e 100644 --- a/src/Illuminate/Routing/Router.php +++ b/src/Illuminate/Routing/Router.php @@ -1155,10 +1155,12 @@ public function auth(array $options = []) } // Password Reset Routes... - $this->get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request'); - $this->post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email'); - $this->get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset'); - $this->post('password/reset', 'Auth\ResetPasswordController@reset')->name('password.update'); + if ($options['reset'] ?? true) { + $this->get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request'); + $this->post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email'); + $this->get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset'); + $this->post('password/reset', 'Auth\ResetPasswordController@reset')->name('password.update'); + } // Email Verification Routes... if ($options['verify'] ?? false) { diff --git a/src/Illuminate/Support/Collection.php b/src/Illuminate/Support/Collection.php index 4f25bacc05ce..f3449cb83ac3 100644 --- a/src/Illuminate/Support/Collection.php +++ b/src/Illuminate/Support/Collection.php @@ -58,7 +58,7 @@ class Collection implements ArrayAccess, Arrayable, Countable, IteratorAggregate protected static $proxies = [ 'average', 'avg', 'contains', 'each', 'every', 'filter', 'first', 'flatMap', 'groupBy', 'keyBy', 'map', 'max', 'min', 'partition', - 'reject', 'sortBy', 'sortByDesc', 'sum', 'unique', + 'reject', 'some', 'sortBy', 'sortByDesc', 'sum', 'unique', ]; /** @@ -238,6 +238,19 @@ public function collapse() return new static(Arr::collapse($this->items)); } + /** + * Alias for the "contains" method. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function some($key, $operator = null, $value = null) + { + return $this->contains($key, $operator, $value); + } + /** * Determine if an item exists in the collection. * @@ -501,6 +514,32 @@ public function when($value, callable $callback, callable $default = null) return $this; } + /** + * Apply the callback if the collection is empty. + * + * @param bool $value + * @param callable $callback + * @param callable $default + * @return static|mixed + */ + public function whenEmpty(callable $callback, callable $default = null) + { + return $this->when($this->isEmpty(), $callback, $default); + } + + /** + * Apply the callback if the collection is not empty. + * + * @param bool $value + * @param callable $callback + * @param callable $default + * @return static|mixed + */ + public function whenNotEmpty(callable $callback, callable $default = null) + { + return $this->when($this->isNotEmpty(), $callback, $default); + } + /** * Apply the callback if the value is falsy. * @@ -514,6 +553,32 @@ public function unless($value, callable $callback, callable $default = null) return $this->when(! $value, $callback, $default); } + /** + * Apply the callback unless the collection is empty. + * + * @param bool $value + * @param callable $callback + * @param callable $default + * @return static|mixed + */ + public function unlessEmpty(callable $callback, callable $default = null) + { + return $this->unless($this->isEmpty(), $callback, $default); + } + + /** + * Apply the callback unless the collection is not empty. + * + * @param bool $value + * @param callable $callback + * @param callable $default + * @return static|mixed + */ + public function unlessNotEmpty(callable $callback, callable $default = null) + { + return $this->unless($this->isNotEmpty(), $callback, $default); + } + /** * Filter items by the given key value pair. * diff --git a/src/Illuminate/Support/Facades/Cache.php b/src/Illuminate/Support/Facades/Cache.php index 6be6f13d52d6..67022817cf01 100755 --- a/src/Illuminate/Support/Facades/Cache.php +++ b/src/Illuminate/Support/Facades/Cache.php @@ -5,6 +5,7 @@ /** * @method static \Illuminate\Contracts\Cache\Repository store(string|null $name = null) * @method static bool has(string $key) + * @method static bool missing(string $key) * @method static mixed get(string $key, mixed $default = null) * @method static mixed pull(string $key, mixed $default = null) * @method static void put(string $key, $value, \DateTimeInterface|\DateInterval|float|int $minutes) diff --git a/src/Illuminate/Validation/Validator.php b/src/Illuminate/Validation/Validator.php index 911c0be2b819..21387d9908a2 100755 --- a/src/Illuminate/Validation/Validator.php +++ b/src/Illuminate/Validation/Validator.php @@ -547,9 +547,13 @@ protected function validateUsingCustomRule($attribute, $value, $rule) if (! $rule->passes($attribute, $value)) { $this->failedRules[$attribute][get_class($rule)] = []; - $this->messages->add($attribute, $this->makeReplacements( - $rule->message(), $attribute, get_class($rule), [] - )); + $messages = (array) $rule->message(); + + foreach ($messages as $message) { + $this->messages->add($attribute, $this->makeReplacements( + $message, $attribute, get_class($rule), [] + )); + } } } diff --git a/src/Illuminate/View/Factory.php b/src/Illuminate/View/Factory.php index 7918155df30e..201486034ce6 100755 --- a/src/Illuminate/View/Factory.php +++ b/src/Illuminate/View/Factory.php @@ -5,6 +5,7 @@ use Illuminate\Support\Arr; use Illuminate\Support\Str; use InvalidArgumentException; +use Illuminate\Support\Traits\Macroable; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Support\Arrayable; use Illuminate\View\Engines\EngineResolver; @@ -13,7 +14,8 @@ class Factory implements FactoryContract { - use Concerns\ManagesComponents, + use Macroable, + Concerns\ManagesComponents, Concerns\ManagesEvents, Concerns\ManagesLayouts, Concerns\ManagesLoops, diff --git a/tests/Auth/AuthAccessGateTest.php b/tests/Auth/AuthAccessGateTest.php index 0a2cdd480846..f95b0b63400e 100644 --- a/tests/Auth/AuthAccessGateTest.php +++ b/tests/Auth/AuthAccessGateTest.php @@ -373,6 +373,24 @@ public function test_invokable_classes_can_be_defined() $this->assertTrue($gate->check('foo')); } + public function test_gates_can_be_defined_using_an_array_callback() + { + $gate = $this->getBasicGate(); + + $gate->define('foo', [new AccessGateTestStaticClass, 'foo']); + + $this->assertTrue($gate->check('foo')); + } + + public function test_gates_can_be_defined_using_an_array_callback_with_static_method() + { + $gate = $this->getBasicGate(); + + $gate->define('foo', [AccessGateTestStaticClass::class, 'foo']); + + $this->assertTrue($gate->check('foo')); + } + public function test_policy_classes_can_be_defined_to_handle_checks_for_given_type() { $gate = $this->getBasicGate(); @@ -480,7 +498,7 @@ public function test_for_user_method_attaches_a_new_user_to_a_new_gate_instance( * @dataProvider notCallableDataProvider * @expectedException \InvalidArgumentException */ - public function test_define_second_parametter_should_be_string_or_callable($callback) + public function test_define_second_parameter_should_be_string_or_callable($callback) { $gate = $this->getBasicGate(); @@ -638,6 +656,14 @@ public function hasAbilitiesTestDataProvider() } } +class AccessGateTestStaticClass +{ + public static function foo() + { + return true; + } +} + class AccessGateTestClass { public function foo() diff --git a/tests/Cache/CacheRepositoryTest.php b/tests/Cache/CacheRepositoryTest.php index 4be76ad9bf59..b88cafec0f45 100755 --- a/tests/Cache/CacheRepositoryTest.php +++ b/tests/Cache/CacheRepositoryTest.php @@ -71,6 +71,16 @@ public function testHasMethod() $this->assertFalse($repo->has('foo')); } + public function testMissingMethod() + { + $repo = $this->getRepository(); + $repo->getStore()->shouldReceive('get')->once()->with('foo')->andReturn(null); + $repo->getStore()->shouldReceive('get')->once()->with('bar')->andReturn('bar'); + + $this->assertTrue($repo->missing('foo')); + $this->assertFalse($repo->missing('bar')); + } + public function testRememberMethodCallsPutAndReturnsDefault() { $repo = $this->getRepository(); diff --git a/tests/Cookie/CookieTest.php b/tests/Cookie/CookieTest.php index d37442e19f2b..8ee3bee5e716 100755 --- a/tests/Cookie/CookieTest.php +++ b/tests/Cookie/CookieTest.php @@ -88,6 +88,15 @@ public function testUnqueue() $this->assertEmpty($cookie->getQueuedCookies()); } + public function testCookieJarIsMacroable() + { + $cookie = $this->getCreator(); + $cookie->macro('foo', function () { + return 'bar'; + }); + $this->assertEquals('bar', $cookie->foo()); + } + public function getCreator() { return new CookieJar(Request::create('/foo', 'GET'), [ diff --git a/tests/Database/DatabaseEloquentBelongsToTest.php b/tests/Database/DatabaseEloquentBelongsToTest.php index 602aeaacea9d..0d3c64d3f8dd 100755 --- a/tests/Database/DatabaseEloquentBelongsToTest.php +++ b/tests/Database/DatabaseEloquentBelongsToTest.php @@ -79,7 +79,9 @@ public function testUpdateMethodRetrievesModelAndUpdates() public function testEagerConstraintsAreProperlyAdded() { $relation = $this->getRelation(); - $relation->getQuery()->shouldReceive('whereIn')->once()->with('relation.id', ['foreign.value', 'foreign.value.two']); + $relation->getRelated()->shouldReceive('getKeyName')->andReturn('id'); + $relation->getRelated()->shouldReceive('getKeyType')->andReturn('int'); + $relation->getQuery()->shouldReceive('whereInRaw')->once()->with('relation.id', ['foreign.value', 'foreign.value.two']); $models = [new EloquentBelongsToModelStub, new EloquentBelongsToModelStub, new AnotherEloquentBelongsToModelStub]; $relation->addEagerConstraints($models); } @@ -87,7 +89,9 @@ public function testEagerConstraintsAreProperlyAdded() public function testIdsInEagerConstraintsCanBeZero() { $relation = $this->getRelation(); - $relation->getQuery()->shouldReceive('whereIn')->once()->with('relation.id', ['foreign.value', 0]); + $relation->getRelated()->shouldReceive('getKeyName')->andReturn('id'); + $relation->getRelated()->shouldReceive('getKeyType')->andReturn('int'); + $relation->getQuery()->shouldReceive('whereInRaw')->once()->with('relation.id', ['foreign.value', 0]); $models = [new EloquentBelongsToModelStub, new EloquentBelongsToModelStubWithZeroId]; $relation->addEagerConstraints($models); } @@ -154,7 +158,9 @@ public function testAssociateMethodSetsForeignKeyOnModelById() public function testDefaultEagerConstraintsWhenIncrementing() { $relation = $this->getRelation(); - $relation->getQuery()->shouldReceive('whereIn')->once()->with('relation.id', m::mustBe([null])); + $relation->getRelated()->shouldReceive('getKeyName')->andReturn('id'); + $relation->getRelated()->shouldReceive('getKeyType')->andReturn('int'); + $relation->getQuery()->shouldReceive('whereInRaw')->once()->with('relation.id', m::mustBe([null])); $models = [new MissingEloquentBelongsToModelStub, new MissingEloquentBelongsToModelStub]; $relation->addEagerConstraints($models); } @@ -170,7 +176,9 @@ public function testDefaultEagerConstraintsWhenIncrementingAndNonIntKeyType() public function testDefaultEagerConstraintsWhenNotIncrementing() { $relation = $this->getRelation(null, false); - $relation->getQuery()->shouldReceive('whereIn')->once()->with('relation.id', m::mustBe([null])); + $relation->getRelated()->shouldReceive('getKeyName')->andReturn('id'); + $relation->getRelated()->shouldReceive('getKeyType')->andReturn('int'); + $relation->getQuery()->shouldReceive('whereInRaw')->once()->with('relation.id', m::mustBe([null])); $models = [new MissingEloquentBelongsToModelStub, new MissingEloquentBelongsToModelStub]; $relation->addEagerConstraints($models); } diff --git a/tests/Database/DatabaseEloquentHasManyTest.php b/tests/Database/DatabaseEloquentHasManyTest.php index f2ce97b81dbd..71a979b3fd86 100755 --- a/tests/Database/DatabaseEloquentHasManyTest.php +++ b/tests/Database/DatabaseEloquentHasManyTest.php @@ -200,6 +200,21 @@ public function testRelationIsProperlyInitialized() public function testEagerConstraintsAreProperlyAdded() { $relation = $this->getRelation(); + $relation->getParent()->shouldReceive('getKeyName')->once()->andReturn('id'); + $relation->getParent()->shouldReceive('getKeyType')->once()->andReturn('int'); + $relation->getQuery()->shouldReceive('whereInRaw')->once()->with('table.foreign_key', [1, 2]); + $model1 = new EloquentHasManyModelStub; + $model1->id = 1; + $model2 = new EloquentHasManyModelStub; + $model2->id = 2; + $relation->addEagerConstraints([$model1, $model2]); + } + + public function testEagerConstraintsAreProperlyAddedWithStringKey() + { + $relation = $this->getRelation(); + $relation->getParent()->shouldReceive('getKeyName')->once()->andReturn('id'); + $relation->getParent()->shouldReceive('getKeyType')->once()->andReturn('string'); $relation->getQuery()->shouldReceive('whereIn')->once()->with('table.foreign_key', [1, 2]); $model1 = new EloquentHasManyModelStub; $model1->id = 1; diff --git a/tests/Database/DatabaseEloquentHasOneTest.php b/tests/Database/DatabaseEloquentHasOneTest.php index f7bc4d74533a..319919c26f7c 100755 --- a/tests/Database/DatabaseEloquentHasOneTest.php +++ b/tests/Database/DatabaseEloquentHasOneTest.php @@ -163,7 +163,9 @@ public function testRelationIsProperlyInitialized() public function testEagerConstraintsAreProperlyAdded() { $relation = $this->getRelation(); - $relation->getQuery()->shouldReceive('whereIn')->once()->with('table.foreign_key', [1, 2]); + $relation->getParent()->shouldReceive('getKeyName')->once()->andReturn('id'); + $relation->getParent()->shouldReceive('getKeyType')->once()->andReturn('int'); + $relation->getQuery()->shouldReceive('whereInRaw')->once()->with('table.foreign_key', [1, 2]); $model1 = new EloquentHasOneModelStub; $model1->id = 1; $model2 = new EloquentHasOneModelStub; diff --git a/tests/Database/DatabaseEloquentMorphTest.php b/tests/Database/DatabaseEloquentMorphTest.php index 92999f432a81..ab84e85a890a 100755 --- a/tests/Database/DatabaseEloquentMorphTest.php +++ b/tests/Database/DatabaseEloquentMorphTest.php @@ -28,6 +28,8 @@ public function testMorphOneSetsProperConstraints() public function testMorphOneEagerConstraintsAreProperlyAdded() { $relation = $this->getOneRelation(); + $relation->getParent()->shouldReceive('getKeyName')->once()->andReturn('id'); + $relation->getParent()->shouldReceive('getKeyType')->once()->andReturn('string'); $relation->getQuery()->shouldReceive('whereIn')->once()->with('table.morph_id', [1, 2]); $relation->getQuery()->shouldReceive('where')->once()->with('table.morph_type', get_class($relation->getParent())); @@ -50,7 +52,9 @@ public function testMorphManySetsProperConstraints() public function testMorphManyEagerConstraintsAreProperlyAdded() { $relation = $this->getManyRelation(); - $relation->getQuery()->shouldReceive('whereIn')->once()->with('table.morph_id', [1, 2]); + $relation->getParent()->shouldReceive('getKeyName')->once()->andReturn('id'); + $relation->getParent()->shouldReceive('getKeyType')->once()->andReturn('int'); + $relation->getQuery()->shouldReceive('whereInRaw')->once()->with('table.morph_id', [1, 2]); $relation->getQuery()->shouldReceive('where')->once()->with('table.morph_type', get_class($relation->getParent())); $model1 = new EloquentMorphResetModelStub; diff --git a/tests/Database/DatabaseEloquentMorphToManyTest.php b/tests/Database/DatabaseEloquentMorphToManyTest.php index 761429aac6b4..95ab8a37c08e 100644 --- a/tests/Database/DatabaseEloquentMorphToManyTest.php +++ b/tests/Database/DatabaseEloquentMorphToManyTest.php @@ -18,7 +18,9 @@ public function tearDown() public function testEagerConstraintsAreProperlyAdded() { $relation = $this->getRelation(); - $relation->getQuery()->shouldReceive('whereIn')->once()->with('taggables.taggable_id', [1, 2]); + $relation->getParent()->shouldReceive('getKeyName')->andReturn('id'); + $relation->getParent()->shouldReceive('getKeyType')->andReturn('int'); + $relation->getQuery()->shouldReceive('whereInRaw')->once()->with('taggables.taggable_id', [1, 2]); $relation->getQuery()->shouldReceive('where')->once()->with('taggables.taggable_type', get_class($relation->getParent())); $model1 = new EloquentMorphToManyModelStub; $model1->id = 1; diff --git a/tests/Database/DatabaseMigrationMakeCommandTest.php b/tests/Database/DatabaseMigrationMakeCommandTest.php index 3481c31d5f23..1e34388da41c 100755 --- a/tests/Database/DatabaseMigrationMakeCommandTest.php +++ b/tests/Database/DatabaseMigrationMakeCommandTest.php @@ -27,7 +27,7 @@ public function testBasicCreateDumpsAutoload() $app = new Application; $app->useDatabasePath(__DIR__); $command->setLaravel($app); - $creator->shouldReceive('create')->once()->with('create_foo', __DIR__.DIRECTORY_SEPARATOR.'migrations', null, false); + $creator->shouldReceive('create')->once()->with('create_foo', __DIR__.DIRECTORY_SEPARATOR.'migrations', 'foo', true); $composer->shouldReceive('dumpAutoloads')->once(); $this->runCommand($command, ['name' => 'create_foo']); @@ -42,7 +42,7 @@ public function testBasicCreateGivesCreatorProperArguments() $app = new Application; $app->useDatabasePath(__DIR__); $command->setLaravel($app); - $creator->shouldReceive('create')->once()->with('create_foo', __DIR__.DIRECTORY_SEPARATOR.'migrations', null, false); + $creator->shouldReceive('create')->once()->with('create_foo', __DIR__.DIRECTORY_SEPARATOR.'migrations', 'foo', true); $this->runCommand($command, ['name' => 'create_foo']); } @@ -56,7 +56,7 @@ public function testBasicCreateGivesCreatorProperArgumentsWhenNameIsStudlyCase() $app = new Application; $app->useDatabasePath(__DIR__); $command->setLaravel($app); - $creator->shouldReceive('create')->once()->with('create_foo', __DIR__.DIRECTORY_SEPARATOR.'migrations', null, false); + $creator->shouldReceive('create')->once()->with('create_foo', __DIR__.DIRECTORY_SEPARATOR.'migrations', 'foo', true); $this->runCommand($command, ['name' => 'CreateFoo']); } diff --git a/tests/Database/DatabaseQueryBuilderTest.php b/tests/Database/DatabaseQueryBuilderTest.php index 952eafd20d4f..70b462b1331c 100755 --- a/tests/Database/DatabaseQueryBuilderTest.php +++ b/tests/Database/DatabaseQueryBuilderTest.php @@ -693,6 +693,14 @@ public function testEmptyWhereNotIns() $this->assertEquals([0 => 1], $builder->getBindings()); } + public function testWhereInRaw() + { + $builder = $this->getBuilder(); + $builder->select('*')->from('users')->whereInRaw('id', ['1a', 2]); + $this->assertEquals('select * from "users" where "id" in (1, 2)', $builder->toSql()); + $this->assertEquals([], $builder->getBindings()); + } + public function testBasicWhereColumn() { $builder = $this->getBuilder(); @@ -827,6 +835,39 @@ public function testMySqlUnionLimitsAndOffsets() $this->assertEquals('(select * from `users`) union (select * from `dogs`) limit 10 offset 5', $builder->toSql()); } + public function testUnionAggregate() + { + $expected = 'select count(*) as aggregate from ((select * from `posts`) union (select * from `videos`)) as `temp_table`'; + $builder = $this->getMySqlBuilder(); + $builder->getConnection()->shouldReceive('select')->once()->with($expected, [], true); + $builder->getProcessor()->shouldReceive('processSelect')->once(); + $builder->from('posts')->union($this->getMySqlBuilder()->from('videos'))->count(); + + $expected = 'select count(*) as aggregate from ((select `id` from `posts`) union (select `id` from `videos`)) as `temp_table`'; + $builder = $this->getMySqlBuilder(); + $builder->getConnection()->shouldReceive('select')->once()->with($expected, [], true); + $builder->getProcessor()->shouldReceive('processSelect')->once(); + $builder->from('posts')->select('id')->union($this->getMySqlBuilder()->from('videos')->select('id'))->count(); + + $expected = 'select count(*) as aggregate from (select * from "posts" union select * from "videos") as "temp_table"'; + $builder = $this->getPostgresBuilder(); + $builder->getConnection()->shouldReceive('select')->once()->with($expected, [], true); + $builder->getProcessor()->shouldReceive('processSelect')->once(); + $builder->from('posts')->union($this->getPostgresBuilder()->from('videos'))->count(); + + $expected = 'select count(*) as aggregate from (select * from (select * from "posts") union select * from (select * from "videos")) as "temp_table"'; + $builder = $this->getSQLiteBuilder(); + $builder->getConnection()->shouldReceive('select')->once()->with($expected, [], true); + $builder->getProcessor()->shouldReceive('processSelect')->once(); + $builder->from('posts')->union($this->getSQLiteBuilder()->from('videos'))->count(); + + $expected = 'select count(*) as aggregate from (select * from [posts] union select * from [videos]) as [temp_table]'; + $builder = $this->getSqlServerBuilder(); + $builder->getConnection()->shouldReceive('select')->once()->with($expected, [], true); + $builder->getProcessor()->shouldReceive('processSelect')->once(); + $builder->from('posts')->union($this->getSqlServerBuilder()->from('videos'))->count(); + } + public function testSubSelectWhereIns() { $builder = $this->getBuilder(); @@ -1060,6 +1101,20 @@ public function testGetCountForPaginationWithColumnAliases() $this->assertEquals(1, $count); } + public function testGetCountForPaginationWithUnion() + { + $builder = $this->getBuilder(); + $builder->from('posts')->select('id')->union($this->getBuilder()->from('videos')->select('id')); + + $builder->getConnection()->shouldReceive('select')->once()->with('select count(*) as aggregate from (select "id" from "posts" union select "id" from "videos") as "temp_table"', [], true)->andReturn([['aggregate' => 1]]); + $builder->getProcessor()->shouldReceive('processSelect')->once()->andReturnUsing(function ($builder, $results) { + return $results; + }); + + $count = $builder->getCountForPagination(); + $this->assertEquals(1, $count); + } + public function testWhereShortcut() { $builder = $this->getBuilder(); diff --git a/tests/Database/TableGuesserTest.php b/tests/Database/TableGuesserTest.php index 6a6aecaa97c2..0a8bd2434c91 100644 --- a/tests/Database/TableGuesserTest.php +++ b/tests/Database/TableGuesserTest.php @@ -17,8 +17,31 @@ public function test_migration_is_properly_parsed() $this->assertEquals('users', $table); $this->assertFalse($create); + [$table, $create] = TableGuesser::guess('change_status_column_in_users_table'); + $this->assertEquals('users', $table); + $this->assertFalse($create); + [$table, $create] = TableGuesser::guess('drop_status_column_from_users_table'); $this->assertEquals('users', $table); $this->assertFalse($create); } + + public function test_migration_is_properly_parsed_without_table_suffix() + { + [$table, $create] = TableGuesser::guess('create_users'); + $this->assertEquals('users', $table); + $this->assertTrue($create); + + [$table, $create] = TableGuesser::guess('add_status_column_to_users'); + $this->assertEquals('users', $table); + $this->assertFalse($create); + + [$table, $create] = TableGuesser::guess('change_status_column_in_users'); + $this->assertEquals('users', $table); + $this->assertFalse($create); + + [$table, $create] = TableGuesser::guess('drop_status_column_from_users'); + $this->assertEquals('users', $table); + $this->assertFalse($create); + } } diff --git a/tests/Foundation/FoundationHelpersTest.php b/tests/Foundation/FoundationHelpersTest.php index 441ccba4b3be..3539a5af4c21 100644 --- a/tests/Foundation/FoundationHelpersTest.php +++ b/tests/Foundation/FoundationHelpersTest.php @@ -65,22 +65,175 @@ public function testUnversionedElixir() public function testMixDoesNotIncludeHost() { - $file = 'unversioned.css'; + $manifest = $this->makeManifest(); + + $result = mix('/unversioned.css'); + + $this->assertSame('/versioned.css', $result->toHtml()); + + unlink($manifest); + } + + public function testMixCachesManifestForSubsequentCalls() + { + $manifest = $this->makeManifest(); + mix('unversioned.css'); + unlink($manifest); + + $result = mix('/unversioned.css'); + + $this->assertSame('/versioned.css', $result->toHtml()); + } + + public function testMixAssetMissingStartingSlashHaveItAdded() + { + $manifest = $this->makeManifest(); + + $result = mix('unversioned.css'); + + $this->assertSame('/versioned.css', $result->toHtml()); + + unlink($manifest); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage The Mix manifest does not exist. + */ + public function testMixMissingManifestThrowsException() + { + mix('unversioned.css', 'missing'); + } + + public function testMixWithManifestDirectory() + { + mkdir($directory = __DIR__.'/mix'); + $manifest = $this->makeManifest('mix'); + + $result = mix('unversioned.css', 'mix'); + + $this->assertSame('/mix/versioned.css', $result->toHtml()); + + unlink($manifest); + rmdir($directory); + } + + public function testMixManifestDirectoryMissingStartingSlashHasItAdded() + { + mkdir($directory = __DIR__.'/mix'); + $manifest = $this->makeManifest('/mix'); + + $result = mix('unversioned.css', 'mix'); + + $this->assertSame('/mix/versioned.css', $result->toHtml()); + + unlink($manifest); + rmdir($directory); + } + + public function testMixHotModuleReloadingGetsUrlFromFileWithHttps() + { + $path = $this->makeHotModuleReloadFile('https://laravel.com/docs'); + + $result = mix('unversioned.css'); + + $this->assertSame('//laravel.com/docs/unversioned.css', $result->toHtml()); + + unlink($path); + } + public function testMixHotModuleReloadingGetsUrlFromFileWithHttp() + { + $path = $this->makeHotModuleReloadFile('http://laravel.com/docs'); + + $result = mix('unversioned.css'); + + $this->assertSame('//laravel.com/docs/unversioned.css', $result->toHtml()); + + unlink($path); + } + + public function testMixHotModuleReloadingGetsUrlFromFileWithManifestDirectoryAndHttps() + { + mkdir($directory = __DIR__.'/mix'); + $path = $this->makeHotModuleReloadFile('https://laravel.com/docs', 'mix'); + + $result = mix('unversioned.css', 'mix'); + + $this->assertSame('//laravel.com/docs/unversioned.css', $result->toHtml()); + + unlink($path); + rmdir($directory); + } + + public function testMixHotModuleReloadingGetsUrlFromFileWithManifestDirectoryAndHttp() + { + mkdir($directory = __DIR__.'/mix'); + $path = $this->makeHotModuleReloadFile('http://laravel.com/docs', 'mix'); + + $result = mix('unversioned.css', 'mix'); + + $this->assertSame('//laravel.com/docs/unversioned.css', $result->toHtml()); + + unlink($path); + rmdir($directory); + } + + public function testMixHotModuleReloadingUsesLocalhostIfNoHttpScheme() + { + $path = $this->makeHotModuleReloadFile(''); + + $result = mix('unversioned.css'); + + $this->assertSame('//localhost:8080/unversioned.css', $result->toHtml()); + + unlink($path); + } + + public function testMixHotModuleReloadingWithManifestDirectoryUsesLocalhostIfNoHttpScheme() + { + mkdir($directory = __DIR__.'/mix'); + $path = $this->makeHotModuleReloadFile('', 'mix'); + + $result = mix('unversioned.css', 'mix'); + + $this->assertSame('//localhost:8080/unversioned.css', $result->toHtml()); + + unlink($path); + rmdir($directory); + } + + protected function makeHotModuleReloadFile($url, $directory = '') + { + app()->singleton('path.public', function () { + return __DIR__; + }); + + $path = public_path(str_finish($directory, '/').'hot'); + + // Laravel mix when run 'hot' has a new line after the + // url, so for consistency this "\n" is added. + file_put_contents($path, "{$url}\n"); + + return $path; + } + + protected function makeManifest($directory = '') + { app()->singleton('path.public', function () { return __DIR__; }); - touch(public_path('mix-manifest.json')); + $path = public_path(str_finish($directory, '/').'mix-manifest.json'); - file_put_contents(public_path('mix-manifest.json'), json_encode([ - '/unversioned.css' => '/versioned.css', - ])); + touch($path); - $result = mix($file); + // Laravel mix prints JSON pretty and with escaped + // slashes, so we are doing that here for consistency. + $content = json_encode(['/unversioned.css' => '/versioned.css'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); - $this->assertEquals('/versioned.css', $result); + file_put_contents($path, $content); - unlink(public_path('mix-manifest.json')); + return $path; } } diff --git a/tests/Foundation/Http/Middleware/TransformsRequestTest.php b/tests/Foundation/Http/Middleware/TransformsRequestTest.php index 6bd555d314cb..f73dd7b8f8fd 100644 --- a/tests/Foundation/Http/Middleware/TransformsRequestTest.php +++ b/tests/Foundation/Http/Middleware/TransformsRequestTest.php @@ -5,19 +5,38 @@ use Illuminate\Http\Request; use PHPUnit\Framework\TestCase; use Illuminate\Foundation\Http\Middleware\TransformsRequest; +use Symfony\Component\HttpFoundation\Request as SymfonyRequest; class TransformsRequestTest extends TestCase { - public function testLowerAgeAndAddBeer() + public function testTransformOncePerKeyWhenMethodIsGet() + { + $middleware = new TruncateInput; + $symfonyRequest = new SymfonyRequest([ + 'bar' => '123', + 'baz' => 'abc', + ]); + $symfonyRequest->server->set('REQUEST_METHOD', 'GET'); + $request = Request::createFromBase($symfonyRequest); + + $middleware->handle($request, function (Request $request) { + $this->assertEquals('12', $request->get('bar')); + $this->assertEquals('ab', $request->get('baz')); + }); + } + + public function testTransformOncePerKeyWhenMethodIsPost() { $middleware = new ManipulateInput; - $request = new Request( + $symfonyRequest = new SymfonyRequest( [ 'name' => 'Damian', 'beers' => 4, ], ['age' => 28] ); + $symfonyRequest->server->set('REQUEST_METHOD', 'POST'); + $request = Request::createFromBase($symfonyRequest); $middleware->handle($request, function (Request $request) { $this->assertEquals('Damian', $request->get('name')); @@ -26,10 +45,10 @@ public function testLowerAgeAndAddBeer() }); } - public function testAjaxLowerAgeAndAddBeer() + public function testTransformOncePerKeyWhenContentTypeIsJson() { $middleware = new ManipulateInput; - $request = new Request( + $symfonyRequest = new SymfonyRequest( [ 'name' => 'Damian', 'beers' => 4, @@ -41,6 +60,8 @@ public function testAjaxLowerAgeAndAddBeer() ['CONTENT_TYPE' => '/json'], json_encode(['age' => 28]) ); + $symfonyRequest->server->set('REQUEST_METHOD', 'GET'); + $request = Request::createFromBase($symfonyRequest); $middleware->handle($request, function (Request $request) { $this->assertEquals('Damian', $request->input('name')); @@ -64,3 +85,11 @@ protected function transform($key, $value) return $value; } } + +class TruncateInput extends TransformsRequest +{ + protected function transform($key, $value) + { + return substr($value, 0, -1); + } +} diff --git a/tests/Integration/Foundation/FoundationHelpersTest.php b/tests/Integration/Foundation/FoundationHelpersTest.php index 8ba83ad9ef59..a44d34e2bb1e 100644 --- a/tests/Integration/Foundation/FoundationHelpersTest.php +++ b/tests/Integration/Foundation/FoundationHelpersTest.php @@ -4,6 +4,8 @@ use Exception; use Orchestra\Testbench\TestCase; +use Illuminate\Support\Facades\Route; +use Illuminate\Contracts\Debug\ExceptionHandler; /** * @group integration @@ -37,4 +39,99 @@ public function test(int $a) $testClass->test([]); }, 'rescued!'), 'rescued!'); } + + public function testMixReportsExceptionWhenAssetIsMissingFromManifest() + { + $handler = new FakeHandler; + $this->app->instance(ExceptionHandler::class, $handler); + $manifest = $this->makeManifest(); + + mix('missing.js'); + + $this->assertInstanceOf(Exception::class, $handler->reported[0]); + $this->assertSame('Unable to locate Mix file: /missing.js.', $handler->reported[0]->getMessage()); + + unlink($manifest); + } + + public function testMixSilentlyFailsWhenAssetIsMissingFromManifestWhenNotInDebugMode() + { + $this->app['config']->set('app.debug', false); + $manifest = $this->makeManifest(); + + $path = mix('missing.js'); + + $this->assertSame('/missing.js', $path); + + unlink($manifest); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage Unable to locate Mix file: /missing.js. + */ + public function testMixThrowsExceptionWhenAssetIsMissingFromManifestWhenInDebugMode() + { + $this->app['config']->set('app.debug', true); + $manifest = $this->makeManifest(); + + try { + mix('missing.js'); + } catch (\Exception $e) { + throw $e; + } finally { // make sure we can cleanup the file + unlink($manifest); + } + } + + public function testMixOnlyThrowsAndReportsOneExceptionWhenAssetIsMissingFromManifestWhenInDebugMode() + { + $handler = new FakeHandler; + $this->app->instance(ExceptionHandler::class, $handler); + $this->app['config']->set('app.debug', true); + $manifest = $this->makeManifest(); + Route::get('test-route', function () { + mix('missing.js'); + }); + + $this->get('/test-route'); + + $this->assertCount(1, $handler->reported); + + unlink($manifest); + } + + protected function makeManifest($directory = '') + { + $this->app->singleton('path.public', function () { + return __DIR__; + }); + + $path = public_path(str_finish($directory, '/').'mix-manifest.json'); + + touch($path); + + // Laravel mix prints JSON pretty and with escaped + // slashes, so we are doing that here for consistency. + $content = json_encode(['/unversioned.css' => '/versioned.css'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + + file_put_contents($path, $content); + + return $path; + } +} + +class FakeHandler +{ + public $reported = []; + + public function report($exception) + { + $this->reported[] = $exception; + } + + public function render($exception) + { + // + } } diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index 975cbd9045d6..06fff7a0e22b 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -2693,6 +2693,70 @@ public function testWhenDefault() $this->assertSame(['michael', 'tom', 'taylor'], $collection->toArray()); } + public function testWhenEmpty() + { + $collection = new Collection(['michael', 'tom']); + + $collection->whenEmpty(function ($collection) { + return $collection->push('adam'); + }); + + $this->assertSame(['michael', 'tom'], $collection->toArray()); + + $collection = new Collection; + + $collection->whenEmpty(function ($collection) { + return $collection->push('adam'); + }); + + $this->assertSame(['adam'], $collection->toArray()); + } + + public function testWhenEmptyDefault() + { + $collection = new Collection(['michael', 'tom']); + + $collection->whenEmpty(function ($collection) { + return $collection->push('adam'); + }, function ($collection) { + return $collection->push('taylor'); + }); + + $this->assertSame(['michael', 'tom', 'taylor'], $collection->toArray()); + } + + public function testWhenNotEmpty() + { + $collection = new Collection(['michael', 'tom']); + + $collection->whenNotEmpty(function ($collection) { + return $collection->push('adam'); + }); + + $this->assertSame(['michael', 'tom', 'adam'], $collection->toArray()); + + $collection = new Collection; + + $collection->whenNotEmpty(function ($collection) { + return $collection->push('adam'); + }); + + $this->assertSame([], $collection->toArray()); + } + + public function testWhenNotEmptyDefault() + { + $collection = new Collection(['michael', 'tom']); + + $collection->whenNotEmpty(function ($collection) { + return $collection->push('adam'); + }, function ($collection) { + return $collection->push('taylor'); + }); + + $this->assertSame(['michael', 'tom', 'adam'], $collection->toArray()); + } + public function testUnless() { $collection = new Collection(['michael', 'tom']); @@ -2725,6 +2789,70 @@ public function testUnlessDefault() $this->assertSame(['michael', 'tom', 'taylor'], $collection->toArray()); } + public function testUnlessEmpty() + { + $collection = new Collection(['michael', 'tom']); + + $collection->unlessEmpty(function ($collection) { + return $collection->push('adam'); + }); + + $this->assertSame(['michael', 'tom', 'adam'], $collection->toArray()); + + $collection = new Collection; + + $collection->unlessEmpty(function ($collection) { + return $collection->push('adam'); + }); + + $this->assertSame([], $collection->toArray()); + } + + public function testUnlessEmptyDefault() + { + $collection = new Collection(['michael', 'tom']); + + $collection->unlessEmpty(function ($collection) { + return $collection->push('adam'); + }, function ($collection) { + return $collection->push('taylor'); + }); + + $this->assertSame(['michael', 'tom', 'adam'], $collection->toArray()); + } + + public function testUnlessNotEmpty() + { + $collection = new Collection(['michael', 'tom']); + + $collection->unlessNotEmpty(function ($collection) { + return $collection->push('adam'); + }); + + $this->assertSame(['michael', 'tom'], $collection->toArray()); + + $collection = new Collection; + + $collection->unlessNotEmpty(function ($collection) { + return $collection->push('adam'); + }); + + $this->assertSame(['adam'], $collection->toArray()); + } + + public function testUnlessNotEmptyDefault() + { + $collection = new Collection(['michael', 'tom']); + + $collection->unlessNotEmpty(function ($collection) { + return $collection->push('adam'); + }, function ($collection) { + return $collection->push('taylor'); + }); + + $this->assertSame(['michael', 'tom', 'taylor'], $collection->toArray()); + } + public function testHasReturnsValidResults() { $collection = new Collection(['foo' => 'one', 'bar' => 'two', 1 => 'three']); diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php index 06e36eeed6c4..501afa4eadf6 100755 --- a/tests/Validation/ValidationValidatorTest.php +++ b/tests/Validation/ValidationValidatorTest.php @@ -4128,6 +4128,49 @@ function ($attribute, $value, $fail) { $this->assertEquals('states.0 must be AR or TX', $v->errors()->get('states.0')[0]); $this->assertEquals('states.1 must be AR or TX', $v->errors()->get('states.1')[0]); $this->assertEquals('number must be divisible by 4', $v->errors()->get('number')[0]); + + // Test array of messages with failing case... + $v = new Validator( + $this->getIlluminateArrayTranslator(), + ['name' => 42], + ['name' => new class implements Rule { + public function passes($attribute, $value) + { + return $value === 'taylor'; + } + + public function message() + { + return [':attribute must be taylor', ':attribute must be a first name']; + } + }] + ); + + $this->assertTrue($v->fails()); + $this->assertEquals('name must be taylor', $v->errors()->get('name')[0]); + $this->assertEquals('name must be a first name', $v->errors()->get('name')[1]); + + // Test array of messages with multiple rules for one attribute case... + $v = new Validator( + $this->getIlluminateArrayTranslator(), + ['name' => 42], + ['name' => [new class implements Rule { + public function passes($attribute, $value) + { + return $value === 'taylor'; + } + + public function message() + { + return [':attribute must be taylor', ':attribute must be a first name']; + } + }, 'string']] + ); + + $this->assertTrue($v->fails()); + $this->assertEquals('name must be taylor', $v->errors()->get('name')[0]); + $this->assertEquals('name must be a first name', $v->errors()->get('name')[1]); + $this->assertEquals('validation.string', $v->errors()->get('name')[2]); } public function testImplicitCustomValidationObjects() diff --git a/tests/View/ViewFactoryTest.php b/tests/View/ViewFactoryTest.php index c52bd7b72c13..bd717340da62 100755 --- a/tests/View/ViewFactoryTest.php +++ b/tests/View/ViewFactoryTest.php @@ -653,6 +653,15 @@ public function testIncrementingLoopIndicesOfUncountable() $this->assertNull($factory->getLoopStack()[0]['last']); } + public function testMacro() + { + $factory = $this->getFactory(); + $factory->macro('getFoo', function () { + return 'Hello World'; + }); + $this->assertEquals('Hello World', $factory->getFoo()); + } + protected function getFactory() { return new Factory(