dependencies-upgrade
This commit is contained in:
240
vendor/laravel/dusk/CHANGELOG.md
vendored
240
vendor/laravel/dusk/CHANGELOG.md
vendored
@@ -1,240 +0,0 @@
|
||||
# Release Notes
|
||||
|
||||
## [Unreleased](https://github.com/laravel/dusk/compare/v5.11.0...5.0)
|
||||
|
||||
|
||||
## [v5.11.0 (2020-03-24)](https://github.com/laravel/dusk/compare/v5.10.0...v5.11.0)
|
||||
|
||||
### Added
|
||||
- Add assert attribute methods ([#751](https://github.com/laravel/dusk/pull/751))
|
||||
|
||||
### Fixed
|
||||
- Fix Call to undefined method ([#750](https://github.com/laravel/dusk/pull/750))
|
||||
- Avoid throwing exception on production ([#755](https://github.com/laravel/dusk/pull/755))
|
||||
|
||||
|
||||
## [v5.10.0 (2020-03-17)](https://github.com/laravel/dusk/compare/v5.9.2...v5.10.0)
|
||||
|
||||
### Added
|
||||
- Adds `typeSlowly` & `appendSlowly` ([#748](https://github.com/laravel/dusk/pull/748))
|
||||
|
||||
|
||||
## [v5.9.2 (2020-02-18)](https://github.com/laravel/dusk/compare/v5.9.1...v5.9.2)
|
||||
|
||||
### Fixed
|
||||
- Bugfix quoting for `InteractsWithElements::value` ([#735](https://github.com/laravel/dusk/pull/735))
|
||||
- Remove php-webdriver constraints ([#737](https://github.com/laravel/dusk/pull/737))
|
||||
|
||||
|
||||
## [v5.9.1 (2020-02-12)](https://github.com/laravel/dusk/compare/v5.9.0...v5.9.1)
|
||||
|
||||
### Fixed
|
||||
- Adds the missing import for `InteractsWithMouse@clickAtXPath` ([#728](https://github.com/laravel/dusk/pull/728))
|
||||
- Size sanity check at fitContent ([#730](https://github.com/laravel/dusk/pull/730))
|
||||
- Lock php-webdriver constraints for now ([#733](https://github.com/laravel/dusk/pull/733))
|
||||
|
||||
|
||||
## [v5.9.0 (2020-01-28)](https://github.com/laravel/dusk/compare/v5.8.2...v5.9.0)
|
||||
|
||||
### Added
|
||||
- Add `clickAtXPath` ([#723](https://github.com/laravel/dusk/pull/723), [effe73d](https://github.com/laravel/dusk/commit/effe73d6eb61b4bd77f88814bcd679e4fceb6f25))
|
||||
- Add `ProvidesBrowser::getCallerName()` ([#725](https://github.com/laravel/dusk/pull/725))
|
||||
|
||||
### Fixed
|
||||
- Fit content to `<html>` instead of `<body>` ([#726](https://github.com/laravel/dusk/pull/726))
|
||||
|
||||
|
||||
## [v5.8.2 (2020-01-21)](https://github.com/laravel/dusk/compare/v5.8.1...v5.8.2)
|
||||
|
||||
### Changed
|
||||
- Rename php-webdriver package ([#720](https://github.com/laravel/dusk/pull/720))
|
||||
- Update jQuery file ([#721](https://github.com/laravel/dusk/pull/721))
|
||||
|
||||
### Fixed
|
||||
- Update `fitContent()` ([#717](https://github.com/laravel/dusk/pull/717))
|
||||
|
||||
|
||||
## [v5.8.1 (2020-01-07)](https://github.com/laravel/dusk/compare/v5.8.0...v5.8.1)
|
||||
|
||||
### Fixed
|
||||
- Cast boolean values to appropriate string ([#713](https://github.com/laravel/dusk/pull/713))
|
||||
|
||||
|
||||
## [v5.8.0 (2019-12-30)](https://github.com/laravel/dusk/compare/v5.7.0...v5.8.0)
|
||||
|
||||
### Added
|
||||
- Add "waitUntilMissingText" ([#706](https://github.com/laravel/dusk/pull/706))
|
||||
- Add ability to store source from browser ([#707](https://github.com/laravel/dusk/pull/707), [9c90e2a](https://github.com/laravel/dusk/commit/9c90e2a716030c9a36e6306c3f67d606a254bbb7), [1d5bc20](https://github.com/laravel/dusk/commit/1d5bc203b67ffc5a17eb1b89f3e22547e3ea174b))
|
||||
|
||||
|
||||
## [v5.7.0 (2019-12-17)](https://github.com/laravel/dusk/compare/v5.6.3...v5.7.0)
|
||||
|
||||
### Added
|
||||
- Automatically fit content on failures ([#704](https://github.com/laravel/dusk/pull/704))
|
||||
|
||||
|
||||
## [v5.6.3 (2019-12-03)](https://github.com/laravel/dusk/compare/v5.6.2...v5.6.3)
|
||||
|
||||
### Added
|
||||
- Support phpdotenv v4 ([#699](https://github.com/laravel/dusk/pull/699))
|
||||
|
||||
### Fixed
|
||||
- scrollTo: add support for selectors with quotes ([#697](https://github.com/laravel/dusk/pull/697))
|
||||
|
||||
|
||||
## [v5.6.2 (2019-11-26)](https://github.com/laravel/dusk/compare/v5.6.1...v5.6.2)
|
||||
|
||||
### Changed
|
||||
- Allow for Symfony 5 ([#696](https://github.com/laravel/dusk/pull/696))
|
||||
|
||||
|
||||
## [v5.6.1 (2019-11-12)](https://github.com/laravel/dusk/compare/v5.6.0...v5.6.1)
|
||||
|
||||
### Fixed
|
||||
- Ensure jQuery for scrollTo ([#686](https://github.com/laravel/dusk/pull/686))
|
||||
- Added missing return statement in withDuskEnvironment ([#691](https://github.com/laravel/dusk/pull/691))
|
||||
- Prevent using pcntl when not installed ([#692](https://github.com/laravel/dusk/pull/692))
|
||||
|
||||
|
||||
## [v5.6.0 (2019-10-29)](https://github.com/laravel/dusk/compare/v5.5.2...v5.6.0)
|
||||
|
||||
### Added
|
||||
- Add scrollTo method ([#684](https://github.com/laravel/dusk/pull/684))
|
||||
|
||||
### Fixed
|
||||
- Add graceful handler for `SIGINT` for .env restoration ([#682](https://github.com/laravel/dusk/pull/682), [f843b8a](https://github.com/laravel/dusk/commit/f843b8a51ae96933cefcc74dec515377d3135611))
|
||||
|
||||
|
||||
## [v5.5.2 (2019-09-24)](https://github.com/laravel/dusk/compare/v5.5.1...v5.5.2)
|
||||
|
||||
### Fixed
|
||||
- Improve detection of latest stable ChromeDriver release ([#677](https://github.com/laravel/dusk/pull/677))
|
||||
|
||||
|
||||
## [v5.5.1 (2019-09-12)](https://github.com/laravel/dusk/compare/v5.5.0...v5.5.1)
|
||||
|
||||
### Fixed
|
||||
- Update regular expression base on website changes ([#674](https://github.com/laravel/dusk/pull/674))
|
||||
|
||||
|
||||
## [v5.5.0 (2019-08-06)](https://github.com/laravel/dusk/compare/v5.4.0...v5.5.0)
|
||||
|
||||
### Added
|
||||
- Allow saving screenshots in a subdirectory ([#662](https://github.com/laravel/dusk/pull/662))
|
||||
|
||||
|
||||
## [v5.4.0 (2019-07-30)](https://github.com/laravel/dusk/compare/v5.3.0...v5.4.0)
|
||||
|
||||
### Added
|
||||
- Add assertion checks if a button is disabled or enabled ([#661](https://github.com/laravel/dusk/pull/661))
|
||||
|
||||
### Fixed
|
||||
- Update constraints for Laravel 6.0 ([e4b4d63](https://github.com/laravel/dusk/commit/e4b4d63c179bb1f228db22852bd776db750d1ec6))
|
||||
|
||||
|
||||
## [v5.3.0 (2019-07-11)](https://github.com/laravel/dusk/compare/v5.2.0...v5.3.0)
|
||||
|
||||
### Added
|
||||
- Add proxy support to the `dusk:install` command ([#659](https://github.com/laravel/dusk/pull/659))
|
||||
|
||||
|
||||
## [v5.2.0 (2019-06-25)](https://github.com/laravel/dusk/compare/v5.1.1...v5.2.0)
|
||||
|
||||
### Added
|
||||
- Add option to fullsize the browser ([#655](https://github.com/laravel/dusk/pull/655))
|
||||
|
||||
|
||||
## [v5.1.1 (2019-06-14)](https://github.com/laravel/dusk/compare/v5.1.0...v5.1.1)
|
||||
|
||||
### Fixed
|
||||
- Add latest version of Facebook Webdriver ([#654](https://github.com/laravel/dusk/pull/654))
|
||||
|
||||
|
||||
## [v5.1.0 (2019-05-02)](https://github.com/laravel/dusk/compare/v5.0.3...v5.1.0)
|
||||
|
||||
### Added
|
||||
- Implement ChromeDriverCommand ([#643](https://github.com/laravel/dusk/pull/643), [60339a5](https://github.com/laravel/dusk/commit/60339a521a1b05e55af7c90b3151557100a0db4d), [#644](https://github.com/laravel/dusk/pull/644))
|
||||
|
||||
|
||||
## [v5.0.3 (2019-04-02)](https://github.com/laravel/dusk/compare/v5.0.2...v5.0.3)
|
||||
|
||||
### Fixed
|
||||
- Fix `assertVueContains` and `assertVueDoesNotContain` ([#638](https://github.com/laravel/dusk/pull/638))
|
||||
|
||||
|
||||
## [v5.0.2 (2019-03-12)](https://github.com/laravel/dusk/compare/v5.0.1...v5.0.2)
|
||||
|
||||
### Fixed
|
||||
- Fix cookies with falsey values ([#617](https://github.com/laravel/dusk/pull/617))
|
||||
- Fix `with()` and page assertions ([#625](https://github.com/laravel/dusk/pull/625))
|
||||
- Avoid deprecation messages on PHPUnit 8 ([#620](https://github.com/laravel/dusk/pull/620))
|
||||
|
||||
|
||||
## [v5.0.1 (2019-02-27)](https://github.com/laravel/dusk/compare/v5.0.0...v5.0.1)
|
||||
|
||||
### Added
|
||||
- Added support for `phpunit.dusk.xml.dist` ([#610](https://github.com/laravel/dusk/pull/610))
|
||||
- Use custom DISPLAY variable when set on Linux ([#613](https://github.com/laravel/dusk/pull/613), [2480495](https://github.com/laravel/dusk/commit/24804953c5b521415a717afbf82ff4b938c10535))
|
||||
|
||||
### Fixed
|
||||
- Added missing dependencies ([98eccfd](https://github.com/laravel/dusk/commit/98eccfd56e9b2b23b093b801f62c766aaf67589f))
|
||||
- Fix installation of Dotenv on Laravel 5.8 ([1f67bf2](https://github.com/laravel/dusk/commit/1f67bf204fab65a212975683b5391c2f55dd3bcf))
|
||||
|
||||
|
||||
## [v5.0.0 (2019-02-12)](https://github.com/laravel/dusk/compare/v4.0.5...v5.0.0)
|
||||
|
||||
### Added
|
||||
- Support PHPUnit 8 ([788e79c](https://github.com/laravel/dusk/commit/788e79c4713a5706eeafaf7270986a71a4ed43be))
|
||||
- Support Carbon 2 ([85cfc50](https://github.com/laravel/dusk/commit/85cfc50e126a3835428577052cc0660d1c3b639e))
|
||||
- Support Laravel 5.8 ([5b2e9aa](https://github.com/laravel/dusk/commit/5b2e9aa9e4a124f4c4878f65fb644101de1644e7))
|
||||
|
||||
### Changed
|
||||
- Update minimum Laravel version ([fcb2226](https://github.com/laravel/dusk/commit/fcb2226a524f47b51b9b49316d87bc51738733d7))
|
||||
- Update Symfony dependencies to latest version ([788e79c](https://github.com/laravel/dusk/commit/788e79c4713a5706eeafaf7270986a71a4ed43be))
|
||||
- Prefer stable dependencies ([fdb2fd4](https://github.com/laravel/dusk/commit/fdb2fd4b2a2e787b08cf44649c4eef84837324ca))
|
||||
|
||||
|
||||
## [v4.0.0 (2018-08-11)](https://github.com/laravel/dusk/compare/v3.0.10...v4.0.0)
|
||||
|
||||
Dusk 4.0.0 disables cookie serialization and is intended for use with Laravel 5.6.30 or later. If you are using a previous version of Laravel, please continue using Dusk 3.0.0.
|
||||
|
||||
|
||||
## 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))
|
8
vendor/laravel/dusk/README.md
vendored
8
vendor/laravel/dusk/README.md
vendored
@@ -1,10 +1,10 @@
|
||||
<p align="center"><img src="https://laravel.com/assets/img/components/logo-dusk.svg"></p>
|
||||
<p align="center"><img src="/art/logo.svg" alt="Logo Laravel Dusk"></p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/laravel/dusk/actions"><img src="https://github.com/laravel/dusk/workflows/tests/badge.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>
|
||||
<a href="https://packagist.org/packages/laravel/dusk"><img src="https://img.shields.io/packagist/dt/laravel/dusk" alt="Total Downloads"></a>
|
||||
<a href="https://packagist.org/packages/laravel/dusk"><img src="https://img.shields.io/packagist/v/laravel/dusk" alt="Latest Stable Version"></a>
|
||||
<a href="https://packagist.org/packages/laravel/dusk"><img src="https://img.shields.io/packagist/l/laravel/dusk" alt="License"></a>
|
||||
</p>
|
||||
|
||||
## Introduction
|
||||
|
17
vendor/laravel/dusk/UPGRADE.md
vendored
17
vendor/laravel/dusk/UPGRADE.md
vendored
@@ -1,17 +0,0 @@
|
||||
# Upgrade Guide
|
||||
|
||||
## Upgrading To 5.0 From 4.0
|
||||
|
||||
### PHPUnit 8
|
||||
|
||||
Dusk now provides optional support for PHPUnit 8, which requires PHP >= 7.2 Please read through the entire list of changes in [the PHPUnit 8 release announcement](https://phpunit.de/announcements/phpunit-8.html). Using PHPUnit 8 will require Laravel 5.8, which will be released at the end of February 2019.
|
||||
|
||||
You may also continue using PHPUnit 7, which requires a minimum of PHP 7.1.
|
||||
|
||||
### Minimum Laravel version
|
||||
|
||||
Laravel 5.7 is now the minimum supported version of the framework and you should upgrade to continue using Dusk.
|
||||
|
||||
### `setUp` and `tearDown` changes
|
||||
|
||||
The `setUp` and `tearDown` methods now require the `void` return type. If you were overwriting these methods you should add it to the method signatures.
|
1
vendor/laravel/dusk/art/logo.svg
vendored
Normal file
1
vendor/laravel/dusk/art/logo.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 5.4 KiB |
23
vendor/laravel/dusk/composer.json
vendored
23
vendor/laravel/dusk/composer.json
vendored
@@ -10,21 +10,22 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.1.0",
|
||||
"php": "^7.2|^8.0",
|
||||
"ext-json": "*",
|
||||
"ext-zip": "*",
|
||||
"php-webdriver/webdriver": "^1.8.1",
|
||||
"nesbot/carbon": "^1.20|^2.0",
|
||||
"illuminate/console": "~5.7.0|~5.8.0|^6.0|^7.0",
|
||||
"illuminate/support": "~5.7.0|~5.8.0|^6.0|^7.0",
|
||||
"symfony/console": "^4.0|^5.0",
|
||||
"symfony/finder": "^4.0|^5.0",
|
||||
"symfony/process": "^4.0|^5.0",
|
||||
"vlucas/phpdotenv": "^2.2|^3.0|^4.0"
|
||||
"php-webdriver/webdriver": "^1.9.0",
|
||||
"nesbot/carbon": "^2.0",
|
||||
"illuminate/console": "^6.0|^7.0|^8.0|^9.0",
|
||||
"illuminate/support": "^6.0|^7.0|^8.0|^9.0",
|
||||
"symfony/console": "^4.3|^5.0|^6.0",
|
||||
"symfony/finder": "^4.3|^5.0|^6.0",
|
||||
"symfony/process": "^4.3|^5.0|^6.0",
|
||||
"vlucas/phpdotenv": "^3.0|^4.0|^5.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.0",
|
||||
"phpunit/phpunit": "^7.5|^8.0"
|
||||
"phpunit/phpunit": "^7.5.15|^8.4|^9.0",
|
||||
"orchestra/testbench": "^4.16|^5.17.1|^6.12.1|^7.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-pcntl": "Used to gracefully terminate Dusk when tests are running."
|
||||
@@ -41,7 +42,7 @@
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "5.0-dev"
|
||||
"dev-master": "6.x-dev"
|
||||
},
|
||||
"laravel": {
|
||||
"providers": [
|
||||
|
140
vendor/laravel/dusk/src/Browser.php
vendored
140
vendor/laravel/dusk/src/Browser.php
vendored
@@ -39,6 +39,38 @@ class Browser
|
||||
*/
|
||||
public static $storeScreenshotsAt;
|
||||
|
||||
/**
|
||||
* The common screen sizes to use for responsive screenshots.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $responsiveScreenSizes = [
|
||||
'xs' => [
|
||||
'width' => 360,
|
||||
'height' => 640,
|
||||
],
|
||||
'sm' => [
|
||||
'width' => 640,
|
||||
'height' => 360,
|
||||
],
|
||||
'md' => [
|
||||
'width' => 768,
|
||||
'height' => 1024,
|
||||
],
|
||||
'lg' => [
|
||||
'width' => 1024,
|
||||
'height' => 768,
|
||||
],
|
||||
'xl' => [
|
||||
'width' => 1280,
|
||||
'height' => 1024,
|
||||
],
|
||||
'2xl' => [
|
||||
'width' => 1536,
|
||||
'height' => 864,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* The directory that will contain any console logs.
|
||||
*
|
||||
@@ -116,7 +148,7 @@ class Browser
|
||||
* Create a browser instance.
|
||||
*
|
||||
* @param \Facebook\WebDriver\Remote\RemoteWebDriver $driver
|
||||
* @param \Laravel\Dusk\ElementResolver $resolver
|
||||
* @param \Laravel\Dusk\ElementResolver|null $resolver
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($driver, $resolver = null)
|
||||
@@ -174,6 +206,18 @@ class Browser
|
||||
return $this->visit(route($route, $parameters));
|
||||
}
|
||||
|
||||
/**
|
||||
* Browse to the "about:blank" page.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function blank()
|
||||
{
|
||||
$this->driver->navigate()->to('about:blank');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current page object.
|
||||
*
|
||||
@@ -233,6 +277,18 @@ class Browser
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the next page.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function forward()
|
||||
{
|
||||
$this->driver->navigate()->forward();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximize the browser window.
|
||||
*
|
||||
@@ -272,7 +328,7 @@ class Browser
|
||||
|
||||
$html = $this->driver->findElement(WebDriverBy::tagName('html'));
|
||||
|
||||
if (! empty($html) && ($html->getSize()->getWidth() <= 0 || $html->getSize()->getHeight() <= 0)) {
|
||||
if (! empty($html) && $html->getSize()->getWidth() > 0 && $html->getSize()->getHeight() > 0) {
|
||||
$this->resize($html->getSize()->getWidth(), $html->getSize()->getHeight());
|
||||
}
|
||||
|
||||
@@ -319,6 +375,21 @@ class Browser
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll element into view at the given selector.
|
||||
*
|
||||
* @param string $selector
|
||||
* @return $this
|
||||
*/
|
||||
public function scrollIntoView($selector)
|
||||
{
|
||||
$selector = addslashes($this->resolver->format($selector));
|
||||
|
||||
$this->driver->executeScript("document.querySelector(\"$selector\").scrollIntoView();");
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll screen to element at the given selector.
|
||||
*
|
||||
@@ -357,6 +428,26 @@ class Browser
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a series of screenshots at different browser sizes to emulate different devices.
|
||||
*
|
||||
* @param string $name
|
||||
* @return $this
|
||||
*/
|
||||
public function responsiveScreenshots($name)
|
||||
{
|
||||
if (substr($name, -1) !== '/') {
|
||||
$name .= '-';
|
||||
}
|
||||
|
||||
foreach (static::$responsiveScreenSizes as $device => $size) {
|
||||
$this->resize($size['width'], $size['height'])
|
||||
->screenshot("$name$device");
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the console output with the given name.
|
||||
*
|
||||
@@ -418,7 +509,7 @@ class Browser
|
||||
/**
|
||||
* Execute a Closure with a scoped browser instance.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param string|\Laravel\Dusk\Component $selector
|
||||
* @param \Closure $callback
|
||||
* @return $this
|
||||
*/
|
||||
@@ -430,7 +521,7 @@ class Browser
|
||||
/**
|
||||
* Execute a Closure with a scoped browser instance.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param string|\Laravel\Dusk\Component $selector
|
||||
* @param \Closure $callback
|
||||
* @return $this
|
||||
*/
|
||||
@@ -453,6 +544,47 @@ class Browser
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a Closure outside of the current browser scope.
|
||||
*
|
||||
* @param string|\Laravel\Dusk\Component $selector
|
||||
* @param \Closure $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function elsewhere($selector, Closure $callback)
|
||||
{
|
||||
$browser = new static(
|
||||
$this->driver, new ElementResolver($this->driver, 'body '.$selector)
|
||||
);
|
||||
|
||||
if ($this->page) {
|
||||
$browser->onWithoutAssert($this->page);
|
||||
}
|
||||
|
||||
if ($selector instanceof Component) {
|
||||
$browser->onComponent($selector, $this->resolver);
|
||||
}
|
||||
|
||||
call_user_func($callback, $browser);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a Closure outside of the current browser scope when the selector is available.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param \Closure $callback
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*/
|
||||
public function elsewhereWhenAvailable($selector, Closure $callback, $seconds = null)
|
||||
{
|
||||
return $this->elsewhere('', function ($browser) use ($selector, $callback, $seconds) {
|
||||
$browser->whenAvailable($selector, $callback, $seconds);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current component state.
|
||||
*
|
||||
|
30
vendor/laravel/dusk/src/Chrome/ChromeProcess.php
vendored
30
vendor/laravel/dusk/src/Chrome/ChromeProcess.php
vendored
@@ -18,7 +18,7 @@ class ChromeProcess
|
||||
/**
|
||||
* Create a new ChromeProcess instance.
|
||||
*
|
||||
* @param string $driver
|
||||
* @param string|null $driver
|
||||
* @return void
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
@@ -44,13 +44,15 @@ class ChromeProcess
|
||||
return $this->process($arguments);
|
||||
}
|
||||
|
||||
if ($this->onWindows()) {
|
||||
$this->driver = realpath(__DIR__.'/../../bin/chromedriver-win.exe');
|
||||
} elseif ($this->onMac()) {
|
||||
$this->driver = realpath(__DIR__.'/../../bin/chromedriver-mac');
|
||||
} else {
|
||||
$this->driver = realpath(__DIR__.'/../../bin/chromedriver-linux');
|
||||
}
|
||||
$filenames = [
|
||||
'linux' => 'chromedriver-linux',
|
||||
'mac' => 'chromedriver-mac',
|
||||
'mac-intel' => 'chromedriver-mac-intel',
|
||||
'mac-arm' => 'chromedriver-mac-arm',
|
||||
'win' => 'chromedriver-win.exe',
|
||||
];
|
||||
|
||||
$this->driver = realpath(__DIR__.'/../../bin').DIRECTORY_SEPARATOR.$filenames[$this->operatingSystemId()];
|
||||
|
||||
return $this->process($arguments);
|
||||
}
|
||||
@@ -64,7 +66,7 @@ class ChromeProcess
|
||||
protected function process(array $arguments = [])
|
||||
{
|
||||
return new Process(
|
||||
array_merge([realpath($this->driver)], $arguments), null, $this->chromeEnvironment()
|
||||
array_merge([$this->driver], $arguments), null, $this->chromeEnvironment()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -101,4 +103,14 @@ class ChromeProcess
|
||||
{
|
||||
return OperatingSystem::onMac();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine OS ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function operatingSystemId()
|
||||
{
|
||||
return OperatingSystem::id();
|
||||
}
|
||||
}
|
||||
|
@@ -21,25 +21,25 @@ trait InteractsWithAuthentication
|
||||
* Log into the application using a given user ID or email.
|
||||
*
|
||||
* @param object|string $userId
|
||||
* @param string $guard
|
||||
* @param string|null $guard
|
||||
* @return $this
|
||||
*/
|
||||
public function loginAs($userId, $guard = null)
|
||||
{
|
||||
$userId = method_exists($userId, 'getKey') ? $userId->getKey() : $userId;
|
||||
$userId = is_object($userId) && method_exists($userId, 'getKey') ? $userId->getKey() : $userId;
|
||||
|
||||
return $this->visit(rtrim('/_dusk/login/'.$userId.'/'.$guard, '/'));
|
||||
return $this->visit(rtrim(route('dusk.login', ['userId' => $userId, 'guard' => $guard], $this->shouldUseAbsoluteRouteForAuthentication())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Log out of the application.
|
||||
*
|
||||
* @param string $guard
|
||||
* @param string|null $guard
|
||||
* @return $this
|
||||
*/
|
||||
public function logout($guard = null)
|
||||
{
|
||||
return $this->visit(rtrim('/_dusk/logout/'.$guard, '/'));
|
||||
return $this->visit(rtrim(route('dusk.logout', ['guard' => $guard], $this->shouldUseAbsoluteRouteForAuthentication()), '/'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,7 +50,7 @@ trait InteractsWithAuthentication
|
||||
*/
|
||||
protected function currentUserInfo($guard = null)
|
||||
{
|
||||
$response = $this->visit("/_dusk/user/{$guard}");
|
||||
$response = $this->visit(route('dusk.user', ['guard' => $guard], $this->shouldUseAbsoluteRouteForAuthentication()));
|
||||
|
||||
return json_decode(strip_tags($response->driver->getPageSource()), true);
|
||||
}
|
||||
@@ -63,9 +63,11 @@ trait InteractsWithAuthentication
|
||||
*/
|
||||
public function assertAuthenticated($guard = null)
|
||||
{
|
||||
$currentUrl = $this->driver->getCurrentURL();
|
||||
|
||||
PHPUnit::assertNotEmpty($this->currentUserInfo($guard), 'The user is not authenticated.');
|
||||
|
||||
return $this;
|
||||
return $this->visit($currentUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,11 +78,13 @@ trait InteractsWithAuthentication
|
||||
*/
|
||||
public function assertGuest($guard = null)
|
||||
{
|
||||
$currentUrl = $this->driver->getCurrentURL();
|
||||
|
||||
PHPUnit::assertEmpty(
|
||||
$this->currentUserInfo($guard), 'The user is unexpectedly authenticated.'
|
||||
);
|
||||
|
||||
return $this;
|
||||
return $this->visit($currentUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,6 +96,8 @@ trait InteractsWithAuthentication
|
||||
*/
|
||||
public function assertAuthenticatedAs($user, $guard = null)
|
||||
{
|
||||
$currentUrl = $this->driver->getCurrentURL();
|
||||
|
||||
$expected = [
|
||||
'id' => $user->getAuthIdentifier(),
|
||||
'className' => get_class($user),
|
||||
@@ -102,6 +108,16 @@ trait InteractsWithAuthentication
|
||||
'The currently authenticated user is not who was expected.'
|
||||
);
|
||||
|
||||
return $this;
|
||||
return $this->visit($currentUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if route() should use an absolute path.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function shouldUseAbsoluteRouteForAuthentication()
|
||||
{
|
||||
return config('dusk.domain') !== null;
|
||||
}
|
||||
}
|
||||
|
@@ -3,6 +3,9 @@
|
||||
namespace Laravel\Dusk\Concerns;
|
||||
|
||||
use DateTimeInterface;
|
||||
use Facebook\WebDriver\Exception\NoSuchCookieException;
|
||||
use Illuminate\Cookie\CookieValuePrefix;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
|
||||
trait InteractsWithCookies
|
||||
{
|
||||
@@ -13,7 +16,7 @@ trait InteractsWithCookies
|
||||
* @param string|null $value
|
||||
* @param int|DateTimeInterface|null $expiry
|
||||
* @param array $options
|
||||
* @return string
|
||||
* @return $this|string
|
||||
*/
|
||||
public function cookie($name, $value = null, $expiry = null, array $options = [])
|
||||
{
|
||||
@@ -21,19 +24,29 @@ trait InteractsWithCookies
|
||||
return $this->addCookie($name, $value, $expiry, $options);
|
||||
}
|
||||
|
||||
if ($cookie = $this->driver->manage()->getCookieNamed($name)) {
|
||||
return decrypt(rawurldecode($cookie['value']), $unserialize = false);
|
||||
try {
|
||||
$cookie = $this->driver->manage()->getCookieNamed($name);
|
||||
} catch (NoSuchCookieException $e) {
|
||||
$cookie = null;
|
||||
}
|
||||
|
||||
if ($cookie) {
|
||||
$decryptedValue = decrypt(rawurldecode($cookie['value']), $unserialize = false);
|
||||
|
||||
$hasValuePrefix = strpos($decryptedValue, CookieValuePrefix::create($name, Crypt::getKey())) === 0;
|
||||
|
||||
return $hasValuePrefix ? CookieValuePrefix::remove($decryptedValue) : $decryptedValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set a plain cookie's value.
|
||||
* Get or set an unencrypted cookie's value.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string|null $value
|
||||
* @param int|DateTimeInterface|null $expiry
|
||||
* @param array $options
|
||||
* @return string
|
||||
* @return $this|string
|
||||
*/
|
||||
public function plainCookie($name, $value = null, $expiry = null, array $options = [])
|
||||
{
|
||||
@@ -41,7 +54,13 @@ trait InteractsWithCookies
|
||||
return $this->addCookie($name, $value, $expiry, $options, false);
|
||||
}
|
||||
|
||||
if ($cookie = $this->driver->manage()->getCookieNamed($name)) {
|
||||
try {
|
||||
$cookie = $this->driver->manage()->getCookieNamed($name);
|
||||
} catch (NoSuchCookieException $e) {
|
||||
$cookie = null;
|
||||
}
|
||||
|
||||
if ($cookie) {
|
||||
return rawurldecode($cookie['value']);
|
||||
}
|
||||
}
|
||||
@@ -59,7 +78,9 @@ trait InteractsWithCookies
|
||||
public function addCookie($name, $value, $expiry = null, array $options = [], $encrypt = true)
|
||||
{
|
||||
if ($encrypt) {
|
||||
$value = encrypt($value, $serialize = false);
|
||||
$prefix = CookieValuePrefix::create($name, Crypt::getKey());
|
||||
|
||||
$value = encrypt($prefix.$value, $serialize = false);
|
||||
}
|
||||
|
||||
if ($expiry instanceof DateTimeInterface) {
|
||||
|
@@ -6,6 +6,8 @@ use Facebook\WebDriver\Interactions\WebDriverActions;
|
||||
use Facebook\WebDriver\Remote\LocalFileDetector;
|
||||
use Facebook\WebDriver\WebDriverBy;
|
||||
use Facebook\WebDriver\WebDriverKeys;
|
||||
use Facebook\WebDriver\WebDriverSelect;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait InteractsWithElements
|
||||
@@ -43,9 +45,11 @@ trait InteractsWithElements
|
||||
{
|
||||
$this->ensurejQueryIsAvailable();
|
||||
|
||||
$selector = addslashes(trim($this->resolver->format("{$element}:contains({$link}):visible")));
|
||||
$selector = addslashes(trim($this->resolver->format("{$element}")));
|
||||
|
||||
$this->driver->executeScript("jQuery.find(\"{$selector}\")[0].click();");
|
||||
$link = str_replace("'", "\\\\'", $link);
|
||||
|
||||
$this->driver->executeScript("jQuery.find(`{$selector}:contains('{$link}'):visible`)[0].click();");
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -99,7 +103,7 @@ trait InteractsWithElements
|
||||
* Send the given keys to the element matching the given selector.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param dynamic $keys
|
||||
* @param mixed $keys
|
||||
* @return $this
|
||||
*/
|
||||
public function keys($selector, ...$keys)
|
||||
@@ -183,7 +187,7 @@ trait InteractsWithElements
|
||||
*/
|
||||
public function appendSlowly($field, $value, $pause = 100)
|
||||
{
|
||||
foreach (str_split($value) as $char) {
|
||||
foreach (preg_split('//u', $value, -1, PREG_SPLIT_NO_EMPTY) as $char) {
|
||||
$this->append($field, $char)->pause($pause);
|
||||
}
|
||||
|
||||
@@ -207,7 +211,7 @@ trait InteractsWithElements
|
||||
* Select the given value or random value of a drop-down field.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
* @param string|array|null $value
|
||||
* @return $this
|
||||
*/
|
||||
public function select($field, $value = null)
|
||||
@@ -216,18 +220,34 @@ trait InteractsWithElements
|
||||
|
||||
$options = $element->findElements(WebDriverBy::cssSelector('option:not([disabled])'));
|
||||
|
||||
if (is_null($value)) {
|
||||
$select = $element->getTagName() === 'select' ? new WebDriverSelect($element) : null;
|
||||
|
||||
$isMultiple = false;
|
||||
|
||||
if (! is_null($select)) {
|
||||
if ($isMultiple = $select->isMultiple()) {
|
||||
$select->deselectAll();
|
||||
}
|
||||
}
|
||||
|
||||
if (func_num_args() === 1) {
|
||||
$options[array_rand($options)]->click();
|
||||
} else {
|
||||
if (is_bool($value)) {
|
||||
$value = $value ? '1' : '0';
|
||||
}
|
||||
$value = collect(Arr::wrap($value))->transform(function ($value) {
|
||||
if (is_bool($value)) {
|
||||
return $value ? '1' : '0';
|
||||
}
|
||||
|
||||
return (string) $value;
|
||||
})->all();
|
||||
|
||||
foreach ($options as $option) {
|
||||
if ((string) $option->getAttribute('value') === (string) $value) {
|
||||
if (in_array((string) $option->getAttribute('value'), $value)) {
|
||||
$option->click();
|
||||
|
||||
break;
|
||||
if (! $isMultiple) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -253,7 +273,7 @@ trait InteractsWithElements
|
||||
* Check the given checkbox.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
* @param string|null $value
|
||||
* @return $this
|
||||
*/
|
||||
public function check($field, $value = null)
|
||||
@@ -271,7 +291,7 @@ trait InteractsWithElements
|
||||
* Uncheck the given checkbox.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
* @param string|null $value
|
||||
* @return $this
|
||||
*/
|
||||
public function uncheck($field, $value = null)
|
||||
|
@@ -7,7 +7,7 @@ trait InteractsWithJavascript
|
||||
/**
|
||||
* Execute JavaScript within the browser.
|
||||
*
|
||||
* @param string|array $scripts
|
||||
* @param string|array $scripts
|
||||
* @return array
|
||||
*/
|
||||
public function script($scripts)
|
||||
|
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Laravel\Dusk\Concerns;
|
||||
|
||||
use Facebook\WebDriver\Exception\ElementClickInterceptedException;
|
||||
use Facebook\WebDriver\Exception\NoSuchElementException;
|
||||
use Facebook\WebDriver\Interactions\WebDriverActions;
|
||||
use Facebook\WebDriver\WebDriverBy;
|
||||
|
||||
@@ -48,17 +50,41 @@ trait InteractsWithMouse
|
||||
{
|
||||
if (is_null($selector)) {
|
||||
(new WebDriverActions($this->driver))->click()->perform();
|
||||
} else {
|
||||
$this->resolver->findOrFail($selector)->click();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
foreach ($this->resolver->all($selector) as $element) {
|
||||
try {
|
||||
$element->click();
|
||||
|
||||
return $this;
|
||||
} catch (ElementClickInterceptedException $e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
throw $e ?? new NoSuchElementException("Unable to locate element with selector [{$selector}].");
|
||||
}
|
||||
|
||||
/**
|
||||
* Click the topmost element at the given pair of coordinates.
|
||||
*
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
* @return $this
|
||||
*/
|
||||
public function clickAtPoint($x, $y)
|
||||
{
|
||||
$this->driver->executeScript("document.elementFromPoint({$x}, {$y}).click()");
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Click the element at the given XPath expression.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param string $expression
|
||||
* @return $this
|
||||
*/
|
||||
public function clickAtXPath($expression)
|
||||
|
390
vendor/laravel/dusk/src/Concerns/MakesAssertions.php
vendored
390
vendor/laravel/dusk/src/Concerns/MakesAssertions.php
vendored
@@ -10,7 +10,14 @@ use PHPUnit\Framework\Assert as PHPUnit;
|
||||
trait MakesAssertions
|
||||
{
|
||||
/**
|
||||
* Assert that the page title is the given value.
|
||||
* Indicates the browser has made an assertion about the source code of the page.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $madeSourceAssertion = false;
|
||||
|
||||
/**
|
||||
* Assert that the page title matches the given text.
|
||||
*
|
||||
* @param string $title
|
||||
* @return $this
|
||||
@@ -26,7 +33,7 @@ trait MakesAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the page title contains the given value.
|
||||
* Assert that the page title contains the given text.
|
||||
*
|
||||
* @param string $title
|
||||
* @return $this
|
||||
@@ -35,16 +42,16 @@ trait MakesAssertions
|
||||
{
|
||||
PHPUnit::assertTrue(
|
||||
Str::contains($this->driver->getTitle(), $title),
|
||||
"Did not see expected value [{$title}] within title [{$this->driver->getTitle()}]."
|
||||
"Did not see expected text [{$title}] within title [{$this->driver->getTitle()}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given cookie is present.
|
||||
* Assert that the given encrypted cookie is present.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $name
|
||||
* @param bool $decrypt
|
||||
* @return $this
|
||||
*/
|
||||
@@ -61,7 +68,7 @@ trait MakesAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given plain cookie is present.
|
||||
* Assert that the given unencrypted cookie is present.
|
||||
*
|
||||
* @param string $name
|
||||
* @return $this
|
||||
@@ -72,9 +79,9 @@ trait MakesAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given cookie is not present.
|
||||
* Assert that the given encrypted cookie is not present.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $name
|
||||
* @param bool $decrypt
|
||||
* @return $this
|
||||
*/
|
||||
@@ -91,7 +98,7 @@ trait MakesAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given plain cookie is not present.
|
||||
* Assert that the given unencrypted cookie is not present.
|
||||
*
|
||||
* @param string $name
|
||||
* @return $this
|
||||
@@ -114,7 +121,8 @@ trait MakesAssertions
|
||||
$actual = $decrypt ? $this->cookie($name) : $this->plainCookie($name);
|
||||
|
||||
PHPUnit::assertEquals(
|
||||
$value, $actual,
|
||||
$value,
|
||||
$actual,
|
||||
"Cookie [{$name}] had value [{$actual}], but expected [{$value}]."
|
||||
);
|
||||
|
||||
@@ -122,7 +130,7 @@ trait MakesAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a cookie has a given value.
|
||||
* Assert that an unencrypted cookie has a given value.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
@@ -134,7 +142,7 @@ trait MakesAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given text appears on the page.
|
||||
* Assert that the given text is present on the page.
|
||||
*
|
||||
* @param string $text
|
||||
* @return $this
|
||||
@@ -145,7 +153,7 @@ trait MakesAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given text does not appear on the page.
|
||||
* Assert that the given text is not present on the page.
|
||||
*
|
||||
* @param string $text
|
||||
* @return $this
|
||||
@@ -156,7 +164,7 @@ trait MakesAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given text appears within the given selector.
|
||||
* Assert that the given text is present within the selector.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param string $text
|
||||
@@ -177,7 +185,7 @@ trait MakesAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given text does not appear within the given selector.
|
||||
* Assert that the given text is not present within the selector.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param string $text
|
||||
@@ -197,6 +205,66 @@ trait MakesAssertions
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that any text is present within the selector.
|
||||
*
|
||||
* @param string $selector
|
||||
* @return $this
|
||||
*/
|
||||
public function assertSeeAnythingIn($selector)
|
||||
{
|
||||
$fullSelector = $this->resolver->format($selector);
|
||||
|
||||
$element = $this->resolver->findOrFail($selector);
|
||||
|
||||
PHPUnit::assertTrue(
|
||||
$element->getText() !== '',
|
||||
"Saw unexpected text [''] within element [{$fullSelector}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that no text is present within the selector.
|
||||
*
|
||||
* @param string $selector
|
||||
* @return $this
|
||||
*/
|
||||
public function assertSeeNothingIn($selector)
|
||||
{
|
||||
$fullSelector = $this->resolver->format($selector);
|
||||
|
||||
$element = $this->resolver->findOrFail($selector);
|
||||
|
||||
PHPUnit::assertTrue(
|
||||
$element->getText() === '',
|
||||
"Did not see expected text [''] within element [{$fullSelector}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given JavaScript expression evaluates to the given value.
|
||||
*
|
||||
* @param string $expression
|
||||
* @param mixed $expected
|
||||
* @return $this
|
||||
*/
|
||||
public function assertScript($expression, $expected = true)
|
||||
{
|
||||
$expression = Str::start($expression, 'return ');
|
||||
|
||||
PHPUnit::assertEquals(
|
||||
$expected,
|
||||
$this->driver->executeScript($expression),
|
||||
"JavaScript expression [{$expression}] mismatched."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given source code is present on the page.
|
||||
*
|
||||
@@ -205,9 +273,11 @@ trait MakesAssertions
|
||||
*/
|
||||
public function assertSourceHas($code)
|
||||
{
|
||||
$this->madeSourceAssertion = true;
|
||||
|
||||
PHPUnit::assertTrue(
|
||||
Str::contains($this->driver->getPageSource(), $code),
|
||||
"Did not find expected source code [{$code}]"
|
||||
"Did not find expected source code [{$code}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
@@ -221,16 +291,18 @@ trait MakesAssertions
|
||||
*/
|
||||
public function assertSourceMissing($code)
|
||||
{
|
||||
$this->madeSourceAssertion = true;
|
||||
|
||||
PHPUnit::assertFalse(
|
||||
Str::contains($this->driver->getPageSource(), $code),
|
||||
"Found unexpected source code [{$code}]"
|
||||
"Found unexpected source code [{$code}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given link is visible.
|
||||
* Assert that the given link is present on the page.
|
||||
*
|
||||
* @param string $link
|
||||
* @return $this
|
||||
@@ -252,7 +324,7 @@ trait MakesAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given link is not visible.
|
||||
* Assert that the given link is not present on the page.
|
||||
*
|
||||
* @param string $link
|
||||
* @return $this
|
||||
@@ -283,10 +355,12 @@ trait MakesAssertions
|
||||
{
|
||||
$this->ensurejQueryIsAvailable();
|
||||
|
||||
$selector = addslashes(trim($this->resolver->format("a:contains('{$link}')")));
|
||||
$selector = addslashes(trim($this->resolver->format('a')));
|
||||
|
||||
$link = str_replace("'", "\\\\'", $link);
|
||||
|
||||
$script = <<<JS
|
||||
var link = jQuery.find("{$selector}");
|
||||
var link = jQuery.find(`{$selector}:contains('{$link}')`);
|
||||
return link.length > 0 && jQuery(link).is(':visible');
|
||||
JS;
|
||||
|
||||
@@ -294,7 +368,7 @@ JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given input or text area contains the given value.
|
||||
* Assert that the given input field has the given value.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
@@ -303,7 +377,8 @@ JS;
|
||||
public function assertInputValue($field, $value)
|
||||
{
|
||||
PHPUnit::assertEquals(
|
||||
$value, $this->inputValue($field),
|
||||
$value,
|
||||
$this->inputValue($field),
|
||||
"Expected value [{$value}] for the [{$field}] input does not equal the actual value [{$this->inputValue($field)}]."
|
||||
);
|
||||
|
||||
@@ -311,7 +386,7 @@ JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given input or text area does not contain the given value.
|
||||
* Assert that the given input field does not have the given value.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
@@ -320,7 +395,8 @@ JS;
|
||||
public function assertInputValueIsNot($field, $value)
|
||||
{
|
||||
PHPUnit::assertNotEquals(
|
||||
$value, $this->inputValue($field),
|
||||
$value,
|
||||
$this->inputValue($field),
|
||||
"Value [{$value}] for the [{$field}] input should not equal the actual value."
|
||||
);
|
||||
|
||||
@@ -343,10 +419,40 @@ JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given checkbox field is checked.
|
||||
* Assert that the given input field is present.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
* @return $this
|
||||
*/
|
||||
public function assertInputPresent($field)
|
||||
{
|
||||
$this->assertPresent(
|
||||
"input[name='{$field}'], textarea[name='{$field}'], select[name='{$field}']"
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given input field is not visible.
|
||||
*
|
||||
* @param string $field
|
||||
* @return $this
|
||||
*/
|
||||
public function assertInputMissing($field)
|
||||
{
|
||||
$this->assertMissing(
|
||||
"input[name='{$field}'], textarea[name='{$field}'], select[name='{$field}']"
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given checkbox is checked.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string|null $value
|
||||
* @return $this
|
||||
*/
|
||||
public function assertChecked($field, $value = null)
|
||||
@@ -362,10 +468,10 @@ JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given checkbox field is not checked.
|
||||
* Assert that the given checkbox is not checked.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
* @param string|null $value
|
||||
* @return $this
|
||||
*/
|
||||
public function assertNotChecked($field, $value = null)
|
||||
@@ -380,6 +486,26 @@ JS;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given checkbox is in an indeterminate state.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string|null $value
|
||||
* @return $this
|
||||
*/
|
||||
public function assertIndeterminate($field, $value = null)
|
||||
{
|
||||
$this->assertNotChecked($field, $value);
|
||||
|
||||
PHPUnit::assertSame(
|
||||
'true',
|
||||
$this->resolver->findOrFail($field)->getAttribute('indeterminate'),
|
||||
"Checkbox [{$field}] was not in indeterminate state."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given radio field is selected.
|
||||
*
|
||||
@@ -403,7 +529,7 @@ JS;
|
||||
* Assert that the given radio field is not selected.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
* @param string|null $value
|
||||
* @return $this
|
||||
*/
|
||||
public function assertRadioNotSelected($field, $value = null)
|
||||
@@ -419,7 +545,7 @@ JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given select field has the given value selected.
|
||||
* Assert that the given dropdown has the given value selected.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
@@ -436,7 +562,7 @@ JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given select field does not have the given value selected.
|
||||
* Assert that the given dropdown does not have the given value selected.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
@@ -468,7 +594,8 @@ JS;
|
||||
})->all();
|
||||
|
||||
PHPUnit::assertCount(
|
||||
count($values), $options,
|
||||
count($values),
|
||||
$options,
|
||||
'Expected options ['.implode(',', $values)."] for selection field [{$field}] to be available."
|
||||
);
|
||||
|
||||
@@ -485,7 +612,8 @@ JS;
|
||||
public function assertSelectMissingOptions($field, array $values)
|
||||
{
|
||||
PHPUnit::assertCount(
|
||||
0, $this->resolver->resolveSelectOptions($field, $values),
|
||||
0,
|
||||
$this->resolver->resolveSelectOptions($field, $values),
|
||||
'Unexpected options ['.implode(',', $values)."] for selection field [{$field}]."
|
||||
);
|
||||
|
||||
@@ -505,7 +633,7 @@ JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given value is not available to be selected on the given field.
|
||||
* Assert that the given value is not available to be selected.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
@@ -533,7 +661,7 @@ JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the element at the given selector has the given value.
|
||||
* Assert that the element matching the given selector has the given value.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param string $value
|
||||
@@ -541,15 +669,75 @@ JS;
|
||||
*/
|
||||
public function assertValue($selector, $value)
|
||||
{
|
||||
$actual = $this->resolver->findOrFail($selector)->getAttribute('value');
|
||||
$fullSelector = $this->resolver->format($selector);
|
||||
|
||||
PHPUnit::assertEquals($value, $actual);
|
||||
$this->ensureElementSupportsValueAttribute(
|
||||
$element = $this->resolver->findOrFail($selector),
|
||||
$fullSelector
|
||||
);
|
||||
|
||||
$actual = $element->getAttribute('value');
|
||||
|
||||
PHPUnit::assertEquals(
|
||||
$value,
|
||||
$actual,
|
||||
"Did not see expected value [{$value}] within element [{$fullSelector}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the element at the given selector has the given attribute value.
|
||||
* Assert that the element matching the given selector does not have the given value.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param string $value
|
||||
* @return $this
|
||||
*/
|
||||
public function assertValueIsNot($selector, $value)
|
||||
{
|
||||
$fullSelector = $this->resolver->format($selector);
|
||||
|
||||
$this->ensureElementSupportsValueAttribute(
|
||||
$element = $this->resolver->findOrFail($selector),
|
||||
$fullSelector
|
||||
);
|
||||
|
||||
$actual = $element->getAttribute('value');
|
||||
|
||||
PHPUnit::assertNotEquals(
|
||||
$value,
|
||||
$actual,
|
||||
"Saw unexpected value [{$value}] within element [{$fullSelector}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the given element supports the 'value' attribute.
|
||||
*
|
||||
* @param mixed $element
|
||||
* @param string $fullSelector
|
||||
* @return void
|
||||
*/
|
||||
public function ensureElementSupportsValueAttribute($element, $fullSelector)
|
||||
{
|
||||
PHPUnit::assertTrue(in_array($element->getTagName(), [
|
||||
'textarea',
|
||||
'select',
|
||||
'button',
|
||||
'input',
|
||||
'li',
|
||||
'meter',
|
||||
'option',
|
||||
'param',
|
||||
'progress',
|
||||
]), "This assertion cannot be used with the element [{$fullSelector}].");
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the element matching the given selector has the given value in the provided attribute.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param string $attribute
|
||||
@@ -568,7 +756,8 @@ JS;
|
||||
);
|
||||
|
||||
PHPUnit::assertEquals(
|
||||
$value, $actual,
|
||||
$value,
|
||||
$actual,
|
||||
"Expected '$attribute' attribute [{$value}] does not equal actual value [$actual]."
|
||||
);
|
||||
|
||||
@@ -576,20 +765,35 @@ JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the element at the given selector has the given data attribute value.
|
||||
* Assert that the element matching the given selector contains the given value in the provided attribute.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param string $attribute
|
||||
* @param string $value
|
||||
* @return $this
|
||||
*/
|
||||
public function assertDataAttribute($selector, $attribute, $value)
|
||||
public function assertAttributeContains($selector, $attribute, $value)
|
||||
{
|
||||
return $this->assertAttribute($selector, 'data-'.$attribute, $value);
|
||||
$fullSelector = $this->resolver->format($selector);
|
||||
|
||||
$actual = $this->resolver->findOrFail($selector)->getAttribute($attribute);
|
||||
|
||||
PHPUnit::assertNotNull(
|
||||
$actual,
|
||||
"Did not see expected attribute [{$attribute}] within element [{$fullSelector}]."
|
||||
);
|
||||
|
||||
PHPUnit::assertStringContainsString(
|
||||
$value,
|
||||
$actual,
|
||||
"Attribute '$attribute' does not contain [{$value}]. Full attribute value was [$actual]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the element at the given selector has the given aria attribute value.
|
||||
* Assert that the element matching the given selector has the given value in the provided aria attribute.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param string $attribute
|
||||
@@ -602,7 +806,20 @@ JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the element with the given selector is visible.
|
||||
* Assert that the element matching the given selector has the given value in the provided data attribute.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param string $attribute
|
||||
* @param string $value
|
||||
* @return $this
|
||||
*/
|
||||
public function assertDataAttribute($selector, $attribute, $value)
|
||||
{
|
||||
return $this->assertAttribute($selector, 'data-'.$attribute, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the element matching the given selector is visible.
|
||||
*
|
||||
* @param string $selector
|
||||
* @return $this
|
||||
@@ -620,7 +837,7 @@ JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the element with the given selector is present in the DOM.
|
||||
* Assert that the element matching the given selector is present.
|
||||
*
|
||||
* @param string $selector
|
||||
* @return $this
|
||||
@@ -638,7 +855,25 @@ JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the element with the given selector is not on the page.
|
||||
* Assert that the element matching the given selector is not present in the source.
|
||||
*
|
||||
* @param string $selector
|
||||
* @return $this
|
||||
*/
|
||||
public function assertNotPresent($selector)
|
||||
{
|
||||
$fullSelector = $this->resolver->format($selector);
|
||||
|
||||
PHPUnit::assertTrue(
|
||||
is_null($this->resolver->find($selector)),
|
||||
"Element [{$fullSelector}] is present."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the element matching the given selector is not visible.
|
||||
*
|
||||
* @param string $selector
|
||||
* @return $this
|
||||
@@ -653,13 +888,16 @@ JS;
|
||||
$missing = true;
|
||||
}
|
||||
|
||||
PHPUnit::assertTrue($missing, "Saw unexpected element [{$fullSelector}].");
|
||||
PHPUnit::assertTrue(
|
||||
$missing,
|
||||
"Saw unexpected element [{$fullSelector}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a JavaScript dialog with given message has been opened.
|
||||
* Assert that a JavaScript dialog with the given message has been opened.
|
||||
*
|
||||
* @param string $message
|
||||
* @return $this
|
||||
@@ -669,7 +907,8 @@ JS;
|
||||
$actualMessage = $this->driver->switchTo()->alert()->getText();
|
||||
|
||||
PHPUnit::assertEquals(
|
||||
$message, $actualMessage,
|
||||
$message,
|
||||
$actualMessage,
|
||||
"Expected dialog message [{$message}] does not equal actual message [{$actualMessage}]."
|
||||
);
|
||||
|
||||
@@ -788,36 +1027,46 @@ JS;
|
||||
* Assert that the Vue component's attribute at the given key has the given value.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
* @param mixed $value
|
||||
* @param string|null $componentSelector
|
||||
* @return $this
|
||||
*/
|
||||
public function assertVue($key, $value, $componentSelector = null)
|
||||
{
|
||||
PHPUnit::assertEquals($value, $this->vueAttribute($componentSelector, $key));
|
||||
$formattedValue = json_encode($value);
|
||||
|
||||
PHPUnit::assertEquals(
|
||||
$value,
|
||||
$this->vueAttribute($componentSelector, $key),
|
||||
"Did not see expected value [{$formattedValue}] at the key [{$key}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the Vue component's attribute at the given key
|
||||
* does not have the given value.
|
||||
* Assert that a given Vue component data property does not match the given value.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
* @param mixed $value
|
||||
* @param string|null $componentSelector
|
||||
* @return $this
|
||||
*/
|
||||
public function assertVueIsNot($key, $value, $componentSelector = null)
|
||||
{
|
||||
PHPUnit::assertNotEquals($value, $this->vueAttribute($componentSelector, $key));
|
||||
$formattedValue = json_encode($value);
|
||||
|
||||
PHPUnit::assertNotEquals(
|
||||
$value,
|
||||
$this->vueAttribute($componentSelector, $key),
|
||||
"Saw unexpected value [{$formattedValue}] at the key [{$key}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the Vue component's attribute at the given key
|
||||
* is an array that contains the given value.
|
||||
* Assert that a given Vue component data propertys is an array and contains the given value.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
@@ -828,15 +1077,18 @@ JS;
|
||||
{
|
||||
$attribute = $this->vueAttribute($componentSelector, $key);
|
||||
|
||||
PHPUnit::assertIsArray($attribute, "The attribute for key [$key] is not an array.");
|
||||
PHPUnit::assertIsArray(
|
||||
$attribute,
|
||||
"The attribute for key [{$key}] is not an array."
|
||||
);
|
||||
|
||||
PHPUnit::assertContains($value, $attribute);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the Vue component's attribute at the given key
|
||||
* is an array that does not contain the given value.
|
||||
* Assert that a given Vue component data property is an array and does not contain the given value.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
@@ -847,7 +1099,11 @@ JS;
|
||||
{
|
||||
$attribute = $this->vueAttribute($componentSelector, $key);
|
||||
|
||||
PHPUnit::assertIsArray($attribute, "The attribute for key [$key] is not an array.");
|
||||
PHPUnit::assertIsArray(
|
||||
$attribute,
|
||||
"The attribute for key [{$key}] is not an array."
|
||||
);
|
||||
|
||||
PHPUnit::assertNotContains($value, $attribute);
|
||||
|
||||
return $this;
|
||||
@@ -865,7 +1121,15 @@ JS;
|
||||
$fullSelector = $this->resolver->format($componentSelector);
|
||||
|
||||
return $this->driver->executeScript(
|
||||
"return document.querySelector('".$fullSelector."').__vue__.".$key
|
||||
"var el = document.querySelector('".$fullSelector."');".
|
||||
"if (typeof el.__vue__ !== 'undefined')".
|
||||
' return el.__vue__.'.$key.';'.
|
||||
'try {'.
|
||||
' var attr = el.__vueParentComponent.ctx.'.$key.';'.
|
||||
" if (typeof attr !== 'undefined')".
|
||||
' return attr;'.
|
||||
'} catch (e) {}'.
|
||||
'return el.__vueParentComponent.setupState.'.$key.';'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -4,11 +4,12 @@ namespace Laravel\Dusk\Concerns;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use PHPUnit\Framework\Assert as PHPUnit;
|
||||
use PHPUnit\Framework\Constraint\RegularExpression;
|
||||
|
||||
trait MakesUrlAssertions
|
||||
{
|
||||
/**
|
||||
* Assert that the current URL matches the given URL.
|
||||
* Assert that the current URL (without the query string) matches the given string.
|
||||
*
|
||||
* @param string $url
|
||||
* @return $this
|
||||
@@ -27,8 +28,8 @@ trait MakesUrlAssertions
|
||||
Arr::get($segments, 'path', '')
|
||||
);
|
||||
|
||||
PHPUnit::assertRegExp(
|
||||
'/^'.$pattern.'$/u', $currentUrl,
|
||||
PHPUnit::assertThat(
|
||||
$currentUrl, new RegularExpression('/^'.$pattern.'$/u'),
|
||||
"Actual URL [{$this->driver->getCurrentURL()}] does not equal expected URL [{$url}]."
|
||||
);
|
||||
|
||||
@@ -36,7 +37,7 @@ trait MakesUrlAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the current scheme matches the given scheme.
|
||||
* Assert that the current URL scheme matches the given scheme.
|
||||
*
|
||||
* @param string $scheme
|
||||
* @return $this
|
||||
@@ -47,8 +48,8 @@ trait MakesUrlAssertions
|
||||
|
||||
$actual = parse_url($this->driver->getCurrentURL(), PHP_URL_SCHEME) ?? '';
|
||||
|
||||
PHPUnit::assertRegExp(
|
||||
'/^'.$pattern.'$/u', $actual,
|
||||
PHPUnit::assertThat(
|
||||
$actual, new RegularExpression('/^'.$pattern.'$/u'),
|
||||
"Actual scheme [{$actual}] does not equal expected scheme [{$pattern}]."
|
||||
);
|
||||
|
||||
@@ -56,7 +57,7 @@ trait MakesUrlAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the current scheme does not match the given scheme.
|
||||
* Assert that the current URL scheme does not match the given scheme.
|
||||
*
|
||||
* @param string $scheme
|
||||
* @return $this
|
||||
@@ -74,7 +75,7 @@ trait MakesUrlAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the current host matches the given host.
|
||||
* Assert that the current URL host matches the given host.
|
||||
*
|
||||
* @param string $host
|
||||
* @return $this
|
||||
@@ -85,8 +86,8 @@ trait MakesUrlAssertions
|
||||
|
||||
$actual = parse_url($this->driver->getCurrentURL(), PHP_URL_HOST) ?? '';
|
||||
|
||||
PHPUnit::assertRegExp(
|
||||
'/^'.$pattern.'$/u', $actual,
|
||||
PHPUnit::assertThat(
|
||||
$actual, new RegularExpression('/^'.$pattern.'$/u'),
|
||||
"Actual host [{$actual}] does not equal expected host [{$pattern}]."
|
||||
);
|
||||
|
||||
@@ -94,7 +95,7 @@ trait MakesUrlAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the current host does not match the given host.
|
||||
* Assert that the current URL host does not match the given host.
|
||||
*
|
||||
* @param string $host
|
||||
* @return $this
|
||||
@@ -112,7 +113,7 @@ trait MakesUrlAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the current port matches the given port.
|
||||
* Assert that the current URL port matches the given port.
|
||||
*
|
||||
* @param string $port
|
||||
* @return $this
|
||||
@@ -121,10 +122,10 @@ trait MakesUrlAssertions
|
||||
{
|
||||
$pattern = str_replace('\*', '.*', preg_quote($port, '/'));
|
||||
|
||||
$actual = parse_url($this->driver->getCurrentURL(), PHP_URL_PORT) ?? '';
|
||||
$actual = (string) parse_url($this->driver->getCurrentURL(), PHP_URL_PORT) ?? '';
|
||||
|
||||
PHPUnit::assertRegExp(
|
||||
'/^'.$pattern.'$/u', $actual,
|
||||
PHPUnit::assertThat(
|
||||
$actual, new RegularExpression('/^'.$pattern.'$/u'),
|
||||
"Actual port [{$actual}] does not equal expected port [{$pattern}]."
|
||||
);
|
||||
|
||||
@@ -132,7 +133,7 @@ trait MakesUrlAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the current host does not match the given host.
|
||||
* Assert that the current URL port does not match the given port.
|
||||
*
|
||||
* @param string $port
|
||||
* @return $this
|
||||
@@ -150,27 +151,7 @@ trait MakesUrlAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the current URL path matches the given pattern.
|
||||
*
|
||||
* @param string $path
|
||||
* @return $this
|
||||
*/
|
||||
public function assertPathIs($path)
|
||||
{
|
||||
$pattern = str_replace('\*', '.*', preg_quote($path, '/'));
|
||||
|
||||
$actualPath = parse_url($this->driver->getCurrentURL(), PHP_URL_PATH) ?? '';
|
||||
|
||||
PHPUnit::assertRegExp(
|
||||
'/^'.$pattern.'$/u', $actualPath,
|
||||
"Actual path [{$actualPath}] does not equal expected path [{$path}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the current URL path begins with given path.
|
||||
* Assert that the current URL path begins with the given path.
|
||||
*
|
||||
* @param string $path
|
||||
* @return $this
|
||||
@@ -188,7 +169,27 @@ trait MakesUrlAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the current URL path does not match the given path.
|
||||
* Assert that the current path matches the given path.
|
||||
*
|
||||
* @param string $path
|
||||
* @return $this
|
||||
*/
|
||||
public function assertPathIs($path)
|
||||
{
|
||||
$pattern = str_replace('\*', '.*', preg_quote($path, '/'));
|
||||
|
||||
$actualPath = parse_url($this->driver->getCurrentURL(), PHP_URL_PATH) ?? '';
|
||||
|
||||
PHPUnit::assertThat(
|
||||
$actualPath, new RegularExpression('/^'.$pattern.'$/u'),
|
||||
"Actual path [{$actualPath}] does not equal expected path [{$path}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the current path does not match the given path.
|
||||
*
|
||||
* @param string $path
|
||||
* @return $this
|
||||
@@ -206,63 +207,7 @@ trait MakesUrlAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the current URL fragment matches the given pattern.
|
||||
*
|
||||
* @param string $fragment
|
||||
* @return $this
|
||||
*/
|
||||
public function assertFragmentIs($fragment)
|
||||
{
|
||||
$pattern = preg_quote($fragment, '/');
|
||||
|
||||
$actualFragment = (string) parse_url($this->driver->executeScript('return window.location.href;'), PHP_URL_FRAGMENT);
|
||||
|
||||
PHPUnit::assertRegExp(
|
||||
'/^'.str_replace('\*', '.*', $pattern).'$/u', $actualFragment,
|
||||
"Actual fragment [{$actualFragment}] does not equal expected fragment [{$fragment}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the current URL fragment begins with given fragment.
|
||||
*
|
||||
* @param string $fragment
|
||||
* @return $this
|
||||
*/
|
||||
public function assertFragmentBeginsWith($fragment)
|
||||
{
|
||||
$actualFragment = (string) parse_url($this->driver->executeScript('return window.location.href;'), PHP_URL_FRAGMENT);
|
||||
|
||||
PHPUnit::assertStringStartsWith(
|
||||
$fragment, $actualFragment,
|
||||
"Actual fragment [$actualFragment] does not begin with expected fragment [$fragment]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the current URL fragment does not match the given fragment.
|
||||
*
|
||||
* @param string $fragment
|
||||
* @return $this
|
||||
*/
|
||||
public function assertFragmentIsNot($fragment)
|
||||
{
|
||||
$actualFragment = (string) parse_url($this->driver->executeScript('return window.location.href;'), PHP_URL_FRAGMENT);
|
||||
|
||||
PHPUnit::assertNotEquals(
|
||||
$fragment, $actualFragment,
|
||||
"Fragment [{$fragment}] should not equal the actual value."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the current URL path matches the given route.
|
||||
* Assert that the current URL matches the given named route's URL.
|
||||
*
|
||||
* @param string $route
|
||||
* @param array $parameters
|
||||
@@ -274,10 +219,10 @@ trait MakesUrlAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a query string parameter is present and has a given value.
|
||||
* Assert that the given query string parameter is present and has a given value.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
* @param string|null $value
|
||||
* @return $this
|
||||
*/
|
||||
public function assertQueryStringHas($name, $value = null)
|
||||
@@ -326,6 +271,62 @@ trait MakesUrlAssertions
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the URL's current hash fragment matches the given fragment.
|
||||
*
|
||||
* @param string $fragment
|
||||
* @return $this
|
||||
*/
|
||||
public function assertFragmentIs($fragment)
|
||||
{
|
||||
$pattern = preg_quote($fragment, '/');
|
||||
|
||||
$actualFragment = (string) parse_url($this->driver->executeScript('return window.location.href;'), PHP_URL_FRAGMENT);
|
||||
|
||||
PHPUnit::assertThat(
|
||||
$actualFragment, new RegularExpression('/^'.str_replace('\*', '.*', $pattern).'$/u'),
|
||||
"Actual fragment [{$actualFragment}] does not equal expected fragment [{$fragment}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the URL's current hash fragment begins with the given fragment.
|
||||
*
|
||||
* @param string $fragment
|
||||
* @return $this
|
||||
*/
|
||||
public function assertFragmentBeginsWith($fragment)
|
||||
{
|
||||
$actualFragment = (string) parse_url($this->driver->executeScript('return window.location.href;'), PHP_URL_FRAGMENT);
|
||||
|
||||
PHPUnit::assertStringStartsWith(
|
||||
$fragment, $actualFragment,
|
||||
"Actual fragment [$actualFragment] does not begin with expected fragment [$fragment]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the URL's current hash fragment does not match the given fragment.
|
||||
*
|
||||
* @param string $fragment
|
||||
* @return $this
|
||||
*/
|
||||
public function assertFragmentIsNot($fragment)
|
||||
{
|
||||
$actualFragment = (string) parse_url($this->driver->executeScript('return window.location.href;'), PHP_URL_FRAGMENT);
|
||||
|
||||
PHPUnit::assertNotEquals(
|
||||
$fragment, $actualFragment,
|
||||
"Fragment [{$fragment}] should not equal the actual value."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given query string parameter is present.
|
||||
*
|
||||
|
@@ -29,6 +29,7 @@ trait ProvidesBrowser
|
||||
* Tear down the Dusk test case class.
|
||||
*
|
||||
* @afterClass
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function tearDownDuskClass()
|
||||
@@ -68,10 +69,12 @@ trait ProvidesBrowser
|
||||
$callback(...$browsers->all());
|
||||
} catch (Exception $e) {
|
||||
$this->captureFailuresFor($browsers);
|
||||
$this->storeSourceLogsFor($browsers);
|
||||
|
||||
throw $e;
|
||||
} catch (Throwable $e) {
|
||||
$this->captureFailuresFor($browsers);
|
||||
$this->storeSourceLogsFor($browsers);
|
||||
|
||||
throw $e;
|
||||
} finally {
|
||||
@@ -162,6 +165,22 @@ trait ProvidesBrowser
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the source code for the given browsers (if necessary).
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $browsers
|
||||
* @return void
|
||||
*/
|
||||
protected function storeSourceLogsFor($browsers)
|
||||
{
|
||||
$browsers->each(function ($browser, $key) {
|
||||
if (property_exists($browser, 'madeSourceAssertion') &&
|
||||
$browser->madeSourceAssertion) {
|
||||
$browser->storeSource($this->getCallerName().'-'.$key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all of the browsers except the primary (first) one.
|
||||
*
|
||||
|
@@ -6,6 +6,7 @@ use Carbon\Carbon;
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Facebook\WebDriver\Exception\NoSuchElementException;
|
||||
use Facebook\WebDriver\Exception\ScriptTimeoutException;
|
||||
use Facebook\WebDriver\Exception\TimeOutException;
|
||||
use Facebook\WebDriver\WebDriverExpectedCondition;
|
||||
use Illuminate\Support\Arr;
|
||||
@@ -18,7 +19,7 @@ trait WaitsForElements
|
||||
*
|
||||
* @param string $selector
|
||||
* @param \Closure $callback
|
||||
* @param int $seconds
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Facebook\WebDriver\Exception\TimeOutException
|
||||
@@ -29,10 +30,10 @@ trait WaitsForElements
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the given selector to be visible.
|
||||
* Wait for the given selector to become visible.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param int $seconds
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Facebook\WebDriver\Exception\TimeOutException
|
||||
@@ -50,7 +51,7 @@ trait WaitsForElements
|
||||
* Wait for the given selector to be removed.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param int $seconds
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Facebook\WebDriver\Exception\TimeOutException
|
||||
@@ -74,7 +75,7 @@ trait WaitsForElements
|
||||
* Wait for the given text to be removed.
|
||||
*
|
||||
* @param string $text
|
||||
* @param int $seconds
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Facebook\WebDriver\Exception\TimeOutException
|
||||
@@ -91,10 +92,10 @@ trait WaitsForElements
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the given text to be visible.
|
||||
* Wait for the given text to become visible.
|
||||
*
|
||||
* @param array|string $text
|
||||
* @param int $seconds
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Facebook\WebDriver\Exception\TimeOutException
|
||||
@@ -111,10 +112,29 @@ trait WaitsForElements
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the given link to be visible.
|
||||
* Wait for the given text to become visible inside the given selector.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param array|string $text
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Facebook\WebDriver\Exception\TimeOutException
|
||||
*/
|
||||
public function waitForTextIn($selector, $text, $seconds = null)
|
||||
{
|
||||
$message = 'Waited %s seconds for text "'.$text.'" in selector '.$selector;
|
||||
|
||||
return $this->waitUsing($seconds, 100, function () use ($selector, $text) {
|
||||
return $this->assertSeeIn($selector, $text);
|
||||
}, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the given link to become visible.
|
||||
*
|
||||
* @param string $link
|
||||
* @param int $seconds
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Facebook\WebDriver\Exception\TimeOutException
|
||||
@@ -128,11 +148,23 @@ trait WaitsForElements
|
||||
}, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for an input field to become visible.
|
||||
*
|
||||
* @param string $field
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*/
|
||||
public function waitForInput($field, $seconds = null)
|
||||
{
|
||||
return $this->waitFor("input[name='{$field}'], textarea[name='{$field}'], select[name='{$field}']", $seconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the given location.
|
||||
*
|
||||
* @param string $path
|
||||
* @param int $seconds
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Facebook\WebDriver\Exception\TimeOutException
|
||||
@@ -141,7 +173,9 @@ trait WaitsForElements
|
||||
{
|
||||
$message = $this->formatTimeOutMessage('Waited %s seconds for location', $path);
|
||||
|
||||
return $this->waitUntil("window.location.pathname == '{$path}'", $seconds, $message);
|
||||
return Str::startsWith($path, ['http://', 'https://'])
|
||||
? $this->waitUntil('`${location.protocol}//${location.host}${location.pathname}` == \''.$path.'\'', $seconds, $message)
|
||||
: $this->waitUntil("window.location.pathname == '{$path}'", $seconds, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,7 +183,7 @@ trait WaitsForElements
|
||||
*
|
||||
* @param string $route
|
||||
* @param array $parameters
|
||||
* @param int $seconds
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Facebook\WebDriver\Exception\TimeOutException
|
||||
@@ -159,12 +193,48 @@ trait WaitsForElements
|
||||
return $this->waitForLocation(route($route, $parameters, false), $seconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until an element is enabled.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*/
|
||||
public function waitUntilEnabled($selector, $seconds = null)
|
||||
{
|
||||
$message = $this->formatTimeOutMessage('Waited %s seconds for element to be enabled', $selector);
|
||||
|
||||
$this->waitUsing($seconds, 100, function () use ($selector) {
|
||||
return $this->resolver->findOrFail($selector)->isEnabled();
|
||||
}, $message);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until an element is disabled.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*/
|
||||
public function waitUntilDisabled($selector, $seconds = null)
|
||||
{
|
||||
$message = $this->formatTimeOutMessage('Waited %s seconds for element to be disabled', $selector);
|
||||
|
||||
$this->waitUsing($seconds, 100, function () use ($selector) {
|
||||
return ! $this->resolver->findOrFail($selector)->isEnabled();
|
||||
}, $message);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until the given script returns true.
|
||||
*
|
||||
* @param string $script
|
||||
* @param int $seconds
|
||||
* @param string $message
|
||||
* @param int|null $seconds
|
||||
* @param string|null $message
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Facebook\WebDriver\Exception\TimeOutException
|
||||
@@ -190,6 +260,7 @@ trait WaitsForElements
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
* @param string|null $componentSelector
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*/
|
||||
public function waitUntilVue($key, $value, $componentSelector = null, $seconds = null)
|
||||
@@ -207,6 +278,7 @@ trait WaitsForElements
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
* @param string|null $componentSelector
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*/
|
||||
public function waitUntilVueIsNot($key, $value, $componentSelector = null, $seconds = null)
|
||||
@@ -221,7 +293,7 @@ trait WaitsForElements
|
||||
/**
|
||||
* Wait for a JavaScript dialog to open.
|
||||
*
|
||||
* @param int $seconds
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*/
|
||||
public function waitForDialog($seconds = null)
|
||||
@@ -238,8 +310,8 @@ trait WaitsForElements
|
||||
/**
|
||||
* Wait for the current page to reload.
|
||||
*
|
||||
* @param \Closure $callback
|
||||
* @param int $seconds
|
||||
* @param \Closure|null $callback
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Facebook\WebDriver\Exception\TimeOutException
|
||||
@@ -259,10 +331,55 @@ trait WaitsForElements
|
||||
}, 'Waited %s seconds for page reload.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Click an element and wait for the page to reload.
|
||||
*
|
||||
* @param string|null $selector
|
||||
* @return $this
|
||||
*/
|
||||
public function clickAndWaitForReload($selector = null)
|
||||
{
|
||||
return $this->waitForReload(function ($browser) use ($selector) {
|
||||
$browser->click($selector);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the given event type to occur on a target.
|
||||
*
|
||||
* @param string $type
|
||||
* @param string|null $target
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Facebook\WebDriver\Exception\TimeOutException
|
||||
*/
|
||||
public function waitForEvent($type, $target = null, $seconds = null)
|
||||
{
|
||||
$seconds = is_null($seconds) ? static::$waitSeconds : $seconds;
|
||||
|
||||
if ($target !== 'document' && $target !== 'window') {
|
||||
$target = $this->resolver->findOrFail($target ?? '');
|
||||
}
|
||||
|
||||
$this->driver->manage()->timeouts()->setScriptTimeout($seconds);
|
||||
|
||||
try {
|
||||
$this->driver->executeAsyncScript(
|
||||
'eval(arguments[0]).addEventListener(arguments[1], () => arguments[2](), { once: true });',
|
||||
[$target, $type]
|
||||
);
|
||||
} catch (ScriptTimeoutException $e) {
|
||||
throw new TimeoutException("Waited {$seconds} seconds for event [{$type}].");
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the given callback to be true.
|
||||
*
|
||||
* @param int $seconds
|
||||
* @param int|null $seconds
|
||||
* @param int $interval
|
||||
* @param \Closure $callback
|
||||
* @param string|null $message
|
||||
|
@@ -4,6 +4,7 @@ namespace Laravel\Dusk\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Laravel\Dusk\OperatingSystem;
|
||||
use Symfony\Component\Process\Process;
|
||||
use ZipArchive;
|
||||
|
||||
/**
|
||||
@@ -18,6 +19,7 @@ class ChromeDriverCommand extends Command
|
||||
*/
|
||||
protected $signature = 'dusk:chrome-driver {version?}
|
||||
{--all : Install a ChromeDriver binary for every OS}
|
||||
{--detect : Detect the installed Chrome / Chromium version}
|
||||
{--proxy= : The proxy to download the binary through (example: "tcp://127.0.0.1:9000")}
|
||||
{--ssl-no-verify : Bypass SSL certificate verification when installing through a proxy}';
|
||||
|
||||
@@ -57,6 +59,8 @@ class ChromeDriverCommand extends Command
|
||||
protected $slugs = [
|
||||
'linux' => 'linux64',
|
||||
'mac' => 'mac64',
|
||||
'mac-intel' => 'mac64',
|
||||
'mac-arm' => 'mac_arm64',
|
||||
'win' => 'win32',
|
||||
];
|
||||
|
||||
@@ -102,6 +106,32 @@ class ChromeDriverCommand extends Command
|
||||
*/
|
||||
protected $directory = __DIR__.'/../../bin/';
|
||||
|
||||
/**
|
||||
* The default commands to detect the installed Chrome / Chromium version.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $chromeVersionCommands = [
|
||||
'linux' => [
|
||||
'/usr/bin/google-chrome --version',
|
||||
'/usr/bin/chromium-browser --version',
|
||||
'/usr/bin/chromium --version',
|
||||
'/usr/bin/google-chrome-stable --version',
|
||||
],
|
||||
'mac' => [
|
||||
'/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version',
|
||||
],
|
||||
'mac-intel' => [
|
||||
'/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version',
|
||||
],
|
||||
'mac-arm' => [
|
||||
'/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version',
|
||||
],
|
||||
'win' => [
|
||||
'reg query "HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon" /v version',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
@@ -139,6 +169,10 @@ class ChromeDriverCommand extends Command
|
||||
{
|
||||
$version = $this->argument('version');
|
||||
|
||||
if ($this->option('detect')) {
|
||||
$version = $this->detectChromeVersion(OperatingSystem::id());
|
||||
}
|
||||
|
||||
if (! $version) {
|
||||
return $this->latestVersion();
|
||||
}
|
||||
@@ -165,7 +199,49 @@ class ChromeDriverCommand extends Command
|
||||
*/
|
||||
protected function latestVersion()
|
||||
{
|
||||
return trim(file_get_contents($this->latestVersionUrl));
|
||||
$streamOptions = [];
|
||||
|
||||
if ($this->option('ssl-no-verify')) {
|
||||
$streamOptions = [
|
||||
'ssl' => [
|
||||
'verify_peer' => false,
|
||||
'verify_peer_name' => false,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if ($this->option('proxy')) {
|
||||
$streamOptions['http'] = ['proxy' => $this->option('proxy'), 'request_fulluri' => true];
|
||||
}
|
||||
|
||||
return trim(file_get_contents($this->latestVersionUrl, false, stream_context_create($streamOptions)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the installed Chrome / Chromium major version.
|
||||
*
|
||||
* @param string $os
|
||||
* @return int|bool
|
||||
*/
|
||||
protected function detectChromeVersion($os)
|
||||
{
|
||||
foreach ($this->chromeVersionCommands[$os] as $command) {
|
||||
$process = Process::fromShellCommandline($command);
|
||||
|
||||
$process->run();
|
||||
|
||||
preg_match('/(\d+)(\.\d+){3}/', $process->getOutput(), $matches);
|
||||
|
||||
if (! isset($matches[1])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
$this->error('Chrome version could not be detected.');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
87
vendor/laravel/dusk/src/Console/DuskCommand.php
vendored
87
vendor/laravel/dusk/src/Console/DuskCommand.php
vendored
@@ -17,7 +17,10 @@ class DuskCommand extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'dusk {--without-tty : Disable output to TTY}';
|
||||
protected $signature = 'dusk
|
||||
{--browse : Open a browser instead of using headless mode}
|
||||
{--without-tty : Disable output to TTY}
|
||||
{--pest : Run the tests using Pest}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@@ -56,12 +59,18 @@ class DuskCommand extends Command
|
||||
|
||||
$this->purgeConsoleLogs();
|
||||
|
||||
$options = array_slice($_SERVER['argv'], $this->option('without-tty') ? 3 : 2);
|
||||
$this->purgeSourceLogs();
|
||||
|
||||
$options = collect($_SERVER['argv'])
|
||||
->slice(2)
|
||||
->diff(['--browse', '--without-tty'])
|
||||
->values()
|
||||
->all();
|
||||
|
||||
return $this->withDuskEnvironment(function () use ($options) {
|
||||
$process = (new Process(array_merge(
|
||||
$this->binary(), $this->phpunitArguments($options)
|
||||
)))->setTimeout(null);
|
||||
), null, $this->env()))->setTimeout(null);
|
||||
|
||||
try {
|
||||
$process->setTty(! $this->option('without-tty'));
|
||||
@@ -88,11 +97,17 @@ class DuskCommand extends Command
|
||||
*/
|
||||
protected function binary()
|
||||
{
|
||||
if ('phpdbg' === PHP_SAPI) {
|
||||
return [PHP_BINARY, '-qrr', 'vendor/phpunit/phpunit/phpunit'];
|
||||
$binaryPath = 'vendor/phpunit/phpunit/phpunit';
|
||||
|
||||
if ($this->option('pest')) {
|
||||
$binaryPath = 'vendor/pestphp/pest/bin/pest';
|
||||
}
|
||||
|
||||
return [PHP_BINARY, 'vendor/phpunit/phpunit/phpunit'];
|
||||
if ('phpdbg' === PHP_SAPI) {
|
||||
return [PHP_BINARY, '-qrr', $binaryPath];
|
||||
}
|
||||
|
||||
return [PHP_BINARY, $binaryPath];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,7 +119,7 @@ class DuskCommand extends Command
|
||||
protected function phpunitArguments($options)
|
||||
{
|
||||
$options = array_values(array_filter($options, function ($option) {
|
||||
return ! Str::startsWith($option, '--env=');
|
||||
return ! Str::startsWith($option, ['--env=', '--pest']);
|
||||
}));
|
||||
|
||||
if (! file_exists($file = base_path('phpunit.dusk.xml'))) {
|
||||
@@ -114,6 +129,18 @@ class DuskCommand extends Command
|
||||
return array_merge(['-c', $file], $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PHP binary environment variables.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
protected function env()
|
||||
{
|
||||
if ($this->option('browse') && ! isset($_ENV['CI']) && ! isset($_SERVER['CI'])) {
|
||||
return ['DUSK_HEADLESS_DISABLED' => true];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge the failure screenshots.
|
||||
*
|
||||
@@ -121,19 +148,9 @@ class DuskCommand extends Command
|
||||
*/
|
||||
protected function purgeScreenshots()
|
||||
{
|
||||
$path = base_path('tests/Browser/screenshots');
|
||||
|
||||
if (! is_dir($path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$files = Finder::create()->files()
|
||||
->in($path)
|
||||
->name('failure-*');
|
||||
|
||||
foreach ($files as $file) {
|
||||
@unlink($file->getRealPath());
|
||||
}
|
||||
$this->purgeDebuggingFiles(
|
||||
base_path('tests/Browser/screenshots'), 'failure-*'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,15 +160,39 @@ class DuskCommand extends Command
|
||||
*/
|
||||
protected function purgeConsoleLogs()
|
||||
{
|
||||
$path = base_path('tests/Browser/console');
|
||||
$this->purgeDebuggingFiles(
|
||||
base_path('tests/Browser/console'), '*.log'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge the source logs.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function purgeSourceLogs()
|
||||
{
|
||||
$this->purgeDebuggingFiles(
|
||||
base_path('tests/Browser/source'), '*.txt'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge debugging files based on path and patterns.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $patterns
|
||||
* @return void
|
||||
*/
|
||||
protected function purgeDebuggingFiles($path, $patterns)
|
||||
{
|
||||
if (! is_dir($path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$files = Finder::create()->files()
|
||||
->in($path)
|
||||
->name('*.log');
|
||||
->in($path)
|
||||
->name($patterns);
|
||||
|
||||
foreach ($files as $file) {
|
||||
@unlink($file->getRealPath());
|
||||
|
@@ -9,7 +9,10 @@ class DuskFailsCommand extends DuskCommand
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'dusk:fails {--without-tty : Disable output to TTY}';
|
||||
protected $signature = 'dusk:fails
|
||||
{--browse : Open a browser instead of using headless mode}
|
||||
{--without-tty : Disable output to TTY}
|
||||
{--pest : Run the tests using Pest}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
|
@@ -45,6 +45,10 @@ class InstallCommand extends Command
|
||||
$this->createConsoleDirectory();
|
||||
}
|
||||
|
||||
if (! is_dir(base_path('tests/Browser/source'))) {
|
||||
$this->createSourceDirectory();
|
||||
}
|
||||
|
||||
$stubs = [
|
||||
'ExampleTest.stub' => base_path('tests/Browser/ExampleTest.php'),
|
||||
'HomePage.stub' => base_path('tests/Browser/Pages/HomePage.php'),
|
||||
@@ -100,6 +104,20 @@ class InstallCommand extends Command
|
||||
|
||||
file_put_contents(base_path('tests/Browser/console/.gitignore'), '*
|
||||
!.gitignore
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the source directory.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function createSourceDirectory()
|
||||
{
|
||||
mkdir(base_path('tests/Browser/source'), 0755, true);
|
||||
|
||||
file_put_contents(base_path('tests/Browser/source/.gitignore'), '*
|
||||
!.gitignore
|
||||
');
|
||||
}
|
||||
}
|
||||
|
114
vendor/laravel/dusk/src/Console/PurgeCommand.php
vendored
Normal file
114
vendor/laravel/dusk/src/Console/PurgeCommand.php
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Dusk\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
class PurgeCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'dusk:purge';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Purge dusk test debugging files';
|
||||
|
||||
/**
|
||||
* 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();
|
||||
$this->purgeSourceLogs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge the failure screenshots.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function purgeScreenshots()
|
||||
{
|
||||
$this->purgeDebuggingFiles(
|
||||
'tests/Browser/screenshots', 'failure-*'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge the console logs.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function purgeConsoleLogs()
|
||||
{
|
||||
$this->purgeDebuggingFiles(
|
||||
'tests/Browser/console', '*.log'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge the source logs.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function purgeSourceLogs()
|
||||
{
|
||||
$this->purgeDebuggingFiles(
|
||||
'tests/Browser/source', '*.txt'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge debugging files based on path and patterns.
|
||||
*
|
||||
* @param string $relativePath
|
||||
* @param string $patterns
|
||||
* @return void
|
||||
*/
|
||||
protected function purgeDebuggingFiles($relativePath, $patterns)
|
||||
{
|
||||
$path = base_path($relativePath);
|
||||
|
||||
if (! is_dir($path)) {
|
||||
$this->warn(
|
||||
"Unable to purge missing directory [{$relativePath}].", OutputInterface::VERBOSITY_DEBUG
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$files = Finder::create()->files()
|
||||
->in($path)
|
||||
->name($patterns);
|
||||
|
||||
foreach ($files as $file) {
|
||||
@unlink($file->getRealPath());
|
||||
}
|
||||
|
||||
$this->info("Purged \"{$patterns}\" from [{$relativePath}].");
|
||||
}
|
||||
}
|
@@ -3,6 +3,7 @@
|
||||
namespace DummyNamespace;
|
||||
|
||||
use Laravel\Dusk\Browser;
|
||||
use Laravel\Dusk\Page;
|
||||
|
||||
class DummyClass extends Page
|
||||
{
|
||||
|
41
vendor/laravel/dusk/src/DuskServiceProvider.php
vendored
41
vendor/laravel/dusk/src/DuskServiceProvider.php
vendored
@@ -15,32 +15,28 @@ class DuskServiceProvider extends ServiceProvider
|
||||
public function boot()
|
||||
{
|
||||
if (! $this->app->environment('production')) {
|
||||
Route::get('/_dusk/login/{userId}/{guard?}', [
|
||||
'middleware' => 'web',
|
||||
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@login',
|
||||
]);
|
||||
Route::group(array_filter([
|
||||
'prefix' => config('dusk.path', '_dusk'),
|
||||
'domain' => config('dusk.domain', null),
|
||||
'middleware' => config('dusk.middleware', 'web'),
|
||||
]), function () {
|
||||
Route::get('/login/{userId}/{guard?}', [
|
||||
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@login',
|
||||
'as' => 'dusk.login',
|
||||
]);
|
||||
|
||||
Route::get('/_dusk/logout/{guard?}', [
|
||||
'middleware' => 'web',
|
||||
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@logout',
|
||||
]);
|
||||
Route::get('/logout/{guard?}', [
|
||||
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@logout',
|
||||
'as' => 'dusk.logout',
|
||||
]);
|
||||
|
||||
Route::get('/_dusk/user/{guard?}', [
|
||||
'middleware' => 'web',
|
||||
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@user',
|
||||
]);
|
||||
Route::get('/user/{guard?}', [
|
||||
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@user',
|
||||
'as' => 'dusk.user',
|
||||
]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register any package services.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
if ($this->app->runningInConsole()) {
|
||||
$this->commands([
|
||||
Console\InstallCommand::class,
|
||||
@@ -48,6 +44,7 @@ class DuskServiceProvider extends ServiceProvider
|
||||
Console\DuskFailsCommand::class,
|
||||
Console\MakeCommand::class,
|
||||
Console\PageCommand::class,
|
||||
Console\PurgeCommand::class,
|
||||
Console\ComponentCommand::class,
|
||||
Console\ChromeDriverCommand::class,
|
||||
]);
|
||||
|
18
vendor/laravel/dusk/src/ElementResolver.php
vendored
18
vendor/laravel/dusk/src/ElementResolver.php
vendored
@@ -87,7 +87,7 @@ class ElementResolver
|
||||
}
|
||||
|
||||
return $this->firstOrFail([
|
||||
$field, "input[name='{$field}']", "textarea[name='{$field}']",
|
||||
"input[name='{$field}']", "textarea[name='{$field}']", $field,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ class ElementResolver
|
||||
}
|
||||
|
||||
return $this->firstOrFail([
|
||||
$field, "select[name='{$field}']",
|
||||
"select[name='{$field}']", $field,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ class ElementResolver
|
||||
* Resolve the element for a given radio "field" / value.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
* @param string|null $value
|
||||
* @return \Facebook\WebDriver\Remote\RemoteWebElement
|
||||
*
|
||||
* @throws \Exception
|
||||
@@ -156,7 +156,7 @@ class ElementResolver
|
||||
}
|
||||
|
||||
return $this->firstOrFail([
|
||||
$field, "input[type=radio][name='{$field}'][value='{$value}']",
|
||||
"input[type=radio][name='{$field}'][value='{$value}']", $field,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ class ElementResolver
|
||||
* Resolve the element for a given checkbox "field".
|
||||
*
|
||||
* @param string|null $field
|
||||
* @param string $value
|
||||
* @param string|null $value
|
||||
* @return \Facebook\WebDriver\Remote\RemoteWebElement
|
||||
*
|
||||
* @throws \Exception
|
||||
@@ -186,7 +186,7 @@ class ElementResolver
|
||||
}
|
||||
|
||||
return $this->firstOrFail([
|
||||
$field, $selector,
|
||||
$selector, $field,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ class ElementResolver
|
||||
}
|
||||
|
||||
return $this->firstOrFail([
|
||||
$field, "input[type=file][name='{$field}']",
|
||||
"input[type=file][name='{$field}']", $field,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -224,8 +224,8 @@ class ElementResolver
|
||||
}
|
||||
|
||||
return $this->firstOrFail([
|
||||
$field, "input[name='{$field}']", "textarea[name='{$field}']",
|
||||
"select[name='{$field}']", "button[name='{$field}']",
|
||||
"input[name='{$field}']", "textarea[name='{$field}']",
|
||||
"select[name='{$field}']", "button[name='{$field}']", $field,
|
||||
]);
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@
|
||||
namespace Laravel\Dusk\Http\Controllers;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class UserController
|
||||
@@ -31,7 +32,7 @@ class UserController
|
||||
* Login using the given user ID / email.
|
||||
*
|
||||
* @param string $userId
|
||||
* @param string $guard
|
||||
* @param string|null $guard
|
||||
* @return void
|
||||
*/
|
||||
public function login($userId, $guard = null)
|
||||
@@ -50,12 +51,16 @@ class UserController
|
||||
/**
|
||||
* Log the user out of the application.
|
||||
*
|
||||
* @param string $guard
|
||||
* @param string|null $guard
|
||||
* @return void
|
||||
*/
|
||||
public function logout($guard = null)
|
||||
{
|
||||
Auth::guard($guard ?: config('auth.defaults.guard'))->logout();
|
||||
$guard = $guard ?: config('auth.defaults.guard');
|
||||
|
||||
Auth::guard($guard)->logout();
|
||||
|
||||
Session::forget('password_hash_'.$guard);
|
||||
}
|
||||
|
||||
/**
|
||||
|
25
vendor/laravel/dusk/src/OperatingSystem.php
vendored
25
vendor/laravel/dusk/src/OperatingSystem.php
vendored
@@ -13,7 +13,13 @@ class OperatingSystem
|
||||
*/
|
||||
public static function id()
|
||||
{
|
||||
return static::onWindows() ? 'win' : (static::onMac() ? 'mac' : 'linux');
|
||||
if (static::onWindows()) {
|
||||
return 'win';
|
||||
} elseif (static::onMac()) {
|
||||
return static::macArchitectureId();
|
||||
}
|
||||
|
||||
return 'linux';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,4 +41,21 @@ class OperatingSystem
|
||||
{
|
||||
return PHP_OS === 'Darwin';
|
||||
}
|
||||
|
||||
/**
|
||||
* Mac platform architecture.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function macArchitectureId()
|
||||
{
|
||||
switch (php_uname('m')) {
|
||||
case 'arm64':
|
||||
return 'mac-arm';
|
||||
case 'x86_64':
|
||||
return 'mac-intel';
|
||||
default:
|
||||
return 'mac';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
12
vendor/laravel/dusk/src/TestCase.php
vendored
12
vendor/laravel/dusk/src/TestCase.php
vendored
@@ -54,7 +54,7 @@ abstract class TestCase extends FoundationTestCase
|
||||
*/
|
||||
protected function baseUrl()
|
||||
{
|
||||
return config('app.url');
|
||||
return rtrim(config('app.url'), '/');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,4 +68,14 @@ abstract class TestCase extends FoundationTestCase
|
||||
{
|
||||
throw new Exception('User resolver has not been set.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the tests are running within Laravel Sail.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function runningInSail()
|
||||
{
|
||||
return isset($_ENV['LARAVEL_SAIL']) && $_ENV['LARAVEL_SAIL'] == '1';
|
||||
}
|
||||
}
|
||||
|
42
vendor/laravel/dusk/stubs/DuskTestCase.stub
vendored
42
vendor/laravel/dusk/stubs/DuskTestCase.stub
vendored
@@ -19,7 +19,9 @@ abstract class DuskTestCase extends BaseTestCase
|
||||
*/
|
||||
public static function prepare()
|
||||
{
|
||||
static::startChromeDriver();
|
||||
if (! static::runningInSail()) {
|
||||
static::startChromeDriver();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,16 +31,42 @@ abstract class DuskTestCase extends BaseTestCase
|
||||
*/
|
||||
protected function driver()
|
||||
{
|
||||
$options = (new ChromeOptions)->addArguments([
|
||||
'--disable-gpu',
|
||||
'--headless',
|
||||
'--window-size=1920,1080',
|
||||
]);
|
||||
$options = (new ChromeOptions)->addArguments(collect([
|
||||
$this->shouldStartMaximized() ? '--start-maximized' : '--window-size=1920,1080',
|
||||
])->unless($this->hasHeadlessDisabled(), function ($items) {
|
||||
return $items->merge([
|
||||
'--disable-gpu',
|
||||
'--headless',
|
||||
]);
|
||||
})->all());
|
||||
|
||||
return RemoteWebDriver::create(
|
||||
'http://localhost:9515', DesiredCapabilities::chrome()->setCapability(
|
||||
$_ENV['DUSK_DRIVER_URL'] ?? 'http://localhost:9515',
|
||||
DesiredCapabilities::chrome()->setCapability(
|
||||
ChromeOptions::CAPABILITY, $options
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the Dusk command has disabled headless mode.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasHeadlessDisabled()
|
||||
{
|
||||
return isset($_SERVER['DUSK_HEADLESS_DISABLED']) ||
|
||||
isset($_ENV['DUSK_HEADLESS_DISABLED']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the browser window should start maximized.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function shouldStartMaximized()
|
||||
{
|
||||
return isset($_SERVER['DUSK_START_MAXIMIZED']) ||
|
||||
isset($_ENV['DUSK_START_MAXIMIZED']);
|
||||
}
|
||||
}
|
||||
|
5
vendor/laravel/dusk/stubs/phpunit.xml
vendored
5
vendor/laravel/dusk/stubs/phpunit.xml
vendored
@@ -13,9 +13,4 @@
|
||||
<directory suffix="Test.php">./tests/Browser</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<filter>
|
||||
<whitelist processUncoveredFilesFromWhitelist="true">
|
||||
<directory suffix=".php">./app</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
||||
|
6
vendor/laravel/framework/README.md
vendored
6
vendor/laravel/framework/README.md
vendored
@@ -2,9 +2,9 @@
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.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>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
|
||||
</p>
|
||||
|
||||
## About Laravel
|
||||
|
97
vendor/laravel/framework/composer.json
vendored
97
vendor/laravel/framework/composer.json
vendored
@@ -15,41 +15,43 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.2.5|^8.0",
|
||||
"php": "^7.3|^8.0",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-openssl": "*",
|
||||
"doctrine/inflector": "^1.4|^2.0",
|
||||
"dragonmantank/cron-expression": "^2.3.1",
|
||||
"dragonmantank/cron-expression": "^3.0.2",
|
||||
"egulias/email-validator": "^2.1.10",
|
||||
"league/commonmark": "^1.3",
|
||||
"laravel/serializable-closure": "^1.0",
|
||||
"league/commonmark": "^1.3|^2.0.2",
|
||||
"league/flysystem": "^1.1",
|
||||
"monolog/monolog": "^2.0",
|
||||
"nesbot/carbon": "^2.31",
|
||||
"nesbot/carbon": "^2.53.1",
|
||||
"opis/closure": "^3.6",
|
||||
"psr/container": "^1.0",
|
||||
"psr/log": "^1.0|^2.0",
|
||||
"psr/simple-cache": "^1.0",
|
||||
"ramsey/uuid": "^3.7|^4.0",
|
||||
"swiftmailer/swiftmailer": "^6.0",
|
||||
"symfony/console": "^5.0",
|
||||
"symfony/error-handler": "^5.0",
|
||||
"symfony/finder": "^5.0",
|
||||
"symfony/http-foundation": "^5.0",
|
||||
"symfony/http-kernel": "^5.0",
|
||||
"symfony/mime": "^5.0",
|
||||
"symfony/polyfill-php73": "^1.17",
|
||||
"symfony/process": "^5.0",
|
||||
"symfony/routing": "^5.0",
|
||||
"symfony/var-dumper": "^5.0",
|
||||
"ramsey/uuid": "^4.2.2",
|
||||
"swiftmailer/swiftmailer": "^6.3",
|
||||
"symfony/console": "^5.4",
|
||||
"symfony/error-handler": "^5.4",
|
||||
"symfony/finder": "^5.4",
|
||||
"symfony/http-foundation": "^5.4",
|
||||
"symfony/http-kernel": "^5.4",
|
||||
"symfony/mime": "^5.4",
|
||||
"symfony/process": "^5.4",
|
||||
"symfony/routing": "^5.4",
|
||||
"symfony/var-dumper": "^5.4",
|
||||
"tijsverkoyen/css-to-inline-styles": "^2.2.2",
|
||||
"vlucas/phpdotenv": "^4.0",
|
||||
"voku/portable-ascii": "^1.4.8"
|
||||
"vlucas/phpdotenv": "^5.4.1",
|
||||
"voku/portable-ascii": "^1.6.1"
|
||||
},
|
||||
"replace": {
|
||||
"illuminate/auth": "self.version",
|
||||
"illuminate/broadcasting": "self.version",
|
||||
"illuminate/bus": "self.version",
|
||||
"illuminate/cache": "self.version",
|
||||
"illuminate/collections": "self.version",
|
||||
"illuminate/config": "self.version",
|
||||
"illuminate/console": "self.version",
|
||||
"illuminate/container": "self.version",
|
||||
@@ -62,6 +64,7 @@
|
||||
"illuminate/hashing": "self.version",
|
||||
"illuminate/http": "self.version",
|
||||
"illuminate/log": "self.version",
|
||||
"illuminate/macroable": "self.version",
|
||||
"illuminate/mail": "self.version",
|
||||
"illuminate/notifications": "self.version",
|
||||
"illuminate/pagination": "self.version",
|
||||
@@ -77,32 +80,35 @@
|
||||
"illuminate/view": "self.version"
|
||||
},
|
||||
"require-dev": {
|
||||
"aws/aws-sdk-php": "^3.155",
|
||||
"doctrine/dbal": "^2.6",
|
||||
"filp/whoops": "^2.8",
|
||||
"guzzlehttp/guzzle": "^6.3.1|^7.0.1",
|
||||
"aws/aws-sdk-php": "^3.198.1",
|
||||
"doctrine/dbal": "^2.13.3|^3.1.4",
|
||||
"filp/whoops": "^2.14.3",
|
||||
"guzzlehttp/guzzle": "^6.5.5|^7.0.1",
|
||||
"league/flysystem-cached-adapter": "^1.0",
|
||||
"mockery/mockery": "~1.3.3|^1.4.2",
|
||||
"moontoast/math": "^1.1",
|
||||
"orchestra/testbench-core": "^5.8",
|
||||
"mockery/mockery": "^1.4.4",
|
||||
"orchestra/testbench-core": "^6.27",
|
||||
"pda/pheanstalk": "^4.0",
|
||||
"phpunit/phpunit": "^8.4|^9.3.3",
|
||||
"predis/predis": "^1.1.1",
|
||||
"symfony/cache": "^5.0"
|
||||
"phpunit/phpunit": "^8.5.19|^9.5.8",
|
||||
"predis/predis": "^1.1.9",
|
||||
"symfony/cache": "^5.4"
|
||||
},
|
||||
"provide": {
|
||||
"psr/container-implementation": "1.0"
|
||||
"psr/container-implementation": "1.0",
|
||||
"psr/simple-cache-implementation": "1.0"
|
||||
},
|
||||
"conflict": {
|
||||
"tightenco/collect": "<5.5.33"
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/Illuminate/Collections/helpers.php",
|
||||
"src/Illuminate/Events/functions.php",
|
||||
"src/Illuminate/Foundation/helpers.php",
|
||||
"src/Illuminate/Support/helpers.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Illuminate\\": "src/Illuminate/"
|
||||
"Illuminate\\": "src/Illuminate/",
|
||||
"Illuminate\\Support\\": ["src/Illuminate/Macroable/", "src/Illuminate/Collections/"]
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
@@ -115,40 +121,45 @@
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "7.x-dev"
|
||||
"dev-master": "8.x-dev"
|
||||
}
|
||||
},
|
||||
"suggest": {
|
||||
"ext-bcmath": "Required to use the multiple_of validation rule.",
|
||||
"ext-ftp": "Required to use the Flysystem FTP driver.",
|
||||
"ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().",
|
||||
"ext-memcached": "Required to use the memcache cache driver.",
|
||||
"ext-pcntl": "Required to use all features of the queue worker.",
|
||||
"ext-posix": "Required to use all features of the queue worker.",
|
||||
"ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).",
|
||||
"aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.155).",
|
||||
"doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.6).",
|
||||
"filp/whoops": "Required for friendly error pages in development (^2.8).",
|
||||
"ably/ably-php": "Required to use the Ably broadcast driver (^1.0).",
|
||||
"aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.198.1).",
|
||||
"brianium/paratest": "Required to run tests in parallel (^6.0).",
|
||||
"doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.13.3|^3.1.4).",
|
||||
"filp/whoops": "Required for friendly error pages in development (^2.14.3).",
|
||||
"fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).",
|
||||
"guzzlehttp/guzzle": "Required to use the HTTP Client, Mailgun mail driver and the ping methods on schedules (^6.3.1|^7.0.1).",
|
||||
"guzzlehttp/guzzle": "Required to use the HTTP Client, Mailgun mail driver and the ping methods on schedules (^6.5.5|^7.0.1).",
|
||||
"laravel/tinker": "Required to use the tinker console command (^2.0).",
|
||||
"league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).",
|
||||
"league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).",
|
||||
"league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0).",
|
||||
"mockery/mockery": "Required to use mocking (~1.3.3|^1.4.2).",
|
||||
"moontoast/math": "Required to use ordered UUIDs (^1.1).",
|
||||
"mockery/mockery": "Required to use mocking (^1.4.4).",
|
||||
"nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).",
|
||||
"pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).",
|
||||
"phpunit/phpunit": "Required to use assertions and run tests (^8.4|^9.3.3).",
|
||||
"predis/predis": "Required to use the predis connector (^1.1.2).",
|
||||
"phpunit/phpunit": "Required to use assertions and run tests (^8.5.19|^9.5.8).",
|
||||
"predis/predis": "Required to use the predis connector (^1.1.9).",
|
||||
"psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).",
|
||||
"pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^4.0).",
|
||||
"symfony/cache": "Required to PSR-6 cache bridge (^5.0).",
|
||||
"symfony/filesystem": "Required to create relative storage directory symbolic links (^5.0).",
|
||||
"pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^4.0|^5.0|^6.0|^7.0).",
|
||||
"symfony/cache": "Required to PSR-6 cache bridge (^5.4).",
|
||||
"symfony/filesystem": "Required to enable support for relative symbolic links (^5.4).",
|
||||
"symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0).",
|
||||
"wildbit/swiftmailer-postmark": "Required to use Postmark mail driver (^3.0)."
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"composer/package-versions-deprecated": true
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
|
51
vendor/laravel/framework/src/Illuminate/Auth/Access/Events/GateEvaluated.php
vendored
Normal file
51
vendor/laravel/framework/src/Illuminate/Auth/Access/Events/GateEvaluated.php
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Auth\Access\Events;
|
||||
|
||||
class GateEvaluated
|
||||
{
|
||||
/**
|
||||
* The authenticatable model.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Auth\Authenticatable|null
|
||||
*/
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* The ability being evaluated.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $ability;
|
||||
|
||||
/**
|
||||
* The result of the evaluation.
|
||||
*
|
||||
* @var bool|null
|
||||
*/
|
||||
public $result;
|
||||
|
||||
/**
|
||||
* The arguments given during evaluation.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $arguments;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
|
||||
* @param string $ability
|
||||
* @param bool|null $result
|
||||
* @param array $arguments
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($user, $ability, $result, $arguments)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->ability = $ability;
|
||||
$this->result = $result;
|
||||
$this->arguments = $arguments;
|
||||
}
|
||||
}
|
@@ -2,10 +2,13 @@
|
||||
|
||||
namespace Illuminate\Auth\Access;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use InvalidArgumentException;
|
||||
use ReflectionClass;
|
||||
@@ -115,6 +118,64 @@ class Gate implements GateContract
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an on-demand authorization check. Throw an authorization exception if the condition or callback is false.
|
||||
*
|
||||
* @param \Illuminate\Auth\Access\Response|\Closure|bool $condition
|
||||
* @param string|null $message
|
||||
* @param string|null $code
|
||||
* @return \Illuminate\Auth\Access\Response
|
||||
*
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function allowIf($condition, $message = null, $code = null)
|
||||
{
|
||||
return $this->authorizeOnDemand($condition, $message, $code, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an on-demand authorization check. Throw an authorization exception if the condition or callback is true.
|
||||
*
|
||||
* @param \Illuminate\Auth\Access\Response|\Closure|bool $condition
|
||||
* @param string|null $message
|
||||
* @param string|null $code
|
||||
* @return \Illuminate\Auth\Access\Response
|
||||
*
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function denyIf($condition, $message = null, $code = null)
|
||||
{
|
||||
return $this->authorizeOnDemand($condition, $message, $code, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorize a given condition or callback.
|
||||
*
|
||||
* @param \Illuminate\Auth\Access\Response|\Closure|bool $condition
|
||||
* @param string|null $message
|
||||
* @param string|null $code
|
||||
* @param bool $allowWhenResponseIs
|
||||
* @return \Illuminate\Auth\Access\Response
|
||||
*
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
protected function authorizeOnDemand($condition, $message, $code, $allowWhenResponseIs)
|
||||
{
|
||||
$user = $this->resolveUser();
|
||||
|
||||
if ($condition instanceof Closure) {
|
||||
$response = $this->canBeCalledWithUser($user, $condition)
|
||||
? $condition($user)
|
||||
: new Response(false, $message, $code);
|
||||
} else {
|
||||
$response = $condition;
|
||||
}
|
||||
|
||||
return with($response instanceof Response ? $response : new Response(
|
||||
(bool) $response === $allowWhenResponseIs, $message, $code
|
||||
))->authorize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a new ability.
|
||||
*
|
||||
@@ -155,10 +216,10 @@ class Gate implements GateContract
|
||||
{
|
||||
$abilities = $abilities ?: [
|
||||
'viewAny' => 'viewAny',
|
||||
'view' => 'view',
|
||||
'create' => 'create',
|
||||
'update' => 'update',
|
||||
'delete' => 'delete',
|
||||
'view' => 'view',
|
||||
'create' => 'create',
|
||||
'update' => 'update',
|
||||
'delete' => 'delete',
|
||||
];
|
||||
|
||||
foreach ($abilities as $ability => $method) {
|
||||
@@ -373,9 +434,11 @@ class Gate implements GateContract
|
||||
// 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.
|
||||
return $this->callAfterCallbacks(
|
||||
return tap($this->callAfterCallbacks(
|
||||
$user, $ability, $arguments, $result
|
||||
);
|
||||
), function ($result) use ($user, $ability, $arguments) {
|
||||
$this->dispatchGateEvaluatedEvent($user, $ability, $arguments, $result);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -518,6 +581,24 @@ class Gate implements GateContract
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch a gate evaluation event.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
|
||||
* @param string $ability
|
||||
* @param array $arguments
|
||||
* @param bool|null $result
|
||||
* @return void
|
||||
*/
|
||||
protected function dispatchGateEvaluatedEvent($user, $ability, array $arguments, $result)
|
||||
{
|
||||
if ($this->container->bound(Dispatcher::class)) {
|
||||
$this->container->make(Dispatcher::class)->dispatch(
|
||||
new Events\GateEvaluated($user, $ability, $result, $arguments)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the callable for the given ability and arguments.
|
||||
*
|
||||
@@ -599,7 +680,15 @@ class Gate implements GateContract
|
||||
|
||||
$classDirname = str_replace('/', '\\', dirname(str_replace('\\', '/', $class)));
|
||||
|
||||
return [$classDirname.'\\Policies\\'.class_basename($class).'Policy'];
|
||||
$classDirnameSegments = explode('\\', $classDirname);
|
||||
|
||||
return Arr::wrap(Collection::times(count($classDirnameSegments), function ($index) use ($class, $classDirnameSegments) {
|
||||
$classDirname = implode('\\', array_slice($classDirnameSegments, 0, $index));
|
||||
|
||||
return $classDirname.'\\Policies\\'.class_basename($class).'Policy';
|
||||
})->reverse()->values()->first(function ($class) {
|
||||
return class_exists($class);
|
||||
}) ?: [$classDirname.'\\Policies\\'.class_basename($class).'Policy']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -770,4 +859,17 @@ class Gate implements GateContract
|
||||
{
|
||||
return $this->policies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the container instance used by the gate.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Container\Container $container
|
||||
* @return $this
|
||||
*/
|
||||
public function setContainer(Container $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@@ -122,7 +122,11 @@ class AuthManager implements FactoryContract
|
||||
{
|
||||
$provider = $this->createUserProvider($config['provider'] ?? null);
|
||||
|
||||
$guard = new SessionGuard($name, $provider, $this->app['session.store']);
|
||||
$guard = new SessionGuard(
|
||||
$name,
|
||||
$provider,
|
||||
$this->app['session.store'],
|
||||
);
|
||||
|
||||
// When using the remember me functionality of the authentication services we
|
||||
// will need to be set the encryption instance of the guard, which allows
|
||||
@@ -139,6 +143,10 @@ class AuthManager implements FactoryContract
|
||||
$guard->setRequest($this->app->refresh('request', $guard, 'setRequest'));
|
||||
}
|
||||
|
||||
if (isset($config['remember'])) {
|
||||
$guard->setRememberDuration($config['remember']);
|
||||
}
|
||||
|
||||
return $guard;
|
||||
}
|
||||
|
||||
@@ -295,6 +303,31 @@ class AuthManager implements FactoryContract
|
||||
return count($this->guards) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forget all of the resolved guard instances.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function forgetGuards()
|
||||
{
|
||||
$this->guards = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the application instance used by the manager.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Foundation\Application $app
|
||||
* @return $this
|
||||
*/
|
||||
public function setApplication($app)
|
||||
{
|
||||
$this->app = $app;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically call the default driver instance.
|
||||
*
|
||||
|
@@ -35,11 +35,6 @@ class AuthServiceProvider extends ServiceProvider
|
||||
protected function registerAuthenticator()
|
||||
{
|
||||
$this->app->singleton('auth', function ($app) {
|
||||
// Once the authentication service has actually been requested by the developer
|
||||
// we will set a variable in the application indicating such. This helps us
|
||||
// know that we need to set any queued cookies in the after event later.
|
||||
$app['auth.loaded'] = true;
|
||||
|
||||
return new AuthManager($app);
|
||||
});
|
||||
|
||||
@@ -55,11 +50,9 @@ class AuthServiceProvider extends ServiceProvider
|
||||
*/
|
||||
protected function registerUserResolver()
|
||||
{
|
||||
$this->app->bind(
|
||||
AuthenticatableContract::class, function ($app) {
|
||||
return call_user_func($app['auth']->userResolver());
|
||||
}
|
||||
);
|
||||
$this->app->bind(AuthenticatableContract::class, function ($app) {
|
||||
return call_user_func($app['auth']->userResolver());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,15 +76,13 @@ class AuthServiceProvider extends ServiceProvider
|
||||
*/
|
||||
protected function registerRequirePassword()
|
||||
{
|
||||
$this->app->bind(
|
||||
RequirePassword::class, function ($app) {
|
||||
return new RequirePassword(
|
||||
$app[ResponseFactory::class],
|
||||
$app[UrlGenerator::class],
|
||||
$app['config']->get('auth.password_timeout')
|
||||
);
|
||||
}
|
||||
);
|
||||
$this->app->bind(RequirePassword::class, function ($app) {
|
||||
return new RequirePassword(
|
||||
$app[ResponseFactory::class],
|
||||
$app[UrlGenerator::class],
|
||||
$app['config']->get('auth.password_timeout')
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,11 +107,8 @@ class AuthServiceProvider extends ServiceProvider
|
||||
protected function registerEventRebindHandler()
|
||||
{
|
||||
$this->app->rebinding('events', function ($app, $dispatcher) {
|
||||
if (! $app->resolved('auth')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($app['auth']->hasResolvedGuards() === false) {
|
||||
if (! $app->resolved('auth') ||
|
||||
$app['auth']->hasResolvedGuards() === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -31,6 +31,16 @@ trait Authenticatable
|
||||
return $this->{$this->getAuthIdentifierName()};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unique broadcast identifier for the user.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAuthIdentifierForBroadcasting()
|
||||
{
|
||||
return $this->getAuthIdentifier();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the password for the user.
|
||||
*
|
||||
|
@@ -16,7 +16,7 @@ class AuthenticationException extends Exception
|
||||
/**
|
||||
* The path the user should be redirected to.
|
||||
*
|
||||
* @var string
|
||||
* @var string|null
|
||||
*/
|
||||
protected $redirectTo;
|
||||
|
||||
@@ -49,7 +49,7 @@ class AuthenticationException extends Exception
|
||||
/**
|
||||
* Get the path the user should be redirected to.
|
||||
*
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
public function redirectTo()
|
||||
{
|
||||
|
@@ -13,8 +13,8 @@
|
||||
<script src="{{ asset('js/app.js') }}" defer></script>
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="dns-prefetch" href="//fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Styles -->
|
||||
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Illuminate\Auth;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
|
||||
use Illuminate\Contracts\Auth\UserProvider;
|
||||
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
|
||||
@@ -117,6 +118,8 @@ class DatabaseUserProvider implements UserProvider
|
||||
|
||||
if (is_array($value) || $value instanceof Arrayable) {
|
||||
$query->whereIn($key, $value);
|
||||
} elseif ($value instanceof Closure) {
|
||||
$value($query);
|
||||
} else {
|
||||
$query->where($key, $value);
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Illuminate\Auth;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
|
||||
use Illuminate\Contracts\Auth\UserProvider;
|
||||
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
|
||||
@@ -123,6 +124,8 @@ class EloquentUserProvider implements UserProvider
|
||||
|
||||
if (is_array($value) || $value instanceof Arrayable) {
|
||||
$query->whereIn($key, $value);
|
||||
} elseif ($value instanceof Closure) {
|
||||
$value($query);
|
||||
} else {
|
||||
$query->where($key, $value);
|
||||
}
|
||||
|
@@ -25,7 +25,7 @@ trait GuardHelpers
|
||||
protected $provider;
|
||||
|
||||
/**
|
||||
* Determine if current user is authenticated. If not, throw an exception.
|
||||
* Determine if the current user is authenticated. If not, throw an exception.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Auth\Authenticatable
|
||||
*
|
||||
|
@@ -5,6 +5,7 @@ namespace Illuminate\Auth\Middleware;
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
|
||||
class EnsureEmailIsVerified
|
||||
{
|
||||
@@ -14,7 +15,7 @@ class EnsureEmailIsVerified
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @param string|null $redirectToRoute
|
||||
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
||||
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse|null
|
||||
*/
|
||||
public function handle($request, Closure $next, $redirectToRoute = null)
|
||||
{
|
||||
@@ -23,7 +24,7 @@ class EnsureEmailIsVerified
|
||||
! $request->user()->hasVerifiedEmail())) {
|
||||
return $request->expectsJson()
|
||||
? abort(403, 'Your email address is not verified.')
|
||||
: Redirect::route($redirectToRoute ?: 'verification.notice');
|
||||
: Redirect::guest(URL::route($redirectToRoute ?: 'verification.notice'));
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
|
@@ -63,15 +63,17 @@ class ResetPassword extends Notification
|
||||
return call_user_func(static::$toMailCallback, $notifiable, $this->token);
|
||||
}
|
||||
|
||||
if (static::$createUrlCallback) {
|
||||
$url = call_user_func(static::$createUrlCallback, $notifiable, $this->token);
|
||||
} else {
|
||||
$url = url(route('password.reset', [
|
||||
'token' => $this->token,
|
||||
'email' => $notifiable->getEmailForPasswordReset(),
|
||||
], false));
|
||||
}
|
||||
return $this->buildMailMessage($this->resetUrl($notifiable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reset password notification mail message for the given URL.
|
||||
*
|
||||
* @param string $url
|
||||
* @return \Illuminate\Notifications\Messages\MailMessage
|
||||
*/
|
||||
protected function buildMailMessage($url)
|
||||
{
|
||||
return (new MailMessage)
|
||||
->subject(Lang::get('Reset Password Notification'))
|
||||
->line(Lang::get('You are receiving this email because we received a password reset request for your account.'))
|
||||
@@ -80,6 +82,24 @@ class ResetPassword extends Notification
|
||||
->line(Lang::get('If you did not request a password reset, no further action is required.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reset URL for the given notifiable.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return string
|
||||
*/
|
||||
protected function resetUrl($notifiable)
|
||||
{
|
||||
if (static::$createUrlCallback) {
|
||||
return call_user_func(static::$createUrlCallback, $notifiable, $this->token);
|
||||
}
|
||||
|
||||
return url(route('password.reset', [
|
||||
'token' => $this->token,
|
||||
'email' => $notifiable->getEmailForPasswordReset(),
|
||||
], false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a callback that should be used when creating the reset password button URL.
|
||||
*
|
||||
|
@@ -11,6 +11,13 @@ use Illuminate\Support\Facades\URL;
|
||||
|
||||
class VerifyEmail extends Notification
|
||||
{
|
||||
/**
|
||||
* The callback that should be used to create the verify email URL.
|
||||
*
|
||||
* @var \Closure|null
|
||||
*/
|
||||
public static $createUrlCallback;
|
||||
|
||||
/**
|
||||
* The callback that should be used to build the mail message.
|
||||
*
|
||||
@@ -43,10 +50,21 @@ class VerifyEmail extends Notification
|
||||
return call_user_func(static::$toMailCallback, $notifiable, $verificationUrl);
|
||||
}
|
||||
|
||||
return $this->buildMailMessage($verificationUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the verify email notification mail message for the given URL.
|
||||
*
|
||||
* @param string $url
|
||||
* @return \Illuminate\Notifications\Messages\MailMessage
|
||||
*/
|
||||
protected function buildMailMessage($url)
|
||||
{
|
||||
return (new MailMessage)
|
||||
->subject(Lang::get('Verify Email Address'))
|
||||
->line(Lang::get('Please click the button below to verify your email address.'))
|
||||
->action(Lang::get('Verify Email Address'), $verificationUrl)
|
||||
->action(Lang::get('Verify Email Address'), $url)
|
||||
->line(Lang::get('If you did not create an account, no further action is required.'));
|
||||
}
|
||||
|
||||
@@ -58,6 +76,10 @@ class VerifyEmail extends Notification
|
||||
*/
|
||||
protected function verificationUrl($notifiable)
|
||||
{
|
||||
if (static::$createUrlCallback) {
|
||||
return call_user_func(static::$createUrlCallback, $notifiable);
|
||||
}
|
||||
|
||||
return URL::temporarySignedRoute(
|
||||
'verification.verify',
|
||||
Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
|
||||
@@ -68,6 +90,17 @@ class VerifyEmail extends Notification
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a callback that should be used when creating the email verification URL.
|
||||
*
|
||||
* @param \Closure $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function createUrlUsing($callback)
|
||||
{
|
||||
static::$createUrlCallback = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a callback that should be used when building the notification mail message.
|
||||
*
|
||||
|
@@ -42,9 +42,10 @@ class PasswordBroker implements PasswordBrokerContract
|
||||
* Send a password reset link to a user.
|
||||
*
|
||||
* @param array $credentials
|
||||
* @param \Closure|null $callback
|
||||
* @return string
|
||||
*/
|
||||
public function sendResetLink(array $credentials)
|
||||
public function sendResetLink(array $credentials, Closure $callback = null)
|
||||
{
|
||||
// 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
|
||||
@@ -59,12 +60,16 @@ class PasswordBroker implements PasswordBrokerContract
|
||||
return static::RESET_THROTTLED;
|
||||
}
|
||||
|
||||
// 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.
|
||||
$user->sendPasswordResetNotification(
|
||||
$this->tokens->create($user)
|
||||
);
|
||||
$token = $this->tokens->create($user);
|
||||
|
||||
if ($callback) {
|
||||
$callback($user, $token);
|
||||
} else {
|
||||
// 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.
|
||||
$user->sendPasswordResetNotification($token);
|
||||
}
|
||||
|
||||
return static::RESET_LINK_SENT;
|
||||
}
|
||||
|
@@ -17,9 +17,12 @@ use Illuminate\Contracts\Auth\UserProvider;
|
||||
use Illuminate\Contracts\Cookie\QueueingFactory as CookieJar;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Contracts\Session\Session;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Timebox;
|
||||
use Illuminate\Support\Traits\Macroable;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
|
||||
@@ -29,7 +32,7 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
|
||||
use GuardHelpers, Macroable;
|
||||
|
||||
/**
|
||||
* The name of the Guard. Typically "session".
|
||||
* The name of the guard. Typically "web".
|
||||
*
|
||||
* Corresponds to guard name in authentication configuration.
|
||||
*
|
||||
@@ -51,6 +54,13 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
|
||||
*/
|
||||
protected $viaRemember = false;
|
||||
|
||||
/**
|
||||
* The number of minutes that the "remember me" cookie should be valid for.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $rememberDuration = 2628000;
|
||||
|
||||
/**
|
||||
* The session used by the guard.
|
||||
*
|
||||
@@ -79,6 +89,13 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
|
||||
*/
|
||||
protected $events;
|
||||
|
||||
/**
|
||||
* The timebox instance.
|
||||
*
|
||||
* @var \Illuminate\Support\Timebox
|
||||
*/
|
||||
protected $timebox;
|
||||
|
||||
/**
|
||||
* Indicates if the logout method has been called.
|
||||
*
|
||||
@@ -100,17 +117,20 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
|
||||
* @param \Illuminate\Contracts\Auth\UserProvider $provider
|
||||
* @param \Illuminate\Contracts\Session\Session $session
|
||||
* @param \Symfony\Component\HttpFoundation\Request|null $request
|
||||
* @param \Illuminate\Support\Timebox|null $timebox
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($name,
|
||||
UserProvider $provider,
|
||||
Session $session,
|
||||
Request $request = null)
|
||||
Request $request = null,
|
||||
Timebox $timebox = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->session = $session;
|
||||
$this->request = $request;
|
||||
$this->provider = $provider;
|
||||
$this->timebox = $timebox ?: new Timebox;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -320,7 +340,7 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the credential array for a HTTP Basic request.
|
||||
* Get the credential array for an HTTP Basic request.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* @param string $field
|
||||
@@ -373,6 +393,34 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to authenticate a user with credentials and additional callbacks.
|
||||
*
|
||||
* @param array $credentials
|
||||
* @param array|callable $callbacks
|
||||
* @param false $remember
|
||||
* @return bool
|
||||
*/
|
||||
public function attemptWhen(array $credentials = [], $callbacks = null, $remember = false)
|
||||
{
|
||||
$this->fireAttemptEvent($credentials, $remember);
|
||||
|
||||
$this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
|
||||
|
||||
// This method does the exact same thing as attempt, but also executes callbacks after
|
||||
// the user is retrieved and validated. If one of the callbacks returns falsy we do
|
||||
// not login the user. Instead, we will fail the specific authentication attempt.
|
||||
if ($this->hasValidCredentials($user, $credentials) && $this->shouldLogin($callbacks, $user)) {
|
||||
$this->login($user, $remember);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->fireFailedEvent($user, $credentials);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user matches the credentials.
|
||||
*
|
||||
@@ -382,13 +430,35 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
|
||||
*/
|
||||
protected function hasValidCredentials($user, $credentials)
|
||||
{
|
||||
$validated = ! is_null($user) && $this->provider->validateCredentials($user, $credentials);
|
||||
return $this->timebox->call(function ($timebox) use ($user, $credentials) {
|
||||
$validated = ! is_null($user) && $this->provider->validateCredentials($user, $credentials);
|
||||
|
||||
if ($validated) {
|
||||
$this->fireValidatedEvent($user);
|
||||
if ($validated) {
|
||||
$timebox->returnEarly();
|
||||
|
||||
$this->fireValidatedEvent($user);
|
||||
}
|
||||
|
||||
return $validated;
|
||||
}, 200 * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user should login by executing the given callbacks.
|
||||
*
|
||||
* @param array|callable|null $callbacks
|
||||
* @param \Illuminate\Contracts\Auth\Authenticatable $user
|
||||
* @return bool
|
||||
*/
|
||||
protected function shouldLogin($callbacks, AuthenticatableContract $user)
|
||||
{
|
||||
foreach (Arr::wrap($callbacks) as $callback) {
|
||||
if (! $callback($user, $this)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $validated;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -484,7 +554,7 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
|
||||
*/
|
||||
protected function createRecaller($value)
|
||||
{
|
||||
return $this->getCookieJar()->forever($this->getRecallerName(), $value);
|
||||
return $this->getCookieJar()->make($this->getRecallerName(), $value, $this->getRememberDuration());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -517,6 +587,34 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
|
||||
$this->loggedOut = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the user out of the application on their current device only.
|
||||
*
|
||||
* This method does not cycle the "remember" token.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function logoutCurrentDevice()
|
||||
{
|
||||
$user = $this->user();
|
||||
|
||||
$this->clearUserDataFromStorage();
|
||||
|
||||
// If we have an event dispatcher instance, we can fire off the logout event
|
||||
// so any further processing can be done. This allows the developer to be
|
||||
// listening for anytime a user signs out of this application manually.
|
||||
if (isset($this->events)) {
|
||||
$this->events->dispatch(new CurrentDeviceLogout($this->name, $user));
|
||||
}
|
||||
|
||||
// Once we have fired the logout event we will clear the users out of memory
|
||||
// so they are no longer available as the user is no longer considered as
|
||||
// being signed into this application and should not be available here.
|
||||
$this->user = null;
|
||||
|
||||
$this->loggedOut = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the user data from the session and cookies.
|
||||
*
|
||||
@@ -545,32 +643,6 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
|
||||
$this->provider->updateRememberToken($user, $token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the user out of the application on their current device only.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function logoutCurrentDevice()
|
||||
{
|
||||
$user = $this->user();
|
||||
|
||||
$this->clearUserDataFromStorage();
|
||||
|
||||
// If we have an event dispatcher instance, we can fire off the logout event
|
||||
// so any further processing can be done. This allows the developer to be
|
||||
// listening for anytime a user signs out of this application manually.
|
||||
if (isset($this->events)) {
|
||||
$this->events->dispatch(new CurrentDeviceLogout($this->name, $user));
|
||||
}
|
||||
|
||||
// Once we have fired the logout event we will clear the users out of memory
|
||||
// so they are no longer available as the user is no longer considered as
|
||||
// being signed into this application and should not be available here.
|
||||
$this->user = null;
|
||||
|
||||
$this->loggedOut = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate other sessions for the current user.
|
||||
*
|
||||
@@ -578,7 +650,9 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
|
||||
*
|
||||
* @param string $password
|
||||
* @param string $attribute
|
||||
* @return bool|null
|
||||
* @return \Illuminate\Contracts\Auth\Authenticatable|null
|
||||
*
|
||||
* @throws \Illuminate\Auth\AuthenticationException
|
||||
*/
|
||||
public function logoutOtherDevices($password, $attribute = 'password')
|
||||
{
|
||||
@@ -586,9 +660,7 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
|
||||
return;
|
||||
}
|
||||
|
||||
$result = tap($this->user()->forceFill([
|
||||
$attribute => Hash::make($password),
|
||||
]))->save();
|
||||
$result = $this->rehashUserPassword($password, $attribute);
|
||||
|
||||
if ($this->recaller() ||
|
||||
$this->getCookieJar()->hasQueued($this->getRecallerName())) {
|
||||
@@ -600,6 +672,26 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rehash the current user's password.
|
||||
*
|
||||
* @param string $password
|
||||
* @param string $attribute
|
||||
* @return \Illuminate\Contracts\Auth\Authenticatable|null
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function rehashUserPassword($password, $attribute)
|
||||
{
|
||||
if (! Hash::check($password, $this->user()->{$attribute})) {
|
||||
throw new InvalidArgumentException('The given password does not match the current password.');
|
||||
}
|
||||
|
||||
return tap($this->user()->forceFill([
|
||||
$attribute => Hash::make($password),
|
||||
]))->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an authentication attempt event listener.
|
||||
*
|
||||
@@ -746,6 +838,29 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
|
||||
return $this->viaRemember;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of minutes the remember me cookie should be valid for.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function getRememberDuration()
|
||||
{
|
||||
return $this->rememberDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of minutes the remember me cookie should be valid for.
|
||||
*
|
||||
* @param int $minutes
|
||||
* @return $this
|
||||
*/
|
||||
public function setRememberDuration($minutes)
|
||||
{
|
||||
$this->rememberDuration = $minutes;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cookie creator instance used by the guard.
|
||||
*
|
||||
@@ -853,4 +968,14 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timebox instance used by the guard.
|
||||
*
|
||||
* @return \Illuminate\Support\Timebox
|
||||
*/
|
||||
public function getTimebox()
|
||||
{
|
||||
return $this->timebox;
|
||||
}
|
||||
}
|
||||
|
@@ -14,11 +14,13 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.2.5|^8.0",
|
||||
"illuminate/contracts": "^7.0",
|
||||
"illuminate/http": "^7.0",
|
||||
"illuminate/queue": "^7.0",
|
||||
"illuminate/support": "^7.0"
|
||||
"php": "^7.3|^8.0",
|
||||
"illuminate/collections": "^8.0",
|
||||
"illuminate/contracts": "^8.0",
|
||||
"illuminate/http": "^8.0",
|
||||
"illuminate/macroable": "^8.0",
|
||||
"illuminate/queue": "^8.0",
|
||||
"illuminate/support": "^8.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@@ -27,13 +29,13 @@
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "7.x-dev"
|
||||
"dev-master": "8.x-dev"
|
||||
}
|
||||
},
|
||||
"suggest": {
|
||||
"illuminate/console": "Required to use the auth:clear-resets command (^7.0).",
|
||||
"illuminate/queue": "Required to fire login / logout events (^7.0).",
|
||||
"illuminate/session": "Required to use the session based guard (^7.0)."
|
||||
"illuminate/console": "Required to use the auth:clear-resets command (^8.0).",
|
||||
"illuminate/queue": "Required to fire login / logout events (^8.0).",
|
||||
"illuminate/session": "Required to use the session based guard (^8.0)."
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
|
@@ -3,7 +3,7 @@
|
||||
namespace Illuminate\Broadcasting;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Broadcasting\Broadcaster;
|
||||
use Illuminate\Contracts\Broadcasting\Factory as BroadcastingFactory;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Support\Arr;
|
||||
@@ -46,23 +46,37 @@ class BroadcastEvent implements ShouldQueue
|
||||
$this->event = $event;
|
||||
$this->tries = property_exists($event, 'tries') ? $event->tries : null;
|
||||
$this->timeout = property_exists($event, 'timeout') ? $event->timeout : null;
|
||||
$this->afterCommit = property_exists($event, 'afterCommit') ? $event->afterCommit : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the queued job.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Broadcasting\Broadcaster $broadcaster
|
||||
* @param \Illuminate\Contracts\Broadcasting\Factory $manager
|
||||
* @return void
|
||||
*/
|
||||
public function handle(Broadcaster $broadcaster)
|
||||
public function handle(BroadcastingFactory $manager)
|
||||
{
|
||||
$name = method_exists($this->event, 'broadcastAs')
|
||||
? $this->event->broadcastAs() : get_class($this->event);
|
||||
|
||||
$broadcaster->broadcast(
|
||||
Arr::wrap($this->event->broadcastOn()), $name,
|
||||
$this->getPayloadFromEvent($this->event)
|
||||
);
|
||||
$channels = Arr::wrap($this->event->broadcastOn());
|
||||
|
||||
if (empty($channels)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$connections = method_exists($this->event, 'broadcastConnections')
|
||||
? $this->event->broadcastConnections()
|
||||
: [null];
|
||||
|
||||
$payload = $this->getPayloadFromEvent($this->event);
|
||||
|
||||
foreach ($connections as $connection) {
|
||||
$manager->connection($connection)->broadcast(
|
||||
$channels, $name, $payload
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,10 +87,9 @@ class BroadcastEvent implements ShouldQueue
|
||||
*/
|
||||
protected function getPayloadFromEvent($event)
|
||||
{
|
||||
if (method_exists($event, 'broadcastWith')) {
|
||||
return array_merge(
|
||||
$event->broadcastWith(), ['socket' => data_get($event, 'socket')]
|
||||
);
|
||||
if (method_exists($event, 'broadcastWith') &&
|
||||
! is_null($payload = $event->broadcastWith())) {
|
||||
return array_merge($payload, ['socket' => data_get($event, 'socket')]);
|
||||
}
|
||||
|
||||
$payload = [];
|
||||
|
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace Illuminate\Broadcasting;
|
||||
|
||||
use Ably\AblyRest;
|
||||
use Closure;
|
||||
use Illuminate\Broadcasting\Broadcasters\AblyBroadcaster;
|
||||
use Illuminate\Broadcasting\Broadcasters\LogBroadcaster;
|
||||
use Illuminate\Broadcasting\Broadcasters\NullBroadcaster;
|
||||
use Illuminate\Broadcasting\Broadcasters\PusherBroadcaster;
|
||||
@@ -70,7 +72,7 @@ class BroadcastManager implements FactoryContract
|
||||
$router->match(
|
||||
['get', 'post'], '/broadcasting/auth',
|
||||
'\\'.BroadcastController::class.'@authenticate'
|
||||
);
|
||||
)->withoutMiddleware([\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class]);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -110,7 +112,10 @@ class BroadcastManager implements FactoryContract
|
||||
*/
|
||||
public function queue($event)
|
||||
{
|
||||
if ($event instanceof ShouldBroadcastNow) {
|
||||
if ($event instanceof ShouldBroadcastNow ||
|
||||
(is_object($event) &&
|
||||
method_exists($event, 'shouldBroadcastNow') &&
|
||||
$event->shouldBroadcastNow())) {
|
||||
return $this->app->make(BusDispatcherContract::class)->dispatchNow(new BroadcastEvent(clone $event));
|
||||
}
|
||||
|
||||
@@ -220,6 +225,17 @@ class BroadcastManager implements FactoryContract
|
||||
return new PusherBroadcaster($pusher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of the driver.
|
||||
*
|
||||
* @param array $config
|
||||
* @return \Illuminate\Contracts\Broadcasting\Broadcaster
|
||||
*/
|
||||
protected function createAblyDriver(array $config)
|
||||
{
|
||||
return new AblyBroadcaster(new AblyRest($config));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of the driver.
|
||||
*
|
||||
@@ -294,6 +310,19 @@ class BroadcastManager implements FactoryContract
|
||||
$this->app['config']['broadcasting.default'] = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect the given disk and remove from local cache.
|
||||
*
|
||||
* @param string|null $name
|
||||
* @return void
|
||||
*/
|
||||
public function purge($name = null)
|
||||
{
|
||||
$name = $name ?? $this->getDefaultDriver();
|
||||
|
||||
unset($this->drivers[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a custom driver creator Closure.
|
||||
*
|
||||
@@ -308,6 +337,41 @@ class BroadcastManager implements FactoryContract
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the application instance used by the manager.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Foundation\Application
|
||||
*/
|
||||
public function getApplication()
|
||||
{
|
||||
return $this->app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the application instance used by the manager.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Foundation\Application $app
|
||||
* @return $this
|
||||
*/
|
||||
public function setApplication($app)
|
||||
{
|
||||
$this->app = $app;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forget all of the resolved driver instances.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function forgetDrivers()
|
||||
{
|
||||
$this->drivers = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically call the default driver instance.
|
||||
*
|
||||
|
225
vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/AblyBroadcaster.php
vendored
Normal file
225
vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/AblyBroadcaster.php
vendored
Normal file
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Broadcasting\Broadcasters;
|
||||
|
||||
use Ably\AblyRest;
|
||||
use Ably\Models\Message as AblyMessage;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
|
||||
/**
|
||||
* @author Matthew Hall (matthall28@gmail.com)
|
||||
* @author Taylor Otwell (taylor@laravel.com)
|
||||
*/
|
||||
class AblyBroadcaster extends Broadcaster
|
||||
{
|
||||
/**
|
||||
* The AblyRest SDK instance.
|
||||
*
|
||||
* @var \Ably\AblyRest
|
||||
*/
|
||||
protected $ably;
|
||||
|
||||
/**
|
||||
* Create a new broadcaster instance.
|
||||
*
|
||||
* @param \Ably\AblyRest $ably
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(AblyRest $ably)
|
||||
{
|
||||
$this->ably = $ably;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
$channelName = $this->normalizeChannelName($request->channel_name);
|
||||
|
||||
if (empty($request->channel_name) ||
|
||||
($this->isGuardedChannel($request->channel_name) &&
|
||||
! $this->retrieveUser($request, $channelName))) {
|
||||
throw new AccessDeniedHttpException;
|
||||
}
|
||||
|
||||
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')) {
|
||||
$signature = $this->generateAblySignature(
|
||||
$request->channel_name, $request->socket_id
|
||||
);
|
||||
|
||||
return ['auth' => $this->getPublicToken().':'.$signature];
|
||||
}
|
||||
|
||||
$channelName = $this->normalizeChannelName($request->channel_name);
|
||||
|
||||
$user = $this->retrieveUser($request, $channelName);
|
||||
|
||||
$broadcastIdentifier = method_exists($user, 'getAuthIdentifierForBroadcasting')
|
||||
? $user->getAuthIdentifierForBroadcasting()
|
||||
: $user->getAuthIdentifier();
|
||||
|
||||
$signature = $this->generateAblySignature(
|
||||
$request->channel_name,
|
||||
$request->socket_id,
|
||||
$userData = array_filter([
|
||||
'user_id' => (string) $broadcastIdentifier,
|
||||
'user_info' => $result,
|
||||
])
|
||||
);
|
||||
|
||||
return [
|
||||
'auth' => $this->getPublicToken().':'.$signature,
|
||||
'channel_data' => json_encode($userData),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the signature needed for Ably authentication headers.
|
||||
*
|
||||
* @param string $channelName
|
||||
* @param string $socketId
|
||||
* @param array|null $userData
|
||||
* @return string
|
||||
*/
|
||||
public function generateAblySignature($channelName, $socketId, $userData = null)
|
||||
{
|
||||
return hash_hmac(
|
||||
'sha256',
|
||||
sprintf('%s:%s%s', $socketId, $channelName, $userData ? ':'.json_encode($userData) : ''),
|
||||
$this->getPrivateToken(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast the given event.
|
||||
*
|
||||
* @param array $channels
|
||||
* @param string $event
|
||||
* @param array $payload
|
||||
* @return void
|
||||
*/
|
||||
public function broadcast(array $channels, $event, array $payload = [])
|
||||
{
|
||||
foreach ($this->formatChannels($channels) as $channel) {
|
||||
$this->ably->channels->get($channel)->publish(
|
||||
$this->buildAblyMessage($event, $payload)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an Ably message object for broadcasting.
|
||||
*
|
||||
* @param string $event
|
||||
* @param array $payload
|
||||
* @return \Ably\Models\Message
|
||||
*/
|
||||
protected function buildAblyMessage($event, array $payload = [])
|
||||
{
|
||||
return tap(new AblyMessage, function ($message) use ($event, $payload) {
|
||||
$message->name = $event;
|
||||
$message->data = $payload;
|
||||
$message->connectionKey = data_get($payload, 'socket');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the channel is protected by authentication.
|
||||
*
|
||||
* @param string $channel
|
||||
* @return bool
|
||||
*/
|
||||
public function isGuardedChannel($channel)
|
||||
{
|
||||
return Str::startsWith($channel, ['private-', 'presence-']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove prefix from channel name.
|
||||
*
|
||||
* @param string $channel
|
||||
* @return string
|
||||
*/
|
||||
public function normalizeChannelName($channel)
|
||||
{
|
||||
if ($this->isGuardedChannel($channel)) {
|
||||
return Str::startsWith($channel, 'private-')
|
||||
? Str::replaceFirst('private-', '', $channel)
|
||||
: Str::replaceFirst('presence-', '', $channel);
|
||||
}
|
||||
|
||||
return $channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the channel array into an array of strings.
|
||||
*
|
||||
* @param array $channels
|
||||
* @return array
|
||||
*/
|
||||
protected function formatChannels(array $channels)
|
||||
{
|
||||
return array_map(function ($channel) {
|
||||
$channel = (string) $channel;
|
||||
|
||||
if (Str::startsWith($channel, ['private-', 'presence-'])) {
|
||||
return Str::startsWith($channel, 'private-')
|
||||
? Str::replaceFirst('private-', 'private:', $channel)
|
||||
: Str::replaceFirst('presence-', 'presence:', $channel);
|
||||
}
|
||||
|
||||
return 'public:'.$channel;
|
||||
}, $channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the public token value from the Ably key.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getPublicToken()
|
||||
{
|
||||
return Str::before($this->ably->options->key, ':');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the private token value from the Ably key.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getPrivateToken()
|
||||
{
|
||||
return Str::after($this->ably->options->key, ':');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying Ably SDK instance.
|
||||
*
|
||||
* @return \Ably\AblyRest
|
||||
*/
|
||||
public function getAbly()
|
||||
{
|
||||
return $this->ably;
|
||||
}
|
||||
}
|
@@ -5,6 +5,7 @@ namespace Illuminate\Broadcasting\Broadcasters;
|
||||
use Exception;
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Contracts\Broadcasting\Broadcaster as BroadcasterContract;
|
||||
use Illuminate\Contracts\Broadcasting\HasBroadcastChannel;
|
||||
use Illuminate\Contracts\Routing\BindingRegistrar;
|
||||
use Illuminate\Contracts\Routing\UrlRoutable;
|
||||
use Illuminate\Support\Arr;
|
||||
@@ -40,13 +41,19 @@ abstract class Broadcaster implements BroadcasterContract
|
||||
/**
|
||||
* Register a channel authenticator.
|
||||
*
|
||||
* @param string $channel
|
||||
* @param \Illuminate\Contracts\Broadcasting\HasBroadcastChannel|string $channel
|
||||
* @param callable|string $callback
|
||||
* @param array $options
|
||||
* @return $this
|
||||
*/
|
||||
public function channel($channel, $callback, $options = [])
|
||||
{
|
||||
if ($channel instanceof HasBroadcastChannel) {
|
||||
$channel = $channel->broadcastChannelRoute();
|
||||
} elseif (is_string($channel) && class_exists($channel) && is_a($channel, HasBroadcastChannel::class, true)) {
|
||||
$channel = (new $channel)->broadcastChannelRoute();
|
||||
}
|
||||
|
||||
$this->channels[$channel] = $callback;
|
||||
|
||||
$this->channelOptions[$channel] = $options;
|
||||
@@ -317,7 +324,7 @@ abstract class Broadcaster implements BroadcasterContract
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if channel name from request match a pattern from registered channels.
|
||||
* Check if the channel name from the request matches a pattern from registered channels.
|
||||
*
|
||||
* @param string $channel
|
||||
* @param string $pattern
|
||||
|
@@ -5,6 +5,7 @@ namespace Illuminate\Broadcasting\Broadcasters;
|
||||
use Illuminate\Broadcasting\BroadcastException;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Pusher\ApiErrorException;
|
||||
use Pusher\Pusher;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
|
||||
@@ -42,8 +43,9 @@ class PusherBroadcaster extends Broadcaster
|
||||
{
|
||||
$channelName = $this->normalizeChannelName($request->channel_name);
|
||||
|
||||
if ($this->isGuardedChannel($request->channel_name) &&
|
||||
! $this->retrieveUser($request, $channelName)) {
|
||||
if (empty($request->channel_name) ||
|
||||
($this->isGuardedChannel($request->channel_name) &&
|
||||
! $this->retrieveUser($request, $channelName))) {
|
||||
throw new AccessDeniedHttpException;
|
||||
}
|
||||
|
||||
@@ -69,11 +71,17 @@ class PusherBroadcaster extends Broadcaster
|
||||
|
||||
$channelName = $this->normalizeChannelName($request->channel_name);
|
||||
|
||||
$user = $this->retrieveUser($request, $channelName);
|
||||
|
||||
$broadcastIdentifier = method_exists($user, 'getAuthIdentifierForBroadcasting')
|
||||
? $user->getAuthIdentifierForBroadcasting()
|
||||
: $user->getAuthIdentifier();
|
||||
|
||||
return $this->decodePusherResponse(
|
||||
$request,
|
||||
$this->pusher->presence_auth(
|
||||
$request->channel_name, $request->socket_id,
|
||||
$this->retrieveUser($request, $channelName)->getAuthIdentifier(), $result
|
||||
$broadcastIdentifier, $result
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -109,20 +117,44 @@ class PusherBroadcaster extends Broadcaster
|
||||
{
|
||||
$socket = Arr::pull($payload, 'socket');
|
||||
|
||||
$response = $this->pusher->trigger(
|
||||
$this->formatChannels($channels), $event, $payload, $socket, true
|
||||
);
|
||||
if ($this->pusherServerIsVersionFiveOrGreater()) {
|
||||
$parameters = $socket !== null ? ['socket_id' => $socket] : [];
|
||||
|
||||
if ((is_array($response) && $response['status'] >= 200 && $response['status'] <= 299)
|
||||
|| $response === true) {
|
||||
return;
|
||||
try {
|
||||
$this->pusher->trigger(
|
||||
$this->formatChannels($channels), $event, $payload, $parameters
|
||||
);
|
||||
} catch (ApiErrorException $e) {
|
||||
throw new BroadcastException(
|
||||
sprintf('Pusher error: %s.', $e->getMessage())
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$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(
|
||||
! empty($response['body'])
|
||||
? sprintf('Pusher error: %s.', $response['body'])
|
||||
: 'Failed to connect to Pusher.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
throw new BroadcastException(
|
||||
! empty($response['body'])
|
||||
? sprintf('Pusher error: %s.', $response['body'])
|
||||
: 'Failed to connect to Pusher.'
|
||||
);
|
||||
/**
|
||||
* Determine if the Pusher PHP server is version 5.0 or greater.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function pusherServerIsVersionFiveOrGreater()
|
||||
{
|
||||
return class_exists(ApiErrorException::class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,4 +166,15 @@ class PusherBroadcaster extends Broadcaster
|
||||
{
|
||||
return $this->pusher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Pusher SDK instance.
|
||||
*
|
||||
* @param \Pusher\Pusher $pusher
|
||||
* @return void
|
||||
*/
|
||||
public function setPusher($pusher)
|
||||
{
|
||||
$this->pusher = $pusher;
|
||||
}
|
||||
}
|
||||
|
@@ -20,16 +20,16 @@ class RedisBroadcaster extends Broadcaster
|
||||
/**
|
||||
* The Redis connection to use for broadcasting.
|
||||
*
|
||||
* @var string
|
||||
* @var ?string
|
||||
*/
|
||||
protected $connection;
|
||||
protected $connection = null;
|
||||
|
||||
/**
|
||||
* The Redis key prefix.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $prefix;
|
||||
protected $prefix = '';
|
||||
|
||||
/**
|
||||
* Create a new broadcaster instance.
|
||||
@@ -60,8 +60,9 @@ class RedisBroadcaster extends Broadcaster
|
||||
str_replace($this->prefix, '', $request->channel_name)
|
||||
);
|
||||
|
||||
if ($this->isGuardedChannel($request->channel_name) &&
|
||||
! $this->retrieveUser($request, $channelName)) {
|
||||
if (empty($request->channel_name) ||
|
||||
($this->isGuardedChannel($request->channel_name) &&
|
||||
! $this->retrieveUser($request, $channelName))) {
|
||||
throw new AccessDeniedHttpException;
|
||||
}
|
||||
|
||||
@@ -85,8 +86,14 @@ class RedisBroadcaster extends Broadcaster
|
||||
|
||||
$channelName = $this->normalizeChannelName($request->channel_name);
|
||||
|
||||
$user = $this->retrieveUser($request, $channelName);
|
||||
|
||||
$broadcastIdentifier = method_exists($user, 'getAuthIdentifierForBroadcasting')
|
||||
? $user->getAuthIdentifierForBroadcasting()
|
||||
: $user->getAuthIdentifier();
|
||||
|
||||
return json_encode(['channel_data' => [
|
||||
'user_id' => $this->retrieveUser($request, $channelName)->getAuthIdentifier(),
|
||||
'user_id' => $broadcastIdentifier,
|
||||
'user_info' => $result,
|
||||
]]);
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ use Illuminate\Support\Str;
|
||||
trait UsePusherChannelConventions
|
||||
{
|
||||
/**
|
||||
* Return true if channel is protected by authentication.
|
||||
* Return true if the channel is protected by authentication.
|
||||
*
|
||||
* @param string $channel
|
||||
* @return bool
|
||||
|
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Illuminate\Broadcasting;
|
||||
|
||||
use Illuminate\Contracts\Broadcasting\HasBroadcastChannel;
|
||||
|
||||
class Channel
|
||||
{
|
||||
/**
|
||||
@@ -14,12 +16,12 @@ class Channel
|
||||
/**
|
||||
* Create a new channel instance.
|
||||
*
|
||||
* @param string $name
|
||||
* @param \Illuminate\Contracts\Broadcasting\HasBroadcastChannel|string $name
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->name = $name instanceof HasBroadcastChannel ? $name->broadcastChannel() : $name;
|
||||
}
|
||||
|
||||
/**
|
||||
|
40
vendor/laravel/framework/src/Illuminate/Broadcasting/InteractsWithBroadcasting.php
vendored
Normal file
40
vendor/laravel/framework/src/Illuminate/Broadcasting/InteractsWithBroadcasting.php
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Broadcasting;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
trait InteractsWithBroadcasting
|
||||
{
|
||||
/**
|
||||
* The broadcaster connection to use to broadcast the event.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $broadcastConnection = [null];
|
||||
|
||||
/**
|
||||
* Broadcast the event using a specific broadcaster.
|
||||
*
|
||||
* @param array|string|null $connection
|
||||
* @return $this
|
||||
*/
|
||||
public function broadcastVia($connection = null)
|
||||
{
|
||||
$this->broadcastConnection = is_null($connection)
|
||||
? [null]
|
||||
: Arr::wrap($connection);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the broadcaster connections the event should be broadcast on.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function broadcastConnections()
|
||||
{
|
||||
return $this->broadcastConnection;
|
||||
}
|
||||
}
|
@@ -33,6 +33,21 @@ class PendingBroadcast
|
||||
$this->events = $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast the event using a specific broadcaster.
|
||||
*
|
||||
* @param string|null $connection
|
||||
* @return $this
|
||||
*/
|
||||
public function via($connection = null)
|
||||
{
|
||||
if (method_exists($this->event, 'broadcastVia')) {
|
||||
$this->event->broadcastVia($connection);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast the event to everyone except the current user.
|
||||
*
|
||||
|
@@ -2,16 +2,20 @@
|
||||
|
||||
namespace Illuminate\Broadcasting;
|
||||
|
||||
use Illuminate\Contracts\Broadcasting\HasBroadcastChannel;
|
||||
|
||||
class PrivateChannel extends Channel
|
||||
{
|
||||
/**
|
||||
* Create a new channel instance.
|
||||
*
|
||||
* @param string $name
|
||||
* @param \Illuminate\Contracts\Broadcasting\HasBroadcastChannel|string $name
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($name)
|
||||
{
|
||||
$name = $name instanceof HasBroadcastChannel ? $name->broadcastChannel() : $name;
|
||||
|
||||
parent::__construct('private-'.$name);
|
||||
}
|
||||
}
|
||||
|
@@ -14,13 +14,14 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.2.5|^8.0",
|
||||
"php": "^7.3|^8.0",
|
||||
"ext-json": "*",
|
||||
"psr/log": "^1.0",
|
||||
"illuminate/bus": "^7.0",
|
||||
"illuminate/contracts": "^7.0",
|
||||
"illuminate/queue": "^7.0",
|
||||
"illuminate/support": "^7.0"
|
||||
"psr/log": "^1.0|^2.0",
|
||||
"illuminate/bus": "^8.0",
|
||||
"illuminate/collections": "^8.0",
|
||||
"illuminate/contracts": "^8.0",
|
||||
"illuminate/queue": "^8.0",
|
||||
"illuminate/support": "^8.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@@ -29,11 +30,12 @@
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "7.x-dev"
|
||||
"dev-master": "8.x-dev"
|
||||
}
|
||||
},
|
||||
"suggest": {
|
||||
"pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^4.0)."
|
||||
"ably/ably-php": "Required to use the Ably broadcast driver (^1.0).",
|
||||
"pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^4.0|^5.0|^6.0|^7.0)."
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
|
481
vendor/laravel/framework/src/Illuminate/Bus/Batch.php
vendored
Normal file
481
vendor/laravel/framework/src/Illuminate/Bus/Batch.php
vendored
Normal file
@@ -0,0 +1,481 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Bus;
|
||||
|
||||
use Carbon\CarbonImmutable;
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Queue\Factory as QueueFactory;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Queue\CallQueuedClosure;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use JsonSerializable;
|
||||
use Throwable;
|
||||
|
||||
class Batch implements Arrayable, JsonSerializable
|
||||
{
|
||||
/**
|
||||
* The queue factory implementation.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Queue\Factory
|
||||
*/
|
||||
protected $queue;
|
||||
|
||||
/**
|
||||
* The repository implementation.
|
||||
*
|
||||
* @var \Illuminate\Bus\BatchRepository
|
||||
*/
|
||||
protected $repository;
|
||||
|
||||
/**
|
||||
* The batch ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The batch name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* The total number of jobs that belong to the batch.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $totalJobs;
|
||||
|
||||
/**
|
||||
* The total number of jobs that are still pending.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $pendingJobs;
|
||||
|
||||
/**
|
||||
* The total number of jobs that have failed.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $failedJobs;
|
||||
|
||||
/**
|
||||
* The IDs of the jobs that have failed.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $failedJobIds;
|
||||
|
||||
/**
|
||||
* The batch options.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $options;
|
||||
|
||||
/**
|
||||
* The date indicating when the batch was created.
|
||||
*
|
||||
* @var \Carbon\CarbonImmutable
|
||||
*/
|
||||
public $createdAt;
|
||||
|
||||
/**
|
||||
* The date indicating when the batch was cancelled.
|
||||
*
|
||||
* @var \Carbon\CarbonImmutable|null
|
||||
*/
|
||||
public $cancelledAt;
|
||||
|
||||
/**
|
||||
* The date indicating when the batch was finished.
|
||||
*
|
||||
* @var \Carbon\CarbonImmutable|null
|
||||
*/
|
||||
public $finishedAt;
|
||||
|
||||
/**
|
||||
* Create a new batch instance.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Queue\Factory $queue
|
||||
* @param \Illuminate\Bus\BatchRepository $repository
|
||||
* @param string $id
|
||||
* @param string $name
|
||||
* @param int $totalJobs
|
||||
* @param int $pendingJobs
|
||||
* @param int $failedJobs
|
||||
* @param array $failedJobIds
|
||||
* @param array $options
|
||||
* @param \Carbon\CarbonImmutable $createdAt
|
||||
* @param \Carbon\CarbonImmutable|null $cancelledAt
|
||||
* @param \Carbon\CarbonImmutable|null $finishedAt
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(QueueFactory $queue,
|
||||
BatchRepository $repository,
|
||||
string $id,
|
||||
string $name,
|
||||
int $totalJobs,
|
||||
int $pendingJobs,
|
||||
int $failedJobs,
|
||||
array $failedJobIds,
|
||||
array $options,
|
||||
CarbonImmutable $createdAt,
|
||||
?CarbonImmutable $cancelledAt = null,
|
||||
?CarbonImmutable $finishedAt = null)
|
||||
{
|
||||
$this->queue = $queue;
|
||||
$this->repository = $repository;
|
||||
$this->id = $id;
|
||||
$this->name = $name;
|
||||
$this->totalJobs = $totalJobs;
|
||||
$this->pendingJobs = $pendingJobs;
|
||||
$this->failedJobs = $failedJobs;
|
||||
$this->failedJobIds = $failedJobIds;
|
||||
$this->options = $options;
|
||||
$this->createdAt = $createdAt;
|
||||
$this->cancelledAt = $cancelledAt;
|
||||
$this->finishedAt = $finishedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a fresh instance of the batch represented by this ID.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function fresh()
|
||||
{
|
||||
return $this->repository->find($this->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional jobs to the batch.
|
||||
*
|
||||
* @param \Illuminate\Support\Enumerable|array $jobs
|
||||
* @return self
|
||||
*/
|
||||
public function add($jobs)
|
||||
{
|
||||
$count = 0;
|
||||
|
||||
$jobs = Collection::wrap($jobs)->map(function ($job) use (&$count) {
|
||||
$job = $job instanceof Closure ? CallQueuedClosure::create($job) : $job;
|
||||
|
||||
if (is_array($job)) {
|
||||
$count += count($job);
|
||||
|
||||
return with($this->prepareBatchedChain($job), function ($chain) {
|
||||
return $chain->first()
|
||||
->allOnQueue($this->options['queue'] ?? null)
|
||||
->allOnConnection($this->options['connection'] ?? null)
|
||||
->chain($chain->slice(1)->values()->all());
|
||||
});
|
||||
} else {
|
||||
$job->withBatchId($this->id);
|
||||
|
||||
$count++;
|
||||
}
|
||||
|
||||
return $job;
|
||||
});
|
||||
|
||||
$this->repository->transaction(function () use ($jobs, $count) {
|
||||
$this->repository->incrementTotalJobs($this->id, $count);
|
||||
|
||||
$this->queue->connection($this->options['connection'] ?? null)->bulk(
|
||||
$jobs->all(),
|
||||
$data = '',
|
||||
$this->options['queue'] ?? null
|
||||
);
|
||||
});
|
||||
|
||||
return $this->fresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a chain that exists within the jobs being added.
|
||||
*
|
||||
* @param array $chain
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
protected function prepareBatchedChain(array $chain)
|
||||
{
|
||||
return collect($chain)->map(function ($job) {
|
||||
$job = $job instanceof Closure ? CallQueuedClosure::create($job) : $job;
|
||||
|
||||
return $job->withBatchId($this->id);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total number of jobs that have been processed by the batch thus far.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function processedJobs()
|
||||
{
|
||||
return $this->totalJobs - $this->pendingJobs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the percentage of jobs that have been processed (between 0-100).
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function progress()
|
||||
{
|
||||
return $this->totalJobs > 0 ? round(($this->processedJobs() / $this->totalJobs) * 100) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record that a job within the batch finished successfully, executing any callbacks if necessary.
|
||||
*
|
||||
* @param string $jobId
|
||||
* @return void
|
||||
*/
|
||||
public function recordSuccessfulJob(string $jobId)
|
||||
{
|
||||
$counts = $this->decrementPendingJobs($jobId);
|
||||
|
||||
if ($counts->pendingJobs === 0) {
|
||||
$this->repository->markAsFinished($this->id);
|
||||
}
|
||||
|
||||
if ($counts->pendingJobs === 0 && $this->hasThenCallbacks()) {
|
||||
$batch = $this->fresh();
|
||||
|
||||
collect($this->options['then'])->each(function ($handler) use ($batch) {
|
||||
$this->invokeHandlerCallback($handler, $batch);
|
||||
});
|
||||
}
|
||||
|
||||
if ($counts->allJobsHaveRanExactlyOnce() && $this->hasFinallyCallbacks()) {
|
||||
$batch = $this->fresh();
|
||||
|
||||
collect($this->options['finally'])->each(function ($handler) use ($batch) {
|
||||
$this->invokeHandlerCallback($handler, $batch);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrement the pending jobs for the batch.
|
||||
*
|
||||
* @param string $jobId
|
||||
* @return \Illuminate\Bus\UpdatedBatchJobCounts
|
||||
*/
|
||||
public function decrementPendingJobs(string $jobId)
|
||||
{
|
||||
return $this->repository->decrementPendingJobs($this->id, $jobId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the batch has finished executing.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function finished()
|
||||
{
|
||||
return ! is_null($this->finishedAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the batch has "success" callbacks.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasThenCallbacks()
|
||||
{
|
||||
return isset($this->options['then']) && ! empty($this->options['then']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the batch allows jobs to fail without cancelling the batch.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function allowsFailures()
|
||||
{
|
||||
return Arr::get($this->options, 'allowFailures', false) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the batch has job failures.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasFailures()
|
||||
{
|
||||
return $this->failedJobs > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record that a job within the batch failed to finish successfully, executing any callbacks if necessary.
|
||||
*
|
||||
* @param string $jobId
|
||||
* @param \Throwable $e
|
||||
* @return void
|
||||
*/
|
||||
public function recordFailedJob(string $jobId, $e)
|
||||
{
|
||||
$counts = $this->incrementFailedJobs($jobId);
|
||||
|
||||
if ($counts->failedJobs === 1 && ! $this->allowsFailures()) {
|
||||
$this->cancel();
|
||||
}
|
||||
|
||||
if ($counts->failedJobs === 1 && $this->hasCatchCallbacks()) {
|
||||
$batch = $this->fresh();
|
||||
|
||||
collect($this->options['catch'])->each(function ($handler) use ($batch, $e) {
|
||||
$this->invokeHandlerCallback($handler, $batch, $e);
|
||||
});
|
||||
}
|
||||
|
||||
if ($counts->allJobsHaveRanExactlyOnce() && $this->hasFinallyCallbacks()) {
|
||||
$batch = $this->fresh();
|
||||
|
||||
collect($this->options['finally'])->each(function ($handler) use ($batch, $e) {
|
||||
$this->invokeHandlerCallback($handler, $batch, $e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the failed jobs for the batch.
|
||||
*
|
||||
* @param string $jobId
|
||||
* @return \Illuminate\Bus\UpdatedBatchJobCounts
|
||||
*/
|
||||
public function incrementFailedJobs(string $jobId)
|
||||
{
|
||||
return $this->repository->incrementFailedJobs($this->id, $jobId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the batch has "catch" callbacks.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasCatchCallbacks()
|
||||
{
|
||||
return isset($this->options['catch']) && ! empty($this->options['catch']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the batch has "finally" callbacks.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasFinallyCallbacks()
|
||||
{
|
||||
return isset($this->options['finally']) && ! empty($this->options['finally']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the batch.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function cancel()
|
||||
{
|
||||
$this->repository->cancel($this->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the batch has been cancelled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canceled()
|
||||
{
|
||||
return $this->cancelled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the batch has been cancelled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function cancelled()
|
||||
{
|
||||
return ! is_null($this->cancelledAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the batch from storage.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
$this->repository->delete($this->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke a batch callback handler.
|
||||
*
|
||||
* @param callable $handler
|
||||
* @param \Illuminate\Bus\Batch $batch
|
||||
* @param \Throwable|null $e
|
||||
* @return void
|
||||
*/
|
||||
protected function invokeHandlerCallback($handler, Batch $batch, Throwable $e = null)
|
||||
{
|
||||
try {
|
||||
return $handler($batch, $e);
|
||||
} catch (Throwable $e) {
|
||||
if (function_exists('report')) {
|
||||
report($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the batch to an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'totalJobs' => $this->totalJobs,
|
||||
'pendingJobs' => $this->pendingJobs,
|
||||
'processedJobs' => $this->processedJobs(),
|
||||
'progress' => $this->progress(),
|
||||
'failedJobs' => $this->failedJobs,
|
||||
'options' => $this->options,
|
||||
'createdAt' => $this->createdAt,
|
||||
'cancelledAt' => $this->cancelledAt,
|
||||
'finishedAt' => $this->finishedAt,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the JSON serializable representation of the object.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically access the batch's "options" via properties.
|
||||
*
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
return $this->options[$key] ?? null;
|
||||
}
|
||||
}
|
58
vendor/laravel/framework/src/Illuminate/Bus/BatchFactory.php
vendored
Normal file
58
vendor/laravel/framework/src/Illuminate/Bus/BatchFactory.php
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Bus;
|
||||
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Contracts\Queue\Factory as QueueFactory;
|
||||
|
||||
class BatchFactory
|
||||
{
|
||||
/**
|
||||
* The queue factory implementation.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Queue\Factory
|
||||
*/
|
||||
protected $queue;
|
||||
|
||||
/**
|
||||
* Create a new batch factory instance.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Queue\Factory $queue
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(QueueFactory $queue)
|
||||
{
|
||||
$this->queue = $queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new batch instance.
|
||||
*
|
||||
* @param \Illuminate\Bus\BatchRepository $repository
|
||||
* @param string $id
|
||||
* @param string $name
|
||||
* @param int $totalJobs
|
||||
* @param int $pendingJobs
|
||||
* @param int $failedJobs
|
||||
* @param array $failedJobIds
|
||||
* @param array $options
|
||||
* @param \Carbon\CarbonImmutable $createdAt
|
||||
* @param \Carbon\CarbonImmutable|null $cancelledAt
|
||||
* @param \Carbon\CarbonImmutable|null $finishedAt
|
||||
* @return \Illuminate\Bus\Batch
|
||||
*/
|
||||
public function make(BatchRepository $repository,
|
||||
string $id,
|
||||
string $name,
|
||||
int $totalJobs,
|
||||
int $pendingJobs,
|
||||
int $failedJobs,
|
||||
array $failedJobIds,
|
||||
array $options,
|
||||
CarbonImmutable $createdAt,
|
||||
?CarbonImmutable $cancelledAt,
|
||||
?CarbonImmutable $finishedAt)
|
||||
{
|
||||
return new Batch($this->queue, $repository, $id, $name, $totalJobs, $pendingJobs, $failedJobs, $failedJobIds, $options, $createdAt, $cancelledAt, $finishedAt);
|
||||
}
|
||||
}
|
92
vendor/laravel/framework/src/Illuminate/Bus/BatchRepository.php
vendored
Normal file
92
vendor/laravel/framework/src/Illuminate/Bus/BatchRepository.php
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Bus;
|
||||
|
||||
use Closure;
|
||||
|
||||
interface BatchRepository
|
||||
{
|
||||
/**
|
||||
* Retrieve a list of batches.
|
||||
*
|
||||
* @param int $limit
|
||||
* @param mixed $before
|
||||
* @return \Illuminate\Bus\Batch[]
|
||||
*/
|
||||
public function get($limit, $before);
|
||||
|
||||
/**
|
||||
* Retrieve information about an existing batch.
|
||||
*
|
||||
* @param string $batchId
|
||||
* @return \Illuminate\Bus\Batch|null
|
||||
*/
|
||||
public function find(string $batchId);
|
||||
|
||||
/**
|
||||
* Store a new pending batch.
|
||||
*
|
||||
* @param \Illuminate\Bus\PendingBatch $batch
|
||||
* @return \Illuminate\Bus\Batch
|
||||
*/
|
||||
public function store(PendingBatch $batch);
|
||||
|
||||
/**
|
||||
* Increment the total number of jobs within the batch.
|
||||
*
|
||||
* @param string $batchId
|
||||
* @param int $amount
|
||||
* @return void
|
||||
*/
|
||||
public function incrementTotalJobs(string $batchId, int $amount);
|
||||
|
||||
/**
|
||||
* Decrement the total number of pending jobs for the batch.
|
||||
*
|
||||
* @param string $batchId
|
||||
* @param string $jobId
|
||||
* @return \Illuminate\Bus\UpdatedBatchJobCounts
|
||||
*/
|
||||
public function decrementPendingJobs(string $batchId, string $jobId);
|
||||
|
||||
/**
|
||||
* Increment the total number of failed jobs for the batch.
|
||||
*
|
||||
* @param string $batchId
|
||||
* @param string $jobId
|
||||
* @return \Illuminate\Bus\UpdatedBatchJobCounts
|
||||
*/
|
||||
public function incrementFailedJobs(string $batchId, string $jobId);
|
||||
|
||||
/**
|
||||
* Mark the batch that has the given ID as finished.
|
||||
*
|
||||
* @param string $batchId
|
||||
* @return void
|
||||
*/
|
||||
public function markAsFinished(string $batchId);
|
||||
|
||||
/**
|
||||
* Cancel the batch that has the given ID.
|
||||
*
|
||||
* @param string $batchId
|
||||
* @return void
|
||||
*/
|
||||
public function cancel(string $batchId);
|
||||
|
||||
/**
|
||||
* Delete the batch that has the given ID.
|
||||
*
|
||||
* @param string $batchId
|
||||
* @return void
|
||||
*/
|
||||
public function delete(string $batchId);
|
||||
|
||||
/**
|
||||
* Execute the given Closure within a storage specific transaction.
|
||||
*
|
||||
* @param \Closure $callback
|
||||
* @return mixed
|
||||
*/
|
||||
public function transaction(Closure $callback);
|
||||
}
|
52
vendor/laravel/framework/src/Illuminate/Bus/Batchable.php
vendored
Normal file
52
vendor/laravel/framework/src/Illuminate/Bus/Batchable.php
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Bus;
|
||||
|
||||
use Illuminate\Container\Container;
|
||||
|
||||
trait Batchable
|
||||
{
|
||||
/**
|
||||
* The batch ID (if applicable).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $batchId;
|
||||
|
||||
/**
|
||||
* Get the batch instance for the job, if applicable.
|
||||
*
|
||||
* @return \Illuminate\Bus\Batch|null
|
||||
*/
|
||||
public function batch()
|
||||
{
|
||||
if ($this->batchId) {
|
||||
return Container::getInstance()->make(BatchRepository::class)->find($this->batchId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the batch is still active and processing.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function batching()
|
||||
{
|
||||
$batch = $this->batch();
|
||||
|
||||
return $batch && ! $batch->cancelled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the batch ID on the job.
|
||||
*
|
||||
* @param string $batchId
|
||||
* @return $this
|
||||
*/
|
||||
public function withBatchId(string $batchId)
|
||||
{
|
||||
$this->batchId = $batchId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
@@ -23,6 +23,8 @@ class BusServiceProvider extends ServiceProvider implements DeferrableProvider
|
||||
});
|
||||
});
|
||||
|
||||
$this->registerBatchServices();
|
||||
|
||||
$this->app->alias(
|
||||
Dispatcher::class, DispatcherContract::class
|
||||
);
|
||||
@@ -32,6 +34,24 @@ class BusServiceProvider extends ServiceProvider implements DeferrableProvider
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the batch handling services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function registerBatchServices()
|
||||
{
|
||||
$this->app->singleton(BatchRepository::class, DatabaseBatchRepository::class);
|
||||
|
||||
$this->app->singleton(DatabaseBatchRepository::class, function ($app) {
|
||||
return new DatabaseBatchRepository(
|
||||
$app->make(BatchFactory::class),
|
||||
$app->make('db')->connection($app->config->get('queue.batching.database')),
|
||||
$app->config->get('queue.batching.table', 'job_batches')
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the services provided by the provider.
|
||||
*
|
||||
@@ -43,6 +63,7 @@ class BusServiceProvider extends ServiceProvider implements DeferrableProvider
|
||||
Dispatcher::class,
|
||||
DispatcherContract::class,
|
||||
QueueingDispatcherContract::class,
|
||||
BatchRepository::class,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
347
vendor/laravel/framework/src/Illuminate/Bus/DatabaseBatchRepository.php
vendored
Normal file
347
vendor/laravel/framework/src/Illuminate/Bus/DatabaseBatchRepository.php
vendored
Normal file
@@ -0,0 +1,347 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Bus;
|
||||
|
||||
use Carbon\CarbonImmutable;
|
||||
use Closure;
|
||||
use DateTimeInterface;
|
||||
use Illuminate\Database\Connection;
|
||||
use Illuminate\Database\PostgresConnection;
|
||||
use Illuminate\Database\Query\Expression;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class DatabaseBatchRepository implements PrunableBatchRepository
|
||||
{
|
||||
/**
|
||||
* The batch factory instance.
|
||||
*
|
||||
* @var \Illuminate\Bus\BatchFactory
|
||||
*/
|
||||
protected $factory;
|
||||
|
||||
/**
|
||||
* The database connection instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Connection
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* The database table to use to store batch information.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table;
|
||||
|
||||
/**
|
||||
* Create a new batch repository instance.
|
||||
*
|
||||
* @param \Illuminate\Bus\BatchFactory $factory
|
||||
* @param \Illuminate\Database\Connection $connection
|
||||
* @param string $table
|
||||
*/
|
||||
public function __construct(BatchFactory $factory, Connection $connection, string $table)
|
||||
{
|
||||
$this->factory = $factory;
|
||||
$this->connection = $connection;
|
||||
$this->table = $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of batches.
|
||||
*
|
||||
* @param int $limit
|
||||
* @param mixed $before
|
||||
* @return \Illuminate\Bus\Batch[]
|
||||
*/
|
||||
public function get($limit = 50, $before = null)
|
||||
{
|
||||
return $this->connection->table($this->table)
|
||||
->orderByDesc('id')
|
||||
->take($limit)
|
||||
->when($before, function ($q) use ($before) {
|
||||
return $q->where('id', '<', $before);
|
||||
})
|
||||
->get()
|
||||
->map(function ($batch) {
|
||||
return $this->toBatch($batch);
|
||||
})
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve information about an existing batch.
|
||||
*
|
||||
* @param string $batchId
|
||||
* @return \Illuminate\Bus\Batch|null
|
||||
*/
|
||||
public function find(string $batchId)
|
||||
{
|
||||
$batch = $this->connection->table($this->table)
|
||||
->where('id', $batchId)
|
||||
->first();
|
||||
|
||||
if ($batch) {
|
||||
return $this->toBatch($batch);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new pending batch.
|
||||
*
|
||||
* @param \Illuminate\Bus\PendingBatch $batch
|
||||
* @return \Illuminate\Bus\Batch
|
||||
*/
|
||||
public function store(PendingBatch $batch)
|
||||
{
|
||||
$id = (string) Str::orderedUuid();
|
||||
|
||||
$this->connection->table($this->table)->insert([
|
||||
'id' => $id,
|
||||
'name' => $batch->name,
|
||||
'total_jobs' => 0,
|
||||
'pending_jobs' => 0,
|
||||
'failed_jobs' => 0,
|
||||
'failed_job_ids' => '[]',
|
||||
'options' => $this->serialize($batch->options),
|
||||
'created_at' => time(),
|
||||
'cancelled_at' => null,
|
||||
'finished_at' => null,
|
||||
]);
|
||||
|
||||
return $this->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the total number of jobs within the batch.
|
||||
*
|
||||
* @param string $batchId
|
||||
* @param int $amount
|
||||
* @return void
|
||||
*/
|
||||
public function incrementTotalJobs(string $batchId, int $amount)
|
||||
{
|
||||
$this->connection->table($this->table)->where('id', $batchId)->update([
|
||||
'total_jobs' => new Expression('total_jobs + '.$amount),
|
||||
'pending_jobs' => new Expression('pending_jobs + '.$amount),
|
||||
'finished_at' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrement the total number of pending jobs for the batch.
|
||||
*
|
||||
* @param string $batchId
|
||||
* @param string $jobId
|
||||
* @return \Illuminate\Bus\UpdatedBatchJobCounts
|
||||
*/
|
||||
public function decrementPendingJobs(string $batchId, string $jobId)
|
||||
{
|
||||
$values = $this->updateAtomicValues($batchId, function ($batch) use ($jobId) {
|
||||
return [
|
||||
'pending_jobs' => $batch->pending_jobs - 1,
|
||||
'failed_jobs' => $batch->failed_jobs,
|
||||
'failed_job_ids' => json_encode(array_values(array_diff(json_decode($batch->failed_job_ids, true), [$jobId]))),
|
||||
];
|
||||
});
|
||||
|
||||
return new UpdatedBatchJobCounts(
|
||||
$values['pending_jobs'],
|
||||
$values['failed_jobs']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the total number of failed jobs for the batch.
|
||||
*
|
||||
* @param string $batchId
|
||||
* @param string $jobId
|
||||
* @return \Illuminate\Bus\UpdatedBatchJobCounts
|
||||
*/
|
||||
public function incrementFailedJobs(string $batchId, string $jobId)
|
||||
{
|
||||
$values = $this->updateAtomicValues($batchId, function ($batch) use ($jobId) {
|
||||
return [
|
||||
'pending_jobs' => $batch->pending_jobs,
|
||||
'failed_jobs' => $batch->failed_jobs + 1,
|
||||
'failed_job_ids' => json_encode(array_values(array_unique(array_merge(json_decode($batch->failed_job_ids, true), [$jobId])))),
|
||||
];
|
||||
});
|
||||
|
||||
return new UpdatedBatchJobCounts(
|
||||
$values['pending_jobs'],
|
||||
$values['failed_jobs']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an atomic value within the batch.
|
||||
*
|
||||
* @param string $batchId
|
||||
* @param \Closure $callback
|
||||
* @return int|null
|
||||
*/
|
||||
protected function updateAtomicValues(string $batchId, Closure $callback)
|
||||
{
|
||||
return $this->connection->transaction(function () use ($batchId, $callback) {
|
||||
$batch = $this->connection->table($this->table)->where('id', $batchId)
|
||||
->lockForUpdate()
|
||||
->first();
|
||||
|
||||
return is_null($batch) ? [] : tap($callback($batch), function ($values) use ($batchId) {
|
||||
$this->connection->table($this->table)->where('id', $batchId)->update($values);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the batch that has the given ID as finished.
|
||||
*
|
||||
* @param string $batchId
|
||||
* @return void
|
||||
*/
|
||||
public function markAsFinished(string $batchId)
|
||||
{
|
||||
$this->connection->table($this->table)->where('id', $batchId)->update([
|
||||
'finished_at' => time(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the batch that has the given ID.
|
||||
*
|
||||
* @param string $batchId
|
||||
* @return void
|
||||
*/
|
||||
public function cancel(string $batchId)
|
||||
{
|
||||
$this->connection->table($this->table)->where('id', $batchId)->update([
|
||||
'cancelled_at' => time(),
|
||||
'finished_at' => time(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the batch that has the given ID.
|
||||
*
|
||||
* @param string $batchId
|
||||
* @return void
|
||||
*/
|
||||
public function delete(string $batchId)
|
||||
{
|
||||
$this->connection->table($this->table)->where('id', $batchId)->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prune all of the entries older than the given date.
|
||||
*
|
||||
* @param \DateTimeInterface $before
|
||||
* @return int
|
||||
*/
|
||||
public function prune(DateTimeInterface $before)
|
||||
{
|
||||
$query = $this->connection->table($this->table)
|
||||
->whereNotNull('finished_at')
|
||||
->where('finished_at', '<', $before->getTimestamp());
|
||||
|
||||
$totalDeleted = 0;
|
||||
|
||||
do {
|
||||
$deleted = $query->take(1000)->delete();
|
||||
|
||||
$totalDeleted += $deleted;
|
||||
} while ($deleted !== 0);
|
||||
|
||||
return $totalDeleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prune all of the unfinished entries older than the given date.
|
||||
*
|
||||
* @param \DateTimeInterface $before
|
||||
* @return int
|
||||
*/
|
||||
public function pruneUnfinished(DateTimeInterface $before)
|
||||
{
|
||||
$query = $this->connection->table($this->table)
|
||||
->whereNull('finished_at')
|
||||
->where('created_at', '<', $before->getTimestamp());
|
||||
|
||||
$totalDeleted = 0;
|
||||
|
||||
do {
|
||||
$deleted = $query->take(1000)->delete();
|
||||
|
||||
$totalDeleted += $deleted;
|
||||
} while ($deleted !== 0);
|
||||
|
||||
return $totalDeleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given Closure within a storage specific transaction.
|
||||
*
|
||||
* @param \Closure $callback
|
||||
* @return mixed
|
||||
*/
|
||||
public function transaction(Closure $callback)
|
||||
{
|
||||
return $this->connection->transaction(function () use ($callback) {
|
||||
return $callback();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the given value.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return string
|
||||
*/
|
||||
protected function serialize($value)
|
||||
{
|
||||
$serialized = serialize($value);
|
||||
|
||||
return $this->connection instanceof PostgresConnection
|
||||
? base64_encode($serialized)
|
||||
: $serialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserialize the given value.
|
||||
*
|
||||
* @param string $serialized
|
||||
* @return mixed
|
||||
*/
|
||||
protected function unserialize($serialized)
|
||||
{
|
||||
if ($this->connection instanceof PostgresConnection &&
|
||||
! Str::contains($serialized, [':', ';'])) {
|
||||
$serialized = base64_decode($serialized);
|
||||
}
|
||||
|
||||
return unserialize($serialized);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given raw batch to a Batch object.
|
||||
*
|
||||
* @param object $batch
|
||||
* @return \Illuminate\Bus\Batch
|
||||
*/
|
||||
protected function toBatch($batch)
|
||||
{
|
||||
return $this->factory->make(
|
||||
$this,
|
||||
$batch->id,
|
||||
$batch->name,
|
||||
(int) $batch->total_jobs,
|
||||
(int) $batch->pending_jobs,
|
||||
(int) $batch->failed_jobs,
|
||||
json_decode($batch->failed_job_ids, true),
|
||||
$this->unserialize($batch->options),
|
||||
CarbonImmutable::createFromTimestamp($batch->created_at),
|
||||
$batch->cancelled_at ? CarbonImmutable::createFromTimestamp($batch->cancelled_at) : $batch->cancelled_at,
|
||||
$batch->finished_at ? CarbonImmutable::createFromTimestamp($batch->finished_at) : $batch->finished_at
|
||||
);
|
||||
}
|
||||
}
|
@@ -7,7 +7,11 @@ use Illuminate\Contracts\Bus\QueueingDispatcher;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Contracts\Queue\Queue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\PendingChain;
|
||||
use Illuminate\Pipeline\Pipeline;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\Jobs\SyncJob;
|
||||
use Illuminate\Support\Collection;
|
||||
use RuntimeException;
|
||||
|
||||
class Dispatcher implements QueueingDispatcher
|
||||
@@ -69,35 +73,100 @@ class Dispatcher implements QueueingDispatcher
|
||||
*/
|
||||
public function dispatch($command)
|
||||
{
|
||||
if ($this->queueResolver && $this->commandShouldBeQueued($command)) {
|
||||
return $this->dispatchToQueue($command);
|
||||
}
|
||||
|
||||
return $this->dispatchNow($command);
|
||||
return $this->queueResolver && $this->commandShouldBeQueued($command)
|
||||
? $this->dispatchToQueue($command)
|
||||
: $this->dispatchNow($command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch a command to its appropriate handler in the current process.
|
||||
*
|
||||
* Queueable jobs will be dispatched to the "sync" queue.
|
||||
*
|
||||
* @param mixed $command
|
||||
* @param mixed $handler
|
||||
* @return mixed
|
||||
*/
|
||||
public function dispatchSync($command, $handler = null)
|
||||
{
|
||||
if ($this->queueResolver &&
|
||||
$this->commandShouldBeQueued($command) &&
|
||||
method_exists($command, 'onConnection')) {
|
||||
return $this->dispatchToQueue($command->onConnection('sync'));
|
||||
}
|
||||
|
||||
return $this->dispatchNow($command, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch a command to its appropriate handler in the current process without using the synchronous queue.
|
||||
*
|
||||
* @param mixed $command
|
||||
* @param mixed $handler
|
||||
* @return mixed
|
||||
*/
|
||||
public function dispatchNow($command, $handler = null)
|
||||
{
|
||||
$uses = class_uses_recursive($command);
|
||||
|
||||
if (in_array(InteractsWithQueue::class, $uses) &&
|
||||
in_array(Queueable::class, $uses) &&
|
||||
! $command->job) {
|
||||
$command->setJob(new SyncJob($this->container, json_encode([]), 'sync', 'sync'));
|
||||
}
|
||||
|
||||
if ($handler || $handler = $this->getCommandHandler($command)) {
|
||||
$callback = function ($command) use ($handler) {
|
||||
return $handler->handle($command);
|
||||
$method = method_exists($handler, 'handle') ? 'handle' : '__invoke';
|
||||
|
||||
return $handler->{$method}($command);
|
||||
};
|
||||
} else {
|
||||
$callback = function ($command) {
|
||||
return $this->container->call([$command, 'handle']);
|
||||
$method = method_exists($command, 'handle') ? 'handle' : '__invoke';
|
||||
|
||||
return $this->container->call([$command, $method]);
|
||||
};
|
||||
}
|
||||
|
||||
return $this->pipeline->send($command)->through($this->pipes)->then($callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to find the batch with the given ID.
|
||||
*
|
||||
* @param string $batchId
|
||||
* @return \Illuminate\Bus\Batch|null
|
||||
*/
|
||||
public function findBatch(string $batchId)
|
||||
{
|
||||
return $this->container->make(BatchRepository::class)->find($batchId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new batch of queueable jobs.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection|array|mixed $jobs
|
||||
* @return \Illuminate\Bus\PendingBatch
|
||||
*/
|
||||
public function batch($jobs)
|
||||
{
|
||||
return new PendingBatch($this->container, Collection::wrap($jobs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new chain of queueable jobs.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection|array $jobs
|
||||
* @return \Illuminate\Foundation\Bus\PendingChain
|
||||
*/
|
||||
public function chain($jobs)
|
||||
{
|
||||
$jobs = Collection::wrap($jobs);
|
||||
|
||||
return new PendingChain($jobs->shift(), $jobs->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given command has a handler.
|
||||
*
|
||||
@@ -140,6 +209,8 @@ class Dispatcher implements QueueingDispatcher
|
||||
*
|
||||
* @param mixed $command
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function dispatchToQueue($command)
|
||||
{
|
||||
|
26
vendor/laravel/framework/src/Illuminate/Bus/Events/BatchDispatched.php
vendored
Normal file
26
vendor/laravel/framework/src/Illuminate/Bus/Events/BatchDispatched.php
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Bus\Events;
|
||||
|
||||
use Illuminate\Bus\Batch;
|
||||
|
||||
class BatchDispatched
|
||||
{
|
||||
/**
|
||||
* The batch instance.
|
||||
*
|
||||
* @var \Illuminate\Bus\Batch
|
||||
*/
|
||||
public $batch;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param \Illuminate\Bus\Batch $batch
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Batch $batch)
|
||||
{
|
||||
$this->batch = $batch;
|
||||
}
|
||||
}
|
272
vendor/laravel/framework/src/Illuminate/Bus/PendingBatch.php
vendored
Normal file
272
vendor/laravel/framework/src/Illuminate/Bus/PendingBatch.php
vendored
Normal file
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Bus;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Bus\Events\BatchDispatched;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Contracts\Events\Dispatcher as EventDispatcher;
|
||||
use Illuminate\Queue\SerializableClosureFactory;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Throwable;
|
||||
|
||||
class PendingBatch
|
||||
{
|
||||
/**
|
||||
* The IoC container instance.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Container\Container
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* The batch name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name = '';
|
||||
|
||||
/**
|
||||
* The jobs that belong to the batch.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
public $jobs;
|
||||
|
||||
/**
|
||||
* The batch options.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $options = [];
|
||||
|
||||
/**
|
||||
* Create a new pending batch instance.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Container\Container $container
|
||||
* @param \Illuminate\Support\Collection $jobs
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Container $container, Collection $jobs)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->jobs = $jobs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add jobs to the batch.
|
||||
*
|
||||
* @param iterable $jobs
|
||||
* @return $this
|
||||
*/
|
||||
public function add($jobs)
|
||||
{
|
||||
foreach ($jobs as $job) {
|
||||
$this->jobs->push($job);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback to be executed after all jobs in the batch have executed successfully.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function then($callback)
|
||||
{
|
||||
$this->options['then'][] = $callback instanceof Closure
|
||||
? SerializableClosureFactory::make($callback)
|
||||
: $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "then" callbacks that have been registered with the pending batch.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function thenCallbacks()
|
||||
{
|
||||
return $this->options['then'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback to be executed after the first failing job in the batch.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function catch($callback)
|
||||
{
|
||||
$this->options['catch'][] = $callback instanceof Closure
|
||||
? SerializableClosureFactory::make($callback)
|
||||
: $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "catch" callbacks that have been registered with the pending batch.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function catchCallbacks()
|
||||
{
|
||||
return $this->options['catch'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback to be executed after the batch has finished executing.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function finally($callback)
|
||||
{
|
||||
$this->options['finally'][] = $callback instanceof Closure
|
||||
? SerializableClosureFactory::make($callback)
|
||||
: $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "finally" callbacks that have been registered with the pending batch.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function finallyCallbacks()
|
||||
{
|
||||
return $this->options['finally'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the batch should not be cancelled when a job within the batch fails.
|
||||
*
|
||||
* @param bool $allowFailures
|
||||
* @return $this
|
||||
*/
|
||||
public function allowFailures($allowFailures = true)
|
||||
{
|
||||
$this->options['allowFailures'] = $allowFailures;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the pending batch allows jobs to fail without cancelling the batch.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function allowsFailures()
|
||||
{
|
||||
return Arr::get($this->options, 'allowFailures', false) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name for the batch.
|
||||
*
|
||||
* @param string $name
|
||||
* @return $this
|
||||
*/
|
||||
public function name(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the queue connection that the batched jobs should run on.
|
||||
*
|
||||
* @param string $connection
|
||||
* @return $this
|
||||
*/
|
||||
public function onConnection(string $connection)
|
||||
{
|
||||
$this->options['connection'] = $connection;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the connection used by the pending batch.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function connection()
|
||||
{
|
||||
return $this->options['connection'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the queue that the batched jobs should run on.
|
||||
*
|
||||
* @param string $queue
|
||||
* @return $this
|
||||
*/
|
||||
public function onQueue(string $queue)
|
||||
{
|
||||
$this->options['queue'] = $queue;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the queue used by the pending batch.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function queue()
|
||||
{
|
||||
return $this->options['queue'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional data into the batch's options array.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*/
|
||||
public function withOption(string $key, $value)
|
||||
{
|
||||
$this->options[$key] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch the batch.
|
||||
*
|
||||
* @return \Illuminate\Bus\Batch
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function dispatch()
|
||||
{
|
||||
$repository = $this->container->make(BatchRepository::class);
|
||||
|
||||
try {
|
||||
$batch = $repository->store($this);
|
||||
|
||||
$batch = $batch->add($this->jobs);
|
||||
} catch (Throwable $e) {
|
||||
if (isset($batch)) {
|
||||
$repository->delete($batch->id);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->container->make(EventDispatcher::class)->dispatch(
|
||||
new BatchDispatched($batch)
|
||||
);
|
||||
|
||||
return $batch;
|
||||
}
|
||||
}
|
16
vendor/laravel/framework/src/Illuminate/Bus/PrunableBatchRepository.php
vendored
Normal file
16
vendor/laravel/framework/src/Illuminate/Bus/PrunableBatchRepository.php
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Bus;
|
||||
|
||||
use DateTimeInterface;
|
||||
|
||||
interface PrunableBatchRepository extends BatchRepository
|
||||
{
|
||||
/**
|
||||
* Prune all of the entries older than the given date.
|
||||
*
|
||||
* @param \DateTimeInterface $before
|
||||
* @return int
|
||||
*/
|
||||
public function prune(DateTimeInterface $before);
|
||||
}
|
@@ -37,6 +37,13 @@ trait Queueable
|
||||
*/
|
||||
public $chainQueue;
|
||||
|
||||
/**
|
||||
* The callbacks to be executed on chain failure.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
public $chainCatchCallbacks;
|
||||
|
||||
/**
|
||||
* The number of seconds before the job should be made available.
|
||||
*
|
||||
@@ -44,6 +51,13 @@ trait Queueable
|
||||
*/
|
||||
public $delay;
|
||||
|
||||
/**
|
||||
* Indicates whether the job should be dispatched after all database transactions have committed.
|
||||
*
|
||||
* @var bool|null
|
||||
*/
|
||||
public $afterCommit;
|
||||
|
||||
/**
|
||||
* The middleware the job should be dispatched through.
|
||||
*
|
||||
@@ -125,6 +139,30 @@ trait Queueable
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the job should be dispatched after all database transactions have committed.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function afterCommit()
|
||||
{
|
||||
$this->afterCommit = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the job should not wait until database transactions have been committed before dispatching.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function beforeCommit()
|
||||
{
|
||||
$this->afterCommit = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the middleware the job should be dispatched through.
|
||||
*
|
||||
@@ -158,6 +196,8 @@ trait Queueable
|
||||
*
|
||||
* @param mixed $job
|
||||
* @return string
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function serializeJob($job)
|
||||
{
|
||||
@@ -190,7 +230,21 @@ trait Queueable
|
||||
|
||||
$next->chainConnection = $this->chainConnection;
|
||||
$next->chainQueue = $this->chainQueue;
|
||||
$next->chainCatchCallbacks = $this->chainCatchCallbacks;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke all of the chain's failed job callbacks.
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @return void
|
||||
*/
|
||||
public function invokeChainCatchCallbacks($e)
|
||||
{
|
||||
collect($this->chainCatchCallbacks)->each(function ($callback) use ($e) {
|
||||
$callback($e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
48
vendor/laravel/framework/src/Illuminate/Bus/UniqueLock.php
vendored
Normal file
48
vendor/laravel/framework/src/Illuminate/Bus/UniqueLock.php
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Bus;
|
||||
|
||||
use Illuminate\Contracts\Cache\Repository as Cache;
|
||||
|
||||
class UniqueLock
|
||||
{
|
||||
/**
|
||||
* The cache repository implementation.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Cache\Repository
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* Create a new unique lock manager instance.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Cache\Repository $cache
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Cache $cache)
|
||||
{
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to acquire a lock for the given job.
|
||||
*
|
||||
* @param mixed $job
|
||||
* @return bool
|
||||
*/
|
||||
public function acquire($job)
|
||||
{
|
||||
$uniqueId = method_exists($job, 'uniqueId')
|
||||
? $job->uniqueId()
|
||||
: ($job->uniqueId ?? '');
|
||||
|
||||
$cache = method_exists($job, 'uniqueVia')
|
||||
? $job->uniqueVia()
|
||||
: $this->cache;
|
||||
|
||||
return (bool) $cache->lock(
|
||||
$key = 'laravel_unique_job:'.get_class($job).$uniqueId,
|
||||
$job->uniqueFor ?? 0
|
||||
)->get();
|
||||
}
|
||||
}
|
43
vendor/laravel/framework/src/Illuminate/Bus/UpdatedBatchJobCounts.php
vendored
Normal file
43
vendor/laravel/framework/src/Illuminate/Bus/UpdatedBatchJobCounts.php
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Bus;
|
||||
|
||||
class UpdatedBatchJobCounts
|
||||
{
|
||||
/**
|
||||
* The number of pending jobs remaining for the batch.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $pendingJobs;
|
||||
|
||||
/**
|
||||
* The number of failed jobs that belong to the batch.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $failedJobs;
|
||||
|
||||
/**
|
||||
* Create a new batch job counts object.
|
||||
*
|
||||
* @param int $pendingJobs
|
||||
* @param int $failedJobs
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(int $pendingJobs = 0, int $failedJobs = 0)
|
||||
{
|
||||
$this->pendingJobs = $pendingJobs;
|
||||
$this->failedJobs = $failedJobs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if all jobs have run exactly once.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function allJobsHaveRanExactlyOnce()
|
||||
{
|
||||
return ($this->pendingJobs - $this->failedJobs) === 0;
|
||||
}
|
||||
}
|
@@ -14,10 +14,11 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.2.5|^8.0",
|
||||
"illuminate/contracts": "^7.0",
|
||||
"illuminate/pipeline": "^7.0",
|
||||
"illuminate/support": "^7.0"
|
||||
"php": "^7.3|^8.0",
|
||||
"illuminate/collections": "^8.0",
|
||||
"illuminate/contracts": "^8.0",
|
||||
"illuminate/pipeline": "^8.0",
|
||||
"illuminate/support": "^8.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@@ -26,7 +27,7 @@
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "7.x-dev"
|
||||
"dev-master": "8.x-dev"
|
||||
}
|
||||
},
|
||||
"suggest": {
|
||||
|
85
vendor/laravel/framework/src/Illuminate/Cache/CacheLock.php
vendored
Normal file
85
vendor/laravel/framework/src/Illuminate/Cache/CacheLock.php
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Cache;
|
||||
|
||||
class CacheLock extends Lock
|
||||
{
|
||||
/**
|
||||
* The cache store implementation.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Cache\Store
|
||||
*/
|
||||
protected $store;
|
||||
|
||||
/**
|
||||
* Create a new lock instance.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Cache\Store $store
|
||||
* @param string $name
|
||||
* @param int $seconds
|
||||
* @param string|null $owner
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($store, $name, $seconds, $owner = null)
|
||||
{
|
||||
parent::__construct($name, $seconds, $owner);
|
||||
|
||||
$this->store = $store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to acquire the lock.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function acquire()
|
||||
{
|
||||
if (method_exists($this->store, 'add') && $this->seconds > 0) {
|
||||
return $this->store->add(
|
||||
$this->name, $this->owner, $this->seconds
|
||||
);
|
||||
}
|
||||
|
||||
if (! is_null($this->store->get($this->name))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ($this->seconds > 0)
|
||||
? $this->store->put($this->name, $this->owner, $this->seconds)
|
||||
: $this->store->forever($this->name, $this->owner, $this->seconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the lock.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function release()
|
||||
{
|
||||
if ($this->isOwnedByCurrentProcess()) {
|
||||
return $this->store->forget($this->name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases this lock regardless of ownership.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function forceRelease()
|
||||
{
|
||||
$this->store->forget($this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the owner value written into the driver for this lock.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getCurrentOwner()
|
||||
{
|
||||
return $this->store->get($this->name);
|
||||
}
|
||||
}
|
@@ -199,7 +199,11 @@ class CacheManager implements FactoryContract
|
||||
|
||||
$connection = $config['connection'] ?? 'default';
|
||||
|
||||
return $this->repository(new RedisStore($redis, $this->getPrefix($config), $connection));
|
||||
$store = new RedisStore($redis, $this->getPrefix($config), $connection);
|
||||
|
||||
return $this->repository(
|
||||
$store->setLockConnection($config['lock_connection'] ?? $connection)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -212,15 +216,17 @@ class CacheManager implements FactoryContract
|
||||
{
|
||||
$connection = $this->app['db']->connection($config['connection'] ?? null);
|
||||
|
||||
return $this->repository(
|
||||
new DatabaseStore(
|
||||
$connection,
|
||||
$config['table'],
|
||||
$this->getPrefix($config),
|
||||
$config['lock_table'] ?? 'cache_locks',
|
||||
$config['lock_lottery'] ?? [2, 100]
|
||||
)
|
||||
$store = new DatabaseStore(
|
||||
$connection,
|
||||
$config['table'],
|
||||
$this->getPrefix($config),
|
||||
$config['lock_table'] ?? 'cache_locks',
|
||||
$config['lock_lottery'] ?? [2, 100]
|
||||
);
|
||||
|
||||
return $this->repository($store->setLockConnection(
|
||||
$this->app['db']->connection($config['lock_connection'] ?? $config['connection'] ?? null)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -231,21 +237,11 @@ class CacheManager implements FactoryContract
|
||||
*/
|
||||
protected function createDynamodbDriver(array $config)
|
||||
{
|
||||
$dynamoConfig = [
|
||||
'region' => $config['region'],
|
||||
'version' => 'latest',
|
||||
'endpoint' => $config['endpoint'] ?? null,
|
||||
];
|
||||
|
||||
if ($config['key'] && $config['secret']) {
|
||||
$dynamoConfig['credentials'] = Arr::only(
|
||||
$config, ['key', 'secret', 'token']
|
||||
);
|
||||
}
|
||||
$client = $this->newDynamodbClient($config);
|
||||
|
||||
return $this->repository(
|
||||
new DynamoDbStore(
|
||||
new DynamoDbClient($dynamoConfig),
|
||||
$client,
|
||||
$config['table'],
|
||||
$config['attributes']['key'] ?? 'key',
|
||||
$config['attributes']['value'] ?? 'value',
|
||||
@@ -255,6 +251,28 @@ class CacheManager implements FactoryContract
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new DynamoDb Client instance.
|
||||
*
|
||||
* @return DynamoDbClient
|
||||
*/
|
||||
protected function newDynamodbClient(array $config)
|
||||
{
|
||||
$dynamoConfig = [
|
||||
'region' => $config['region'],
|
||||
'version' => 'latest',
|
||||
'endpoint' => $config['endpoint'] ?? null,
|
||||
];
|
||||
|
||||
if (isset($config['key']) && isset($config['secret'])) {
|
||||
$dynamoConfig['credentials'] = Arr::only(
|
||||
$config, ['key', 'secret', 'token']
|
||||
);
|
||||
}
|
||||
|
||||
return new DynamoDbClient($dynamoConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new cache repository with the given implementation.
|
||||
*
|
||||
@@ -314,7 +332,11 @@ class CacheManager implements FactoryContract
|
||||
*/
|
||||
protected function getConfig($name)
|
||||
{
|
||||
return $this->app['config']["cache.stores.{$name}"];
|
||||
if (! is_null($name) && $name !== 'null') {
|
||||
return $this->app['config']["cache.stores.{$name}"];
|
||||
}
|
||||
|
||||
return ['driver' => 'null'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -357,6 +379,19 @@ class CacheManager implements FactoryContract
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect the given driver and remove from local cache.
|
||||
*
|
||||
* @param string|null $name
|
||||
* @return void
|
||||
*/
|
||||
public function purge($name = null)
|
||||
{
|
||||
$name = $name ?? $this->getDefaultDriver();
|
||||
|
||||
unset($this->stores[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a custom driver creator Closure.
|
||||
*
|
||||
|
@@ -30,6 +30,12 @@ class CacheServiceProvider extends ServiceProvider implements DeferrableProvider
|
||||
$this->app->singleton('memcached.connector', function () {
|
||||
return new MemcachedConnector;
|
||||
});
|
||||
|
||||
$this->app->singleton(RateLimiter::class, function ($app) {
|
||||
return new RateLimiter($app->make('cache')->driver(
|
||||
$app['config']->get('cache.limiter')
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,7 +46,7 @@ class CacheServiceProvider extends ServiceProvider implements DeferrableProvider
|
||||
public function provides()
|
||||
{
|
||||
return [
|
||||
'cache', 'cache.store', 'cache.psr6', 'memcached.connector',
|
||||
'cache', 'cache.store', 'cache.psr6', 'memcached.connector', RateLimiter::class,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -116,7 +116,7 @@ class ClearCommand extends Command
|
||||
*/
|
||||
protected function tags()
|
||||
{
|
||||
return array_filter(explode(',', $this->option('tags')));
|
||||
return array_filter(explode(',', $this->option('tags') ?? ''));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -14,10 +14,16 @@ class CreateCacheTable extends Migration
|
||||
public function up()
|
||||
{
|
||||
Schema::create('cache', function (Blueprint $table) {
|
||||
$table->string('key')->unique();
|
||||
$table->string('key')->primary();
|
||||
$table->mediumText('value');
|
||||
$table->integer('expiration');
|
||||
});
|
||||
|
||||
Schema::create('cache_locks', function (Blueprint $table) {
|
||||
$table->string('key')->primary();
|
||||
$table->string('owner');
|
||||
$table->integer('expiration');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -28,5 +34,6 @@ class CreateCacheTable extends Migration
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('cache');
|
||||
Schema::dropIfExists('cache_locks');
|
||||
}
|
||||
}
|
||||
|
@@ -136,4 +136,14 @@ class DatabaseLock extends Lock
|
||||
{
|
||||
return optional($this->connection->table($this->table)->where('key', $this->name)->first())->owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the database connection being used to manage the lock.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getConnectionName()
|
||||
{
|
||||
return $this->connection->getName();
|
||||
}
|
||||
}
|
||||
|
@@ -23,6 +23,13 @@ class DatabaseStore implements LockProvider, Store
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* The database connection instance that should be used to manage locks.
|
||||
*
|
||||
* @var \Illuminate\Database\ConnectionInterface
|
||||
*/
|
||||
protected $lockConnection;
|
||||
|
||||
/**
|
||||
* The name of the cache table.
|
||||
*
|
||||
@@ -155,8 +162,6 @@ class DatabaseStore implements LockProvider, Store
|
||||
'expiration' => $expiration,
|
||||
]) >= 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -267,7 +272,7 @@ class DatabaseStore implements LockProvider, Store
|
||||
public function lock($name, $seconds = 0, $owner = null)
|
||||
{
|
||||
return new DatabaseLock(
|
||||
$this->connection,
|
||||
$this->lockConnection ?? $this->connection,
|
||||
$this->lockTable,
|
||||
$this->prefix.$name,
|
||||
$seconds,
|
||||
@@ -333,6 +338,19 @@ class DatabaseStore implements LockProvider, Store
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the name of the connection that should be used to manage locks.
|
||||
*
|
||||
* @param \Illuminate\Database\ConnectionInterface $connection
|
||||
* @return $this
|
||||
*/
|
||||
public function setLockConnection($connection)
|
||||
{
|
||||
$this->lockConnection = $connection;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache key prefix.
|
||||
*
|
||||
|
@@ -34,9 +34,11 @@ class DynamoDbLock extends Lock
|
||||
*/
|
||||
public function acquire()
|
||||
{
|
||||
return $this->dynamo->add(
|
||||
$this->name, $this->owner, $this->seconds
|
||||
);
|
||||
if ($this->seconds > 0) {
|
||||
return $this->dynamo->add($this->name, $this->owner, $this->seconds);
|
||||
} else {
|
||||
return $this->dynamo->add($this->name, $this->owner, 86400);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -525,4 +525,14 @@ class DynamoDbStore implements LockProvider, Store
|
||||
{
|
||||
$this->prefix = ! empty($prefix) ? $prefix.':' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DynamoDb Client instance.
|
||||
*
|
||||
* @return DynamoDbClient
|
||||
*/
|
||||
public function getClient()
|
||||
{
|
||||
return $this->dynamo;
|
||||
}
|
||||
}
|
||||
|
@@ -3,13 +3,16 @@
|
||||
namespace Illuminate\Cache;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Cache\LockProvider;
|
||||
use Illuminate\Contracts\Cache\Store;
|
||||
use Illuminate\Contracts\Filesystem\LockTimeoutException;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Filesystem\LockableFile;
|
||||
use Illuminate\Support\InteractsWithTime;
|
||||
|
||||
class FileStore implements Store
|
||||
class FileStore implements Store, LockProvider
|
||||
{
|
||||
use InteractsWithTime, RetrievesMultipleKeys;
|
||||
use InteractsWithTime, HasCacheLock, RetrievesMultipleKeys;
|
||||
|
||||
/**
|
||||
* The Illuminate Filesystem instance.
|
||||
@@ -75,7 +78,7 @@ class FileStore implements Store
|
||||
);
|
||||
|
||||
if ($result !== false && $result > 0) {
|
||||
$this->ensureFileHasCorrectPermissions($path);
|
||||
$this->ensurePermissionsAreCorrect($path);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -83,6 +86,45 @@ class FileStore implements Store
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store an item in the cache if the key doesn't exist.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param int $seconds
|
||||
* @return bool
|
||||
*/
|
||||
public function add($key, $value, $seconds)
|
||||
{
|
||||
$this->ensureCacheDirectoryExists($path = $this->path($key));
|
||||
|
||||
$file = new LockableFile($path, 'c+');
|
||||
|
||||
try {
|
||||
$file->getExclusiveLock();
|
||||
} catch (LockTimeoutException $e) {
|
||||
$file->close();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$expire = $file->read(10);
|
||||
|
||||
if (empty($expire) || $this->currentTime() >= $expire) {
|
||||
$file->truncate()
|
||||
->write($this->expiration($seconds).serialize($value))
|
||||
->close();
|
||||
|
||||
$this->ensurePermissionsAreCorrect($path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$file->close();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the file cache directory if necessary.
|
||||
*
|
||||
@@ -91,18 +133,24 @@ class FileStore implements Store
|
||||
*/
|
||||
protected function ensureCacheDirectoryExists($path)
|
||||
{
|
||||
if (! $this->files->exists(dirname($path))) {
|
||||
$this->files->makeDirectory(dirname($path), 0777, true, true);
|
||||
$directory = dirname($path);
|
||||
|
||||
if (! $this->files->exists($directory)) {
|
||||
$this->files->makeDirectory($directory, 0777, true, true);
|
||||
|
||||
// We're creating two levels of directories (e.g. 7e/24), so we check them both...
|
||||
$this->ensurePermissionsAreCorrect($directory);
|
||||
$this->ensurePermissionsAreCorrect(dirname($directory));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the cache file has the correct permissions.
|
||||
* Ensure the created node has the correct permissions.
|
||||
*
|
||||
* @param string $path
|
||||
* @return void
|
||||
*/
|
||||
protected function ensureFileHasCorrectPermissions($path)
|
||||
protected function ensurePermissionsAreCorrect($path)
|
||||
{
|
||||
if (is_null($this->filePermission) ||
|
||||
intval($this->files->chmod($path), 8) == $this->filePermission) {
|
||||
|
31
vendor/laravel/framework/src/Illuminate/Cache/HasCacheLock.php
vendored
Normal file
31
vendor/laravel/framework/src/Illuminate/Cache/HasCacheLock.php
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Cache;
|
||||
|
||||
trait HasCacheLock
|
||||
{
|
||||
/**
|
||||
* Get a lock instance.
|
||||
*
|
||||
* @param string $name
|
||||
* @param int $seconds
|
||||
* @param string|null $owner
|
||||
* @return \Illuminate\Contracts\Cache\Lock
|
||||
*/
|
||||
public function lock($name, $seconds = 0, $owner = null)
|
||||
{
|
||||
return new CacheLock($this, $name, $seconds, $owner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a lock instance using the owner identifier.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $owner
|
||||
* @return \Illuminate\Contracts\Cache\Lock
|
||||
*/
|
||||
public function restoreLock($name, $owner)
|
||||
{
|
||||
return $this->lock($name, 0, $owner);
|
||||
}
|
||||
}
|
@@ -105,7 +105,7 @@ abstract class Lock implements LockContract
|
||||
*
|
||||
* @param int $seconds
|
||||
* @param callable|null $callback
|
||||
* @return bool
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Illuminate\Contracts\Cache\LockTimeoutException
|
||||
*/
|
||||
@@ -153,7 +153,7 @@ abstract class Lock implements LockContract
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the number of milliseconds to sleep in between blocked lock aquisition attempts.
|
||||
* Specify the number of milliseconds to sleep in between blocked lock acquisition attempts.
|
||||
*
|
||||
* @param int $milliseconds
|
||||
* @return $this
|
||||
|
46
vendor/laravel/framework/src/Illuminate/Cache/NoLock.php
vendored
Normal file
46
vendor/laravel/framework/src/Illuminate/Cache/NoLock.php
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Cache;
|
||||
|
||||
class NoLock extends Lock
|
||||
{
|
||||
/**
|
||||
* Attempt to acquire the lock.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function acquire()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the lock.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function release()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases this lock in disregard of ownership.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function forceRelease()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the owner value written into the driver for this lock.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getCurrentOwner()
|
||||
{
|
||||
return $this->owner;
|
||||
}
|
||||
}
|
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace Illuminate\Cache;
|
||||
|
||||
class NullStore extends TaggableStore
|
||||
use Illuminate\Contracts\Cache\LockProvider;
|
||||
|
||||
class NullStore extends TaggableStore implements LockProvider
|
||||
{
|
||||
use RetrievesMultipleKeys;
|
||||
|
||||
@@ -10,7 +12,7 @@ class NullStore extends TaggableStore
|
||||
* Retrieve an item from the cache by key.
|
||||
*
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
* @return void
|
||||
*/
|
||||
public function get($key)
|
||||
{
|
||||
@@ -35,7 +37,7 @@ class NullStore extends TaggableStore
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return int|bool
|
||||
* @return bool
|
||||
*/
|
||||
public function increment($key, $value = 1)
|
||||
{
|
||||
@@ -47,7 +49,7 @@ class NullStore extends TaggableStore
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return int|bool
|
||||
* @return bool
|
||||
*/
|
||||
public function decrement($key, $value = 1)
|
||||
{
|
||||
@@ -66,6 +68,31 @@ class NullStore extends TaggableStore
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a lock instance.
|
||||
*
|
||||
* @param string $name
|
||||
* @param int $seconds
|
||||
* @param string|null $owner
|
||||
* @return \Illuminate\Contracts\Cache\Lock
|
||||
*/
|
||||
public function lock($name, $seconds = 0, $owner = null)
|
||||
{
|
||||
return new NoLock($name, $seconds, $owner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a lock instance using the owner identifier.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $owner
|
||||
* @return \Illuminate\Contracts\Cache\Lock
|
||||
*/
|
||||
public function restoreLock($name, $owner)
|
||||
{
|
||||
return $this->lock($name, 0, $owner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item from the cache.
|
||||
*
|
||||
|
35
vendor/laravel/framework/src/Illuminate/Cache/PhpRedisLock.php
vendored
Normal file
35
vendor/laravel/framework/src/Illuminate/Cache/PhpRedisLock.php
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Cache;
|
||||
|
||||
use Illuminate\Redis\Connections\PhpRedisConnection;
|
||||
|
||||
class PhpRedisLock extends RedisLock
|
||||
{
|
||||
/**
|
||||
* Create a new phpredis lock instance.
|
||||
*
|
||||
* @param \Illuminate\Redis\Connections\PhpRedisConnection $redis
|
||||
* @param string $name
|
||||
* @param int $seconds
|
||||
* @param string|null $owner
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(PhpRedisConnection $redis, string $name, int $seconds, ?string $owner = null)
|
||||
{
|
||||
parent::__construct($redis, $name, $seconds, $owner);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function release()
|
||||
{
|
||||
return (bool) $this->redis->eval(
|
||||
LuaScripts::releaseLock(),
|
||||
1,
|
||||
$this->name,
|
||||
...$this->redis->pack([$this->owner])
|
||||
);
|
||||
}
|
||||
}
|
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Illuminate\Cache;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Cache\Repository as Cache;
|
||||
use Illuminate\Support\InteractsWithTime;
|
||||
|
||||
@@ -16,6 +17,13 @@ class RateLimiter
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* The configured limit object resolvers.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $limiters = [];
|
||||
|
||||
/**
|
||||
* Create a new rate limiter instance.
|
||||
*
|
||||
@@ -27,6 +35,51 @@ class RateLimiter
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a named limiter configuration.
|
||||
*
|
||||
* @param string $name
|
||||
* @param \Closure $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function for(string $name, Closure $callback)
|
||||
{
|
||||
$this->limiters[$name] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the given named rate limiter.
|
||||
*
|
||||
* @param string $name
|
||||
* @return \Closure
|
||||
*/
|
||||
public function limiter(string $name)
|
||||
{
|
||||
return $this->limiters[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to execute a callback if it's not limited.
|
||||
*
|
||||
* @param string $key
|
||||
* @param int $maxAttempts
|
||||
* @param \Closure $callback
|
||||
* @param int $decaySeconds
|
||||
* @return mixed
|
||||
*/
|
||||
public function attempt($key, $maxAttempts, Closure $callback, $decaySeconds = 60)
|
||||
{
|
||||
if ($this->tooManyAttempts($key, $maxAttempts)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return tap($callback() ?: true, function () use ($key, $decaySeconds) {
|
||||
$this->hit($key, $decaySeconds);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given key has been "accessed" too many times.
|
||||
*
|
||||
@@ -36,6 +89,8 @@ class RateLimiter
|
||||
*/
|
||||
public function tooManyAttempts($key, $maxAttempts)
|
||||
{
|
||||
$key = $this->cleanRateLimiterKey($key);
|
||||
|
||||
if ($this->attempts($key) >= $maxAttempts) {
|
||||
if ($this->cache->has($key.':timer')) {
|
||||
return true;
|
||||
@@ -56,6 +111,8 @@ class RateLimiter
|
||||
*/
|
||||
public function hit($key, $decaySeconds = 60)
|
||||
{
|
||||
$key = $this->cleanRateLimiterKey($key);
|
||||
|
||||
$this->cache->add(
|
||||
$key.':timer', $this->availableAt($decaySeconds), $decaySeconds
|
||||
);
|
||||
@@ -79,6 +136,8 @@ class RateLimiter
|
||||
*/
|
||||
public function attempts($key)
|
||||
{
|
||||
$key = $this->cleanRateLimiterKey($key);
|
||||
|
||||
return $this->cache->get($key, 0);
|
||||
}
|
||||
|
||||
@@ -90,9 +149,27 @@ class RateLimiter
|
||||
*/
|
||||
public function resetAttempts($key)
|
||||
{
|
||||
$key = $this->cleanRateLimiterKey($key);
|
||||
|
||||
return $this->cache->forget($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of retries left for the given key.
|
||||
*
|
||||
* @param string $key
|
||||
* @param int $maxAttempts
|
||||
* @return int
|
||||
*/
|
||||
public function remaining($key, $maxAttempts)
|
||||
{
|
||||
$key = $this->cleanRateLimiterKey($key);
|
||||
|
||||
$attempts = $this->attempts($key);
|
||||
|
||||
return $maxAttempts - $attempts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of retries left for the given key.
|
||||
*
|
||||
@@ -102,9 +179,7 @@ class RateLimiter
|
||||
*/
|
||||
public function retriesLeft($key, $maxAttempts)
|
||||
{
|
||||
$attempts = $this->attempts($key);
|
||||
|
||||
return $maxAttempts - $attempts;
|
||||
return $this->remaining($key, $maxAttempts);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,6 +190,8 @@ class RateLimiter
|
||||
*/
|
||||
public function clear($key)
|
||||
{
|
||||
$key = $this->cleanRateLimiterKey($key);
|
||||
|
||||
$this->resetAttempts($key);
|
||||
|
||||
$this->cache->forget($key.':timer');
|
||||
@@ -128,6 +205,19 @@ class RateLimiter
|
||||
*/
|
||||
public function availableIn($key)
|
||||
{
|
||||
return $this->cache->get($key.':timer') - $this->currentTime();
|
||||
$key = $this->cleanRateLimiterKey($key);
|
||||
|
||||
return max(0, $this->cache->get($key.':timer') - $this->currentTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean the rate limiter key from unicode characters.
|
||||
*
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
public function cleanRateLimiterKey($key)
|
||||
{
|
||||
return preg_replace('/&([a-z])[a-z]+;/i', '$1', htmlentities($key));
|
||||
}
|
||||
}
|
||||
|
18
vendor/laravel/framework/src/Illuminate/Cache/RateLimiting/GlobalLimit.php
vendored
Normal file
18
vendor/laravel/framework/src/Illuminate/Cache/RateLimiting/GlobalLimit.php
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Cache\RateLimiting;
|
||||
|
||||
class GlobalLimit extends Limit
|
||||
{
|
||||
/**
|
||||
* Create a new limit instance.
|
||||
*
|
||||
* @param int $maxAttempts
|
||||
* @param int $decayMinutes
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(int $maxAttempts, int $decayMinutes = 1)
|
||||
{
|
||||
parent::__construct('', $maxAttempts, $decayMinutes);
|
||||
}
|
||||
}
|
132
vendor/laravel/framework/src/Illuminate/Cache/RateLimiting/Limit.php
vendored
Normal file
132
vendor/laravel/framework/src/Illuminate/Cache/RateLimiting/Limit.php
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Cache\RateLimiting;
|
||||
|
||||
class Limit
|
||||
{
|
||||
/**
|
||||
* The rate limit signature key.
|
||||
*
|
||||
* @var mixed|string
|
||||
*/
|
||||
public $key;
|
||||
|
||||
/**
|
||||
* The maximum number of attempts allowed within the given number of minutes.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $maxAttempts;
|
||||
|
||||
/**
|
||||
* The number of minutes until the rate limit is reset.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $decayMinutes;
|
||||
|
||||
/**
|
||||
* The response generator callback.
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
public $responseCallback;
|
||||
|
||||
/**
|
||||
* Create a new limit instance.
|
||||
*
|
||||
* @param mixed|string $key
|
||||
* @param int $maxAttempts
|
||||
* @param int $decayMinutes
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($key = '', int $maxAttempts = 60, int $decayMinutes = 1)
|
||||
{
|
||||
$this->key = $key;
|
||||
$this->maxAttempts = $maxAttempts;
|
||||
$this->decayMinutes = $decayMinutes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new rate limit.
|
||||
*
|
||||
* @param int $maxAttempts
|
||||
* @return static
|
||||
*/
|
||||
public static function perMinute($maxAttempts)
|
||||
{
|
||||
return new static('', $maxAttempts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new rate limit using minutes as decay time.
|
||||
*
|
||||
* @param int $decayMinutes
|
||||
* @param int $maxAttempts
|
||||
* @return static
|
||||
*/
|
||||
public static function perMinutes($decayMinutes, $maxAttempts)
|
||||
{
|
||||
return new static('', $maxAttempts, $decayMinutes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new rate limit using hours as decay time.
|
||||
*
|
||||
* @param int $maxAttempts
|
||||
* @param int $decayHours
|
||||
* @return static
|
||||
*/
|
||||
public static function perHour($maxAttempts, $decayHours = 1)
|
||||
{
|
||||
return new static('', $maxAttempts, 60 * $decayHours);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new rate limit using days as decay time.
|
||||
*
|
||||
* @param int $maxAttempts
|
||||
* @param int $decayDays
|
||||
* @return static
|
||||
*/
|
||||
public static function perDay($maxAttempts, $decayDays = 1)
|
||||
{
|
||||
return new static('', $maxAttempts, 60 * 24 * $decayDays);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new unlimited rate limit.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function none()
|
||||
{
|
||||
return new Unlimited;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the key of the rate limit.
|
||||
*
|
||||
* @param string $key
|
||||
* @return $this
|
||||
*/
|
||||
public function by($key)
|
||||
{
|
||||
$this->key = $key;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the callback that should generate the response when the limit is exceeded.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function response(callable $callback)
|
||||
{
|
||||
$this->responseCallback = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
16
vendor/laravel/framework/src/Illuminate/Cache/RateLimiting/Unlimited.php
vendored
Normal file
16
vendor/laravel/framework/src/Illuminate/Cache/RateLimiting/Unlimited.php
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Cache\RateLimiting;
|
||||
|
||||
class Unlimited extends GlobalLimit
|
||||
{
|
||||
/**
|
||||
* Create a new limit instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(PHP_INT_MAX);
|
||||
}
|
||||
}
|
@@ -70,4 +70,14 @@ class RedisLock extends Lock
|
||||
{
|
||||
return $this->redis->get($this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the Redis connection being used to manage the lock.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getConnectionName()
|
||||
{
|
||||
return $this->redis->getName();
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ namespace Illuminate\Cache;
|
||||
|
||||
use Illuminate\Contracts\Cache\LockProvider;
|
||||
use Illuminate\Contracts\Redis\Factory as Redis;
|
||||
use Illuminate\Redis\Connections\PhpRedisConnection;
|
||||
|
||||
class RedisStore extends TaggableStore implements LockProvider
|
||||
{
|
||||
@@ -22,12 +23,19 @@ class RedisStore extends TaggableStore implements LockProvider
|
||||
protected $prefix;
|
||||
|
||||
/**
|
||||
* The Redis connection that should be used.
|
||||
* The Redis connection instance that should be used to manage locks.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* The name of the connection that should be used for locks.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $lockConnection;
|
||||
|
||||
/**
|
||||
* Create a new Redis store.
|
||||
*
|
||||
@@ -181,7 +189,15 @@ class RedisStore extends TaggableStore implements LockProvider
|
||||
*/
|
||||
public function lock($name, $seconds = 0, $owner = null)
|
||||
{
|
||||
return new RedisLock($this->connection(), $this->prefix.$name, $seconds, $owner);
|
||||
$lockName = $this->prefix.$name;
|
||||
|
||||
$lockConnection = $this->lockConnection();
|
||||
|
||||
if ($lockConnection instanceof PhpRedisConnection) {
|
||||
return new PhpRedisLock($lockConnection, $lockName, $seconds, $owner);
|
||||
}
|
||||
|
||||
return new RedisLock($lockConnection, $lockName, $seconds, $owner);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -243,7 +259,17 @@ class RedisStore extends TaggableStore implements LockProvider
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection name to be used.
|
||||
* Get the Redis connection instance that should be used to manage locks.
|
||||
*
|
||||
* @return \Illuminate\Redis\Connections\Connection
|
||||
*/
|
||||
public function lockConnection()
|
||||
{
|
||||
return $this->redis->connection($this->lockConnection ?? $this->connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the name of the connection that should be used to store data.
|
||||
*
|
||||
* @param string $connection
|
||||
* @return void
|
||||
@@ -253,6 +279,19 @@ class RedisStore extends TaggableStore implements LockProvider
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the name of the connection that should be used to manage locks.
|
||||
*
|
||||
* @param string $connection
|
||||
* @return $this
|
||||
*/
|
||||
public function setLockConnection($connection)
|
||||
{
|
||||
$this->lockConnection = $connection;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Redis database instance.
|
||||
*
|
||||
|
@@ -10,6 +10,7 @@ class RedisTaggedCache extends TaggedCache
|
||||
* @var string
|
||||
*/
|
||||
const REFERENCE_KEY_FOREVER = 'forever_ref';
|
||||
|
||||
/**
|
||||
* Standard reference key.
|
||||
*
|
||||
@@ -41,13 +42,13 @@ class RedisTaggedCache extends TaggedCache
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
* @return int|bool
|
||||
*/
|
||||
public function increment($key, $value = 1)
|
||||
{
|
||||
$this->pushStandardKeys($this->tags->getNamespace(), $key);
|
||||
|
||||
parent::increment($key, $value);
|
||||
return parent::increment($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,13 +56,13 @@ class RedisTaggedCache extends TaggedCache
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
* @return int|bool
|
||||
*/
|
||||
public function decrement($key, $value = 1)
|
||||
{
|
||||
$this->pushStandardKeys($this->tags->getNamespace(), $key);
|
||||
|
||||
parent::decrement($key, $value);
|
||||
return parent::decrement($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,7 +89,9 @@ class RedisTaggedCache extends TaggedCache
|
||||
$this->deleteForeverKeys();
|
||||
$this->deleteStandardKeys();
|
||||
|
||||
return parent::flush();
|
||||
$this->tags->flush();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,13 +178,26 @@ class RedisTaggedCache extends TaggedCache
|
||||
*/
|
||||
protected function deleteValues($referenceKey)
|
||||
{
|
||||
$values = array_unique($this->store->connection()->smembers($referenceKey));
|
||||
$cursor = $defaultCursorValue = '0';
|
||||
|
||||
if (count($values) > 0) {
|
||||
foreach (array_chunk($values, 1000) as $valuesChunk) {
|
||||
do {
|
||||
[$cursor, $valuesChunk] = $this->store->connection()->sscan(
|
||||
$referenceKey, $cursor, ['match' => '*', 'count' => 1000]
|
||||
);
|
||||
|
||||
// PhpRedis client returns false if set does not exist or empty. Array destruction
|
||||
// on false stores null in each variable. If valuesChunk is null, it means that
|
||||
// there were not results from the previously executed "sscan" Redis command.
|
||||
if (is_null($valuesChunk)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$valuesChunk = array_unique($valuesChunk);
|
||||
|
||||
if (count($valuesChunk) > 0) {
|
||||
$this->store->connection()->del(...$valuesChunk);
|
||||
}
|
||||
}
|
||||
} while (((string) $cursor) !== $defaultCursorValue);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -131,6 +131,8 @@ class Repository implements ArrayAccess, CacheContract
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return iterable
|
||||
*/
|
||||
public function getMultiple($keys, $default = null)
|
||||
{
|
||||
@@ -219,6 +221,8 @@ class Repository implements ArrayAccess, CacheContract
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function set($key, $value, $ttl = null)
|
||||
{
|
||||
@@ -276,6 +280,8 @@ class Repository implements ArrayAccess, CacheContract
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function setMultiple($values, $ttl = null)
|
||||
{
|
||||
@@ -292,8 +298,12 @@ class Repository implements ArrayAccess, CacheContract
|
||||
*/
|
||||
public function add($key, $value, $ttl = null)
|
||||
{
|
||||
$seconds = null;
|
||||
|
||||
if ($ttl !== null) {
|
||||
if ($this->getSeconds($ttl) <= 0) {
|
||||
$seconds = $this->getSeconds($ttl);
|
||||
|
||||
if ($seconds <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -301,8 +311,6 @@ class Repository implements ArrayAccess, CacheContract
|
||||
// has a chance to override this logic. Some drivers better support the way
|
||||
// this operation should work with a total "atomic" implementation of it.
|
||||
if (method_exists($this->store, 'add')) {
|
||||
$seconds = $this->getSeconds($ttl);
|
||||
|
||||
return $this->store->add(
|
||||
$this->itemKey($key), $value, $seconds
|
||||
);
|
||||
@@ -313,7 +321,7 @@ class Repository implements ArrayAccess, CacheContract
|
||||
// so it exists for subsequent requests. Then, we will return true so it is
|
||||
// easy to know if the value gets added. Otherwise, we will return false.
|
||||
if (is_null($this->get($key))) {
|
||||
return $this->put($key, $value, $ttl);
|
||||
return $this->put($key, $value, $seconds);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -365,7 +373,7 @@ class Repository implements ArrayAccess, CacheContract
|
||||
* Get an item from the cache, or execute the given Closure and store the result.
|
||||
*
|
||||
* @param string $key
|
||||
* @param \DateTimeInterface|\DateInterval|int|null $ttl
|
||||
* @param \Closure|\DateTimeInterface|\DateInterval|int|null $ttl
|
||||
* @param \Closure $callback
|
||||
* @return mixed
|
||||
*/
|
||||
@@ -380,7 +388,7 @@ class Repository implements ArrayAccess, CacheContract
|
||||
return $value;
|
||||
}
|
||||
|
||||
$this->put($key, $value = $callback(), $ttl);
|
||||
$this->put($key, $value = $callback(), value($ttl));
|
||||
|
||||
return $value;
|
||||
}
|
||||
@@ -437,6 +445,8 @@ class Repository implements ArrayAccess, CacheContract
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete($key)
|
||||
{
|
||||
@@ -445,6 +455,8 @@ class Repository implements ArrayAccess, CacheContract
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteMultiple($keys)
|
||||
{
|
||||
@@ -461,6 +473,8 @@ class Repository implements ArrayAccess, CacheContract
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
@@ -477,7 +491,7 @@ class Repository implements ArrayAccess, CacheContract
|
||||
*/
|
||||
public function tags($names)
|
||||
{
|
||||
if (! method_exists($this->store, 'tags')) {
|
||||
if (! $this->supportsTags()) {
|
||||
throw new BadMethodCallException('This cache store does not support tagging.');
|
||||
}
|
||||
|
||||
@@ -501,6 +515,33 @@ class Repository implements ArrayAccess, CacheContract
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the number of seconds for the given TTL.
|
||||
*
|
||||
* @param \DateTimeInterface|\DateInterval|int $ttl
|
||||
* @return int
|
||||
*/
|
||||
protected function getSeconds($ttl)
|
||||
{
|
||||
$duration = $this->parseDateInterval($ttl);
|
||||
|
||||
if ($duration instanceof DateTimeInterface) {
|
||||
$duration = Carbon::now()->diffInRealSeconds($duration, false);
|
||||
}
|
||||
|
||||
return (int) ($duration > 0 ? $duration : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current store supports tags.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function supportsTags()
|
||||
{
|
||||
return method_exists($this->store, 'tags');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default cache time.
|
||||
*
|
||||
@@ -537,7 +578,7 @@ class Repository implements ArrayAccess, CacheContract
|
||||
/**
|
||||
* Fire an event for this cache instance.
|
||||
*
|
||||
* @param string $event
|
||||
* @param object|string $event
|
||||
* @return void
|
||||
*/
|
||||
protected function event($event)
|
||||
@@ -574,6 +615,7 @@ class Repository implements ArrayAccess, CacheContract
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetExists($key)
|
||||
{
|
||||
return $this->has($key);
|
||||
@@ -585,6 +627,7 @@ class Repository implements ArrayAccess, CacheContract
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($key)
|
||||
{
|
||||
return $this->get($key);
|
||||
@@ -597,6 +640,7 @@ class Repository implements ArrayAccess, CacheContract
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetSet($key, $value)
|
||||
{
|
||||
$this->put($key, $value, $this->default);
|
||||
@@ -608,28 +652,12 @@ class Repository implements ArrayAccess, CacheContract
|
||||
* @param string $key
|
||||
* @return void
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetUnset($key)
|
||||
{
|
||||
$this->forget($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the number of seconds for the given TTL.
|
||||
*
|
||||
* @param \DateTimeInterface|\DateInterval|int $ttl
|
||||
* @return int
|
||||
*/
|
||||
protected function getSeconds($ttl)
|
||||
{
|
||||
$duration = $this->parseDateInterval($ttl);
|
||||
|
||||
if ($duration instanceof DateTimeInterface) {
|
||||
$duration = Carbon::now()->diffInRealSeconds($duration, false);
|
||||
}
|
||||
|
||||
return (int) $duration > 0 ? $duration : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle dynamic calls into macros or pass missing methods to the store.
|
||||
*
|
||||
|
@@ -16,8 +16,12 @@ trait RetrievesMultipleKeys
|
||||
{
|
||||
$return = [];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$return[$key] = $this->get($key);
|
||||
$keys = collect($keys)->mapWithKeys(function ($value, $key) {
|
||||
return [is_string($key) ? $key : $value => is_string($key) ? $value : null];
|
||||
})->all();
|
||||
|
||||
foreach ($keys as $key => $default) {
|
||||
$return[$key] = $this->get($key, $default);
|
||||
}
|
||||
|
||||
return $return;
|
||||
|
@@ -56,6 +56,26 @@ class TagSet
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush all the tags in the set.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function flush()
|
||||
{
|
||||
array_walk($this->names, [$this, 'flushTag']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the tag from the cache.
|
||||
*
|
||||
* @param string $name
|
||||
*/
|
||||
public function flushTag($name)
|
||||
{
|
||||
$this->store->forget($this->tagKey($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a unique namespace that changes when any of the tags are flushed.
|
||||
*
|
||||
|
@@ -52,11 +52,11 @@ class TaggedCache extends Repository
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
* @return int|bool
|
||||
*/
|
||||
public function increment($key, $value = 1)
|
||||
{
|
||||
$this->store->increment($this->itemKey($key), $value);
|
||||
return $this->store->increment($this->itemKey($key), $value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,11 +64,11 @@ class TaggedCache extends Repository
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
* @return int|bool
|
||||
*/
|
||||
public function decrement($key, $value = 1)
|
||||
{
|
||||
$this->store->decrement($this->itemKey($key), $value);
|
||||
return $this->store->decrement($this->itemKey($key), $value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,7 +105,7 @@ class TaggedCache extends Repository
|
||||
/**
|
||||
* Fire an event for this cache instance.
|
||||
*
|
||||
* @param string $event
|
||||
* @param \Illuminate\Cache\Events\CacheEvent $event
|
||||
* @return void
|
||||
*/
|
||||
protected function event($event)
|
||||
|
@@ -14,9 +14,14 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.2.5|^8.0",
|
||||
"illuminate/contracts": "^7.0",
|
||||
"illuminate/support": "^7.0"
|
||||
"php": "^7.3|^8.0",
|
||||
"illuminate/collections": "^8.0",
|
||||
"illuminate/contracts": "^8.0",
|
||||
"illuminate/macroable": "^8.0",
|
||||
"illuminate/support": "^8.0"
|
||||
},
|
||||
"provide": {
|
||||
"psr/simple-cache-implementation": "1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@@ -25,15 +30,15 @@
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "7.x-dev"
|
||||
"dev-master": "8.x-dev"
|
||||
}
|
||||
},
|
||||
"suggest": {
|
||||
"ext-memcached": "Required to use the memcache cache driver.",
|
||||
"illuminate/database": "Required to use the database cache driver (^7.0).",
|
||||
"illuminate/filesystem": "Required to use the file cache driver (^7.0).",
|
||||
"illuminate/redis": "Required to use the redis cache driver (^7.0).",
|
||||
"symfony/cache": "Required to PSR-6 cache bridge (^5.0)."
|
||||
"illuminate/database": "Required to use the database cache driver (^8.0).",
|
||||
"illuminate/filesystem": "Required to use the file cache driver (^8.0).",
|
||||
"illuminate/redis": "Required to use the redis cache driver (^8.0).",
|
||||
"symfony/cache": "Required to PSR-6 cache bridge (^5.4)."
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user