Laravel version update

Laravel version update
This commit is contained in:
Manish Verma
2018-08-06 18:48:58 +05:30
parent d143048413
commit 126fbb0255
13678 changed files with 1031482 additions and 778530 deletions

43
vendor/laravel/dusk/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,43 @@
# Release Notes for 1.0.x
## v1.0.13 (2017-04-20)
### Added
- Added `illuminate/console` as dependency ([#232](https://github.com/laravel/dusk/pull/232))
- Added security measurement against registering Dusk on production ([#229](https://github.com/laravel/dusk/pull/229))
- Added `PHP_BINARY` constant to the list of PHP's executable binaries ([#240](https://github.com/laravel/dusk/pull/240))
### Changed
- Changed `propagateScaffoldingToBrowser()` to `setUp()` for compatibility with PHPUnit ~6.0 ([#227](https://github.com/laravel/dusk/pull/227))
- Changed `selected()` comparison to always cast the value to string ([#239](https://github.com/laravel/dusk/pull/239))
### Fixed
- No longer throws exception when Tty is not available ([#226](https://github.com/laravel/dusk/pull/226))
- Use `getAttribute('value')` instead of `getText()` for `textarea` elements ([#237](https://github.com/laravel/dusk/pull/237))
- Fixed bug when giving strings with apostrophe to `clickLink()` ([#228](https://github.com/laravel/dusk/pull/228))
## v1.0.12 (2017-04-07)
### Added
- Added automated tests for HTML elements identified by strings with a colon ([#214](https://github.com/laravel/dusk/pull/214))
### Fixed
- Support for colon on HTML id tag ([#214](https://github.com/laravel/dusk/pull/214))
## v1.0.11 (2017-03-20)
### Added
- Added `assertSelectHasOptions()`, `assertSelectMissingOptions()`, `assertSelectHasOption()` and `and assertSelectMissingOption()` ([#195](https://github.com/laravel/dusk/pull/195))
- Added purge console logs before starting tests ([#193](https://github.com/laravel/dusk/pull/193))
- Added `assertPathIsNot()` ([#183](https://github.com/laravel/dusk/pull/183))
- Added support for back button ([#187](https://github.com/laravel/dusk/pull/187))
- Added `waitForLocation()` to allow waiting on `window.location` to be changed ([#176](https://github.com/laravel/dusk/pull/176))
### Changed
- Updated ChromeDriver to v2.28 so that it works with Chrome 57 ([#199](https://github.com/laravel/dusk/pull/199))
- Comparison to `option` inside `select` will no longer be strict ([#178](https://github.com/laravel/dusk/pull/178))
- Type-hint Browser for auto-complete support ([#174](https://github.com/laravel/dusk/pull/174))
## v1.0.10 (2017-03-03)

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) <Taylor Otwell>
Copyright (c) Taylor Otwell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

Binary file not shown.

BIN
vendor/laravel/dusk/bin/chromedriver-mac vendored Normal file

Binary file not shown.

Binary file not shown.

4
vendor/laravel/dusk/bin/jquery.js vendored Normal file

File diff suppressed because one or more lines are too long

41
vendor/laravel/dusk/composer.json vendored Normal file
View File

@@ -0,0 +1,41 @@
{
"name": "laravel/dusk",
"description": "Laravel Dusk provides simple end-to-end testing and browser automation.",
"keywords": ["laravel", "testing", "webdriver"],
"license": "MIT",
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"require": {
"php": ">=5.6.4",
"facebook/webdriver": "~1.0",
"nesbot/carbon": "~1.20",
"illuminate/console": "~5.5",
"illuminate/support": "~5.5",
"symfony/console": "~3.2",
"symfony/process": "~3.2"
},
"require-dev": {
"phpunit/phpunit": "^5.7",
"mockery/mockery": "^0.9.6"
},
"autoload": {
"psr-4": {
"Laravel\\Dusk\\": "src/"
}
},
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
},
"laravel": {
"providers": [
"Laravel\\Dusk\\DuskServiceProvider"
]
}
},
"minimum-stability": "dev"
}

26
vendor/laravel/dusk/phpunit.xml.dist vendored Normal file
View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
failOnRisky="true"
failOnWarning="true"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
verbose="true"
>
<testsuites>
<testsuite name="Laravel Dusk Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
</phpunit>

20
vendor/laravel/dusk/readme.md vendored Normal file
View File

@@ -0,0 +1,20 @@
<p align="center"><img src="https://laravel.com/assets/img/components/logo-dusk.svg"></p>
<p align="center">
<a href="https://travis-ci.org/laravel/dusk"><img src="https://travis-ci.org/laravel/dusk.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/laravel/dusk"><img src="https://poser.pugx.org/laravel/dusk/d/total.svg" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/laravel/dusk"><img src="https://poser.pugx.org/laravel/dusk/v/stable.svg" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/dusk"><img src="https://poser.pugx.org/laravel/dusk/license.svg" alt="License"></a>
</p>
## Introduction
Laravel Dusk provides an expressive, easy-to-use browser automation and testing API. By default, Dusk does not require you to install JDK or Selenium on your machine. Instead, Dusk uses a standalone Chromedriver. However, you are free to utilize any other Selenium driver you wish.
## Official Documentation
Documentation for Dusk can be found on the [Laravel website](https://laravel.com/docs/master/dusk).
## License
Laravel Dusk is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT)

386
vendor/laravel/dusk/src/Browser.php vendored Normal file
View File

@@ -0,0 +1,386 @@
<?php
namespace Laravel\Dusk;
use Closure;
use BadMethodCallException;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Macroable;
use Facebook\WebDriver\WebDriverDimension;
class Browser
{
use Concerns\InteractsWithAuthentication,
Concerns\InteractsWithCookies,
Concerns\InteractsWithElements,
Concerns\InteractsWithJavascript,
Concerns\InteractsWithMouse,
Concerns\MakesAssertions,
Concerns\WaitsForElements,
Macroable {
__call as macroCall;
}
/**
* The base URL for all URLs.
*
* @var string
*/
public static $baseUrl;
/**
* The directory that will contain any screenshots.
*
* @var string
*/
public static $storeScreenshotsAt;
/**
* The directory that will contain any console logs.
*
* @var string
*/
public static $storeConsoleLogAt;
/**
* Get the callback which resolves the default user to authenticate.
*
* @var \Closure
*/
public static $userResolver;
/**
* The RemoteWebDriver instance.
*
* @var \Facebook\WebDriver\Remote\RemoteWebDriver
*/
public $driver;
/**
* The element resolver instance.
*
* @var ElementResolver
*/
public $resolver;
/**
* The page object currently being viewed.
*
* @var mixed
*/
public $page;
/**
* Create a browser instance.
*
* @param \Facebook\WebDriver\Remote\RemoteWebDriver $driver
* @param ElementResolver $resolver
* @return void
*/
public function __construct($driver, $resolver = null)
{
$this->driver = $driver;
$this->resolver = $resolver ?: new ElementResolver($driver);
}
/**
* Browse to the given URL.
*
* @param string $url
* @return $this
*/
public function visit($url)
{
// First, if the URL is an object it means we are actually dealing with a page
// and we need to create this page then get the URL from the page object as
// it contains the URL. Once that is done, we will be ready to format it.
if (is_object($url)) {
$page = $url;
$url = $page->url();
}
// If the URL does not start with http or https, then we will prepend the base
// URL onto the URL and navigate to the URL. This will actually navigate to
// the URL in the browser. Then we will be ready to make assertions, etc.
if (! Str::startsWith($url, ['http://', 'https://'])) {
$url = static::$baseUrl.'/'.ltrim($url, '/');
}
$this->driver->navigate()->to($url);
// If the page variable was set, we will call the "on" method which will set a
// page instance variable and call an assert method on the page so that the
// page can have the chance to verify that we are within the right pages.
if (isset($page)) {
$this->on($page);
}
return $this;
}
/**
* Browse to the given route.
*
* @param string $route
* @param array $parameters
* @return $this
*/
public function visitRoute($route, $parameters = [])
{
return $this->visit(route($route, $parameters));
}
/**
* Set the current page object.
*
* @param mixed $page
* @return $this
*/
public function on($page)
{
$this->page = $page;
// Here we will set the page elements on the resolver instance, which will allow
// the developer to access short-cuts for CSS selectors on the page which can
// allow for more expressive navigation and interaction with all the pages.
$this->resolver->pageElements(array_merge(
$page::siteElements(), $page->elements()
));
$page->assert($this);
return $this;
}
/**
* Refresh the page.
*
* @return $this
*/
public function refresh()
{
$this->driver->navigate()->refresh();
return $this;
}
/**
* Navigate to the previous page.
*
* @return $this
*/
public function back()
{
$this->driver->navigate()->back();
return $this;
}
/**
* Maximize the browser window.
*
* @return $this
*/
public function maximize()
{
$this->driver->manage()->window()->maximize();
return $this;
}
/**
* Resize the browser window.
*
* @param int $width
* @param int $height
* @return $this
*/
public function resize($width, $height)
{
$this->driver->manage()->window()->setSize(
new WebDriverDimension($width, $height)
);
return $this;
}
/**
* Take a screenshot and store it with the given name.
*
* @param string $name
* @return $this
*/
public function screenshot($name)
{
$this->driver->takeScreenshot(
sprintf('%s/%s.png', rtrim(static::$storeScreenshotsAt, '/'), $name)
);
return $this;
}
/**
* Store the console output with the given name.
*
* @param string $name
* @return $this
*/
public function storeConsoleLog($name)
{
$console = $this->driver->manage()->getLog('browser');
if (! empty($console)) {
file_put_contents(
sprintf('%s/%s.log', rtrim(static::$storeConsoleLogAt, '/'), $name)
, json_encode($console, JSON_PRETTY_PRINT)
);
}
return $this;
}
/**
* Execute a Closure with a scoped browser instance.
*
* @param string $selector
* @param \Closure $callback
* @return $this
*/
public function within($selector, Closure $callback)
{
return $this->with($selector, $callback);
}
/**
* Execute a Closure with a scoped browser instance.
*
* @param string $selector
* @param \Closure $callback
* @return $this
*/
public function with($selector, Closure $callback)
{
$browser = new static(
$this->driver, new ElementResolver($this->driver, $this->resolver->format($selector))
);
if ($this->page) {
$browser->on($this->page);
}
call_user_func($callback, $browser);
return $this;
}
/**
* Ensure that jQuery is available on the page.
*
* @return void
*/
public function ensurejQueryIsAvailable()
{
if ($this->driver->executeScript("return window.jQuery == null")) {
$this->driver->executeScript(file_get_contents(__DIR__.'/../bin/jquery.js'));
}
}
/**
* Pause for the given amount of milliseconds.
*
* @param int $milliseconds
* @return $this
*/
public function pause($milliseconds)
{
usleep($milliseconds * 1000);
return $this;
}
/**
* Close the browser.
*
* @return void
*/
public function quit()
{
$this->driver->quit();
}
/**
* Tap the browser into a callback.
*
* @param \Closure $callback
* @return $this
*/
public function tap($callback)
{
$callback($this);
return $this;
}
/**
* Dump the content from the last response.
*
* @return void
*/
public function dump()
{
dd($this->driver->getPageSource());
}
/**
* Pause execution of test and open Laravel Tinker (PsySH) REPL.
*
* @return $this
*/
public function tinker()
{
\Psy\Shell::debug([
'browser' => $this,
'driver' => $this->driver,
'resolver' => $this->resolver,
'page' => $this->page,
], $this);
return $this;
}
/**
* Stop running tests but leave the browser open.
*
* @return void
*/
public function stop()
{
exit();
}
/**
* Dynamically call a method on the browser.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
if (static::hasMacro($method)) {
return $this->macroCall($method, $parameters);
}
if ($this->page && method_exists($this->page, $method)) {
array_unshift($parameters, $this);
$this->page->{$method}(...$parameters);
return $this;
}
throw new BadMethodCallException("Call to undefined method [{$method}].");
}
}

View File

@@ -0,0 +1,107 @@
<?php
namespace Laravel\Dusk\Concerns;
use Laravel\Dusk\Browser;
use PHPUnit\Framework\Assert as PHPUnit;
trait InteractsWithAuthentication
{
/**
* Log into the application as the default user.
*
* @return $this
*/
public function login()
{
return $this->loginAs(call_user_func(Browser::$userResolver));
}
/**
* Log into the application using a given user ID or email.
*
* @param object|string $userId
* @param string $guard
* @return $this
*/
public function loginAs($userId, $guard = null)
{
$userId = method_exists($userId, 'getKey') ? $userId->getKey() : $userId;
return $this->visit(rtrim('/_dusk/login/'.$userId.'/'.$guard, '/'));
}
/**
* Log out of the application.
*
* @param string $guard
* @return $this
*/
public function logout($guard = null)
{
return $this->visit(rtrim('/_dusk/logout/'.$guard, '/'));
}
/**
* Get the ID and the class name of the authenticated user.
*
* @param string|null $guard
* @return array
*/
protected function currentUserInfo($guard = null)
{
$response = $this->visit("/_dusk/user/{$guard}");
return json_decode(strip_tags($response->driver->getPageSource()), true);
}
/**
* Assert that the user is authenticated.
*
* @param string|null $guard
* @return $this
*/
public function assertAuthenticated($guard = null)
{
PHPUnit::assertNotEmpty($this->currentUserInfo($guard), 'The user is not authenticated.');
return $this;
}
/**
* Assert that the user is not authenticated.
*
* @param string|null $guard
* @return $this
*/
public function assertGuest($guard = null)
{
PHPUnit::assertEmpty(
$this->currentUserInfo($guard), 'The user is unexpectedly authenticated.'
);
return $this;
}
/**
* Assert that the user is authenticated as the given user.
*
* @param $user
* @param string|null $guard
* @return $this
*/
public function assertAuthenticatedAs($user, $guard = null)
{
$expected = [
'id' => $user->getAuthIdentifier(),
'className' => get_class($user),
];
PHPUnit::assertSame(
$expected, $this->currentUserInfo($guard),
'The currently authenticated user is not who was expected.'
);
return $this;
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace Laravel\Dusk\Concerns;
use DateTimeInterface;
trait InteractsWithCookies
{
/**
* Get or set an encrypted cookie's value.
*
* @param string $name
* @param string|null $value
* @param int|DateTimeInterface|null $expiry
* @param array $options
* @return string
*/
public function cookie($name, $value = null, $expiry = null, array $options = [])
{
if ($value) {
return $this->addCookie($name, $value, $expiry, $options);
}
if ($cookie = $this->driver->manage()->getCookieNamed($name)) {
return decrypt(rawurldecode($cookie['value']));
}
}
/**
* Get or set a plain cookie's value.
*
* @param string $name
* @param string|null $value
* @param int|DateTimeInterface|null $expiry
* @param array $options
* @return string
*/
public function plainCookie($name, $value = null, $expiry = null, array $options = [])
{
if ($value) {
return $this->addCookie($name, $value, $expiry, $options, false);
}
if ($cookie = $this->driver->manage()->getCookieNamed($name)) {
return rawurldecode($cookie['value']);
}
}
/**
* Add the given cookie.
*
* @param string $name
* @param string $value
* @param int|DateTimeInterface|null $expiry
* @param array $options
* @param bool $encrypt
* @return $this
*/
public function addCookie($name, $value, $expiry = null, array $options = [], $encrypt = true)
{
if ($encrypt) {
$value = encrypt($value);
}
if ($expiry instanceof DateTimeInterface) {
$expiry = $expiry->getTimestamp();
}
$this->driver->manage()->addCookie(
array_merge($options, compact('expiry', 'name', 'value'))
);
return $this;
}
/**
* Delete the given cookie.
*
* @param string $name
* @return $this
*/
public function deleteCookie($name)
{
$this->driver->manage()->deleteCookieNamed($name);
return $this;
}
}

View File

@@ -0,0 +1,432 @@
<?php
namespace Laravel\Dusk\Concerns;
use Illuminate\Support\Str;
use Facebook\WebDriver\WebDriverBy;
use Facebook\WebDriver\WebDriverKeys;
use Facebook\WebDriver\Remote\LocalFileDetector;
use Facebook\WebDriver\Interactions\WebDriverActions;
trait InteractsWithElements
{
/**
* Get all of the elements matching the given selector.
*
* @param string $selector
* @return array
*/
public function elements($selector)
{
return $this->resolver->all($selector);
}
/**
* Get the element matching the given selector.
*
* @param string $selector
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
*/
public function element($selector)
{
return $this->resolver->find($selector);
}
/**
* Click the element at the given selector.
*
* @param string $selector
* @return $this
*/
public function click($selector)
{
$this->resolver->findOrFail($selector)->click();
return $this;
}
/**
* Right click the element at the given selector.
*
* @param string $selector
* @return $this
*/
public function rightClick($selector)
{
(new WebDriverActions($this->driver))->contextClick(
$this->resolver->findOrFail($selector)
)->perform();
return $this;
}
/**
* Click the link with the given text.
*
* @param string $link
* @return $this
*/
public function clickLink($link)
{
$this->ensurejQueryIsAvailable();
$selector = addslashes(trim($this->resolver->format("a:contains({$link})")));
$this->driver->executeScript("jQuery.find(\"{$selector}\")[0].click();");
return $this;
}
/**
* Directly get or set the value attribute of an input field.
*
* @param string $selector
* @param string|null $value
* @return $this
*/
public function value($selector, $value = null)
{
if (is_null($value)) {
return $this->resolver->findOrFail($selector)->getAttribute('value');
}
$selector = $this->resolver->format($selector);
$this->driver->executeScript(
"document.querySelector('{$selector}').value = '{$value}';"
);
return $this;
}
/**
* Get the text of the element matching the given selector.
*
* @param string $selector
* @return string
*/
public function text($selector)
{
return $this->resolver->findOrFail($selector)->getText();
}
/**
* Get the given attribute from the element matching the given selector.
*
* @param string $selector
* @param string $attribute
* @return string
*/
public function attribute($selector, $attribute)
{
return $this->resolver->findOrFail($selector)->getAttribute($attribute);
}
/**
* Send the given keys to the element matching the given selector.
*
* @param string $selector
* @param dynamic $keys
* @return $this
*/
public function keys($selector, ...$keys)
{
$this->resolver->findOrFail($selector)->sendKeys($this->parseKeys($keys));
return $this;
}
/**
* Parse the keys before sending to the keyboard.
*
* @param array $keys
* @return array
*/
protected function parseKeys($keys)
{
return collect($keys)->map(function ($key) {
if (is_string($key) && Str::startsWith($key, '{') && Str::endsWith($key, '}')) {
$key = constant(WebDriverKeys::class.'::'.strtoupper(trim($key, '{}')));
}
if (is_array($key) && Str::startsWith($key[0], '{')) {
$key[0] = constant(WebDriverKeys::class.'::'.strtoupper(trim($key[0], '{}')));
}
return $key;
})->all();
}
/**
* Type the given value in the given field.
*
* @param string $field
* @param string $value
* @return $this
*/
public function type($field, $value)
{
$this->resolver->resolveForTyping($field)->clear()->sendKeys($value);
return $this;
}
/**
* Type the given value in the given field without clearing it.
*
* @param string $field
* @param string $value
* @return $this
*/
public function append($field, $value)
{
$this->resolver->resolveForTyping($field)->sendKeys($value);
return $this;
}
/**
* Clear the given field.
*
* @param string $field
* @return $this
*/
public function clear($field)
{
$this->resolver->resolveForTyping($field)->clear();
return $this;
}
/**
* Select the given value or random value of a drop-down field.
*
* @param string $field
* @param string $value
* @return $this
*/
public function select($field, $value = null)
{
$element = $this->resolver->resolveForSelection($field);
$options = $element->findElements(WebDriverBy::tagName('option'));
if (is_null($value)) {
$options[array_rand($options)]->click();
}
else {
foreach ($options as $option) {
if ((string) $option->getAttribute('value') === (string) $value) {
$option->click();
break;
}
}
}
return $this;
}
/**
* Select the given value of a radio button field.
*
* @param string $field
* @param string $value
* @return $this
*/
public function radio($field, $value)
{
$this->resolver->resolveForRadioSelection($field, $value)->click();
return $this;
}
/**
* Check the given checkbox.
*
* @param string $field
* @param string $value
* @return $this
*/
public function check($field, $value = null)
{
$element = $this->resolver->resolveForChecking($field, $value);
if (! $element->isSelected()) {
$element->click();
}
return $this;
}
/**
* Uncheck the given checkbox.
*
* @param string $field
* @param string $value
* @return $this
*/
public function uncheck($field, $value = null)
{
$element = $this->resolver->resolveForChecking($field, $value);
if ($element->isSelected()) {
$element->click();
}
return $this;
}
/**
* Attach the given file to the field.
*
* @param string $field
* @param string $path
* @return $this
*/
public function attach($field, $path)
{
$element = $this->resolver->resolveForAttachment($field);
$element->setFileDetector(new LocalFileDetector)->sendKeys($path);
return $this;
}
/**
* Press the button with the given text or name.
*
* @param string $button
* @return $this
*/
public function press($button)
{
$this->resolver->resolveForButtonPress($button)->click();
return $this;
}
/**
* Press the button with the given text or name.
*
* @param string $button
* @param int $seconds
* @return $this
*/
public function pressAndWaitFor($button, $seconds = 5)
{
$element = $this->resolver->resolveForButtonPress($button);
$element->click();
return $this->waitUsing($seconds, 100, function () use ($element) {
return $element->isEnabled();
});
}
/**
* Drag an element to another element using selectors.
*
* @param string $from
* @param string $to
* @return $this
*/
public function drag($from, $to)
{
(new WebDriverActions($this->driver))->dragAndDrop(
$this->resolver->findOrFail($from), $this->resolver->findOrFail($to)
)->perform();
return $this;
}
/**
* Drag an element up.
*
* @param string $selector
* @param int $offset
* @return $this
*/
public function dragUp($selector, $offset)
{
return $this->dragOffset($selector, 0, -$offset);
}
/**
* Drag an element down.
*
* @param string $selector
* @param int $offset
* @return $this
*/
public function dragDown($selector, $offset)
{
return $this->dragOffset($selector, 0, $offset);
}
/**
* Drag an element to the left.
*
* @param string $selector
* @param int $offset
* @return $this
*/
public function dragLeft($selector, $offset)
{
return $this->dragOffset($selector, -$offset, 0);
}
/**
* Drag an element to the right.
*
* @param string $selector
* @param int $offset
* @return $this
*/
public function dragRight($selector, $offset)
{
return $this->dragOffset($selector, $offset, 0);
}
/**
* Drag an element by the given offset.
*
* @param string $selector
* @param int $x
* @param int $y
* @return $this
*/
public function dragOffset($selector, $x = 0, $y = 0)
{
(new WebDriverActions($this->driver))->dragAndDropBy(
$this->resolver->findOrFail($selector), $x, $y
)->perform();
return $this;
}
/**
* Accept a JavaScript dialog.
*
* @return $this
*/
public function acceptDialog()
{
$this->driver->switchTo()->alert()->accept();
return $this;
}
/**
* Dismiss a JavaScript dialog.
*
* @return $this
*/
public function dismissDialog()
{
$this->driver->switchTo()->alert()->dismiss();
return $this;
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Laravel\Dusk\Concerns;
trait InteractsWithJavascript
{
/**
* Execute JavaScript within the browser.
*
* @param string|array $scripts
* @return array
*/
public function script($scripts)
{
return collect((array) $scripts)->map(function ($script) {
return $this->driver->executeScript($script);
})->all();
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Laravel\Dusk\Concerns;
trait InteractsWithMouse
{
/**
* Move the mouse over the given selector.
*
* @param string $selector
* @return $this
*/
public function mouseover($selector)
{
$element = $this->resolver->findOrFail($selector);
$this->driver->getMouse()->mouseMove($element->getCoordinates());
return $this;
}
}

View File

@@ -0,0 +1,676 @@
<?php
namespace Laravel\Dusk\Concerns;
use Illuminate\Support\Str;
use PHPUnit\Framework\Assert as PHPUnit;
use Facebook\WebDriver\Exception\NoSuchElementException;
trait MakesAssertions
{
/**
* Assert that the page title is the given value.
*
* @param string $title
* @return $this
*/
public function assertTitle($title)
{
PHPUnit::assertEquals($title, $this->driver->getTitle());
return $this;
}
/**
* Assert that the page title contains the given value.
*
* @param string $title
* @return $this
*/
public function assertTitleContains($title)
{
PHPUnit::assertTrue(
Str::contains($this->driver->getTitle(), $title)
);
return $this;
}
/**
* Assert that the current URL path matches the given pattern.
*
* @param string $path
* @return $this
*/
public function assertPathIs($path)
{
$pattern = preg_quote($path, '/');
$pattern = str_replace('\*', '.*', $pattern);
PHPUnit::assertRegExp('/^'.$pattern.'/u', parse_url(
$this->driver->getCurrentURL()
)['path']);
return $this;
}
/**
* Assert that the current URL path begins with given path.
*
* @param string $path
* @return $this
*/
public function assertPathBeginsWith($path)
{
PHPUnit::assertStringStartsWith($path, parse_url(
$this->driver->getCurrentURL()
)['path']);
return $this;
}
/**
* Assert that the current URL path does not match the given path.
*
* @param string $path
* @return $this
*/
public function assertPathIsNot($path)
{
PHPUnit::assertNotEquals($path, parse_url(
$this->driver->getCurrentURL()
)['path']);
return $this;
}
/**
* Assert that the current URL path matches the given route.
*
* @param string $route
* @param array $parameters
* @return $this
*/
public function assertRouteIs($route, $parameters = [])
{
return $this->assertPathIs(route($route, $parameters, false));
}
/**
* Assert that a query string parameter is present and has a given value.
*
* @param string $name
* @param string $value
* @return $this
*/
public function assertQueryStringHas($name, $value = null)
{
$output = $this->assertHasQueryStringParameter($name);
if (is_null($value)) {
return $this;
}
PHPUnit::assertEquals(
$value, $output[$name],
"Query string parameter [{$name}] had value [{$output[$name]}], but expected [{$value}]."
);
return $this;
}
/**
* Assert that the given query string parameter is missing.
*
* @param string $name
* @return $this
*/
public function assertQueryStringMissing($name)
{
$parsedUrl = parse_url($this->driver->getCurrentURL());
if (! array_key_exists('query', $parsedUrl)) {
PHPUnit::assertTrue(true);
return $this;
}
parse_str($parsedUrl['query'], $output);
PHPUnit::assertArrayNotHasKey(
$name, $output,
"Found unexpected query string parameter [{$name}] in [".$this->driver->getCurrentURL()."]."
);
return $this;
}
/**
* Assert that the given query string parameter is present.
*
* @param string $name
* @return $this
*/
protected function assertHasQueryStringParameter($name)
{
$parsedUrl = parse_url($this->driver->getCurrentURL());
PHPUnit::assertArrayHasKey(
'query', $parsedUrl,
"Did not see expected query string in [".$this->driver->getCurrentURL()."]."
);
parse_str($parsedUrl['query'], $output);
PHPUnit::assertArrayHasKey(
$name, $output,
"Did not see expected query string parameter [{$name}] in [".$this->driver->getCurrentURL()."]."
);
return $output;
}
/**
* Assert that the given cookie is present.
*
* @param string $name
* @return $this
*/
public function assertHasCookie($name)
{
PHPUnit::assertTrue(
! is_null($this->cookie($name)),
"Did not find expected cookie [{$name}]."
);
return $this;
}
/**
* Assert that an encrypted cookie has a given value.
*
* @param string $name
* @param string $value
* @param bool $decrypt
* @return $this
*/
public function assertCookieValue($name, $value, $decrypt = true)
{
$actual = $decrypt ? $this->cookie($name) : $this->plainCookie($name);
PHPUnit::assertEquals(
$value, $actual,
"Cookie [{$name}] had value [{$actual}], but expected [{$value}]."
);
return $this;
}
/**
* Assert that a cookie has a given value.
*
* @param string $name
* @param string $value
* @return $this
*/
public function assertPlainCookieValue($name, $value)
{
return $this->assertCookieValue($name, $value, false);
}
/**
* Assert that the given text appears on the page.
*
* @param string $text
* @return $this
*/
public function assertSee($text)
{
return $this->assertSeeIn('', $text);
}
/**
* Assert that the given text does not appear on the page.
*
* @param string $text
* @return $this
*/
public function assertDontSee($text)
{
return $this->assertDontSeeIn('', $text);
}
/**
* Assert that the given text appears within the given selector.
*
* @param string $selector
* @param string $text
* @return $this
*/
public function assertSeeIn($selector, $text)
{
$fullSelector = $this->resolver->format($selector);
$element = $this->resolver->findOrFail($selector);
PHPUnit::assertTrue(
Str::contains($element->getText(), $text),
"Did not see expected text [{$text}] within element [{$fullSelector}]."
);
return $this;
}
/**
* Assert that the given text does not appear within the given selector.
*
* @param string $selector
* @param string $text
* @return $this
*/
public function assertDontSeeIn($selector, $text)
{
$fullSelector = $this->resolver->format($selector);
$element = $this->resolver->findOrFail($selector);
PHPUnit::assertFalse(
Str::contains($element->getText(), $text),
"Saw unexpected text [{$text}] within element [{$fullSelector}]."
);
return $this;
}
/**
* Assert that the given source code is present on the page.
*
* @param string $code
* @return $this
*/
public function assertSourceHas($code)
{
PHPUnit::assertContains(
$code, $this->driver->getPageSource(),
"Did not find expected source code [{$code}]"
);
return $this;
}
/**
* Assert that the given source code is not present on the page.
*
* @param string $code
* @return $this
*/
public function assertSourceMissing($code)
{
PHPUnit::assertNotContains(
$code, $this->driver->getPageSource(),
"Found unexpected source code [{$code}]"
);
return $this;
}
/**
* Assert that the given link is visible.
*
* @param string $link
* @return $this
*/
public function assertSeeLink($link)
{
if ($this->resolver->prefix) {
$message = "Did not see expected link [{$link}] within [{$this->resolver->prefix}].";
} else {
$message = "Did not see expected link [{$link}].";
}
PHPUnit::assertTrue(
$this->seeLink($link),
$message
);
return $this;
}
/**
* Assert that the given link is not visible.
*
* @param string $link
* @return $this
*/
public function assertDontSeeLink($link)
{
if ($this->resolver->prefix) {
$message = "Saw unexpected link [{$link}] within [{$this->resolver->prefix}].";
} else {
$message = "Saw unexpected expected link [{$link}].";
}
PHPUnit::assertFalse(
$this->seeLink($link),
$message
);
return $this;
}
/**
* Determine if the given link is visible.
*
* @param string $link
* @return bool
*/
public function seeLink($link)
{
$this->ensurejQueryIsAvailable();
$selector = addslashes(trim($this->resolver->format("a:contains('{$link}')")));
$script = <<<JS
var link = jQuery.find("{$selector}");
return link.length > 0 && jQuery(link).is(':visible');
JS;
return $this->driver->executeScript($script);
}
/**
* Assert that the given input or text area contains the given value.
*
* @param string $field
* @param string $value
* @return $this
*/
public function assertInputValue($field, $value)
{
PHPUnit::assertEquals($value, $this->inputValue($field));
return $this;
}
/**
* Assert that the given input or text area does not contain the given value.
*
* @param string $field
* @param string $value
* @return $this
*/
public function assertInputValueIsNot($field, $value)
{
PHPUnit::assertNotEquals($value, $this->inputValue($field));
return $this;
}
/**
* Get the value of the given input or text area field.
*
* @param string $field
* @return string
*/
public function inputValue($field)
{
$element = $this->resolver->resolveForTyping($field);
return in_array($element->getTagName(), ['input', 'textarea'])
? $element->getAttribute('value')
: $element->getText();
}
/**
* Assert that the given checkbox field is checked.
*
* @param string $field
* @param string $value
* @return $this
*/
public function assertChecked($field, $value = null)
{
$element = $this->resolver->resolveForChecking($field, $value);
PHPUnit::assertTrue(
$element->isSelected(),
"Expected checkbox [{$field}] to be checked, but it wasn't."
);
return $this;
}
/**
* Assert that the given checkbox field is not checked.
*
* @param string $field
* @param string $value
* @return $this
*/
public function assertNotChecked($field, $value = null)
{
$element = $this->resolver->resolveForChecking($field, $value);
PHPUnit::assertFalse(
$element->isSelected(),
"Checkbox [{$field}] was unexpectedly checked."
);
return $this;
}
/**
* Assert that the given radio field is selected.
*
* @param string $field
* @param string $value
* @return $this
*/
function assertRadioSelected($field, $value)
{
$element = $this->resolver->resolveForRadioSelection($field, $value);
PHPUnit::assertTrue(
$element->isSelected(),
"Expected radio [{$field}] to be selected, but it wasn't."
);
return $this;
}
/**
* Assert that the given radio field is not selected.
*
* @param string $field
* @param string $value
* @return $this
*/
public function assertRadioNotSelected($field, $value = null)
{
$element = $this->resolver->resolveForRadioSelection($field, $value);
PHPUnit::assertFalse(
$element->isSelected(),
"Radio [{$field}] was unexpectedly selected."
);
return $this;
}
/**
* Assert that the given select field has the given value selected.
*
* @param string $field
* @param string $value
* @return $this
*/
public function assertSelected($field, $value)
{
PHPUnit::assertTrue(
$this->selected($field, $value),
"Expected value [{$value}] to be selected for [{$field}], but it wasn't."
);
return $this;
}
/**
* Assert that the given select field does not have the given value selected.
*
* @param string $field
* @param string $value
* @return $this
*/
public function assertNotSelected($field, $value)
{
PHPUnit::assertFalse(
$this->selected($field, $value),
"Unexpected value [{$value}] selected for [{$field}]."
);
return $this;
}
/**
* Assert that the given array of values are available to be selected.
*
* @param string $field
* @param array $values
* @return $this
*/
public function assertSelectHasOptions($field, array $values)
{
PHPUnit::assertCount(
count($values),
$this->resolver->resolveSelectOptions($field, $values),
"Expected options [".implode(',', $values)."] for selection field [{$field}] to be available."
);
return $this;
}
/**
* Assert that the given array of values are not available to be selected.
*
* @param string $field
* @param array $values
* @return $this
*/
public function assertSelectMissingOptions($field, array $values)
{
PHPUnit::assertCount(
0, $this->resolver->resolveSelectOptions($field, $values),
"Unexpected options [".implode(',', $values)."] for selection field [{$field}]."
);
return $this;
}
/**
* Assert that the given value is available to be selected on the given field.
*
* @param string $field
* @param string $value
* @return $this
*/
public function assertSelectHasOption($field, $value)
{
return $this->assertSelectHasOptions($field, [$value]);
}
/**
* Assert that the given value is not available to be selected on the given field.
*
* @param string $field
* @param string $value
* @return $this
*/
public function assertSelectMissingOption($field, $value)
{
return $this->assertSelectMissingOptions($field, [$value]);
}
/**
* Determine if the given value is selected for the given select field.
*
* @param string $field
* @param string $value
* @return bool
*/
public function selected($field, $value)
{
$element = $this->resolver->resolveForSelection($field);
return (string) $element->getAttribute('value') === (string) $value;
}
/**
* Assert that the element at the given selector has the given value.
*
* @param string $selector
* @param string $value
* @return $this
*/
public function assertValue($selector, $value)
{
$actual = $this->resolver->findOrFail($selector)->getAttribute('value');
PHPUnit::assertEquals($value, $actual);
return $this;
}
/**
* Assert that the element with the given selector is visible.
*
* @param string $selector
* @return $this
*/
public function assertVisible($selector)
{
$fullSelector = $this->resolver->format($selector);
PHPUnit::assertTrue(
$this->resolver->findOrFail($selector)->isDisplayed(),
"Element [{$fullSelector}] is not visible."
);
return $this;
}
/**
* Assert that the element with the given selector is not on the page.
*
* @param string $selector
* @return $this
*/
public function assertMissing($selector)
{
$fullSelector = $this->resolver->format($selector);
try {
$missing = ! $this->resolver->findOrFail($selector)->isDisplayed();
} catch (NoSuchElementException $e) {
$missing = true;
}
PHPUnit::assertTrue($missing, "Saw unexpected element [{$fullSelector}].");
return $this;
}
/**
* Assert that a JavaScript dialog with given message has been opened.
*
* @param string $message
* @return $this
*/
public function assertDialogOpened($message)
{
PHPUnit::assertEquals(
$message, $this->driver->switchTo()->alert()->getText()
);
return $this;
}
}

View File

@@ -0,0 +1,179 @@
<?php
namespace Laravel\Dusk\Concerns;
use Closure;
use Exception;
use Carbon\Carbon;
use Illuminate\Support\Str;
use Facebook\WebDriver\Exception\TimeOutException;
use Facebook\WebDriver\Exception\NoSuchElementException;
trait WaitsForElements
{
/**
* Execute the given callback in a scoped browser once the selector is available.
*
* @param string $selector
* @param Closure $callback
* @param int $seconds
* @return $this
*/
public function whenAvailable($selector, Closure $callback, $seconds = 5)
{
return $this->waitFor($selector, $seconds)->with($selector, $callback);
}
/**
* Wait for the given selector to be visible.
*
* @param string $selector
* @param int $seconds
* @return $this
*/
public function waitFor($selector, $seconds = 5)
{
return $this->waitUsing($seconds, 100, function () use ($selector) {
return $this->resolver->findOrFail($selector)->isDisplayed();
}, "Waited {$seconds} seconds for selector [{$selector}].");
}
/**
* Wait for the given selector to be removed.
*
* @param string $selector
* @param int $seconds
* @return $this
*/
public function waitUntilMissing($selector, $seconds = 5)
{
return $this->waitUsing($seconds, 100, function () use ($selector) {
try {
$missing = ! $this->resolver->findOrFail($selector)->isDisplayed();
} catch (NoSuchElementException $e) {
$missing = true;
}
return $missing;
}, "Waited {$seconds} seconds for removal of selector [{$selector}].");
}
/**
* Wait for the given text to be visible.
*
* @param string $text
* @param int $seconds
* @return $this
*/
public function waitForText($text, $seconds = 5)
{
return $this->waitUsing($seconds, 100, function () use ($text) {
return Str::contains($this->resolver->findOrFail('')->getText(), $text);
}, "Waited {$seconds} seconds for text [{$text}].");
}
/**
* Wait for the given link to be visible.
*
* @param string $link
* @param int $seconds
* @return $this
*/
public function waitForLink($link, $seconds = 5)
{
return $this->waitUsing($seconds, 100, function () use ($link) {
return $this->seeLink($link);
});
}
/**
* Wait for the given location.
*
* @param string $path
* @param int $seconds
* @return $this
*/
public function waitForLocation($path, $seconds = 5)
{
return $this->waitUntil("window.location.pathname == '{$path}'", $seconds);
}
/**
* Wait until the given script returns true.
*
* @param string $script
* @param int $seconds
* @return $this
*/
public function waitUntil($script, $seconds = 5)
{
if (! Str::startsWith($script, 'return ')) {
$script = 'return '.$script;
}
if (! Str::endsWith($script, ';')) {
$script = $script.';';
}
return $this->waitUsing($seconds, 100, function () use ($script) {
return $this->driver->executeScript($script);
});
}
/**
* Wait for the current page to reload.
*
* @param Closure $callback
* @param int $seconds
* @return $this
*/
public function waitForReload($callback = null, $seconds = 5)
{
$token = str_random();
$this->driver->executeScript("window['{$token}'] = {};");
if ($callback) {
$callback($this);
}
return $this->waitUsing($seconds, 100, function () use ($token) {
return $this->driver->executeScript("return typeof window['{$token}'] === 'undefined';");
});
}
/**
* Wait for the given callback to be true.
*
* @param int $seconds
* @param int $interval
* @param Closure $callback
* @param string|null $message
* @return $this
* @throws TimeOutException
*/
public function waitUsing($seconds, $interval, Closure $callback, $message = null)
{
$this->pause($interval);
$started = Carbon::now();
while (true) {
try {
if ($callback()) {
break;
}
} catch (Exception $e) {
//
}
if ($started->lt(Carbon::now()->subSeconds($seconds))) {
throw new TimeOutException($message ?: "Waited {$seconds} seconds for callback.");
}
$this->pause($interval);
}
return $this;
}
}

View File

@@ -0,0 +1,230 @@
<?php
namespace Laravel\Dusk\Console;
use Dotenv\Dotenv;
use Illuminate\Console\Command;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Process\ProcessBuilder;
use Symfony\Component\Process\Exception\RuntimeException;
class DuskCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'dusk';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Run the Dusk tests for the application';
/**
* Indicates if the project has its own PHPUnit configuration.
*
* @var boolean
*/
protected $hasPhpUnitConfiguration = false;
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
$this->ignoreValidationErrors();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->purgeScreenshots();
$this->purgeConsoleLogs();
$options = array_slice($_SERVER['argv'], 2);
return $this->withDuskEnvironment(function () use ($options) {
$process = (new ProcessBuilder())
->setTimeout(null)
->setPrefix($this->binary())
->setArguments($this->phpunitArguments($options))
->getProcess();
try {
$process->setTty(true);
} catch (RuntimeException $e) {
$this->output->writeln('Warning: '.$e->getMessage());
}
return $process->run(function ($type, $line) {
$this->output->write($line);
});
});
}
/**
* Get the PHP binary to execute.
*
* @return array
*/
protected function binary()
{
return [PHP_BINARY, 'vendor/phpunit/phpunit/phpunit'];
}
/**
* Get the array of arguments for running PHPUnit.
*
* @return array
*/
protected function phpunitArguments($options)
{
return array_merge(['-c', base_path('phpunit.dusk.xml')], $options);
}
/**
* Purge the failure screenshots
*
* @return void
*/
protected function purgeScreenshots()
{
$files = Finder::create()->files()
->in(base_path('tests/Browser/screenshots'))
->name('failure-*');
foreach ($files as $file) {
@unlink($file->getRealPath());
}
}
/**
* Purge the console logs.
*
* @return void
*/
protected function purgeConsoleLogs()
{
$files = Finder::create()->files()
->in(base_path('tests/Browser/console'))
->name('*.log');
foreach ($files as $file) {
@unlink($file->getRealPath());
}
}
/**
* Run the given callback with the Dusk configuration files.
*
* @param \Closure $callback
* @return mixed
*/
protected function withDuskEnvironment($callback)
{
if (file_exists(base_path($this->duskFile()))) {
if (file_get_contents(base_path('.env')) !== file_get_contents(base_path($this->duskFile()))) {
$this->backupEnvironment();
}
$this->refreshEnvironment();
}
$this->writeConfiguration();
return tap($callback(), function () {
$this->removeConfiguration();
if (file_exists(base_path($this->duskFile())) && file_exists(base_path('.env.backup'))) {
$this->restoreEnvironment();
}
});
}
/**
* Backup the current environment file.
*
* @return void
*/
protected function backupEnvironment()
{
copy(base_path('.env'), base_path('.env.backup'));
copy(base_path($this->duskFile()), base_path('.env'));
}
/**
* Restore the backed-up environment file.
*
* @return void
*/
protected function restoreEnvironment()
{
copy(base_path('.env.backup'), base_path('.env'));
unlink(base_path('.env.backup'));
}
/**
* Refresh the current environment variables.
*
* @return void
*/
protected function refreshEnvironment()
{
(new Dotenv(base_path()))->overload();
}
/**
* Write the Dusk PHPUnit configuration.
*
* @return void
*/
protected function writeConfiguration()
{
if (! file_exists($file = base_path('phpunit.dusk.xml'))) {
copy(realpath(__DIR__.'/../../stubs/phpunit.xml'), $file);
} else {
$this->hasPhpUnitConfiguration = true;
}
}
/**
* Remove the Dusk PHPUnit configuration.
*
* @return void
*/
protected function removeConfiguration()
{
if (! $this->hasPhpUnitConfiguration) {
unlink(base_path('phpunit.dusk.xml'));
}
}
/**
* Get the name of the Dusk file for the environment.
*
* @return string
*/
protected function duskFile()
{
if (file_exists(base_path($file = '.env.dusk.'.$this->laravel->environment()))) {
return $file;
}
return '.env.dusk';
}
}

View File

@@ -0,0 +1,95 @@
<?php
namespace Laravel\Dusk\Console;
use Illuminate\Console\Command;
class InstallCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'dusk:install';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Install Dusk into the application';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
if (! is_dir(base_path('tests/Browser/Pages'))) {
mkdir(base_path('tests/Browser/Pages'), 0755, true);
}
if (! is_dir(base_path('tests/Browser/screenshots'))) {
$this->createScreenshotsDirectory();
}
if (! is_dir(base_path('tests/Browser/console'))) {
$this->createConsoleDirectory();
}
$subs = [
'ExampleTest.stub' => base_path('tests/Browser/ExampleTest.php'),
'HomePage.stub' => base_path('tests/Browser/Pages/HomePage.php'),
'DuskTestCase.stub' => base_path('tests/DuskTestCase.php'),
'Page.stub' => base_path('tests/Browser/Pages/Page.php'),
];
foreach ($subs as $stub => $file) {
if (! is_file($file)) {
copy(__DIR__.'/../../stubs/'.$stub, $file);
}
}
$this->info('Dusk scaffolding installed successfully.');
}
/**
* Create the screenshots directory.
*
* @return void
*/
protected function createScreenshotsDirectory()
{
mkdir(base_path('tests/Browser/screenshots'), 0755, true);
file_put_contents(base_path('tests/Browser/screenshots/.gitignore'), '*
!.gitignore
');
}
/**
* Create the console directory.
*
* @return void
*/
protected function createConsoleDirectory()
{
mkdir(base_path('tests/Browser/console'), 0755, true);
file_put_contents(base_path('tests/Browser/console/.gitignore'), '*
!.gitignore
');
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace Laravel\Dusk\Console;
use Illuminate\Console\GeneratorCommand;
class MakeCommand extends GeneratorCommand
{
/**
* The console command name.
*
* @var string
*/
protected $signature = 'dusk:make {name : The name of the class}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new Dusk test class';
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Test';
/**
* Get the stub file for the generator.
*
* @return string
*/
protected function getStub()
{
return __DIR__.'/stubs/test.stub';
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
protected function getPath($name)
{
$name = str_replace_first($this->rootNamespace(), '', $name);
return $this->laravel->basePath().'/tests'.str_replace('\\', '/', $name).'.php';
}
/**
* Get the default namespace for the class.
*
* @param string $rootNamespace
* @return string
*/
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace.'\Browser';
}
/**
* Get the root namespace for the class.
*
* @return string
*/
protected function rootNamespace()
{
return 'Tests';
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace Laravel\Dusk\Console;
use Illuminate\Console\GeneratorCommand;
class PageCommand extends GeneratorCommand
{
/**
* The console command name.
*
* @var string
*/
protected $signature = 'dusk:page {name : The name of the class}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new Dusk page class';
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Page';
/**
* Get the stub file for the generator.
*
* @return string
*/
protected function getStub()
{
return __DIR__.'/stubs/page.stub';
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
protected function getPath($name)
{
$name = str_replace_first($this->rootNamespace(), '', $name);
return $this->laravel->basePath().'/tests'.str_replace('\\', '/', $name).'.php';
}
/**
* Get the default namespace for the class.
*
* @param string $rootNamespace
* @return string
*/
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace.'\Browser\Pages';
}
/**
* Get the root namespace for the class.
*
* @return string
*/
protected function rootNamespace()
{
return 'Tests';
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace DummyNamespace;
use Laravel\Dusk\Browser;
use Laravel\Dusk\Page as BasePage;
class DummyClass extends BasePage
{
/**
* Get the URL for the page.
*
* @return string
*/
public function url()
{
return '/';
}
/**
* Assert that the browser is on the page.
*
* @param Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
$browser->assertPathIs($this->url());
}
/**
* Get the element shortcuts for the page.
*
* @return array
*/
public function elements()
{
return [
'@element' => '#selector',
];
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace DummyNamespace;
use Tests\DuskTestCase;
use Laravel\Dusk\Browser;
use Illuminate\Foundation\Testing\DatabaseMigrations;
class DummyClass extends DuskTestCase
{
/**
* A Dusk test example.
*
* @return void
*/
public function testExample()
{
$this->browse(function (Browser $browser) {
$browser->visit('/')
->assertSee('Laravel');
});
}
}

44
vendor/laravel/dusk/src/Dusk.php vendored Normal file
View File

@@ -0,0 +1,44 @@
<?php
namespace Laravel\Dusk;
use InvalidArgumentException;
class Dusk
{
/**
* Register the Dusk service provider.
*
* @param array $options
* @return void
*/
public static function register(array $options = [])
{
if (static::duskEnvironment($options)) {
app()->register(DuskServiceProvider::class);
}
}
/**
* Determine if Dusk may run in this environment.
*
* @param array $options
* @return bool
*/
protected static function duskEnvironment($options)
{
if (! isset($options['environments'])) {
return false;
}
if (is_string($options['environments'])) {
$options['environments'] = [$options['environments']];
}
if (! is_array($options['environments'])) {
throw new InvalidArgumentException("Dusk environments must be listed as an array.");
}
return app()->environment(...$options['environments']);
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Laravel\Dusk;
use Exception;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
class DuskServiceProvider extends ServiceProvider
{
/**
* Bootstrap any package services.
*
* @return void
*/
public function boot()
{
Route::get('/_dusk/login/{userId}/{guard?}', [
'middleware' => 'web',
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@login',
]);
Route::get('/_dusk/logout/{guard?}', [
'middleware' => 'web',
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@logout',
]);
Route::get('/_dusk/user/{guard?}', [
'middleware' => 'web',
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@user',
]);
}
/**
* Register any package services.
*
* @return void
* @throws Exception
*/
public function register()
{
if ($this->app->environment('production')) {
throw new Exception('It is unsafe to run Dusk in production.');
}
if ($this->app->runningInConsole()) {
$this->commands([
Console\InstallCommand::class,
Console\DuskCommand::class,
Console\MakeCommand::class,
Console\PageCommand::class,
]);
}
}
}

View File

@@ -0,0 +1,377 @@
<?php
namespace Laravel\Dusk;
use Exception;
use Illuminate\Support\Str;
use InvalidArgumentException;
use Facebook\WebDriver\WebDriverBy;
use Illuminate\Support\Traits\Macroable;
class ElementResolver
{
use Macroable;
/**
* The remote web driver instance.
*
* @var \Facebook\WebDriver\Remote\RemoteWebDriver
*/
public $driver;
/**
* The selector prefix for the resolver.
*
* @var string
*/
public $prefix;
/**
* Set the elements the resolver should use as shortcuts.
*
* @var array
*/
public $elements = [];
/**
* The button finding methods.
*
* @var array
*/
protected $buttonFinders = [
'findById',
'findButtonBySelector',
'findButtonByName',
'findButtonByValue',
'findButtonByText'
];
/**
* Create a new element resolver instance.
*
* @param \Facebook\WebDriver\Remote\RemoteWebDriver $driver
* @param string $prefix
* @return void
*/
public function __construct($driver, $prefix = 'body')
{
$this->driver = $driver;
$this->prefix = trim($prefix);
}
/**
* Set the page elements the resolver should use as shortcuts.
*
* @param array $elements
* @return $this
*/
public function pageElements(array $elements)
{
$this->elements = $elements;
return $this;
}
/**
* Resolve the element for a given input "field".
*
* @param string $field
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*/
public function resolveForTyping($field)
{
if (! is_null($element = $this->findById($field))) {
return $element;
}
return $this->firstOrFail([
$field, "input[name='{$field}']", "textarea[name='{$field}']"
]);
}
/**
* Resolve the element for a given select "field".
*
* @param string $field
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*/
public function resolveForSelection($field)
{
if (! is_null($element = $this->findById($field))) {
return $element;
}
return $this->firstOrFail([
$field, "select[name='{$field}']"
]);
}
/**
* Resolve all the options with the given value on the select field.
*
* @param string $field
* @param array $values
* @return array
*/
public function resolveSelectOptions($field, array $values)
{
$options = $this->resolveForSelection($field)
->findElements(WebDriverBy::tagName('option'));
if (empty($options)) {
return [];
}
return array_filter($options, function($option) use ($values) {
return in_array($option->getAttribute('value'), $values);
});
}
/**
* Resolve the element for a given radio "field" / value.
*
* @param string $field
* @param string $value
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*/
public function resolveForRadioSelection($field, $value = null)
{
if (! is_null($element = $this->findById($field))) {
return $element;
}
if (is_null($value)) {
throw new InvalidArgumentException(
"No value was provided for radio button [{$field}]."
);
}
return $this->firstOrFail([
$field, "input[type=radio][name='{$field}'][value='{$value}']"
]);
}
/**
* Resolve the element for a given checkbox "field".
*
* @param string $field
* @param string $value
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*/
public function resolveForChecking($field, $value = null)
{
if (! is_null($element = $this->findById($field))) {
return $element;
}
$selector = "input[type=checkbox][name='{$field}']";
if (! is_null($value)) {
$selector .= "[value='{$value}']";
}
return $this->firstOrFail([
$field, $selector
]);
}
/**
* Resolve the element for a given file "field".
*
* @param string $field
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*/
public function resolveForAttachment($field)
{
if (! is_null($element = $this->findById($field))) {
return $element;
}
return $this->firstOrFail([
$field, "input[type=file][name='{$field}']"
]);
}
/**
* Resolve the element for a given button.
*
* @param string $button
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*/
public function resolveForButtonPress($button)
{
foreach ($this->buttonFinders as $method) {
if (! is_null($element = $this->{$method}($button))) {
return $element;
}
}
throw new InvalidArgumentException(
"Unable to locate button [{$button}]."
);
}
/**
* Resolve the element for a given button by selector.
*
* @param string $button
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
*/
protected function findButtonBySelector($button)
{
if (! is_null($element = $this->find($button))) {
return $element;
}
}
/**
* Resolve the element for a given button by name.
*
* @param string $button
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
*/
protected function findButtonByName($button)
{
if (! is_null($element = $this->find("input[type=submit][name='{$button}']")) ||
! is_null($element = $this->find("input[type=button][value='{$button}']")) ||
! is_null($element = $this->find("button[name='{$button}']"))) {
return $element;
}
}
/**
* Resolve the element for a given button by value.
*
* @param string $button
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
*/
protected function findButtonByValue($button)
{
foreach ($this->all("input[type=submit]") as $element) {
if ($element->getAttribute('value') === $button) {
return $element;
}
}
}
/**
* Resolve the element for a given button by text.
*
* @param string $button
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
*/
protected function findButtonByText($button)
{
foreach ($this->all('button') as $element) {
if (Str::contains($element->getText(), $button)) {
return $element;
}
}
}
/**
* Attempt to find the selector by ID.
*
* @param string $selector
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
*/
protected function findById($selector)
{
if (preg_match('/^#[\w\-:]+$/', $selector)) {
return $this->driver->findElement(WebDriverBy::id(substr($selector, 1)));
}
}
/**
* Find an element by the given selector or return null.
*
* @param string $selector
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
*/
public function find($selector)
{
try {
return $this->findOrFail($selector);
} catch (Exception $e) {
//
}
}
/**
* Get the first element matching the given selectors.
*
* @param array $selectors
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*/
public function firstOrFail($selectors)
{
foreach ((array) $selectors as $selector) {
try {
return $this->findOrFail($selector);
} catch (Exception $e) {
//
}
}
throw $e;
}
/**
* Find an element by the given selector or throw an exception.
*
* @param string $selector
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*/
public function findOrFail($selector)
{
if (! is_null($element = $this->findById($selector))) {
return $element;
}
return $this->driver->findElement(
WebDriverBy::cssSelector($this->format($selector))
);
}
/**
* Find the elements by the given selector or return an empty array.
*
* @param string $selector
* @return array
*/
public function all($selector)
{
try {
return $this->driver->findElements(
WebDriverBy::cssSelector($this->format($selector))
);
} catch (Exception $e) {
//
}
return [];
}
/**
* Format the given selector with the current prefix.
*
* @param string $selector
* @return string
*/
public function format($selector)
{
$sortedElements = collect($this->elements)->sortByDesc(function($element, $key){
return strlen($key);
})->toArray();
$selector = str_replace(
array_keys($sortedElements), array_values($sortedElements), $originalSelector = $selector
);
if (starts_with($selector, '@') && $selector === $originalSelector) {
$selector = '[dusk="'.explode('@', $selector)[1].'"]';
}
return trim($this->prefix.' '.$selector);
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace Laravel\Dusk\Http\Controllers;
use Illuminate\Support\Facades\Auth;
class UserController
{
/**
* Retrieve the authenticated user identifier and class name.
*
* @param string|null $guard
* @return array
*/
public function user($guard = null)
{
$user = Auth::guard($guard)->user();
if (! $user) {
return [];
}
return [
'id' => $user->getAuthIdentifier(),
'className' => get_class($user),
];
}
/**
* Login using the given user ID / email.
*
* @param string $userId
* @param string $guard
* @return Response
*/
public function login($userId, $guard = null)
{
$model = $this->modelForGuard(
$guard = $guard ?: config('auth.defaults.guard')
);
if (str_contains($userId, '@')) {
$user = (new $model)->where('email', $userId)->first();
} else {
$user = (new $model)->find($userId);
}
Auth::guard($guard)->login($user);
}
/**
* Log the user out of the application.
*
* @param string $guard
* @return Response
*/
public function logout($guard = null)
{
Auth::guard($guard ?: config('auth.defaults.guard'))->logout();
}
/**
* Get the model for the given guard.
*
* @param string $guard
* @return string
*/
protected function modelForGuard($guard)
{
$provider = config("auth.guards.{$guard}.provider");
return config("auth.providers.{$provider}.model");
}
}

44
vendor/laravel/dusk/src/Page.php vendored Normal file
View File

@@ -0,0 +1,44 @@
<?php
namespace Laravel\Dusk;
abstract class Page
{
/**
* Get the URL for the page.
*
* @return string
*/
abstract public function url();
/**
* Assert that the browser is on the page.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
//
}
/**
* Get the element shortcuts for the page.
*
* @return array
*/
public function elements()
{
return [];
}
/**
* Get the global element shortcuts for the site.
*
* @return array
*/
public static function siteElements()
{
return [];
}
}

View File

@@ -0,0 +1,118 @@
<?php
namespace Laravel\Dusk;
use RuntimeException;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\ProcessBuilder;
trait SupportsChrome
{
/**
* The path to the custom Chromedriver binary.
*
* @var string|null
*/
protected static $chromeDriver;
/**
* The Chromedriver process instance.
*
* @var \Symfony\Component\Process\Process
*/
protected static $chromeProcess;
/**
* Start the Chromedriver process.
*
* @throws \RuntimeException if the driver file path doesn't exist.
*
* @return void
*/
public static function startChromeDriver()
{
static::$chromeProcess = static::buildChromeProcess();
static::$chromeProcess->start();
static::afterClass(function () {
static::stopChromeDriver();
});
}
/**
* Stop the Chromedriver process.
*
* @return void
*/
public static function stopChromeDriver()
{
if (static::$chromeProcess) {
static::$chromeProcess->stop();
}
}
/**
* Build the process to run the Chromedriver.
*
* @throws \RuntimeException if the driver file path doesn't exist.
*
* @return \Symfony\Component\Process\Process
*/
protected static function buildChromeProcess()
{
$driver = static::$chromeDriver
?: realpath(__DIR__.'/../bin/chromedriver-'.static::driverSuffix());
if (realpath($driver) === false) {
throw new RuntimeException("Invalid path to Chromedriver [{$driver}].");
}
return (new ProcessBuilder())
->setPrefix(realpath($driver))
->getProcess()
->setEnv(static::chromeEnvironment());
}
/**
* Set the path to the custom Chromedriver.
*
* @param string $path
* @return void
*/
public static function useChromedriver($path)
{
static::$chromeDriver = $path;
}
/**
* Get the Chromedriver environment variables.
*
* @return array
*/
protected static function chromeEnvironment()
{
if (PHP_OS === 'Darwin' || PHP_OS === 'WINNT') {
return [];
}
return ['DISPLAY' => ':0'];
}
/**
* Get the suffix for the Chromedriver binary.
*
* @return string
*/
protected static function driverSuffix()
{
switch (PHP_OS) {
case 'Darwin':
return 'mac';
case 'WINNT':
return 'win.exe';
default:
return 'linux';
}
}
}

245
vendor/laravel/dusk/src/TestCase.php vendored Normal file
View File

@@ -0,0 +1,245 @@
<?php
namespace Laravel\Dusk;
use Closure;
use Exception;
use Throwable;
use ReflectionFunction;
use Illuminate\Support\Collection;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Illuminate\Foundation\Testing\TestCase as FoundationTestCase;
abstract class TestCase extends FoundationTestCase
{
use SupportsChrome;
/**
* All of the active browser instances.
*
* @var array
*/
protected static $browsers = [];
/**
* The callbacks that should be run on class tear down.
*
* @var array
*/
protected static $afterClassCallbacks = [];
/**
* Register the base URL with Dusk.
*
* @return void
*/
protected function setUp()
{
parent::setUp();
Browser::$baseUrl = $this->baseUrl();
Browser::$storeScreenshotsAt = base_path('tests/Browser/screenshots');
Browser::$storeConsoleLogAt = base_path('tests/Browser/console');
Browser::$userResolver = function () {
return $this->user();
};
}
/**
* Tear down the Dusk test case class.
*
* @afterClass
* @return void
*/
public static function tearDownDuskClass()
{
static::closeAll();
foreach (static::$afterClassCallbacks as $callback) {
$callback();
}
}
/**
* Register an "after class" tear down callback.
*
* @param \Closure $callback
* @return void
*/
public static function afterClass(Closure $callback)
{
static::$afterClassCallbacks[] = $callback;
}
/**
* Create a new browser instance.
*
* @param \Closure $callback
* @return \Laravel\Dusk\Browser|void
* @throws \Exception
* @throws \Throwable
*/
public function browse(Closure $callback)
{
$browsers = $this->createBrowsersFor($callback);
try {
$callback(...$browsers->all());
} catch (Exception $e) {
$this->captureFailuresFor($browsers);
throw $e;
} catch (Throwable $e) {
$this->captureFailuresFor($browsers);
throw $e;
} finally {
$this->storeConsoleLogsFor($browsers);
static::$browsers = $this->closeAllButPrimary($browsers);
}
}
/**
* Create the browser instances needed for the given callback.
*
* @param \Closure $callback
* @return array
*/
protected function createBrowsersFor(Closure $callback)
{
if (count(static::$browsers) === 0) {
static::$browsers = collect([$this->newBrowser($this->createWebDriver())]);
}
$additional = $this->browsersNeededFor($callback) - 1;
for ($i = 0; $i < $additional; $i++) {
static::$browsers->push($this->newBrowser($this->createWebDriver()));
}
return static::$browsers;
}
/**
* Create a new Browser instance.
*
* @param \Facebook\WebDriver\Remote\RemoteWebDriver $driver
* @return \Laravel\Dusk\Browser
*/
protected function newBrowser($driver)
{
return new Browser($driver);
}
/**
* Get the number of browsers needed for a given callback.
*
* @param \Closure $callback
* @return int
*/
protected function browsersNeededFor(Closure $callback)
{
return (new ReflectionFunction($callback))->getNumberOfParameters();
}
/**
* Capture failure screenshots for each browser.
*
* @param \Illuminate\Support\Collection $browsers
* @return void
*/
protected function captureFailuresFor($browsers)
{
$browsers->each(function ($browser, $key) {
$browser->screenshot('failure-'.$this->getName().'-'.$key);
});
}
/**
* Store the console output for the given browsers.
*
* @param \Illuminate\Support\Collection $browsers
* @return void
*/
protected function storeConsoleLogsFor($browsers)
{
$browsers->each(function ($browser, $key) {
$browser->storeConsoleLog($this->getName().'-'.$key);
});
}
/**
* Close all of the browsers except the primary (first) one.
*
* @param \Illuminate\Support\Collection $browsers
* @return \Illuminate\Support\Collection
*/
protected function closeAllButPrimary($browsers)
{
$browsers->slice(1)->each->quit();
return $browsers->take(1);
}
/**
* Close all of the active browsers.
*
* @return void
*/
public static function closeAll()
{
Collection::make(static::$browsers)->each->quit();
static::$browsers = collect();
}
/**
* Create the remote web driver instance.
*
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
*/
protected function createWebDriver()
{
return retry(5, function () {
return $this->driver();
}, 50);
}
/**
* Create the RemoteWebDriver instance.
*
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
*/
protected function driver()
{
return RemoteWebDriver::create(
'http://localhost:9515', DesiredCapabilities::chrome()
);
}
/**
* Determine the application's base URL.
*
* @var string
*/
protected function baseUrl()
{
return config('app.url');
}
/**
* Get a callback that returns the default user to authenticate.
*
* @return \Closure
* @throws \Exception
*/
protected function user()
{
throw new Exception("User resolver has not been set.");
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Tests;
use Laravel\Dusk\TestCase as BaseTestCase;
use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\DesiredCapabilities;
abstract class DuskTestCase extends BaseTestCase
{
use CreatesApplication;
/**
* Prepare for Dusk test execution.
*
* @beforeClass
* @return void
*/
public static function prepare()
{
static::startChromeDriver();
}
/**
* Create the RemoteWebDriver instance.
*
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
*/
protected function driver()
{
$options = (new ChromeOptions)->addArguments([
'--disable-gpu',
'--headless'
]);
return RemoteWebDriver::create(
'http://localhost:9515', DesiredCapabilities::chrome()->setCapability(
ChromeOptions::CAPABILITY, $options
)
);
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Tests\Browser;
use Tests\DuskTestCase;
use Laravel\Dusk\Browser;
use Illuminate\Foundation\Testing\DatabaseMigrations;
class ExampleTest extends DuskTestCase
{
/**
* A basic browser test example.
*
* @return void
*/
public function testBasicExample()
{
$this->browse(function (Browser $browser) {
$browser->visit('/')
->assertSee('Laravel');
});
}
}

41
vendor/laravel/dusk/stubs/HomePage.stub vendored Normal file
View File

@@ -0,0 +1,41 @@
<?php
namespace Tests\Browser\Pages;
use Laravel\Dusk\Browser;
class HomePage extends Page
{
/**
* Get the URL for the page.
*
* @return string
*/
public function url()
{
return '/';
}
/**
* Assert that the browser is on the page.
*
* @param Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
//
}
/**
* Get the element shortcuts for the page.
*
* @return array
*/
public function elements()
{
return [
'@element' => '#selector',
];
}
}

20
vendor/laravel/dusk/stubs/Page.stub vendored Normal file
View File

@@ -0,0 +1,20 @@
<?php
namespace Tests\Browser\Pages;
use Laravel\Dusk\Page as BasePage;
abstract class Page extends BasePage
{
/**
* Get the global element shortcuts for the site.
*
* @return array
*/
public static function siteElements()
{
return [
'@element' => '#selector',
];
}
}

21
vendor/laravel/dusk/stubs/phpunit.xml vendored Normal file
View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="Browser Test Suite">
<directory suffix="Test.php">./tests/Browser</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./app</directory>
</whitelist>
</filter>
</phpunit>

21
vendor/laravel/framework/LICENSE.md vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Taylor Otwell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -3,7 +3,7 @@
"description": "The Laravel Framework.",
"keywords": ["framework", "laravel"],
"license": "MIT",
"homepage": "http://laravel.com",
"homepage": "https://laravel.com",
"support": {
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
@@ -11,33 +11,32 @@
"authors": [
{
"name": "Taylor Otwell",
"email": "taylorotwell@gmail.com"
"email": "taylor@laravel.com"
}
],
"require": {
"php": ">=5.5.9",
"php": ">=7.0",
"ext-mbstring": "*",
"ext-openssl": "*",
"classpreloader/classpreloader": "~3.0",
"doctrine/inflector": "~1.0",
"jeremeamia/superclosure": "~2.2",
"league/flysystem": "~1.0",
"monolog/monolog": "~1.11",
"doctrine/inflector": "~1.1",
"erusev/parsedown": "~1.7",
"league/flysystem": "^1.0.8",
"monolog/monolog": "~1.12",
"mtdowling/cron-expression": "~1.0",
"nesbot/carbon": "~1.20",
"paragonie/random_compat": "~1.4",
"psy/psysh": "0.7.*",
"swiftmailer/swiftmailer": "~5.1",
"symfony/console": "2.8.*|3.0.*",
"symfony/debug": "2.8.*|3.0.*",
"symfony/finder": "2.8.*|3.0.*",
"symfony/http-foundation": "2.8.*|3.0.*",
"symfony/http-kernel": "2.8.*|3.0.*",
"symfony/polyfill-php56": "~1.0",
"symfony/process": "2.8.*|3.0.*",
"symfony/routing": "2.8.*|3.0.*",
"symfony/translation": "2.8.*|3.0.*",
"symfony/var-dumper": "2.8.*|3.0.*",
"nesbot/carbon": "^1.24.1",
"psr/container": "~1.0",
"psr/simple-cache": "^1.0",
"ramsey/uuid": "~3.0",
"swiftmailer/swiftmailer": "~6.0",
"symfony/console": "~3.3",
"symfony/debug": "~3.3",
"symfony/finder": "~3.3",
"symfony/http-foundation": "~3.3",
"symfony/http-kernel": "~3.3",
"symfony/process": "~3.3",
"symfony/routing": "~3.3",
"symfony/var-dumper": "~3.3",
"tijsverkoyen/css-to-inline-styles": "~2.2",
"vlucas/phpdotenv": "~2.2"
},
"replace": {
@@ -53,12 +52,12 @@
"illuminate/database": "self.version",
"illuminate/encryption": "self.version",
"illuminate/events": "self.version",
"illuminate/exception": "self.version",
"illuminate/filesystem": "self.version",
"illuminate/hashing": "self.version",
"illuminate/http": "self.version",
"illuminate/log": "self.version",
"illuminate/mail": "self.version",
"illuminate/notifications": "self.version",
"illuminate/pagination": "self.version",
"illuminate/pipeline": "self.version",
"illuminate/queue": "self.version",
@@ -69,21 +68,21 @@
"illuminate/translation": "self.version",
"illuminate/validation": "self.version",
"illuminate/view": "self.version",
"tightenco/collect": "self.version"
"tightenco/collect": "<5.5.33"
},
"require-dev": {
"aws/aws-sdk-php": "~3.0",
"mockery/mockery": "~0.9.4",
"doctrine/dbal": "~2.5",
"filp/whoops": "^2.1.4",
"mockery/mockery": "~1.0",
"orchestra/testbench-core": "3.5.*",
"pda/pheanstalk": "~3.0",
"phpunit/phpunit": "~4.1",
"predis/predis": "~1.0",
"symfony/css-selector": "2.8.*|3.0.*",
"symfony/dom-crawler": "2.8.*|3.0.*"
"phpunit/phpunit": "~6.0",
"predis/predis": "^1.1.1",
"symfony/css-selector": "~3.3",
"symfony/dom-crawler": "~3.3"
},
"autoload": {
"classmap": [
"src/Illuminate/Queue/IlluminateQueueClosure.php"
],
"files": [
"src/Illuminate/Foundation/helpers.php",
"src/Illuminate/Support/helpers.php"
@@ -92,24 +91,41 @@
"Illuminate\\": "src/Illuminate/"
}
},
"autoload-dev": {
"files": [
"tests/Database/stubs/MigrationCreatorFakeMigration.php"
],
"psr-4": {
"Illuminate\\Tests\\": "tests/"
}
},
"extra": {
"branch-alias": {
"dev-master": "5.2-dev"
"dev-master": "5.5-dev"
}
},
"suggest": {
"ext-pcntl": "Required to use all features of the queue worker.",
"ext-posix": "Required to use all features of the queue worker.",
"aws/aws-sdk-php": "Required to use the SQS queue driver and SES mail driver (~3.0).",
"doctrine/dbal": "Required to rename columns and drop SQLite columns (~2.4).",
"doctrine/dbal": "Required to rename columns and drop SQLite columns (~2.5).",
"fzaninotto/faker": "Required to use the eloquent factory builder (~1.4).",
"guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers and the ping methods on schedules (~5.3|~6.0).",
"guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers and the ping methods on schedules (~6.0).",
"laravel/tinker": "Required to use the tinker console command (~1.0).",
"league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (~1.0).",
"league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (~1.0).",
"league/flysystem-cached-adapter": "Required to use Flysystem caching (~1.0).",
"nexmo/client": "Required to use the Nexmo transport (~1.0).",
"pda/pheanstalk": "Required to use the beanstalk queue driver (~3.0).",
"predis/predis": "Required to use the redis cache and queue drivers (~1.0).",
"pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~2.0).",
"symfony/css-selector": "Required to use some of the crawler integration testing tools (2.8.*|3.0.*).",
"symfony/dom-crawler": "Required to use most of the crawler integration testing tools (2.8.*|3.0.*).",
"symfony/psr-http-message-bridge": "Required to use psr7 bridging features (0.2.*)."
"pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~3.0).",
"symfony/css-selector": "Required to use some of the crawler integration testing tools (~3.3).",
"symfony/dom-crawler": "Required to use most of the crawler integration testing tools (~3.3).",
"symfony/psr-http-message-bridge": "Required to psr7 bridging features (~1.0)."
},
"minimum-stability": "dev"
"config": {
"sort-packages": true
},
"minimum-stability": "dev",
"prefer-stable": true
}

View File

@@ -1,31 +1,44 @@
# Laravel Framework (Kernel)
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://laravel.com/assets/img/components/logo-laravel.svg"></a></p>
[![StyleCI](https://styleci.io/repos/7548986/shield?style=flat)](https://styleci.io/repos/7548986)
[![Build Status](https://travis-ci.org/laravel/framework.svg)](https://travis-ci.org/laravel/framework)
[![Total Downloads](https://poser.pugx.org/laravel/framework/d/total.svg)](https://packagist.org/packages/laravel/framework)
[![Latest Stable Version](https://poser.pugx.org/laravel/framework/v/stable.svg)](https://packagist.org/packages/laravel/framework)
[![Latest Unstable Version](https://poser.pugx.org/laravel/framework/v/unstable.svg)](https://packagist.org/packages/laravel/framework)
[![License](https://poser.pugx.org/laravel/framework/license.svg)](https://packagist.org/packages/laravel/framework)
<p align="center">
<a href="https://travis-ci.org/laravel/framework"><img src="https://travis-ci.org/laravel/framework.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://poser.pugx.org/laravel/framework/d/total.svg" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://poser.pugx.org/laravel/framework/v/stable.svg" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://poser.pugx.org/laravel/framework/license.svg" alt="License"></a>
</p>
## About Laravel
> **Note:** This repository contains the core code of the Laravel framework. If you want to build an application using Laravel 5, visit the main [Laravel repository](https://github.com/laravel/laravel).
## Laravel PHP Framework
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable, creative experience to be truly fulfilling. Laravel attempts to take the pain out of development by easing common tasks used in the majority of web projects, such as:
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable, creative experience to be truly fulfilling. Laravel attempts to take the pain out of development by easing common tasks used in the majority of web projects, such as authentication, routing, sessions, queueing, and caching.
- [Simple, fast routing engine](https://laravel.com/docs/routing).
- [Powerful dependency injection container](https://laravel.com/docs/container).
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
- [Robust background job processing](https://laravel.com/docs/queues).
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
Laravel is accessible, yet powerful, providing powerful tools needed for large, robust applications. A superb inversion of control container, expressive migration system, and tightly integrated unit testing support give you the tools you need to build any application with which you are tasked.
Laravel is accessible, yet powerful, providing tools needed for large, robust applications. A superb combination of simplicity, elegance, and innovation gives you a complete toolset required to build any application with which you are tasked
## Official Documentation
## Learning Laravel
Documentation for the framework can be found on the [Laravel website](http://laravel.com/docs).
Laravel has the most extensive and thorough documentation and video tutorial library of any modern web application framework. The [Laravel documentation](https://laravel.com/docs) is in-depth and complete, making it a breeze to get started learning the framework.
If you're not in the mood to read, [Laracasts](https://laracasts.com) contains over 1100 video tutorials covering a range of topics including Laravel, modern PHP, unit testing, JavaScript, and more. Boost the skill level of yourself and your entire team by digging into our comprehensive video library.
## Contributing
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](http://laravel.com/docs/contributions).
## Code of Conduct
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](CODE_OF_CONDUCT.md).
## Security Vulnerabilities
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell at taylor@laravel.com. All security vulnerabilities will be promptly addressed.
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
## License

View File

@@ -2,6 +2,7 @@
namespace Illuminate\Auth\Access;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use InvalidArgumentException;
use Illuminate\Contracts\Container\Container;
@@ -64,7 +65,8 @@ class Gate implements GateContract
* @param array $afterCallbacks
* @return void
*/
public function __construct(Container $container, callable $userResolver, array $abilities = [], array $policies = [], array $beforeCallbacks = [], array $afterCallbacks = [])
public function __construct(Container $container, callable $userResolver, array $abilities = [],
array $policies = [], array $beforeCallbacks = [], array $afterCallbacks = [])
{
$this->policies = $policies;
$this->container = $container;
@@ -77,12 +79,20 @@ class Gate implements GateContract
/**
* Determine if a given ability has been defined.
*
* @param string $ability
* @param string|array $ability
* @return bool
*/
public function has($ability)
{
return isset($this->abilities[$ability]);
$abilities = is_array($ability) ? $ability : func_get_args();
foreach ($abilities as $ability) {
if (! isset($this->abilities[$ability])) {
return false;
}
}
return true;
}
/**
@@ -99,7 +109,7 @@ class Gate implements GateContract
if (is_callable($callback)) {
$this->abilities[$ability] = $callback;
} elseif (is_string($callback) && Str::contains($callback, '@')) {
$this->abilities[$ability] = $this->buildAbilityCallback($callback);
$this->abilities[$ability] = $this->buildAbilityCallback($ability, $callback);
} else {
throw new InvalidArgumentException("Callback must be a callable or a 'Class@method' string.");
}
@@ -107,18 +117,57 @@ class Gate implements GateContract
return $this;
}
/**
* Define abilities for a resource.
*
* @param string $name
* @param string $class
* @param array $abilities
* @return $this
*/
public function resource($name, $class, array $abilities = null)
{
$abilities = $abilities ?: [
'view' => 'view',
'create' => 'create',
'update' => 'update',
'delete' => 'delete',
];
foreach ($abilities as $ability => $method) {
$this->define($name.'.'.$ability, $class.'@'.$method);
}
return $this;
}
/**
* Create the ability callback for a callback string.
*
* @param string $ability
* @param string $callback
* @return \Closure
*/
protected function buildAbilityCallback($callback)
protected function buildAbilityCallback($ability, $callback)
{
return function () use ($callback) {
list($class, $method) = explode('@', $callback);
return function () use ($ability, $callback) {
list($class, $method) = Str::parseCallback($callback);
return call_user_func_array([$this->resolvePolicy($class), $method], func_get_args());
$policy = $this->resolvePolicy($class);
$arguments = func_get_args();
$user = array_shift($arguments);
$result = $this->callPolicyBefore(
$policy, $user, $ability, $arguments
);
if (! is_null($result)) {
return $result;
}
return $policy->{$method}(...func_get_args());
};
}
@@ -187,21 +236,35 @@ class Gate implements GateContract
}
/**
* Determine if the given ability should be granted for the current user.
* Determine if all of the given abilities should be granted for the current user.
*
* @param string $ability
* @param iterable|string $abilities
* @param array|mixed $arguments
* @return bool
*/
public function check($ability, $arguments = [])
public function check($abilities, $arguments = [])
{
try {
$result = $this->raw($ability, $arguments);
} catch (AuthorizationException $e) {
return false;
}
return collect($abilities)->every(function ($ability) use ($arguments) {
try {
return (bool) $this->raw($ability, $arguments);
} catch (AuthorizationException $e) {
return false;
}
});
}
return (bool) $result;
/**
* Determine if any one of the given abilities should be granted for the current user.
*
* @param iterable|string $abilities
* @param array|mixed $arguments
* @return bool
*/
public function any($abilities, $arguments = [])
{
return collect($abilities)->contains(function ($ability) use ($arguments) {
return $this->check($ability, $arguments);
});
}
/**
@@ -225,7 +288,7 @@ class Gate implements GateContract
}
/**
* Get the raw result for the given ability for the current user.
* Get the raw result from the authorization callback.
*
* @param string $ability
* @param array|mixed $arguments
@@ -237,12 +300,22 @@ class Gate implements GateContract
return false;
}
$arguments = is_array($arguments) ? $arguments : [$arguments];
$arguments = Arr::wrap($arguments);
if (is_null($result = $this->callBeforeCallbacks($user, $ability, $arguments))) {
// First we will call the "before" callbacks for the Gate. If any of these give
// back a non-null response, we will immediately return that result in order
// to let the developers override all checks for some authorization cases.
$result = $this->callBeforeCallbacks(
$user, $ability, $arguments
);
if (is_null($result)) {
$result = $this->callAuthCallback($user, $ability, $arguments);
}
// After calling the authorization callback, we will call the "after" callbacks
// that are registered with the Gate, which allows a developer to do logging
// if that is required for this application. Then we'll return the result.
$this->callAfterCallbacks(
$user, $ability, $arguments, $result
);
@@ -260,13 +333,9 @@ class Gate implements GateContract
*/
protected function callAuthCallback($user, $ability, array $arguments)
{
$callback = $this->resolveAuthCallback(
$user, $ability, $arguments
);
$callback = $this->resolveAuthCallback($user, $ability, $arguments);
return call_user_func_array(
$callback, array_merge([$user], $arguments)
);
return $callback($user, ...$arguments);
}
/**
@@ -282,7 +351,7 @@ class Gate implements GateContract
$arguments = array_merge([$user, $ability], [$arguments]);
foreach ($this->beforeCallbacks as $before) {
if (! is_null($result = call_user_func_array($before, $arguments))) {
if (! is_null($result = $before(...$arguments))) {
return $result;
}
}
@@ -302,7 +371,7 @@ class Gate implements GateContract
$arguments = array_merge([$user, $ability, $result], [$arguments]);
foreach ($this->afterCallbacks as $after) {
call_user_func_array($after, $arguments);
$after(...$arguments);
}
}
@@ -316,78 +385,18 @@ class Gate implements GateContract
*/
protected function resolveAuthCallback($user, $ability, array $arguments)
{
if ($this->firstArgumentCorrespondsToPolicy($arguments)) {
return $this->resolvePolicyCallback($user, $ability, $arguments);
} elseif (isset($this->abilities[$ability])) {
if (isset($arguments[0]) &&
! is_null($policy = $this->getPolicyFor($arguments[0])) &&
$callback = $this->resolvePolicyCallback($user, $ability, $arguments, $policy)) {
return $callback;
}
if (isset($this->abilities[$ability])) {
return $this->abilities[$ability];
} else {
return function () {
return false;
};
}
}
/**
* Determine if the first argument in the array corresponds to a policy.
*
* @param array $arguments
* @return bool
*/
protected function firstArgumentCorrespondsToPolicy(array $arguments)
{
if (! isset($arguments[0])) {
return function () {
return false;
}
if (is_object($arguments[0])) {
return isset($this->policies[get_class($arguments[0])]);
}
return is_string($arguments[0]) && isset($this->policies[$arguments[0]]);
}
/**
* Resolve the callback for a policy check.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param string $ability
* @param array $arguments
* @return callable
*/
protected function resolvePolicyCallback($user, $ability, array $arguments)
{
return function () use ($user, $ability, $arguments) {
$instance = $this->getPolicyFor($arguments[0]);
if (method_exists($instance, 'before')) {
// We will prepend the user and ability onto the arguments so that the before
// callback can determine which ability is being called. Then we will call
// into the policy before methods with the arguments and get the result.
$beforeArguments = array_merge([$user, $ability], $arguments);
$result = call_user_func_array(
[$instance, 'before'], $beforeArguments
);
// If we received a non-null result from the before method, we will return it
// as the result of a check. This allows developers to override the checks
// in the policy and return a result for all rules defined in the class.
if (! is_null($result)) {
return $result;
}
}
if (strpos($ability, '-') !== false) {
$ability = Str::camel($ability);
}
if (! is_callable([$instance, $ability])) {
return false;
}
return call_user_func_array(
[$instance, $ability], array_merge([$user], $arguments)
);
};
}
@@ -396,8 +405,6 @@ class Gate implements GateContract
*
* @param object|string $class
* @return mixed
*
* @throws \InvalidArgumentException
*/
public function getPolicyFor($class)
{
@@ -405,11 +412,19 @@ class Gate implements GateContract
$class = get_class($class);
}
if (! isset($this->policies[$class])) {
throw new InvalidArgumentException("Policy not defined for [{$class}].");
if (! is_string($class)) {
return;
}
return $this->resolvePolicy($this->policies[$class]);
if (isset($this->policies[$class])) {
return $this->resolvePolicy($this->policies[$class]);
}
foreach ($this->policies as $expected => $policy) {
if (is_subclass_of($class, $expected)) {
return $this->resolvePolicy($policy);
}
}
}
/**
@@ -423,6 +438,78 @@ class Gate implements GateContract
return $this->container->make($class);
}
/**
* Resolve the callback for a policy check.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param string $ability
* @param array $arguments
* @param mixed $policy
* @return bool|callable
*/
protected function resolvePolicyCallback($user, $ability, array $arguments, $policy)
{
if (! is_callable([$policy, $this->formatAbilityToMethod($ability)])) {
return false;
}
return function () use ($user, $ability, $arguments, $policy) {
// This callback will be responsible for calling the policy's before method and
// running this policy method if necessary. This is used to when objects are
// mapped to policy objects in the user's configurations or on this class.
$result = $this->callPolicyBefore(
$policy, $user, $ability, $arguments
);
// When we receive a non-null result from this before method, we will return it
// as the "final" results. This will allow developers to override the checks
// in this policy to return the result for all rules defined in the class.
if (! is_null($result)) {
return $result;
}
$ability = $this->formatAbilityToMethod($ability);
// If this first argument is a string, that means they are passing a class name
// to the policy. We will remove the first argument from this argument array
// because this policy already knows what type of models it can authorize.
if (isset($arguments[0]) && is_string($arguments[0])) {
array_shift($arguments);
}
return is_callable([$policy, $ability])
? $policy->{$ability}($user, ...$arguments)
: false;
};
}
/**
* Call the "before" method on the given policy, if applicable.
*
* @param mixed $policy
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param string $ability
* @param array $arguments
* @return mixed
*/
protected function callPolicyBefore($policy, $user, $ability, $arguments)
{
if (method_exists($policy, 'before')) {
return $policy->before($user, $ability, ...$arguments);
}
}
/**
* Format the policy ability into a method name.
*
* @param string $ability
* @return string
*/
protected function formatAbilityToMethod($ability)
{
return strpos($ability, '-') !== false ? Str::camel($ability) : $ability;
}
/**
* Get a gate instance for the given user.
*
@@ -450,4 +537,24 @@ class Gate implements GateContract
{
return call_user_func($this->userResolver);
}
/**
* Get all of the defined abilities.
*
* @return array
*/
public function abilities()
{
return $this->abilities;
}
/**
* Get all of the defined policies.
*
* @return array
*/
public function policies()
{
return $this->policies;
}
}

View File

@@ -15,6 +15,7 @@ class Response
* Create a new response.
*
* @param string|null $message
* @return void
*/
public function __construct($message = null)
{

View File

@@ -65,9 +65,7 @@ class AuthManager implements FactoryContract
{
$name = $name ?: $this->getDefaultDriver();
return isset($this->guards[$name])
? $this->guards[$name]
: $this->guards[$name] = $this->resolve($name);
return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
}
/**
@@ -88,15 +86,15 @@ class AuthManager implements FactoryContract
if (isset($this->customCreators[$config['driver']])) {
return $this->callCustomCreator($name, $config);
} else {
$driverMethod = 'create'.ucfirst($config['driver']).'Driver';
if (method_exists($this, $driverMethod)) {
return $this->{$driverMethod}($name, $config);
} else {
throw new InvalidArgumentException("Auth guard driver [{$name}] is not defined.");
}
}
$driverMethod = 'create'.ucfirst($config['driver']).'Driver';
if (method_exists($this, $driverMethod)) {
return $this->{$driverMethod}($name, $config);
}
throw new InvalidArgumentException("Auth guard driver [{$name}] is not defined.");
}
/**
@@ -120,7 +118,7 @@ class AuthManager implements FactoryContract
*/
public function createSessionDriver($name, $config)
{
$provider = $this->createUserProvider($config['provider']);
$provider = $this->createUserProvider($config['provider'] ?? null);
$guard = new SessionGuard($name, $provider, $this->app['session.store']);
@@ -155,7 +153,7 @@ class AuthManager implements FactoryContract
// that takes an API token field from the request and matches it to the
// user in the database or another persistence layer where users are.
$guard = new TokenGuard(
$this->createUserProvider($config['provider']),
$this->createUserProvider($config['provider'] ?? null),
$this->app['request']
);
@@ -193,6 +191,8 @@ class AuthManager implements FactoryContract
*/
public function shouldUse($name)
{
$name = $name ?: $this->getDefaultDriver();
$this->setDefaultDriver($name);
$this->userResolver = function ($name = null) {
@@ -221,7 +221,7 @@ class AuthManager implements FactoryContract
public function viaRequest($driver, callable $callback)
{
return $this->extend($driver, function () use ($callback) {
$guard = new RequestGuard($callback, $this->app['request']);
$guard = new RequestGuard($callback, $this->app['request'], $this->createUserProvider());
$this->app->refresh('request', $guard, 'setRequest');
@@ -289,6 +289,6 @@ class AuthManager implements FactoryContract
*/
public function __call($method, $parameters)
{
return call_user_func_array([$this->guard(), $method], $parameters);
return $this->guard()->{$method}(...$parameters);
}
}

View File

@@ -4,6 +4,13 @@ namespace Illuminate\Auth;
trait Authenticatable
{
/**
* The column name of the "remember me" token.
*
* @var string
*/
protected $rememberTokenName = 'remember_token';
/**
* Get the name of the unique identifier for the user.
*
@@ -21,7 +28,7 @@ trait Authenticatable
*/
public function getAuthIdentifier()
{
return $this->getKey();
return $this->{$this->getAuthIdentifierName()};
}
/**
@@ -37,11 +44,13 @@ trait Authenticatable
/**
* Get the token value for the "remember me" session.
*
* @return string
* @return string|null
*/
public function getRememberToken()
{
return $this->{$this->getRememberTokenName()};
if (! empty($this->getRememberTokenName())) {
return (string) $this->{$this->getRememberTokenName()};
}
}
/**
@@ -52,7 +61,9 @@ trait Authenticatable
*/
public function setRememberToken($value)
{
$this->{$this->getRememberTokenName()} = $value;
if (! empty($this->getRememberTokenName())) {
$this->{$this->getRememberTokenName()} = $value;
}
}
/**
@@ -62,6 +73,6 @@ trait Authenticatable
*/
public function getRememberTokenName()
{
return 'remember_token';
return $this->rememberTokenName;
}
}

View File

@@ -7,31 +7,33 @@ use Exception;
class AuthenticationException extends Exception
{
/**
* The guard instance.
* All of the guards that were checked.
*
* @var \Illuminate\Contracts\Auth\Guard
* @var array
*/
protected $guard;
protected $guards;
/**
* Create a new authentication exception.
*
* @param \Illuminate\Contracts\Auth\Guard|null $guard
* @param string $message
* @param array $guards
* @return void
*/
public function __construct($guard = null)
public function __construct($message = 'Unauthenticated.', array $guards = [])
{
$this->guard = $guard;
parent::__construct($message);
parent::__construct('Unauthenticated.');
$this->guards = $guards;
}
/**
* Get the guard instance.
* Get the guards that were checked.
*
* @return \Illuminate\Contracts\Auth\Guard|null
* @return array
*/
public function guard()
public function guards()
{
return $this->guard;
return $this->guards;
}
}

View File

@@ -3,18 +3,20 @@
namespace Illuminate\Auth\Console;
use Illuminate\Console\Command;
use Illuminate\Console\AppNamespaceDetectorTrait;
use Illuminate\Console\DetectsApplicationNamespace;
class MakeAuthCommand extends Command
class AuthMakeCommand extends Command
{
use AppNamespaceDetectorTrait;
use DetectsApplicationNamespace;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'make:auth {--views : Only scaffold the authentication views}';
protected $signature = 'make:auth
{--views : Only scaffold the authentication views}
{--force : Overwrite existing views by default}';
/**
* The console command description.
@@ -33,10 +35,8 @@ class MakeAuthCommand extends Command
'auth/register.stub' => 'auth/register.blade.php',
'auth/passwords/email.stub' => 'auth/passwords/email.blade.php',
'auth/passwords/reset.stub' => 'auth/passwords/reset.blade.php',
'auth/emails/password.stub' => 'auth/emails/password.blade.php',
'layouts/app.stub' => 'layouts/app.blade.php',
'home.stub' => 'home.blade.php',
'welcome.stub' => 'welcome.blade.php',
];
/**
@@ -44,30 +44,26 @@ class MakeAuthCommand extends Command
*
* @return void
*/
public function fire()
public function handle()
{
$this->createDirectories();
$this->exportViews();
if (! $this->option('views')) {
$this->info('Installed HomeController.');
file_put_contents(
app_path('Http/Controllers/HomeController.php'),
$this->compileControllerStub()
);
$this->info('Updated Routes File.');
file_put_contents(
app_path('Http/routes.php'),
base_path('routes/web.php'),
file_get_contents(__DIR__.'/stubs/make/routes.stub'),
FILE_APPEND
);
}
$this->comment('Authentication scaffolding generated successfully!');
$this->info('Authentication scaffolding generated successfully.');
}
/**
@@ -77,16 +73,12 @@ class MakeAuthCommand extends Command
*/
protected function createDirectories()
{
if (! is_dir(base_path('resources/views/layouts'))) {
mkdir(base_path('resources/views/layouts'), 0755, true);
if (! is_dir($directory = resource_path('views/layouts'))) {
mkdir($directory, 0755, true);
}
if (! is_dir(base_path('resources/views/auth/passwords'))) {
mkdir(base_path('resources/views/auth/passwords'), 0755, true);
}
if (! is_dir(base_path('resources/views/auth/emails'))) {
mkdir(base_path('resources/views/auth/emails'), 0755, true);
if (! is_dir($directory = resource_path('views/auth/passwords'))) {
mkdir($directory, 0755, true);
}
}
@@ -98,11 +90,16 @@ class MakeAuthCommand extends Command
protected function exportViews()
{
foreach ($this->views as $key => $value) {
$path = base_path('resources/views/'.$value);
if (file_exists($view = resource_path('views/'.$value)) && ! $this->option('force')) {
if (! $this->confirm("The [{$value}] view already exists. Do you want to replace it?")) {
continue;
}
}
$this->line('<info>Created View:</info> '.$path);
copy(__DIR__.'/stubs/make/views/'.$key, $path);
copy(
__DIR__.'/stubs/make/views/'.$key,
$view
);
}
}

View File

@@ -25,7 +25,7 @@ class ClearResetsCommand extends Command
*
* @return void
*/
public function fire()
public function handle()
{
$this->laravel['auth.password']->broker($this->argument('name'))->getRepository()->deleteExpired();

View File

@@ -2,7 +2,6 @@
namespace {{namespace}}Http\Controllers;
use {{namespace}}Http\Requests;
use Illuminate\Http\Request;
class HomeController extends Controller

View File

@@ -1,4 +1,4 @@
Route::auth();
Auth::routes();
Route::get('/home', 'HomeController@index');
Route::get('/home', 'HomeController@index')->name('home');

View File

@@ -1 +0,0 @@
Click here to reset your password: <a href="{{ $link = url('password/reset', $token).'?email='.urlencode($user->getEmailForPasswordReset()) }}"> {{ $link }} </a>

View File

@@ -6,15 +6,16 @@
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Login</div>
<div class="panel-body">
<form class="form-horizontal" role="form" method="POST" action="{{ url('/login') }}">
<form class="form-horizontal" method="POST" action="{{ route('login') }}">
{{ csrf_field() }}
<div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}">
<label for="email" class="col-md-4 control-label">E-Mail Address</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control" name="email" value="{{ old('email') }}">
<input id="email" type="email" class="form-control" name="email" value="{{ old('email') }}" required autofocus>
@if ($errors->has('email'))
<span class="help-block">
@@ -28,7 +29,7 @@
<label for="password" class="col-md-4 control-label">Password</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control" name="password">
<input id="password" type="password" class="form-control" name="password" required>
@if ($errors->has('password'))
<span class="help-block">
@@ -42,19 +43,21 @@
<div class="col-md-6 col-md-offset-4">
<div class="checkbox">
<label>
<input type="checkbox" name="remember"> Remember Me
<input type="checkbox" name="remember" {{ old('remember') ? 'checked' : '' }}> Remember Me
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-6 col-md-offset-4">
<div class="col-md-8 col-md-offset-4">
<button type="submit" class="btn btn-primary">
<i class="fa fa-btn fa-sign-in"></i> Login
Login
</button>
<a class="btn btn-link" href="{{ url('/password/reset') }}">Forgot Your Password?</a>
<a class="btn btn-link" href="{{ route('password.request') }}">
Forgot Your Password?
</a>
</div>
</div>
</form>

View File

@@ -1,12 +1,12 @@
@extends('layouts.app')
<!-- Main Content -->
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Reset Password</div>
<div class="panel-body">
@if (session('status'))
<div class="alert alert-success">
@@ -14,14 +14,14 @@
</div>
@endif
<form class="form-horizontal" role="form" method="POST" action="{{ url('/password/email') }}">
<form class="form-horizontal" method="POST" action="{{ route('password.email') }}">
{{ csrf_field() }}
<div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}">
<label for="email" class="col-md-4 control-label">E-Mail Address</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control" name="email" value="{{ old('email') }}">
<input id="email" type="email" class="form-control" name="email" value="{{ old('email') }}" required>
@if ($errors->has('email'))
<span class="help-block">
@@ -34,7 +34,7 @@
<div class="form-group">
<div class="col-md-6 col-md-offset-4">
<button type="submit" class="btn btn-primary">
<i class="fa fa-btn fa-envelope"></i> Send Password Reset Link
Send Password Reset Link
</button>
</div>
</div>

View File

@@ -8,7 +8,7 @@
<div class="panel-heading">Reset Password</div>
<div class="panel-body">
<form class="form-horizontal" role="form" method="POST" action="{{ url('/password/reset') }}">
<form class="form-horizontal" method="POST" action="{{ route('password.request') }}">
{{ csrf_field() }}
<input type="hidden" name="token" value="{{ $token }}">
@@ -17,7 +17,7 @@
<label for="email" class="col-md-4 control-label">E-Mail Address</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control" name="email" value="{{ $email or old('email') }}">
<input id="email" type="email" class="form-control" name="email" value="{{ $email or old('email') }}" required autofocus>
@if ($errors->has('email'))
<span class="help-block">
@@ -31,7 +31,7 @@
<label for="password" class="col-md-4 control-label">Password</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control" name="password">
<input id="password" type="password" class="form-control" name="password" required>
@if ($errors->has('password'))
<span class="help-block">
@@ -44,7 +44,7 @@
<div class="form-group{{ $errors->has('password_confirmation') ? ' has-error' : '' }}">
<label for="password-confirm" class="col-md-4 control-label">Confirm Password</label>
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required>
@if ($errors->has('password_confirmation'))
<span class="help-block">
@@ -57,7 +57,7 @@
<div class="form-group">
<div class="col-md-6 col-md-offset-4">
<button type="submit" class="btn btn-primary">
<i class="fa fa-btn fa-refresh"></i> Reset Password
Reset Password
</button>
</div>
</div>

View File

@@ -6,15 +6,16 @@
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Register</div>
<div class="panel-body">
<form class="form-horizontal" role="form" method="POST" action="{{ url('/register') }}">
<form class="form-horizontal" method="POST" action="{{ route('register') }}">
{{ csrf_field() }}
<div class="form-group{{ $errors->has('name') ? ' has-error' : '' }}">
<label for="name" class="col-md-4 control-label">Name</label>
<div class="col-md-6">
<input id="name" type="text" class="form-control" name="name" value="{{ old('name') }}">
<input id="name" type="text" class="form-control" name="name" value="{{ old('name') }}" required autofocus>
@if ($errors->has('name'))
<span class="help-block">
@@ -28,7 +29,7 @@
<label for="email" class="col-md-4 control-label">E-Mail Address</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control" name="email" value="{{ old('email') }}">
<input id="email" type="email" class="form-control" name="email" value="{{ old('email') }}" required>
@if ($errors->has('email'))
<span class="help-block">
@@ -42,7 +43,7 @@
<label for="password" class="col-md-4 control-label">Password</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control" name="password">
<input id="password" type="password" class="form-control" name="password" required>
@if ($errors->has('password'))
<span class="help-block">
@@ -52,24 +53,18 @@
</div>
</div>
<div class="form-group{{ $errors->has('password_confirmation') ? ' has-error' : '' }}">
<div class="form-group">
<label for="password-confirm" class="col-md-4 control-label">Confirm Password</label>
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation">
@if ($errors->has('password_confirmation'))
<span class="help-block">
<strong>{{ $errors->first('password_confirmation') }}</strong>
</span>
@endif
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required>
</div>
</div>
<div class="form-group">
<div class="col-md-6 col-md-offset-4">
<button type="submit" class="btn btn-primary">
<i class="fa fa-btn fa-user"></i> Register
Register
</button>
</div>
</div>

View File

@@ -3,11 +3,17 @@
@section('content')
<div class="container">
<div class="row">
<div class="col-md-10 col-md-offset-1">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Dashboard</div>
<div class="panel-body">
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
You are logged in!
</div>
</div>

View File

@@ -1,82 +1,80 @@
<!DOCTYPE html>
<html lang="en">
<html lang="{{ app()->getLocale() }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laravel</title>
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<!-- Fonts -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css" integrity="sha384-XdYbMnZ/QjLh6iI4ogqCTaIjrFk87ip+ekIjefZch0Y+PvJ8CDYtEs1ipDmPorQ+" crossorigin="anonymous">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lato:100,300,400,700">
<title>{{ config('app.name', 'Laravel') }}</title>
<!-- Styles -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
{{-- <link href="{{ elixir('css/app.css') }}" rel="stylesheet"> --}}
<style>
body {
font-family: 'Lato';
}
.fa-btn {
margin-right: 6px;
}
</style>
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body id="app-layout">
<nav class="navbar navbar-default navbar-static-top">
<div class="container">
<div class="navbar-header">
<body>
<div id="app">
<nav class="navbar navbar-default navbar-static-top">
<div class="container">
<div class="navbar-header">
<!-- Collapsed Hamburger -->
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#app-navbar-collapse">
<span class="sr-only">Toggle Navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Collapsed Hamburger -->
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#app-navbar-collapse" aria-expanded="false">
<span class="sr-only">Toggle Navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Branding Image -->
<a class="navbar-brand" href="{{ url('/') }}">
Laravel
</a>
<!-- Branding Image -->
<a class="navbar-brand" href="{{ url('/') }}">
{{ config('app.name', 'Laravel') }}
</a>
</div>
<div class="collapse navbar-collapse" id="app-navbar-collapse">
<!-- Left Side Of Navbar -->
<ul class="nav navbar-nav">
&nbsp;
</ul>
<!-- Right Side Of Navbar -->
<ul class="nav navbar-nav navbar-right">
<!-- Authentication Links -->
@guest
<li><a href="{{ route('login') }}">Login</a></li>
<li><a href="{{ route('register') }}">Register</a></li>
@else
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false" aria-haspopup="true" v-pre>
{{ Auth::user()->name }} <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li>
<a href="{{ route('logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
Logout
</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
{{ csrf_field() }}
</form>
</li>
</ul>
</li>
@endguest
</ul>
</div>
</div>
</nav>
<div class="collapse navbar-collapse" id="app-navbar-collapse">
<!-- Left Side Of Navbar -->
<ul class="nav navbar-nav">
<li><a href="{{ url('/home') }}">Home</a></li>
</ul>
@yield('content')
</div>
<!-- Right Side Of Navbar -->
<ul class="nav navbar-nav navbar-right">
<!-- Authentication Links -->
@if (Auth::guest())
<li><a href="{{ url('/login') }}">Login</a></li>
<li><a href="{{ url('/register') }}">Register</a></li>
@else
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
{{ Auth::user()->name }} <span class="caret"></span>
</a>
<ul class="dropdown-menu" role="menu">
<li><a href="{{ url('/logout') }}"><i class="fa fa-btn fa-sign-out"></i>Logout</a></li>
</ul>
</li>
@endif
</ul>
</div>
</div>
</nav>
@yield('content')
<!-- JavaScripts -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js" integrity="sha384-I6F5OKECLVtK/BL+8iSLDEHowSAfUo76ZL9+kGAgTRdiByINKJaqTPH/QVNS1VDb" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
{{-- <script src="{{ elixir('js/app.js') }}"></script> --}}
<!-- Scripts -->
<script src="{{ asset('js/app.js') }}"></script>
</body>
</html>

View File

@@ -1,17 +0,0 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-10 col-md-offset-1">
<div class="panel panel-default">
<div class="panel-heading">Welcome</div>
<div class="panel-body">
Your Application's Landing Page.
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@@ -16,28 +16,45 @@ trait CreatesUserProviders
/**
* Create the user provider implementation for the driver.
*
* @param string $provider
* @return \Illuminate\Contracts\Auth\UserProvider
* @param string|null $provider
* @return \Illuminate\Contracts\Auth\UserProvider|null
*
* @throws \InvalidArgumentException
*/
public function createUserProvider($provider)
public function createUserProvider($provider = null)
{
$config = $this->app['config']['auth.providers.'.$provider];
if (is_null($config = $this->getProviderConfiguration($provider))) {
return;
}
if (isset($this->customProviderCreators[$config['driver']])) {
if (isset($this->customProviderCreators[$driver = ($config['driver'] ?? null)])) {
return call_user_func(
$this->customProviderCreators[$config['driver']], $this->app, $config
$this->customProviderCreators[$driver], $this->app, $config
);
}
switch ($config['driver']) {
switch ($driver) {
case 'database':
return $this->createDatabaseProvider($config);
case 'eloquent':
return $this->createEloquentProvider($config);
default:
throw new InvalidArgumentException("Authentication user provider [{$config['driver']}] is not defined.");
throw new InvalidArgumentException(
"Authentication user provider [{$driver}] is not defined."
);
}
}
/**
* Get the user provider configuration.
*
* @param string|null $provider
* @return array|null
*/
protected function getProviderConfiguration($provider)
{
if ($provider = $provider ?: $this->getDefaultUserProvider()) {
return $this->app['config']['auth.providers.'.$provider];
}
}
@@ -64,4 +81,14 @@ trait CreatesUserProviders
{
return new EloquentUserProvider($this->app['hash'], $config['model']);
}
/**
* Get the default user provider name.
*
* @return string
*/
public function getDefaultUserProvider()
{
return $this->app['config']['auth.defaults.provider'];
}
}

View File

@@ -68,12 +68,12 @@ class DatabaseUserProvider implements UserProvider
*/
public function retrieveByToken($identifier, $token)
{
$user = $this->conn->table($this->table)
->where('id', $identifier)
->where('remember_token', $token)
->first();
$user = $this->getGenericUser(
$this->conn->table($this->table)->find($identifier)
);
return $this->getGenericUser($user);
return $user && $user->getRememberToken() && hash_equals($user->getRememberToken(), $token)
? $user : null;
}
/**
@@ -86,8 +86,8 @@ class DatabaseUserProvider implements UserProvider
public function updateRememberToken(UserContract $user, $token)
{
$this->conn->table($this->table)
->where('id', $user->getAuthIdentifier())
->update(['remember_token' => $token]);
->where($user->getAuthIdentifierName(), $user->getAuthIdentifier())
->update([$user->getRememberTokenName() => $token]);
}
/**
@@ -98,6 +98,12 @@ class DatabaseUserProvider implements UserProvider
*/
public function retrieveByCredentials(array $credentials)
{
if (empty($credentials) ||
(count($credentials) === 1 &&
array_key_exists('password', $credentials))) {
return;
}
// First we will add each credential element to the query as a where clause.
// Then we can execute the query and, if we found a user, return it in a
// generic "user" object that will be utilized by the Guard instances.
@@ -125,7 +131,7 @@ class DatabaseUserProvider implements UserProvider
*/
protected function getGenericUser($user)
{
if ($user !== null) {
if (! is_null($user)) {
return new GenericUser((array) $user);
}
}
@@ -139,8 +145,8 @@ class DatabaseUserProvider implements UserProvider
*/
public function validateCredentials(UserContract $user, array $credentials)
{
$plain = $credentials['password'];
return $this->hasher->check($plain, $user->getAuthPassword());
return $this->hasher->check(
$credentials['password'], $user->getAuthPassword()
);
}
}

View File

@@ -44,7 +44,11 @@ class EloquentUserProvider implements UserProvider
*/
public function retrieveById($identifier)
{
return $this->createModel()->newQuery()->find($identifier);
$model = $this->createModel();
return $model->newQuery()
->where($model->getAuthIdentifierName(), $identifier)
->first();
}
/**
@@ -58,10 +62,15 @@ class EloquentUserProvider implements UserProvider
{
$model = $this->createModel();
return $model->newQuery()
->where($model->getAuthIdentifierName(), $identifier)
->where($model->getRememberTokenName(), $token)
->first();
$model = $model->where($model->getAuthIdentifierName(), $identifier)->first();
if (! $model) {
return null;
}
$rememberToken = $model->getRememberToken();
return $rememberToken && hash_equals($rememberToken, $token) ? $model : null;
}
/**
@@ -75,7 +84,13 @@ class EloquentUserProvider implements UserProvider
{
$user->setRememberToken($token);
$timestamps = $user->timestamps;
$user->timestamps = false;
$user->save();
$user->timestamps = $timestamps;
}
/**
@@ -86,7 +101,9 @@ class EloquentUserProvider implements UserProvider
*/
public function retrieveByCredentials(array $credentials)
{
if (empty($credentials)) {
if (empty($credentials) ||
(count($credentials) === 1 &&
array_key_exists('password', $credentials))) {
return;
}

View File

@@ -18,23 +18,15 @@ class Attempting
*/
public $remember;
/**
* Indicates if the user should be authenticated if successful.
*
* @var bool
*/
public $login;
/**
* Create a new event instance.
*
* @param array $credentials
* @param bool $remember
* @param bool $login
* @return void
*/
public function __construct($credentials, $remember, $login)
public function __construct($credentials, $remember)
{
$this->login = $login;
$this->remember = $remember;
$this->credentials = $credentials;
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Illuminate\Auth\Events;
use Illuminate\Queue\SerializesModels;
class Authenticated
{
use SerializesModels;
/**
* The authenticated user.
*
* @var \Illuminate\Contracts\Auth\Authenticatable
*/
public $user;
/**
* Create a new event instance.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
public function __construct($user)
{
$this->user = $user;
}
}

View File

@@ -23,6 +23,7 @@ class Failed
*
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param array $credentials
* @return void
*/
public function __construct($user, $credentials)
{

View File

@@ -0,0 +1,28 @@
<?php
namespace Illuminate\Auth\Events;
use Illuminate\Queue\SerializesModels;
class PasswordReset
{
use SerializesModels;
/**
* The user.
*
* @var \Illuminate\Contracts\Auth\Authenticatable
*/
public $user;
/**
* Create a new event instance.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
public function __construct($user)
{
$this->user = $user;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Illuminate\Auth\Events;
use Illuminate\Queue\SerializesModels;
class Registered
{
use SerializesModels;
/**
* The authenticated user.
*
* @var \Illuminate\Contracts\Auth\Authenticatable
*/
public $user;
/**
* Create a new event instance.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
public function __construct($user)
{
$this->user = $user;
}
}

View File

@@ -2,6 +2,7 @@
namespace Illuminate\Auth;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
/**
@@ -36,7 +37,7 @@ trait GuardHelpers
return $user;
}
throw new AuthenticationException($this);
throw new AuthenticationException;
}
/**
@@ -83,4 +84,25 @@ trait GuardHelpers
return $this;
}
/**
* Get the user provider used by the guard.
*
* @return \Illuminate\Contracts\Auth\UserProvider
*/
public function getProvider()
{
return $this->provider;
}
/**
* Set the user provider used by the guard.
*
* @param \Illuminate\Contracts\Auth\UserProvider $provider
* @return void
*/
public function setProvider(UserProvider $provider)
{
$this->provider = $provider;
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace Illuminate\Auth\Middleware;
use Closure;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Contracts\Auth\Factory as Auth;
class Authenticate
{
/**
* The authentication factory instance.
*
* @var \Illuminate\Contracts\Auth\Factory
*/
protected $auth;
/**
* Create a new middleware instance.
*
* @param \Illuminate\Contracts\Auth\Factory $auth
* @return void
*/
public function __construct(Auth $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string[] ...$guards
* @return mixed
*
* @throws \Illuminate\Auth\AuthenticationException
*/
public function handle($request, Closure $next, ...$guards)
{
$this->authenticate($guards);
return $next($request);
}
/**
* Determine if the user is logged in to any of the given guards.
*
* @param array $guards
* @return void
*
* @throws \Illuminate\Auth\AuthenticationException
*/
protected function authenticate(array $guards)
{
if (empty($guards)) {
return $this->auth->authenticate();
}
foreach ($guards as $guard) {
if ($this->auth->guard($guard)->check()) {
return $this->auth->shouldUse($guard);
}
}
throw new AuthenticationException('Unauthenticated.', $guards);
}
}

View File

@@ -0,0 +1,100 @@
<?php
namespace Illuminate\Auth\Middleware;
use Closure;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Contracts\Auth\Access\Gate;
use Illuminate\Contracts\Auth\Factory as Auth;
class Authorize
{
/**
* The authentication factory instance.
*
* @var \Illuminate\Contracts\Auth\Factory
*/
protected $auth;
/**
* The gate instance.
*
* @var \Illuminate\Contracts\Auth\Access\Gate
*/
protected $gate;
/**
* Create a new middleware instance.
*
* @param \Illuminate\Contracts\Auth\Factory $auth
* @param \Illuminate\Contracts\Auth\Access\Gate $gate
* @return void
*/
public function __construct(Auth $auth, Gate $gate)
{
$this->auth = $auth;
$this->gate = $gate;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string $ability
* @param array|null $models
* @return mixed
*
* @throws \Illuminate\Auth\AuthenticationException
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function handle($request, Closure $next, $ability, ...$models)
{
$this->auth->authenticate();
$this->gate->authorize($ability, $this->getGateArguments($request, $models));
return $next($request);
}
/**
* Get the arguments parameter for the gate.
*
* @param \Illuminate\Http\Request $request
* @param array|null $models
* @return array|string|\Illuminate\Database\Eloquent\Model
*/
protected function getGateArguments($request, $models)
{
if (is_null($models)) {
return [];
}
return collect($models)->map(function ($model) use ($request) {
return $model instanceof Model ? $model : $this->getModel($request, $model);
})->all();
}
/**
* Get the model to authorize.
*
* @param \Illuminate\Http\Request $request
* @param string $model
* @return \Illuminate\Database\Eloquent\Model|string
*/
protected function getModel($request, $model)
{
return $this->isClassName($model) ? $model : $request->route($model);
}
/**
* Checks if the given string looks like a fully qualified class name.
*
* @param string $value
* @return bool
*/
protected function isClassName($value)
{
return strpos($value, '\\') !== false;
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Illuminate\Auth\Notifications;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;
class ResetPassword extends Notification
{
/**
* The password reset token.
*
* @var string
*/
public $token;
/**
* Create a notification instance.
*
* @param string $token
* @return void
*/
public function __construct($token)
{
$this->token = $token;
}
/**
* Get the notification's channels.
*
* @param mixed $notifiable
* @return array|string
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Build the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)
->line('You are receiving this email because we received a password reset request for your account.')
->action('Reset Password', url(config('app.url').route('password.reset', $this->token, false)))
->line('If you did not request a password reset, no further action is required.');
}
}

View File

@@ -2,6 +2,8 @@
namespace Illuminate\Auth\Passwords;
use Illuminate\Auth\Notifications\ResetPassword as ResetPasswordNotification;
trait CanResetPassword
{
/**
@@ -13,4 +15,15 @@ trait CanResetPassword
{
return $this->email;
}
/**
* Send the password reset notification.
*
* @param string $token
* @return void
*/
public function sendPasswordResetNotification($token)
{
$this->notify(new ResetPasswordNotification($token));
}
}

View File

@@ -2,9 +2,10 @@
namespace Illuminate\Auth\Passwords;
use Carbon\Carbon;
use Illuminate\Support\Str;
use Illuminate\Support\Carbon;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
class DatabaseTokenRepository implements TokenRepositoryInterface
@@ -16,6 +17,13 @@ class DatabaseTokenRepository implements TokenRepositoryInterface
*/
protected $connection;
/**
* The Hasher implementation.
*
* @var \Illuminate\Contracts\Hashing\Hasher
*/
protected $hasher;
/**
* The token database table.
*
@@ -41,14 +49,17 @@ class DatabaseTokenRepository implements TokenRepositoryInterface
* Create a new token repository instance.
*
* @param \Illuminate\Database\ConnectionInterface $connection
* @param \Illuminate\Contracts\Hashing\Hasher $hasher
* @param string $table
* @param string $hashKey
* @param int $expires
* @return void
*/
public function __construct(ConnectionInterface $connection, $table, $hashKey, $expires = 60)
public function __construct(ConnectionInterface $connection, HasherContract $hasher,
$table, $hashKey, $expires = 60)
{
$this->table = $table;
$this->hasher = $hasher;
$this->hashKey = $hashKey;
$this->expires = $expires * 60;
$this->connection = $connection;
@@ -96,7 +107,7 @@ class DatabaseTokenRepository implements TokenRepositoryInterface
*/
protected function getPayload($email, $token)
{
return ['email' => $email, 'token' => $token, 'created_at' => new Carbon];
return ['email' => $email, 'token' => $this->hasher->make($token), 'created_at' => new Carbon];
}
/**
@@ -108,35 +119,35 @@ class DatabaseTokenRepository implements TokenRepositoryInterface
*/
public function exists(CanResetPasswordContract $user, $token)
{
$email = $user->getEmailForPasswordReset();
$record = (array) $this->getTable()->where(
'email', $user->getEmailForPasswordReset()
)->first();
$token = (array) $this->getTable()->where('email', $email)->where('token', $token)->first();
return $token && ! $this->tokenExpired($token);
return $record &&
! $this->tokenExpired($record['created_at']) &&
$this->hasher->check($token, $record['token']);
}
/**
* Determine if the token has expired.
*
* @param array $token
* @param string $createdAt
* @return bool
*/
protected function tokenExpired($token)
protected function tokenExpired($createdAt)
{
$expiresAt = Carbon::parse($token['created_at'])->addSeconds($this->expires);
return $expiresAt->isPast();
return Carbon::parse($createdAt)->addSeconds($this->expires)->isPast();
}
/**
* Delete a token record by token.
* Delete a token record by user.
*
* @param string $token
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @return void
*/
public function delete($token)
public function delete(CanResetPasswordContract $user)
{
$this->getTable()->where('token', $token)->delete();
$this->deleteExisting($user);
}
/**
@@ -161,6 +172,16 @@ class DatabaseTokenRepository implements TokenRepositoryInterface
return hash_hmac('sha256', Str::random(40), $this->hashKey);
}
/**
* Get the database connection instance.
*
* @return \Illuminate\Database\ConnectionInterface
*/
public function getConnection()
{
return $this->connection;
}
/**
* Begin a new database query against the table.
*
@@ -172,12 +193,12 @@ class DatabaseTokenRepository implements TokenRepositoryInterface
}
/**
* Get the database connection instance.
* Get the hasher instance.
*
* @return \Illuminate\Database\ConnectionInterface
* @return \Illuminate\Contracts\Hashing\Hasher
*/
public function getConnection()
public function getHasher()
{
return $this->connection;
return $this->hasher;
}
}

View File

@@ -6,7 +6,6 @@ use Closure;
use Illuminate\Support\Arr;
use UnexpectedValueException;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Mail\Mailer as MailerContract;
use Illuminate\Contracts\Auth\PasswordBroker as PasswordBrokerContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
@@ -26,20 +25,6 @@ class PasswordBroker implements PasswordBrokerContract
*/
protected $users;
/**
* The mailer instance.
*
* @var \Illuminate\Contracts\Mail\Mailer
*/
protected $mailer;
/**
* The view of the password reset link e-mail.
*
* @var string
*/
protected $emailView;
/**
* The custom password validator callback.
*
@@ -52,29 +37,22 @@ class PasswordBroker implements PasswordBrokerContract
*
* @param \Illuminate\Auth\Passwords\TokenRepositoryInterface $tokens
* @param \Illuminate\Contracts\Auth\UserProvider $users
* @param \Illuminate\Contracts\Mail\Mailer $mailer
* @param string $emailView
* @return void
*/
public function __construct(TokenRepositoryInterface $tokens,
UserProvider $users,
MailerContract $mailer,
$emailView)
UserProvider $users)
{
$this->users = $users;
$this->mailer = $mailer;
$this->tokens = $tokens;
$this->emailView = $emailView;
}
/**
* Send a password reset link to a user.
*
* @param array $credentials
* @param \Closure|null $callback
* @return string
*/
public function sendResetLink(array $credentials, Closure $callback = null)
public function sendResetLink(array $credentials)
{
// First we will check to see if we found a user at the given credentials and
// if we did not we will redirect back to this current URI with a piece of
@@ -88,37 +66,13 @@ class PasswordBroker implements PasswordBrokerContract
// Once we have the reset token, we are ready to send the message out to this
// user with a link to reset their password. We will then redirect back to
// the current URI having nothing set in the session to indicate errors.
$token = $this->tokens->create($user);
$this->emailResetLink($user, $token, $callback);
$user->sendPasswordResetNotification(
$this->tokens->create($user)
);
return static::RESET_LINK_SENT;
}
/**
* Send the password reset link via e-mail.
*
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @param string $token
* @param \Closure|null $callback
* @return int
*/
public function emailResetLink(CanResetPasswordContract $user, $token, Closure $callback = null)
{
// We will use the reminder view that was given to the broker to display the
// password reminder e-mail. We'll pass a "token" variable into the views
// so that it may be displayed for an user to click for password reset.
$view = $this->emailView;
return $this->mailer->send($view, compact('token', 'user'), function ($m) use ($user, $token, $callback) {
$m->to($user->getEmailForPasswordReset());
if (! is_null($callback)) {
call_user_func($callback, $m, $user, $token);
}
});
}
/**
* Reset the password for the given token.
*
@@ -137,14 +91,14 @@ class PasswordBroker implements PasswordBrokerContract
return $user;
}
$pass = $credentials['password'];
$password = $credentials['password'];
// Once we have called this callback, we will remove this token row from the
// table and return the response from this callback so the user gets sent
// to the destination given by the developers from the callback return.
call_user_func($callback, $user, $pass);
// Once the reset has been validated, we'll call the given callback with the
// new password. This gives the user an opportunity to store the password
// in their persistent storage. Then we'll delete the token and return.
$callback($user, $password);
$this->tokens->delete($credentials['token']);
$this->tokens->delete($user);
return static::PASSWORD_RESET;
}
@@ -153,7 +107,7 @@ class PasswordBroker implements PasswordBrokerContract
* Validate a password reset for the given credentials.
*
* @param array $credentials
* @return \Illuminate\Contracts\Auth\CanResetPassword
* @return \Illuminate\Contracts\Auth\CanResetPassword|string
*/
protected function validateReset(array $credentials)
{
@@ -191,14 +145,15 @@ class PasswordBroker implements PasswordBrokerContract
*/
public function validateNewPassword(array $credentials)
{
list($password, $confirm) = [
$credentials['password'],
$credentials['password_confirmation'],
];
if (isset($this->passwordValidator)) {
list($password, $confirm) = [
$credentials['password'],
$credentials['password_confirmation'],
];
return call_user_func(
$this->passwordValidator, $credentials) && $password === $confirm;
$this->passwordValidator, $credentials
) && $password === $confirm;
}
return $this->validatePasswordWithDefaults($credentials);
@@ -224,7 +179,7 @@ class PasswordBroker implements PasswordBrokerContract
* Get the user for the given credentials.
*
* @param array $credentials
* @return \Illuminate\Contracts\Auth\CanResetPassword
* @return \Illuminate\Contracts\Auth\CanResetPassword|null
*
* @throws \UnexpectedValueException
*/
@@ -244,7 +199,7 @@ class PasswordBroker implements PasswordBrokerContract
/**
* Create a new password reset token for the given user.
*
* @param CanResetPasswordContract $user
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @return string
*/
public function createToken(CanResetPasswordContract $user)
@@ -253,20 +208,20 @@ class PasswordBroker implements PasswordBrokerContract
}
/**
* Delete the given password reset token.
* Delete password reset tokens of the given user.
*
* @param string $token
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @return void
*/
public function deleteToken($token)
public function deleteToken(CanResetPasswordContract $user)
{
$this->tokens->delete($token);
$this->tokens->delete($user);
}
/**
* Validate the given password reset token.
*
* @param CanResetPasswordContract $user
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @param string $token
* @return bool
*/

View File

@@ -6,6 +6,9 @@ use Illuminate\Support\Str;
use InvalidArgumentException;
use Illuminate\Contracts\Auth\PasswordBrokerFactory as FactoryContract;
/**
* @mixin \Illuminate\Contracts\Auth\PasswordBroker
*/
class PasswordBrokerManager implements FactoryContract
{
/**
@@ -69,9 +72,7 @@ class PasswordBrokerManager implements FactoryContract
// aggregate service of sorts providing a convenient interface for resets.
return new PasswordBroker(
$this->createTokenRepository($config),
$this->app['auth']->createUserProvider($config['provider']),
$this->app['mailer'],
$config['email']
$this->app['auth']->createUserProvider($config['provider'] ?? null)
);
}
@@ -89,10 +90,11 @@ class PasswordBrokerManager implements FactoryContract
$key = base64_decode(substr($key, 7));
}
$connection = isset($config['connection']) ? $config['connection'] : null;
$connection = $config['connection'] ?? null;
return new DatabaseTokenRepository(
$this->app['db']->connection($connection),
$this->app['hash'],
$config['table'],
$key,
$config['expire']
@@ -140,6 +142,6 @@ class PasswordBrokerManager implements FactoryContract
*/
public function __call($method, $parameters)
{
return call_user_func_array([$this->broker(), $method], $parameters);
return $this->broker()->{$method}(...$parameters);
}
}

View File

@@ -26,10 +26,10 @@ interface TokenRepositoryInterface
/**
* Delete a token record.
*
* @param string $token
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @return void
*/
public function delete($token);
public function delete(CanResetPasswordContract $user);
/**
* Delete expired tokens.

View File

@@ -0,0 +1,88 @@
<?php
namespace Illuminate\Auth;
use Illuminate\Support\Str;
class Recaller
{
/**
* The "recaller" / "remember me" cookie string.
*
* @var string
*/
protected $recaller;
/**
* Create a new recaller instance.
*
* @param string $recaller
* @return void
*/
public function __construct($recaller)
{
$this->recaller = $recaller;
}
/**
* Get the user ID from the recaller.
*
* @return string
*/
public function id()
{
return explode('|', $this->recaller, 3)[0];
}
/**
* Get the "remember token" token from the recaller.
*
* @return string
*/
public function token()
{
return explode('|', $this->recaller, 3)[1];
}
/**
* Get the password from the recaller.
*
* @return string
*/
public function hash()
{
return explode('|', $this->recaller, 3)[2];
}
/**
* Determine if the recaller is valid.
*
* @return bool
*/
public function valid()
{
return $this->properString() && $this->hasAllSegments();
}
/**
* Determine if the recaller is an invalid string.
*
* @return bool
*/
protected function properString()
{
return is_string($this->recaller) && Str::contains($this->recaller, '|');
}
/**
* Determine if the recaller has all segments.
*
* @return bool
*/
protected function hasAllSegments()
{
$segments = explode('|', $this->recaller);
return count($segments) == 3 && trim($segments[0]) !== '' && trim($segments[1]) !== '';
}
}

View File

@@ -4,10 +4,12 @@ namespace Illuminate\Auth;
use Illuminate\Http\Request;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Contracts\Auth\UserProvider;
class RequestGuard implements Guard
{
use GuardHelpers;
use GuardHelpers, Macroable;
/**
* The guard callback.
@@ -28,12 +30,14 @@ class RequestGuard implements Guard
*
* @param callable $callback
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Contracts\Auth\UserProvider|null $provider
* @return void
*/
public function __construct(callable $callback, Request $request)
public function __construct(callable $callback, Request $request, UserProvider $provider = null)
{
$this->request = $request;
$this->callback = $callback;
$this->provider = $provider;
}
/**
@@ -50,7 +54,9 @@ class RequestGuard implements Guard
return $this->user;
}
return $this->user = call_user_func($this->callback, $this->request);
return $this->user = call_user_func(
$this->callback, $this->request, $this->getProvider()
);
}
/**
@@ -62,7 +68,7 @@ class RequestGuard implements Guard
public function validate(array $credentials = [])
{
return ! is_null((new static(
$this->callback, $credentials['request']
$this->callback, $credentials['request'], $this->getProvider()
))->user());
}

View File

@@ -4,24 +4,25 @@ namespace Illuminate\Auth;
use RuntimeException;
use Illuminate\Support\Str;
use Illuminate\Http\Response;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Contracts\Session\Session;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Auth\StatefulGuard;
use Symfony\Component\HttpFoundation\Request;
use Illuminate\Contracts\Auth\SupportsBasicAuth;
use Illuminate\Contracts\Cookie\QueueingFactory as CookieJar;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
class SessionGuard implements StatefulGuard, SupportsBasicAuth
{
use GuardHelpers;
use GuardHelpers, Macroable;
/**
* The name of the Guard. Typically "session".
*
* Corresponds to driver name in authentication configuration.
* Corresponds to guard name in authentication configuration.
*
* @var string
*/
@@ -44,7 +45,7 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
/**
* The session used by the guard.
*
* @var \Symfony\Component\HttpFoundation\Session\SessionInterface
* @var \Illuminate\Contracts\Session\Session
*/
protected $session;
@@ -81,20 +82,20 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
*
* @var bool
*/
protected $tokenRetrievalAttempted = false;
protected $recallAttempted = false;
/**
* Create a new authentication guard.
*
* @param string $name
* @param \Illuminate\Contracts\Auth\UserProvider $provider
* @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session
* @param \Illuminate\Contracts\Session\Session $session
* @param \Symfony\Component\HttpFoundation\Request $request
* @return void
*/
public function __construct($name,
UserProvider $provider,
SessionInterface $session,
Session $session,
Request $request = null)
{
$this->name = $name;
@@ -126,28 +127,68 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
// First we will try to load the user using the identifier in the session if
// one exists. Otherwise we will check for a "remember me" cookie in this
// request, and if one exists, attempt to retrieve the user using that.
$user = null;
if (! is_null($id)) {
$user = $this->provider->retrieveById($id);
if ($this->user = $this->provider->retrieveById($id)) {
$this->fireAuthenticatedEvent($this->user);
}
}
// If the user is null, but we decrypt a "recaller" cookie we can attempt to
// pull the user data on that cookie which serves as a remember cookie on
// the application. Once we have a user we can return it to the caller.
$recaller = $this->getRecaller();
$recaller = $this->recaller();
if (is_null($user) && ! is_null($recaller)) {
$user = $this->getUserByRecaller($recaller);
if (is_null($this->user) && ! is_null($recaller)) {
$this->user = $this->userFromRecaller($recaller);
if ($user) {
$this->updateSession($user->getAuthIdentifier());
if ($this->user) {
$this->updateSession($this->user->getAuthIdentifier());
$this->fireLoginEvent($user, true);
$this->fireLoginEvent($this->user, true);
}
}
return $this->user = $user;
return $this->user;
}
/**
* Pull a user from the repository by its "remember me" cookie token.
*
* @param \Illuminate\Auth\Recaller $recaller
* @return mixed
*/
protected function userFromRecaller($recaller)
{
if (! $recaller->valid() || $this->recallAttempted) {
return;
}
// If the user is null, but we decrypt a "recaller" cookie we can attempt to
// pull the user data on that cookie which serves as a remember cookie on
// the application. Once we have a user we can return it to the caller.
$this->recallAttempted = true;
$this->viaRemember = ! is_null($user = $this->provider->retrieveByToken(
$recaller->id(), $recaller->token()
));
return $user;
}
/**
* Get the decrypted recaller cookie for the request.
*
* @return \Illuminate\Auth\Recaller|null
*/
protected function recaller()
{
if (is_null($this->request)) {
return;
}
if ($recaller = $this->request->cookies->get($this->getRecallerName())) {
return new Recaller($recaller);
}
}
/**
@@ -161,71 +202,9 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
return;
}
$id = $this->session->get($this->getName());
if (is_null($id) && $this->user()) {
$id = $this->user()->getAuthIdentifier();
}
return $id;
}
/**
* Pull a user from the repository by its recaller ID.
*
* @param string $recaller
* @return mixed
*/
protected function getUserByRecaller($recaller)
{
if ($this->validRecaller($recaller) && ! $this->tokenRetrievalAttempted) {
$this->tokenRetrievalAttempted = true;
list($id, $token) = explode('|', $recaller, 2);
$this->viaRemember = ! is_null($user = $this->provider->retrieveByToken($id, $token));
return $user;
}
}
/**
* Get the decrypted recaller cookie for the request.
*
* @return string|null
*/
protected function getRecaller()
{
return $this->request->cookies->get($this->getRecallerName());
}
/**
* Get the user ID from the recaller cookie.
*
* @return string|null
*/
protected function getRecallerId()
{
if ($this->validRecaller($recaller = $this->getRecaller())) {
return head(explode('|', $recaller));
}
}
/**
* Determine if the recaller cookie is in a valid format.
*
* @param mixed $recaller
* @return bool
*/
protected function validRecaller($recaller)
{
if (! is_string($recaller) || ! Str::contains($recaller, '|')) {
return false;
}
$segments = explode('|', $recaller);
return count($segments) == 2 && trim($segments[0]) !== '' && trim($segments[1]) !== '';
return $this->user()
? $this->user()->getAuthIdentifier()
: $this->session->get($this->getName());
}
/**
@@ -236,6 +215,8 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
*/
public function once(array $credentials = [])
{
$this->fireAttemptEvent($credentials);
if ($this->validate($credentials)) {
$this->setUser($this->lastAttempted);
@@ -245,6 +226,23 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
return false;
}
/**
* Log the given user ID into the application without sessions or cookies.
*
* @param mixed $id
* @return \Illuminate\Contracts\Auth\Authenticatable|false
*/
public function onceUsingId($id)
{
if (! is_null($user = $this->provider->retrieveById($id))) {
$this->setUser($user);
return $user;
}
return false;
}
/**
* Validate a user's credentials.
*
@@ -253,7 +251,9 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
*/
public function validate(array $credentials = [])
{
return $this->attempt($credentials, false, false);
$this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
return $this->hasValidCredentials($user, $credentials);
}
/**
@@ -276,7 +276,7 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
return;
}
return $this->getBasicResponse();
return $this->failedBasicResponse();
}
/**
@@ -288,10 +288,10 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
*/
public function onceBasic($field = 'email', $extraConditions = [])
{
$credentials = $this->getBasicCredentials($this->getRequest(), $field);
$credentials = $this->basicCredentials($this->getRequest(), $field);
if (! $this->once(array_merge($credentials, $extraConditions))) {
return $this->getBasicResponse();
return $this->failedBasicResponse();
}
}
@@ -309,9 +309,9 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
return false;
}
$credentials = $this->getBasicCredentials($request, $field);
return $this->attempt(array_merge($credentials, $extraConditions));
return $this->attempt(array_merge(
$this->basicCredentials($request, $field), $extraConditions
));
}
/**
@@ -321,7 +321,7 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
* @param string $field
* @return array
*/
protected function getBasicCredentials(Request $request, $field)
protected function basicCredentials(Request $request, $field)
{
return [$field => $request->getUser(), 'password' => $request->getPassword()];
}
@@ -329,13 +329,12 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
/**
* Get the response for basic authentication.
*
* @return \Symfony\Component\HttpFoundation\Response
* @return void
* @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException
*/
protected function getBasicResponse()
protected function failedBasicResponse()
{
$headers = ['WWW-Authenticate' => 'Basic'];
return new Response('Invalid credentials.', 401, $headers);
throw new UnauthorizedHttpException('Basic', 'Invalid credentials.');
}
/**
@@ -343,12 +342,11 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
*
* @param array $credentials
* @param bool $remember
* @param bool $login
* @return bool
*/
public function attempt(array $credentials = [], $remember = false, $login = true)
public function attempt(array $credentials = [], $remember = false)
{
$this->fireAttemptEvent($credentials, $remember, $login);
$this->fireAttemptEvent($credentials, $remember);
$this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
@@ -356,9 +354,7 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
// to validate the user against the given credentials, and if they are in
// fact valid we'll log the users into the application and return true.
if ($this->hasValidCredentials($user, $credentials)) {
if ($login) {
$this->login($user, $remember);
}
$this->login($user, $remember);
return true;
}
@@ -366,9 +362,7 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
// If the authentication attempt fails we will fire an event so that the user
// may be notified of any suspicious attempts to access their account from
// an unrecognized user. A developer may listen to this event as needed.
if ($login) {
$this->fireFailedEvent($user, $credentials);
}
$this->fireFailedEvent($user, $credentials);
return false;
}
@@ -386,47 +380,21 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
}
/**
* Fire the attempt event with the arguments.
* Log the given user ID into the application.
*
* @param array $credentials
* @param bool $remember
* @param bool $login
* @return void
* @param mixed $id
* @param bool $remember
* @return \Illuminate\Contracts\Auth\Authenticatable|false
*/
protected function fireAttemptEvent(array $credentials, $remember, $login)
public function loginUsingId($id, $remember = false)
{
if (isset($this->events)) {
$this->events->fire(new Events\Attempting(
$credentials, $remember, $login
));
}
}
if (! is_null($user = $this->provider->retrieveById($id))) {
$this->login($user, $remember);
/**
* Fire the failed authentication attempt event with the given arguments.
*
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param array $credentials
* @return void
*/
protected function fireFailedEvent($user, array $credentials)
{
if (isset($this->events)) {
$this->events->fire(new Events\Failed($user, $credentials));
return $user;
}
}
/**
* Register an authentication attempt event listener.
*
* @param mixed $callback
* @return void
*/
public function attempting($callback)
{
if (isset($this->events)) {
$this->events->listen(Events\Attempting::class, $callback);
}
return false;
}
/**
@@ -444,7 +412,7 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
// queue a permanent cookie that contains the encrypted copy of the user
// identifier. We will then decrypt this later to retrieve the users.
if ($remember) {
$this->createRememberTokenIfDoesntExist($user);
$this->ensureRememberTokenIsSet($user);
$this->queueRecallerCookie($user);
}
@@ -457,20 +425,6 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
$this->setUser($user);
}
/**
* Fire the login event if the dispatcher is set.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param bool $remember
* @return void
*/
protected function fireLoginEvent($user, $remember = false)
{
if (isset($this->events)) {
$this->events->fire(new Events\Login($user, $remember));
}
}
/**
* Update the session with the given ID.
*
@@ -479,48 +433,22 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
*/
protected function updateSession($id)
{
$this->session->set($this->getName(), $id);
$this->session->put($this->getName(), $id);
$this->session->migrate(true);
}
/**
* Log the given user ID into the application.
* Create a new "remember me" token for the user if one doesn't already exist.
*
* @param mixed $id
* @param bool $remember
* @return \Illuminate\Contracts\Auth\Authenticatable
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
public function loginUsingId($id, $remember = false)
protected function ensureRememberTokenIsSet(AuthenticatableContract $user)
{
$user = $this->provider->retrieveById($id);
if (! is_null($user)) {
$this->login($user, $remember);
return $user;
if (empty($user->getRememberToken())) {
$this->cycleRememberToken($user);
}
return false;
}
/**
* Log the given user ID into the application without sessions or cookies.
*
* @param mixed $id
* @return bool
*/
public function onceUsingId($id)
{
$user = $this->provider->retrieveById($id);
if (! is_null($user)) {
$this->setUser($user);
return true;
}
return false;
}
/**
@@ -531,9 +459,9 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
*/
protected function queueRecallerCookie(AuthenticatableContract $user)
{
$value = $user->getAuthIdentifier().'|'.$user->getRememberToken();
$this->getCookieJar()->queue($this->createRecaller($value));
$this->getCookieJar()->queue($this->createRecaller(
$user->getAuthIdentifier().'|'.$user->getRememberToken().'|'.$user->getAuthPassword()
));
}
/**
@@ -562,11 +490,11 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
$this->clearUserDataFromStorage();
if (! is_null($this->user)) {
$this->refreshRememberToken($user);
$this->cycleRememberToken($user);
}
if (isset($this->events)) {
$this->events->fire(new Events\Logout($user));
$this->events->dispatch(new Events\Logout($user));
}
// Once we have fired the logout event we will clear the users out of memory
@@ -586,10 +514,9 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
{
$this->session->remove($this->getName());
if (! is_null($this->getRecaller())) {
$recaller = $this->getRecallerName();
$this->getCookieJar()->queue($this->getCookieJar()->forget($recaller));
if (! is_null($this->recaller())) {
$this->getCookieJar()->queue($this->getCookieJar()
->forget($this->getRecallerName()));
}
}
@@ -599,7 +526,7 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
protected function refreshRememberToken(AuthenticatableContract $user)
protected function cycleRememberToken(AuthenticatableContract $user)
{
$user->setRememberToken($token = Str::random(60));
@@ -607,18 +534,115 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
}
/**
* Create a new "remember me" token for the user if one doesn't already exist.
* Register an authentication attempt event listener.
*
* @param mixed $callback
* @return void
*/
public function attempting($callback)
{
if (isset($this->events)) {
$this->events->listen(Events\Attempting::class, $callback);
}
}
/**
* Fire the attempt event with the arguments.
*
* @param array $credentials
* @param bool $remember
* @return void
*/
protected function fireAttemptEvent(array $credentials, $remember = false)
{
if (isset($this->events)) {
$this->events->dispatch(new Events\Attempting(
$credentials, $remember
));
}
}
/**
* Fire the login event if the dispatcher is set.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param bool $remember
* @return void
*/
protected function fireLoginEvent($user, $remember = false)
{
if (isset($this->events)) {
$this->events->dispatch(new Events\Login($user, $remember));
}
}
/**
* Fire the authenticated event if the dispatcher is set.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
protected function createRememberTokenIfDoesntExist(AuthenticatableContract $user)
protected function fireAuthenticatedEvent($user)
{
if (empty($user->getRememberToken())) {
$this->refreshRememberToken($user);
if (isset($this->events)) {
$this->events->dispatch(new Events\Authenticated($user));
}
}
/**
* Fire the failed authentication attempt event with the given arguments.
*
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param array $credentials
* @return void
*/
protected function fireFailedEvent($user, array $credentials)
{
if (isset($this->events)) {
$this->events->dispatch(new Events\Failed($user, $credentials));
}
}
/**
* Get the last user we attempted to authenticate.
*
* @return \Illuminate\Contracts\Auth\Authenticatable
*/
public function getLastAttempted()
{
return $this->lastAttempted;
}
/**
* Get a unique identifier for the auth session value.
*
* @return string
*/
public function getName()
{
return 'login_'.$this->name.'_'.sha1(static::class);
}
/**
* Get the name of the cookie used to store the "recaller".
*
* @return string
*/
public function getRecallerName()
{
return 'remember_'.$this->name.'_'.sha1(static::class);
}
/**
* Determine if the user was authenticated via "remember me" cookie.
*
* @return bool
*/
public function viaRemember()
{
return $this->viaRemember;
}
/**
* Get the cookie creator instance used by the guard.
*
@@ -670,34 +694,13 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
/**
* Get the session store used by the guard.
*
* @return \Illuminate\Session\Store
* @return \Illuminate\Contracts\Session\Session
*/
public function getSession()
{
return $this->session;
}
/**
* Get the user provider used by the guard.
*
* @return \Illuminate\Contracts\Auth\UserProvider
*/
public function getProvider()
{
return $this->provider;
}
/**
* Set the user provider used by the guard.
*
* @param \Illuminate\Contracts\Auth\UserProvider $provider
* @return void
*/
public function setProvider(UserProvider $provider)
{
$this->provider = $provider;
}
/**
* Return the currently cached user.
*
@@ -720,6 +723,8 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
$this->loggedOut = false;
$this->fireAuthenticatedEvent($user);
return $this;
}
@@ -745,44 +750,4 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
return $this;
}
/**
* Get the last user we attempted to authenticate.
*
* @return \Illuminate\Contracts\Auth\Authenticatable
*/
public function getLastAttempted()
{
return $this->lastAttempted;
}
/**
* Get a unique identifier for the auth session value.
*
* @return string
*/
public function getName()
{
return 'login_'.$this->name.'_'.sha1(static::class);
}
/**
* Get the name of the cookie used to store the "recaller".
*
* @return string
*/
public function getRecallerName()
{
return 'remember_'.$this->name.'_'.sha1(static::class);
}
/**
* Determine if the user was authenticated via "remember me" cookie.
*
* @return bool
*/
public function viaRemember()
{
return $this->viaRemember;
}
}

View File

@@ -18,7 +18,7 @@ class TokenGuard implements Guard
protected $request;
/**
* The name of the field on the request containing the API token.
* The name of the query string item from the request containing the API token.
*
* @var string
*/
@@ -78,9 +78,13 @@ class TokenGuard implements Guard
*
* @return string
*/
protected function getTokenForRequest()
public function getTokenForRequest()
{
$token = $this->request->input($this->inputKey);
$token = $this->request->query($this->inputKey);
if (empty($token)) {
$token = $this->request->input($this->inputKey);
}
if (empty($token)) {
$token = $this->request->bearerToken();

View File

@@ -2,7 +2,7 @@
"name": "illuminate/auth",
"description": "The Illuminate Auth package.",
"license": "MIT",
"homepage": "http://laravel.com",
"homepage": "https://laravel.com",
"support": {
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
@@ -14,11 +14,11 @@
}
],
"require": {
"php": ">=5.5.9",
"illuminate/contracts": "5.2.*",
"illuminate/http": "5.2.*",
"illuminate/support": "5.2.*",
"nesbot/carbon": "~1.20"
"php": ">=7.0",
"illuminate/contracts": "5.5.*",
"illuminate/http": "5.5.*",
"illuminate/queue": "5.5.*",
"illuminate/support": "5.5.*"
},
"autoload": {
"psr-4": {
@@ -27,13 +27,16 @@
},
"extra": {
"branch-alias": {
"dev-master": "5.2-dev"
"dev-master": "5.5-dev"
}
},
"suggest": {
"illuminate/console": "Required to use the auth:clear-resets command (5.2.*).",
"illuminate/queue": "Required to fire login / logout events (5.2.*).",
"illuminate/session": "Required to use the session based guard (5.2.*)."
"illuminate/console": "Required to use the auth:clear-resets command (5.5.*).",
"illuminate/queue": "Required to fire login / logout events (5.5.*).",
"illuminate/session": "Required to use the session based guard (5.5.*)."
},
"config": {
"sort-packages": true
},
"minimum-stability": "dev"
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Illuminate\Broadcasting;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Broadcast;
class BroadcastController extends Controller
{
/**
* Authenticate the request for channel access.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function authenticate(Request $request)
{
return Broadcast::auth($request);
}
}

View File

@@ -4,49 +4,49 @@ namespace Illuminate\Broadcasting;
use ReflectionClass;
use ReflectionProperty;
use Illuminate\Contracts\Queue\Job;
use Illuminate\Support\Arr;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Broadcasting\Broadcaster;
class BroadcastEvent
class BroadcastEvent implements ShouldQueue
{
use Queueable;
/**
* The broadcaster implementation.
* The event instance.
*
* @var \Illuminate\Contracts\Broadcasting\Broadcaster
* @var mixed
*/
protected $broadcaster;
public $event;
/**
* Create a new job handler instance.
*
* @param \Illuminate\Contracts\Broadcasting\Broadcaster $broadcaster
* @param mixed $event
* @return void
*/
public function __construct(Broadcaster $broadcaster)
public function __construct($event)
{
$this->broadcaster = $broadcaster;
$this->event = $event;
}
/**
* Handle the queued job.
*
* @param \Illuminate\Contracts\Queue\Job $job
* @param array $data
* @param \Illuminate\Contracts\Broadcasting\Broadcaster $broadcaster
* @return void
*/
public function fire(Job $job, array $data)
public function handle(Broadcaster $broadcaster)
{
$event = unserialize($data['event']);
$name = method_exists($this->event, 'broadcastAs')
? $this->event->broadcastAs() : get_class($this->event);
$name = method_exists($event, 'broadcastAs')
? $event->broadcastAs() : get_class($event);
$this->broadcaster->broadcast(
$event->broadcastOn(), $name, $this->getPayloadFromEvent($event)
$broadcaster->broadcast(
Arr::wrap($this->event->broadcastOn()), $name,
$this->getPayloadFromEvent($this->event)
);
$job->delete();
}
/**
@@ -58,7 +58,9 @@ class BroadcastEvent
protected function getPayloadFromEvent($event)
{
if (method_exists($event, 'broadcastWith')) {
return $event->broadcastWith();
return array_merge(
$event->broadcastWith(), ['socket' => data_get($event, 'socket')]
);
}
$payload = [];
@@ -67,6 +69,8 @@ class BroadcastEvent
$payload[$property->getName()] = $this->formatProperty($property->getValue($event));
}
unset($payload['broadcastQueue']);
return $payload;
}
@@ -84,4 +88,24 @@ class BroadcastEvent
return $value;
}
/**
* Get the display name for the queued job.
*
* @return string
*/
public function displayName()
{
return get_class($this->event);
}
/**
* Prepare the instance for cloning.
*
* @return void
*/
public function __clone()
{
$this->event = clone $this->event;
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Illuminate\Broadcasting;
use RuntimeException;
class BroadcastException extends RuntimeException
{
//
}

View File

@@ -2,15 +2,20 @@
namespace Illuminate\Broadcasting;
use Pusher;
use Closure;
use Illuminate\Support\Arr;
use Pusher\Pusher;
use Psr\Log\LoggerInterface;
use InvalidArgumentException;
use Illuminate\Broadcasting\Broadcasters\LogBroadcaster;
use Illuminate\Broadcasting\Broadcasters\NullBroadcaster;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Broadcasting\Broadcasters\RedisBroadcaster;
use Illuminate\Broadcasting\Broadcasters\PusherBroadcaster;
use Illuminate\Contracts\Broadcasting\Factory as FactoryContract;
/**
* @mixin \Illuminate\Contracts\Broadcasting\Broadcaster
*/
class BroadcastManager implements FactoryContract
{
/**
@@ -45,6 +50,82 @@ class BroadcastManager implements FactoryContract
$this->app = $app;
}
/**
* Register the routes for handling broadcast authentication and sockets.
*
* @param array|null $attributes
* @return void
*/
public function routes(array $attributes = null)
{
if ($this->app->routesAreCached()) {
return;
}
$attributes = $attributes ?: ['middleware' => ['web']];
$this->app['router']->group($attributes, function ($router) {
$router->post('/broadcasting/auth', '\\'.BroadcastController::class.'@authenticate');
});
}
/**
* Get the socket ID for the given request.
*
* @param \Illuminate\Http\Request|null $request
* @return string|null
*/
public function socket($request = null)
{
if (! $request && ! $this->app->bound('request')) {
return;
}
$request = $request ?: $this->app['request'];
return $request->header('X-Socket-ID');
}
/**
* Begin broadcasting an event.
*
* @param mixed|null $event
* @return \Illuminate\Broadcasting\PendingBroadcast|void
*/
public function event($event = null)
{
return new PendingBroadcast($this->app->make('events'), $event);
}
/**
* Queue the given event for broadcast.
*
* @param mixed $event
* @return void
*/
public function queue($event)
{
$connection = $event instanceof ShouldBroadcastNow ? 'sync' : null;
if (is_null($connection) && isset($event->connection)) {
$connection = $event->connection;
}
$queue = null;
if (method_exists($event, 'broadcastQueue')) {
$queue = $event->broadcastQueue();
} elseif (isset($event->broadcastQueue)) {
$queue = $event->broadcastQueue;
} elseif (isset($event->queue)) {
$queue = $event->queue;
}
$this->app->make('queue')->connection($connection)->pushOn(
$queue, new BroadcastEvent(clone $event)
);
}
/**
* Get a driver instance.
*
@@ -77,7 +158,7 @@ class BroadcastManager implements FactoryContract
*/
protected function get($name)
{
return isset($this->drivers[$name]) ? $this->drivers[$name] : $this->resolve($name);
return $this->drivers[$name] ?? $this->resolve($name);
}
/**
@@ -98,15 +179,15 @@ class BroadcastManager implements FactoryContract
if (isset($this->customCreators[$config['driver']])) {
return $this->callCustomCreator($config);
} else {
$driverMethod = 'create'.ucfirst($config['driver']).'Driver';
if (method_exists($this, $driverMethod)) {
return $this->{$driverMethod}($config);
} else {
throw new InvalidArgumentException("Driver [{$config['driver']}] is not supported.");
}
}
$driverMethod = 'create'.ucfirst($config['driver']).'Driver';
if (! method_exists($this, $driverMethod)) {
throw new InvalidArgumentException("Driver [{$config['driver']}] is not supported.");
}
return $this->{$driverMethod}($config);
}
/**
@@ -129,7 +210,8 @@ class BroadcastManager implements FactoryContract
protected function createPusherDriver(array $config)
{
return new PusherBroadcaster(
new Pusher($config['key'], $config['secret'], $config['app_id'], Arr::get($config, 'options', []))
new Pusher($config['key'], $config['secret'],
$config['app_id'], $config['options'] ?? [])
);
}
@@ -142,7 +224,7 @@ class BroadcastManager implements FactoryContract
protected function createRedisDriver(array $config)
{
return new RedisBroadcaster(
$this->app->make('redis'), Arr::get($config, 'connection')
$this->app->make('redis'), $config['connection'] ?? null
);
}
@@ -155,10 +237,21 @@ class BroadcastManager implements FactoryContract
protected function createLogDriver(array $config)
{
return new LogBroadcaster(
$this->app->make('Psr\Log\LoggerInterface')
$this->app->make(LoggerInterface::class)
);
}
/**
* Create an instance of the driver.
*
* @param array $config
* @return \Illuminate\Contracts\Broadcasting\Broadcaster
*/
protected function createNullDriver(array $config)
{
return new NullBroadcaster;
}
/**
* Get the connection configuration.
*
@@ -214,6 +307,6 @@ class BroadcastManager implements FactoryContract
*/
public function __call($method, $parameters)
{
return call_user_func_array([$this->driver(), $method], $parameters);
return $this->driver()->$method(...$parameters);
}
}

View File

@@ -3,6 +3,8 @@
namespace Illuminate\Broadcasting;
use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Broadcasting\Factory as BroadcastingFactory;
use Illuminate\Contracts\Broadcasting\Broadcaster as BroadcasterContract;
class BroadcastServiceProvider extends ServiceProvider
{
@@ -20,16 +22,16 @@ class BroadcastServiceProvider extends ServiceProvider
*/
public function register()
{
$this->app->singleton('Illuminate\Broadcasting\BroadcastManager', function ($app) {
$this->app->singleton(BroadcastManager::class, function ($app) {
return new BroadcastManager($app);
});
$this->app->singleton('Illuminate\Contracts\Broadcasting\Broadcaster', function ($app) {
return $app->make('Illuminate\Broadcasting\BroadcastManager')->connection();
$this->app->singleton(BroadcasterContract::class, function ($app) {
return $app->make(BroadcastManager::class)->connection();
});
$this->app->alias(
'Illuminate\Broadcasting\BroadcastManager', 'Illuminate\Contracts\Broadcasting\Factory'
BroadcastManager::class, BroadcastingFactory::class
);
}
@@ -41,9 +43,9 @@ class BroadcastServiceProvider extends ServiceProvider
public function provides()
{
return [
'Illuminate\Broadcasting\BroadcastManager',
'Illuminate\Contracts\Broadcasting\Factory',
'Illuminate\Contracts\Broadcasting\Broadcaster',
BroadcastManager::class,
BroadcastingFactory::class,
BroadcasterContract::class,
];
}
}

View File

@@ -0,0 +1,204 @@
<?php
namespace Illuminate\Broadcasting\Broadcasters;
use ReflectionFunction;
use Illuminate\Support\Str;
use Illuminate\Container\Container;
use Illuminate\Contracts\Routing\UrlRoutable;
use Illuminate\Contracts\Routing\BindingRegistrar;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Illuminate\Contracts\Broadcasting\Broadcaster as BroadcasterContract;
abstract class Broadcaster implements BroadcasterContract
{
/**
* The registered channel authenticators.
*
* @var array
*/
protected $channels = [];
/**
* The binding registrar instance.
*
* @var BindingRegistrar
*/
protected $bindingRegistrar;
/**
* Register a channel authenticator.
*
* @param string $channel
* @param callable $callback
* @return $this
*/
public function channel($channel, callable $callback)
{
$this->channels[$channel] = $callback;
return $this;
}
/**
* Authenticate the incoming request for a given channel.
*
* @param \Illuminate\Http\Request $request
* @param string $channel
* @return mixed
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/
protected function verifyUserCanAccessChannel($request, $channel)
{
foreach ($this->channels as $pattern => $callback) {
if (! Str::is(preg_replace('/\{(.*?)\}/', '*', $pattern), $channel)) {
continue;
}
$parameters = $this->extractAuthParameters($pattern, $channel, $callback);
if ($result = $callback($request->user(), ...$parameters)) {
return $this->validAuthenticationResponse($request, $result);
}
}
throw new AccessDeniedHttpException;
}
/**
* Extract the parameters from the given pattern and channel.
*
* @param string $pattern
* @param string $channel
* @param callable $callback
* @return array
*/
protected function extractAuthParameters($pattern, $channel, $callback)
{
$callbackParameters = (new ReflectionFunction($callback))->getParameters();
return collect($this->extractChannelKeys($pattern, $channel))->reject(function ($value, $key) {
return is_numeric($key);
})->map(function ($value, $key) use ($callbackParameters) {
return $this->resolveBinding($key, $value, $callbackParameters);
})->values()->all();
}
/**
* Extract the channel keys from the incoming channel name.
*
* @param string $pattern
* @param string $channel
* @return array
*/
protected function extractChannelKeys($pattern, $channel)
{
preg_match('/^'.preg_replace('/\{(.*?)\}/', '(?<$1>[^\.]+)', $pattern).'/', $channel, $keys);
return $keys;
}
/**
* Resolve the given parameter binding.
*
* @param string $key
* @param string $value
* @param array $callbackParameters
* @return mixed
*/
protected function resolveBinding($key, $value, $callbackParameters)
{
$newValue = $this->resolveExplicitBindingIfPossible($key, $value);
return $newValue === $value ? $this->resolveImplicitBindingIfPossible(
$key, $value, $callbackParameters
) : $newValue;
}
/**
* Resolve an explicit parameter binding if applicable.
*
* @param string $key
* @param mixed $value
* @return mixed
*/
protected function resolveExplicitBindingIfPossible($key, $value)
{
$binder = $this->binder();
if ($binder && $binder->getBindingCallback($key)) {
return call_user_func($binder->getBindingCallback($key), $value);
}
return $value;
}
/**
* Resolve an implicit parameter binding if applicable.
*
* @param string $key
* @param mixed $value
* @param array $callbackParameters
* @return mixed
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/
protected function resolveImplicitBindingIfPossible($key, $value, $callbackParameters)
{
foreach ($callbackParameters as $parameter) {
if (! $this->isImplicitlyBindable($key, $parameter)) {
continue;
}
$instance = $parameter->getClass()->newInstance();
if (! $model = $instance->resolveRouteBinding($value)) {
throw new AccessDeniedHttpException;
}
return $model;
}
return $value;
}
/**
* Determine if a given key and parameter is implicitly bindable.
*
* @param string $key
* @param \ReflectionParameter $parameter
* @return bool
*/
protected function isImplicitlyBindable($key, $parameter)
{
return $parameter->name === $key && $parameter->getClass() &&
$parameter->getClass()->isSubclassOf(UrlRoutable::class);
}
/**
* Format the channel array into an array of strings.
*
* @param array $channels
* @return array
*/
protected function formatChannels(array $channels)
{
return array_map(function ($channel) {
return (string) $channel;
}, $channels);
}
/**
* Get the model binding registrar instance.
*
* @return \Illuminate\Contracts\Routing\BindingRegistrar
*/
protected function binder()
{
if (! $this->bindingRegistrar) {
$this->bindingRegistrar = Container::getInstance()->bound(BindingRegistrar::class)
? Container::getInstance()->make(BindingRegistrar::class) : null;
}
return $this->bindingRegistrar;
}
}

View File

@@ -3,9 +3,8 @@
namespace Illuminate\Broadcasting\Broadcasters;
use Psr\Log\LoggerInterface;
use Illuminate\Contracts\Broadcasting\Broadcaster;
class LogBroadcaster implements Broadcaster
class LogBroadcaster extends Broadcaster
{
/**
* The logger implementation.
@@ -25,12 +24,28 @@ class LogBroadcaster implements Broadcaster
$this->logger = $logger;
}
/**
* {@inheritdoc}
*/
public function auth($request)
{
//
}
/**
* {@inheritdoc}
*/
public function validAuthenticationResponse($request, $result)
{
//
}
/**
* {@inheritdoc}
*/
public function broadcast(array $channels, $event, array $payload = [])
{
$channels = implode(', ', $channels);
$channels = implode(', ', $this->formatChannels($channels));
$payload = json_encode($payload, JSON_PRETTY_PRINT);

View File

@@ -0,0 +1,30 @@
<?php
namespace Illuminate\Broadcasting\Broadcasters;
class NullBroadcaster extends Broadcaster
{
/**
* {@inheritdoc}
*/
public function auth($request)
{
//
}
/**
* {@inheritdoc}
*/
public function validAuthenticationResponse($request, $result)
{
//
}
/**
* {@inheritdoc}
*/
public function broadcast(array $channels, $event, array $payload = [])
{
//
}
}

View File

@@ -2,22 +2,25 @@
namespace Illuminate\Broadcasting\Broadcasters;
use Pusher;
use Illuminate\Contracts\Broadcasting\Broadcaster;
use Pusher\Pusher;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Broadcasting\BroadcastException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class PusherBroadcaster implements Broadcaster
class PusherBroadcaster extends Broadcaster
{
/**
* The Pusher SDK instance.
*
* @var \Pusher
* @var \Pusher\Pusher
*/
protected $pusher;
/**
* Create a new broadcaster instance.
*
* @param \Pusher $pusher
* @param \Pusher\Pusher $pusher
* @return void
*/
public function __construct(Pusher $pusher)
@@ -26,17 +29,90 @@ class PusherBroadcaster implements Broadcaster
}
/**
* {@inheritdoc}
* Authenticate the incoming request for a given channel.
*
* @param \Illuminate\Http\Request $request
* @return mixed
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/
public function auth($request)
{
if (Str::startsWith($request->channel_name, ['private-', 'presence-']) &&
! $request->user()) {
throw new AccessDeniedHttpException;
}
$channelName = Str::startsWith($request->channel_name, 'private-')
? Str::replaceFirst('private-', '', $request->channel_name)
: Str::replaceFirst('presence-', '', $request->channel_name);
return parent::verifyUserCanAccessChannel(
$request, $channelName
);
}
/**
* Return the valid authentication response.
*
* @param \Illuminate\Http\Request $request
* @param mixed $result
* @return mixed
*/
public function validAuthenticationResponse($request, $result)
{
if (Str::startsWith($request->channel_name, 'private')) {
return $this->decodePusherResponse(
$this->pusher->socket_auth($request->channel_name, $request->socket_id)
);
}
return $this->decodePusherResponse(
$this->pusher->presence_auth(
$request->channel_name, $request->socket_id, $request->user()->getAuthIdentifier(), $result)
);
}
/**
* Decode the given Pusher response.
*
* @param mixed $response
* @return array
*/
protected function decodePusherResponse($response)
{
return json_decode($response, true);
}
/**
* Broadcast the given event.
*
* @param array $channels
* @param string $event
* @param array $payload
* @return void
*/
public function broadcast(array $channels, $event, array $payload = [])
{
$this->pusher->trigger($channels, $event, $payload);
$socket = Arr::pull($payload, 'socket');
$response = $this->pusher->trigger(
$this->formatChannels($channels), $event, $payload, $socket, true
);
if ((is_array($response) && $response['status'] >= 200 && $response['status'] <= 299)
|| $response === true) {
return;
}
throw new BroadcastException(
is_bool($response) ? 'Failed to connect to Pusher.' : $response['body']
);
}
/**
* Get the Pusher SDK instance.
*
* @return \Pusher
* @return \Pusher\Pusher
*/
public function getPusher()
{

View File

@@ -2,15 +2,17 @@
namespace Illuminate\Broadcasting\Broadcasters;
use Illuminate\Contracts\Broadcasting\Broadcaster;
use Illuminate\Contracts\Redis\Database as RedisDatabase;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Contracts\Redis\Factory as Redis;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class RedisBroadcaster implements Broadcaster
class RedisBroadcaster extends Broadcaster
{
/**
* The Redis instance.
*
* @var \Illuminate\Contracts\Redis\Database
* @var \Illuminate\Contracts\Redis\Factory
*/
protected $redis;
@@ -24,26 +26,77 @@ class RedisBroadcaster implements Broadcaster
/**
* Create a new broadcaster instance.
*
* @param \Illuminate\Contracts\Redis\Database $redis
* @param \Illuminate\Contracts\Redis\Factory $redis
* @param string $connection
* @return void
*/
public function __construct(RedisDatabase $redis, $connection = null)
public function __construct(Redis $redis, $connection = null)
{
$this->redis = $redis;
$this->connection = $connection;
}
/**
* {@inheritdoc}
* Authenticate the incoming request for a given channel.
*
* @param \Illuminate\Http\Request $request
* @return mixed
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/
public function auth($request)
{
if (Str::startsWith($request->channel_name, ['private-', 'presence-']) &&
! $request->user()) {
throw new AccessDeniedHttpException;
}
$channelName = Str::startsWith($request->channel_name, 'private-')
? Str::replaceFirst('private-', '', $request->channel_name)
: Str::replaceFirst('presence-', '', $request->channel_name);
return parent::verifyUserCanAccessChannel(
$request, $channelName
);
}
/**
* Return the valid authentication response.
*
* @param \Illuminate\Http\Request $request
* @param mixed $result
* @return mixed
*/
public function validAuthenticationResponse($request, $result)
{
if (is_bool($result)) {
return json_encode($result);
}
return json_encode(['channel_data' => [
'user_id' => $request->user()->getAuthIdentifier(),
'user_info' => $result,
]]);
}
/**
* Broadcast the given event.
*
* @param array $channels
* @param string $event
* @param array $payload
* @return void
*/
public function broadcast(array $channels, $event, array $payload = [])
{
$connection = $this->redis->connection($this->connection);
$payload = json_encode(['event' => $event, 'data' => $payload]);
$payload = json_encode([
'event' => $event,
'data' => $payload,
'socket' => Arr::pull($payload, 'socket'),
]);
foreach ($channels as $channel) {
foreach ($this->formatChannels($channels) as $channel) {
$connection->publish($channel, $payload);
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Illuminate\Broadcasting;
class Channel
{
/**
* The channel's name.
*
* @var string
*/
public $name;
/**
* Create a new channel instance.
*
* @param string $name
* @return void
*/
public function __construct($name)
{
$this->name = $name;
}
/**
* Convert the channel instance to a string.
*
* @return string
*/
public function __toString()
{
return $this->name;
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Illuminate\Broadcasting;
use Illuminate\Support\Facades\Broadcast;
trait InteractsWithSockets
{
/**
* The socket ID for the user that raised the event.
*
* @var string|null
*/
public $socket;
/**
* Exclude the current user from receiving the broadcast.
*
* @return $this
*/
public function dontBroadcastToCurrentUser()
{
$this->socket = Broadcast::socket();
return $this;
}
/**
* Broadcast the event to everyone.
*
* @return $this
*/
public function broadcastToEveryone()
{
$this->socket = null;
return $this;
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace Illuminate\Broadcasting;
use Illuminate\Contracts\Events\Dispatcher;
class PendingBroadcast
{
/**
* The event dispatcher implementation.
*
* @var \Illuminate\Contracts\Events\Dispatcher
*/
protected $events;
/**
* The event instance.
*
* @var mixed
*/
protected $event;
/**
* Create a new pending broadcast instance.
*
* @param \Illuminate\Contracts\Events\Dispatcher $events
* @param mixed $event
* @return void
*/
public function __construct(Dispatcher $events, $event)
{
$this->event = $event;
$this->events = $events;
}
/**
* Broadcast the event to everyone except the current user.
*
* @return $this
*/
public function toOthers()
{
if (method_exists($this->event, 'dontBroadcastToCurrentUser')) {
$this->event->dontBroadcastToCurrentUser();
}
return $this;
}
/**
* Handle the object's destruction.
*
* @return void
*/
public function __destruct()
{
$this->events->dispatch($this->event);
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Illuminate\Broadcasting;
class PresenceChannel extends Channel
{
/**
* Create a new channel instance.
*
* @param string $name
* @return void
*/
public function __construct($name)
{
parent::__construct('presence-'.$name);
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Illuminate\Broadcasting;
class PrivateChannel extends Channel
{
/**
* Create a new channel instance.
*
* @param string $name
* @return void
*/
public function __construct($name)
{
parent::__construct('private-'.$name);
}
}

View File

@@ -2,7 +2,7 @@
"name": "illuminate/broadcasting",
"description": "The Illuminate Broadcasting package.",
"license": "MIT",
"homepage": "http://laravel.com",
"homepage": "https://laravel.com",
"support": {
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
@@ -14,9 +14,12 @@
}
],
"require": {
"php": ">=5.5.9",
"illuminate/contracts": "5.2.*",
"illuminate/support": "5.2.*"
"php": ">=7.0",
"psr/log": "~1.0",
"illuminate/bus": "5.5.*",
"illuminate/contracts": "5.5.*",
"illuminate/queue": "5.5.*",
"illuminate/support": "5.5.*"
},
"autoload": {
"psr-4": {
@@ -25,11 +28,14 @@
},
"extra": {
"branch-alias": {
"dev-master": "5.2-dev"
"dev-master": "5.5-dev"
}
},
"suggest": {
"pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~2.0)."
"pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~3.0)."
},
"config": {
"sort-packages": true
},
"minimum-stability": "dev"
}

View File

@@ -3,6 +3,9 @@
namespace Illuminate\Bus;
use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Bus\Dispatcher as DispatcherContract;
use Illuminate\Contracts\Queue\Factory as QueueFactoryContract;
use Illuminate\Contracts\Bus\QueueingDispatcher as QueueingDispatcherContract;
class BusServiceProvider extends ServiceProvider
{
@@ -20,18 +23,18 @@ class BusServiceProvider extends ServiceProvider
*/
public function register()
{
$this->app->singleton('Illuminate\Bus\Dispatcher', function ($app) {
$this->app->singleton(Dispatcher::class, function ($app) {
return new Dispatcher($app, function ($connection = null) use ($app) {
return $app['Illuminate\Contracts\Queue\Factory']->connection($connection);
return $app[QueueFactoryContract::class]->connection($connection);
});
});
$this->app->alias(
'Illuminate\Bus\Dispatcher', 'Illuminate\Contracts\Bus\Dispatcher'
Dispatcher::class, DispatcherContract::class
);
$this->app->alias(
'Illuminate\Bus\Dispatcher', 'Illuminate\Contracts\Bus\QueueingDispatcher'
Dispatcher::class, QueueingDispatcherContract::class
);
}
@@ -43,9 +46,9 @@ class BusServiceProvider extends ServiceProvider
public function provides()
{
return [
'Illuminate\Bus\Dispatcher',
'Illuminate\Contracts\Bus\Dispatcher',
'Illuminate\Contracts\Bus\QueueingDispatcher',
Dispatcher::class,
DispatcherContract::class,
QueueingDispatcherContract::class,
];
}
}

View File

@@ -33,6 +33,13 @@ class Dispatcher implements QueueingDispatcher
*/
protected $pipes = [];
/**
* The command to handler mapping for non-self-handling events.
*
* @var array
*/
protected $handlers = [];
/**
* The queue resolver callback.
*
@@ -64,22 +71,57 @@ class Dispatcher implements QueueingDispatcher
{
if ($this->queueResolver && $this->commandShouldBeQueued($command)) {
return $this->dispatchToQueue($command);
} else {
return $this->dispatchNow($command);
}
return $this->dispatchNow($command);
}
/**
* Dispatch a command to its appropriate handler in the current process.
*
* @param mixed $command
* @param mixed $handler
* @return mixed
*/
public function dispatchNow($command)
public function dispatchNow($command, $handler = null)
{
return $this->pipeline->send($command)->through($this->pipes)->then(function ($command) {
return $this->container->call([$command, 'handle']);
});
if ($handler || $handler = $this->getCommandHandler($command)) {
$callback = function ($command) use ($handler) {
return $handler->handle($command);
};
} else {
$callback = function ($command) {
return $this->container->call([$command, 'handle']);
};
}
return $this->pipeline->send($command)->through($this->pipes)->then($callback);
}
/**
* Determine if the given command has a handler.
*
* @param mixed $command
* @return bool
*/
public function hasCommandHandler($command)
{
return array_key_exists(get_class($command), $this->handlers);
}
/**
* Retrieve the handler for a command.
*
* @param mixed $command
* @return bool|mixed
*/
public function getCommandHandler($command)
{
if ($this->hasCommandHandler($command)) {
return $this->container->make($this->handlers[get_class($command)]);
}
return false;
}
/**
@@ -103,7 +145,7 @@ class Dispatcher implements QueueingDispatcher
*/
public function dispatchToQueue($command)
{
$connection = isset($command->connection) ? $command->connection : null;
$connection = $command->connection ?? null;
$queue = call_user_func($this->queueResolver, $connection);
@@ -113,9 +155,9 @@ class Dispatcher implements QueueingDispatcher
if (method_exists($command, 'queue')) {
return $command->queue($queue, $command);
} else {
return $this->pushCommandToQueue($queue, $command);
}
return $this->pushCommandToQueue($queue, $command);
}
/**
@@ -154,4 +196,17 @@ class Dispatcher implements QueueingDispatcher
return $this;
}
/**
* Map a command to a handler.
*
* @param array $map
* @return $this
*/
public function map(array $map)
{
$this->handlers = array_merge($this->handlers, $map);
return $this;
}
}

View File

@@ -18,13 +18,34 @@ trait Queueable
*/
public $queue;
/**
* The name of the connection the chain should be sent to.
*
* @var string|null
*/
public $chainConnection;
/**
* The name of the queue the chain should be sent to.
*
* @var string|null
*/
public $chainQueue;
/**
* The number of seconds before the job should be made available.
*
* @var \DateTime|int|null
* @var \DateTimeInterface|\DateInterval|int|null
*/
public $delay;
/**
* The jobs that should run if this job is successful.
*
* @var array
*/
public $chained = [];
/**
* Set the desired connection for the job.
*
@@ -51,10 +72,38 @@ trait Queueable
return $this;
}
/**
* Set the desired connection for the chain.
*
* @param string|null $connection
* @return $this
*/
public function allOnConnection($connection)
{
$this->chainConnection = $connection;
$this->connection = $connection;
return $this;
}
/**
* Set the desired queue for the chain.
*
* @param string|null $queue
* @return $this
*/
public function allOnQueue($queue)
{
$this->chainQueue = $queue;
$this->queue = $queue;
return $this;
}
/**
* Set the desired delay for the job.
*
* @param int|null $delay
* @param \DateTimeInterface|\DateInterval|int|null $delay
* @return $this
*/
public function delay($delay)
@@ -63,4 +112,39 @@ trait Queueable
return $this;
}
/**
* Set the jobs that should run if this job is successful.
*
* @param array $chain
* @return $this
*/
public function chain($chain)
{
$this->chained = collect($chain)->map(function ($job) {
return serialize($job);
})->all();
return $this;
}
/**
* Dispatch the next job on the chain.
*
* @return void
*/
public function dispatchNextJobInChain()
{
if (! empty($this->chained)) {
dispatch(tap(unserialize(array_shift($this->chained)), function ($next) {
$next->chained = $this->chained;
$next->onConnection($next->connection ?: $this->chainConnection);
$next->onQueue($next->queue ?: $this->chainQueue);
$next->chainConnection = $this->chainConnection;
$next->chainQueue = $this->chainQueue;
}));
}
}
}

View File

@@ -2,7 +2,7 @@
"name": "illuminate/bus",
"description": "The Illuminate Bus package.",
"license": "MIT",
"homepage": "http://laravel.com",
"homepage": "https://laravel.com",
"support": {
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
@@ -14,10 +14,10 @@
}
],
"require": {
"php": ">=5.5.9",
"illuminate/contracts": "5.2.*",
"illuminate/pipeline": "5.2.*",
"illuminate/support": "5.2.*"
"php": ">=7.0",
"illuminate/contracts": "5.5.*",
"illuminate/pipeline": "5.5.*",
"illuminate/support": "5.5.*"
},
"autoload": {
"psr-4": {
@@ -26,8 +26,11 @@
},
"extra": {
"branch-alias": {
"dev-master": "5.2-dev"
"dev-master": "5.5-dev"
}
},
"config": {
"sort-packages": true
},
"minimum-stability": "dev"
}

View File

@@ -55,12 +55,12 @@ class ApcStore extends TaggableStore implements Store
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @param float|int $minutes
* @return void
*/
public function put($key, $value, $minutes)
{
$this->apc->put($this->prefix.$key, $value, $minutes * 60);
$this->apc->put($this->prefix.$key, $value, (int) ($minutes * 60));
}
/**
@@ -113,11 +113,11 @@ class ApcStore extends TaggableStore implements Store
/**
* Remove all items from the cache.
*
* @return void
* @return bool
*/
public function flush()
{
$this->apc->flush();
return $this->apc->flush();
}
/**

View File

@@ -83,10 +83,10 @@ class ApcWrapper
/**
* Remove all items from the cache.
*
* @return void
* @return bool
*/
public function flush()
{
$this->apcu ? apcu_clear_cache() : apc_clear_cache('user');
return $this->apcu ? apcu_clear_cache() : apc_clear_cache('user');
}
}

View File

@@ -33,7 +33,7 @@ class ArrayStore extends TaggableStore implements Store
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @param float|int $minutes
* @return void
*/
public function put($key, $value, $minutes)
@@ -50,7 +50,8 @@ class ArrayStore extends TaggableStore implements Store
*/
public function increment($key, $value = 1)
{
$this->storage[$key] = ((int) $this->storage[$key]) + $value;
$this->storage[$key] = ! isset($this->storage[$key])
? $value : ((int) $this->storage[$key]) + $value;
return $this->storage[$key];
}
@@ -95,11 +96,13 @@ class ArrayStore extends TaggableStore implements Store
/**
* Remove all items from the cache.
*
* @return void
* @return bool
*/
public function flush()
{
$this->storage = [];
return true;
}
/**

Some files were not shown because too many files have changed in this diff Show More