tipo: modificación del cpanel y gitignore de la carpeta vendor
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Actions;
|
||||
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Laravel\Jetstream\Events\TeamMemberUpdated;
|
||||
use Laravel\Jetstream\Jetstream;
|
||||
use Laravel\Jetstream\Rules\Role;
|
||||
|
||||
class UpdateTeamMemberRole
|
||||
{
|
||||
/**
|
||||
* Update the role for the given team member.
|
||||
*
|
||||
* @param mixed $user
|
||||
* @param mixed $team
|
||||
* @param int $teamMemberId
|
||||
* @param string $role
|
||||
* @return void
|
||||
*/
|
||||
public function update($user, $team, $teamMemberId, string $role)
|
||||
{
|
||||
Gate::forUser($user)->authorize('updateTeamMember', $team);
|
||||
|
||||
Validator::make([
|
||||
'role' => $role,
|
||||
], [
|
||||
'role' => ['required', 'string', new Role],
|
||||
])->validate();
|
||||
|
||||
$team->users()->updateExistingPivot($teamMemberId, [
|
||||
'role' => $role,
|
||||
]);
|
||||
|
||||
TeamMemberUpdated::dispatch($team->fresh(), Jetstream::findUserByIdOrFail($teamMemberId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Actions;
|
||||
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class ValidateTeamDeletion
|
||||
{
|
||||
/**
|
||||
* Validate that the team can be deleted by the given user.
|
||||
*
|
||||
* @param mixed $user
|
||||
* @param mixed $team
|
||||
* @return void
|
||||
*/
|
||||
public function validate($user, $team)
|
||||
{
|
||||
Gate::forUser($user)->authorize('delete', $team);
|
||||
|
||||
if ($team->personal_team) {
|
||||
throw ValidationException::withMessages([
|
||||
'team' => __('You may not delete your personal team.'),
|
||||
])->errorBag('deleteTeam');
|
||||
}
|
||||
}
|
||||
}
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream;
|
||||
|
||||
use Closure;
|
||||
use Detection\MobileDetect;
|
||||
|
||||
/**
|
||||
* @copyright Originally created by Jens Segers: https://github.com/jenssegers/agent
|
||||
*/
|
||||
class Agent extends MobileDetect
|
||||
{
|
||||
/**
|
||||
* List of additional operating systems.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected static $additionalOperatingSystems = [
|
||||
'Windows' => 'Windows',
|
||||
'Windows NT' => 'Windows NT',
|
||||
'OS X' => 'Mac OS X',
|
||||
'Debian' => 'Debian',
|
||||
'Ubuntu' => 'Ubuntu',
|
||||
'Macintosh' => 'PPC',
|
||||
'OpenBSD' => 'OpenBSD',
|
||||
'Linux' => 'Linux',
|
||||
'ChromeOS' => 'CrOS',
|
||||
];
|
||||
|
||||
/**
|
||||
* List of additional browsers.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected static $additionalBrowsers = [
|
||||
'Opera Mini' => 'Opera Mini',
|
||||
'Opera' => 'Opera|OPR',
|
||||
'Edge' => 'Edge|Edg',
|
||||
'Coc Coc' => 'coc_coc_browser',
|
||||
'UCBrowser' => 'UCBrowser',
|
||||
'Vivaldi' => 'Vivaldi',
|
||||
'Chrome' => 'Chrome',
|
||||
'Firefox' => 'Firefox',
|
||||
'Safari' => 'Safari',
|
||||
'IE' => 'MSIE|IEMobile|MSIEMobile|Trident/[.0-9]+',
|
||||
'Netscape' => 'Netscape',
|
||||
'Mozilla' => 'Mozilla',
|
||||
'WeChat' => 'MicroMessenger',
|
||||
];
|
||||
|
||||
/**
|
||||
* Key value store for resolved strings.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected $store = [];
|
||||
|
||||
/**
|
||||
* Get the platform name from the User Agent.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function platform()
|
||||
{
|
||||
return $this->retrieveUsingCacheOrResolve('jetstream.platform', function () {
|
||||
return $this->findDetectionRulesAgainstUserAgent(
|
||||
$this->mergeRules(MobileDetect::getOperatingSystems(), static::$additionalOperatingSystems)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the browser name from the User Agent.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function browser()
|
||||
{
|
||||
return $this->retrieveUsingCacheOrResolve('jetstream.browser', function () {
|
||||
return $this->findDetectionRulesAgainstUserAgent(
|
||||
$this->mergeRules(static::$additionalBrowsers, MobileDetect::getBrowsers())
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the device is a desktop computer.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDesktop()
|
||||
{
|
||||
return $this->retrieveUsingCacheOrResolve('jetstream.desktop', function () {
|
||||
// Check specifically for cloudfront headers if the useragent === 'Amazon CloudFront'
|
||||
if (
|
||||
$this->getUserAgent() === static::$cloudFrontUA
|
||||
&& $this->getHttpHeader('HTTP_CLOUDFRONT_IS_DESKTOP_VIEWER') === 'true'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ! $this->isMobile() && ! $this->isTablet();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Match a detection rule and return the matched key.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function findDetectionRulesAgainstUserAgent(array $rules)
|
||||
{
|
||||
$userAgent = $this->getUserAgent();
|
||||
|
||||
foreach ($rules as $key => $regex) {
|
||||
if (empty($regex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->match($regex, $userAgent)) {
|
||||
return $key ?: reset($this->matchesArray);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve from the given key from the cache or resolve the value.
|
||||
*
|
||||
* @param string $key
|
||||
* @param \Closure():mixed $callback
|
||||
* @return mixed
|
||||
*/
|
||||
protected function retrieveUsingCacheOrResolve(string $key, Closure $callback)
|
||||
{
|
||||
$cacheKey = $this->createCacheKey($key);
|
||||
|
||||
if (! is_null($cacheItem = $this->store[$cacheKey] ?? null)) {
|
||||
return $cacheItem;
|
||||
}
|
||||
|
||||
return tap(call_user_func($callback), function ($result) use ($cacheKey) {
|
||||
$this->store[$cacheKey] = $result;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge multiple rules into one array.
|
||||
*
|
||||
* @param array $all
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function mergeRules(...$all)
|
||||
{
|
||||
$merged = [];
|
||||
|
||||
foreach ($all as $rules) {
|
||||
foreach ($rules as $key => $value) {
|
||||
if (empty($merged[$key])) {
|
||||
$merged[$key] = $value;
|
||||
} elseif (is_array($merged[$key])) {
|
||||
$merged[$key][] = $value;
|
||||
} else {
|
||||
$merged[$key] .= '|'.$value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $merged;
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream;
|
||||
|
||||
use Illuminate\Contracts\Auth\StatefulGuard;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Laravel\Fortify\Actions\ConfirmPassword;
|
||||
|
||||
trait ConfirmsPasswords
|
||||
{
|
||||
/**
|
||||
* Indicates if the user's password is being confirmed.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $confirmingPassword = false;
|
||||
|
||||
/**
|
||||
* The ID of the operation being confirmed.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $confirmableId = null;
|
||||
|
||||
/**
|
||||
* The user's password.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $confirmablePassword = '';
|
||||
|
||||
/**
|
||||
* Start confirming the user's password.
|
||||
*
|
||||
* @param string $confirmableId
|
||||
* @return void
|
||||
*/
|
||||
public function startConfirmingPassword(string $confirmableId)
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
|
||||
if ($this->passwordIsConfirmed()) {
|
||||
return $this->dispatch('password-confirmed',
|
||||
id: $confirmableId,
|
||||
);
|
||||
}
|
||||
|
||||
$this->confirmingPassword = true;
|
||||
$this->confirmableId = $confirmableId;
|
||||
$this->confirmablePassword = '';
|
||||
|
||||
$this->dispatch('confirming-password');
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop confirming the user's password.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function stopConfirmingPassword()
|
||||
{
|
||||
$this->confirmingPassword = false;
|
||||
$this->confirmableId = null;
|
||||
$this->confirmablePassword = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm the user's password.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function confirmPassword()
|
||||
{
|
||||
if (! app(ConfirmPassword::class)(app(StatefulGuard::class), Auth::user(), $this->confirmablePassword)) {
|
||||
throw ValidationException::withMessages([
|
||||
'confirmable_password' => [__('This password does not match our records.')],
|
||||
]);
|
||||
}
|
||||
|
||||
session(['auth.password_confirmed_at' => time()]);
|
||||
|
||||
$this->dispatch('password-confirmed',
|
||||
id: $this->confirmableId,
|
||||
);
|
||||
|
||||
$this->stopConfirmingPassword();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the user's password has been recently confirmed.
|
||||
*
|
||||
* @param int|null $maximumSecondsSinceConfirmation
|
||||
* @return void
|
||||
*/
|
||||
protected function ensurePasswordIsConfirmed($maximumSecondsSinceConfirmation = null)
|
||||
{
|
||||
$maximumSecondsSinceConfirmation = $maximumSecondsSinceConfirmation ?: config('auth.password_timeout', 900);
|
||||
|
||||
$this->passwordIsConfirmed($maximumSecondsSinceConfirmation) ? null : abort(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user's password has been recently confirmed.
|
||||
*
|
||||
* @param int|null $maximumSecondsSinceConfirmation
|
||||
* @return bool
|
||||
*/
|
||||
protected function passwordIsConfirmed($maximumSecondsSinceConfirmation = null)
|
||||
{
|
||||
$maximumSecondsSinceConfirmation = $maximumSecondsSinceConfirmation ?: config('auth.password_timeout', 900);
|
||||
|
||||
return (time() - session('auth.password_confirmed_at', 0)) < $maximumSecondsSinceConfirmation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,901 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Console;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Console\PromptsForMissingInput;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Support\Str;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
use function Laravel\Prompts\confirm;
|
||||
use function Laravel\Prompts\multiselect;
|
||||
use function Laravel\Prompts\select;
|
||||
|
||||
#[AsCommand(name: 'jetstream:install')]
|
||||
class InstallCommand extends Command implements PromptsForMissingInput
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'jetstream:install {stack : The development stack that should be installed (inertia,livewire)}
|
||||
{--dark : Indicate that dark mode support should be installed}
|
||||
{--teams : Indicates if team support should be installed}
|
||||
{--api : Indicates if API support should be installed}
|
||||
{--verification : Indicates if email verification support should be installed}
|
||||
{--pest : Indicates if Pest should be installed}
|
||||
{--ssr : Indicates if Inertia SSR support should be installed}
|
||||
{--composer=global : Absolute path to the Composer binary which should be used to install packages}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Install the Jetstream components and resources';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! in_array($this->argument('stack'), ['inertia', 'livewire'])) {
|
||||
$this->components->error('Invalid stack. Supported stacks are [inertia] and [livewire].');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Publish...
|
||||
$this->callSilent('vendor:publish', ['--tag' => 'jetstream-config', '--force' => true]);
|
||||
$this->callSilent('vendor:publish', ['--tag' => 'jetstream-migrations', '--force' => true]);
|
||||
|
||||
$this->callSilent('vendor:publish', ['--tag' => 'fortify-config', '--force' => true]);
|
||||
$this->callSilent('vendor:publish', ['--tag' => 'fortify-support', '--force' => true]);
|
||||
$this->callSilent('vendor:publish', ['--tag' => 'fortify-migrations', '--force' => true]);
|
||||
|
||||
// Storage...
|
||||
$this->callSilent('storage:link');
|
||||
|
||||
$this->replaceInFile('/home', '/dashboard', config_path('fortify.php'));
|
||||
|
||||
if (file_exists(resource_path('views/welcome.blade.php'))) {
|
||||
$this->replaceInFile('/home', '/dashboard', resource_path('views/welcome.blade.php'));
|
||||
$this->replaceInFile('Home', 'Dashboard', resource_path('views/welcome.blade.php'));
|
||||
}
|
||||
|
||||
// Fortify Provider...
|
||||
ServiceProvider::addProviderToBootstrapFile('App\Providers\FortifyServiceProvider');
|
||||
|
||||
// Configure Session...
|
||||
$this->configureSession();
|
||||
|
||||
// Configure API...
|
||||
if ($this->option('api')) {
|
||||
$this->replaceInFile('// Features::api(),', 'Features::api(),', config_path('jetstream.php'));
|
||||
}
|
||||
|
||||
// Configure Email Verification...
|
||||
if ($this->option('verification')) {
|
||||
$this->replaceInFile('// Features::emailVerification(),', 'Features::emailVerification(),', config_path('fortify.php'));
|
||||
}
|
||||
|
||||
// Install Stack...
|
||||
if ($this->argument('stack') === 'livewire') {
|
||||
if (! $this->installLivewireStack()) {
|
||||
return 1;
|
||||
}
|
||||
} elseif ($this->argument('stack') === 'inertia') {
|
||||
if (! $this->installInertiaStack()) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Emails...
|
||||
(new Filesystem)->ensureDirectoryExists(resource_path('views/emails'));
|
||||
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/resources/views/emails', resource_path('views/emails'));
|
||||
|
||||
// Tests...
|
||||
$stubs = $this->getTestStubsPath();
|
||||
|
||||
if ($this->option('pest') || $this->isUsingPest()) {
|
||||
if ($this->hasComposerPackage('phpunit/phpunit')) {
|
||||
$this->removeComposerDevPackages(['phpunit/phpunit']);
|
||||
}
|
||||
|
||||
if (! $this->requireComposerDevPackages(['pestphp/pest', 'pestphp/pest-plugin-laravel'])) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
copy($stubs.'/Pest.php', base_path('tests/Pest.php'));
|
||||
copy($stubs.'/ExampleTest.php', base_path('tests/Feature/ExampleTest.php'));
|
||||
copy($stubs.'/ExampleUnitTest.php', base_path('tests/Unit/ExampleTest.php'));
|
||||
}
|
||||
|
||||
copy($stubs.'/AuthenticationTest.php', base_path('tests/Feature/AuthenticationTest.php'));
|
||||
copy($stubs.'/EmailVerificationTest.php', base_path('tests/Feature/EmailVerificationTest.php'));
|
||||
copy($stubs.'/PasswordConfirmationTest.php', base_path('tests/Feature/PasswordConfirmationTest.php'));
|
||||
copy($stubs.'/PasswordResetTest.php', base_path('tests/Feature/PasswordResetTest.php'));
|
||||
copy($stubs.'/RegistrationTest.php', base_path('tests/Feature/RegistrationTest.php'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the session driver for Jetstream.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function configureSession()
|
||||
{
|
||||
$this->replaceInFile('SESSION_DRIVER=cookie', 'SESSION_DRIVER=database', base_path('.env'));
|
||||
$this->replaceInFile('SESSION_DRIVER=cookie', 'SESSION_DRIVER=database', base_path('.env.example'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the Livewire stack into the application.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function installLivewireStack()
|
||||
{
|
||||
// Install Livewire...
|
||||
if (! $this->requireComposerPackages('livewire/livewire:^3.6.4')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->call('install:api', [
|
||||
'--without-migration-prompt' => true,
|
||||
]);
|
||||
|
||||
// Update Configuration...
|
||||
$this->replaceInFile('inertia', 'livewire', config_path('jetstream.php'));
|
||||
|
||||
// NPM Packages...
|
||||
$this->updateNodePackages(function ($packages) {
|
||||
return [
|
||||
'@tailwindcss/forms' => '^0.5.7',
|
||||
'@tailwindcss/typography' => '^0.5.10',
|
||||
'autoprefixer' => '^10.4.16',
|
||||
'postcss' => '^8.4.32',
|
||||
'tailwindcss' => '^3.4.0',
|
||||
] + $packages;
|
||||
});
|
||||
|
||||
// Tailwind Configuration...
|
||||
copy(__DIR__.'/../../stubs/livewire/tailwind.config.js', base_path('tailwind.config.js'));
|
||||
copy(__DIR__.'/../../stubs/livewire/postcss.config.js', base_path('postcss.config.js'));
|
||||
copy(__DIR__.'/../../stubs/livewire/vite.config.js', base_path('vite.config.js'));
|
||||
|
||||
// Directories...
|
||||
(new Filesystem)->ensureDirectoryExists(app_path('Actions/Fortify'));
|
||||
(new Filesystem)->ensureDirectoryExists(app_path('Actions/Jetstream'));
|
||||
(new Filesystem)->ensureDirectoryExists(app_path('View/Components'));
|
||||
(new Filesystem)->ensureDirectoryExists(resource_path('css'));
|
||||
(new Filesystem)->ensureDirectoryExists(resource_path('markdown'));
|
||||
(new Filesystem)->ensureDirectoryExists(resource_path('views/api'));
|
||||
(new Filesystem)->ensureDirectoryExists(resource_path('views/auth'));
|
||||
(new Filesystem)->ensureDirectoryExists(resource_path('views/components'));
|
||||
(new Filesystem)->ensureDirectoryExists(resource_path('views/layouts'));
|
||||
(new Filesystem)->ensureDirectoryExists(resource_path('views/profile'));
|
||||
|
||||
(new Filesystem)->deleteDirectory(resource_path('sass'));
|
||||
|
||||
// Terms Of Service / Privacy Policy...
|
||||
copy(__DIR__.'/../../stubs/resources/markdown/terms.md', resource_path('markdown/terms.md'));
|
||||
copy(__DIR__.'/../../stubs/resources/markdown/policy.md', resource_path('markdown/policy.md'));
|
||||
|
||||
// Service Providers...
|
||||
copy(__DIR__.'/../../stubs/app/Providers/JetstreamServiceProvider.php', $provider = app_path('Providers/JetstreamServiceProvider.php'));
|
||||
|
||||
$this->replaceInFile([
|
||||
PHP_EOL.'use Illuminate\Support\Facades\Vite;',
|
||||
PHP_EOL.PHP_EOL.' Vite::prefetch(concurrency: 3);',
|
||||
], '', $provider);
|
||||
|
||||
ServiceProvider::addProviderToBootstrapFile('App\Providers\JetstreamServiceProvider');
|
||||
|
||||
// Models...
|
||||
copy(__DIR__.'/../../stubs/app/Models/User.php', app_path('Models/User.php'));
|
||||
|
||||
// Factories...
|
||||
copy(__DIR__.'/../../database/factories/UserFactory.php', base_path('database/factories/UserFactory.php'));
|
||||
|
||||
// Actions...
|
||||
copy(__DIR__.'/../../stubs/app/Actions/Fortify/CreateNewUser.php', app_path('Actions/Fortify/CreateNewUser.php'));
|
||||
copy(__DIR__.'/../../stubs/app/Actions/Fortify/UpdateUserProfileInformation.php', app_path('Actions/Fortify/UpdateUserProfileInformation.php'));
|
||||
copy(__DIR__.'/../../stubs/app/Actions/Jetstream/DeleteUser.php', app_path('Actions/Jetstream/DeleteUser.php'));
|
||||
|
||||
// Components...
|
||||
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/livewire/resources/views/components', resource_path('views/components'));
|
||||
|
||||
// View Components...
|
||||
copy(__DIR__.'/../../stubs/livewire/app/View/Components/AppLayout.php', app_path('View/Components/AppLayout.php'));
|
||||
copy(__DIR__.'/../../stubs/livewire/app/View/Components/GuestLayout.php', app_path('View/Components/GuestLayout.php'));
|
||||
|
||||
// Layouts...
|
||||
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/livewire/resources/views/layouts', resource_path('views/layouts'));
|
||||
|
||||
// Single Blade Views...
|
||||
copy(__DIR__.'/../../stubs/livewire/resources/views/dashboard.blade.php', resource_path('views/dashboard.blade.php'));
|
||||
copy(__DIR__.'/../../stubs/livewire/resources/views/navigation-menu.blade.php', resource_path('views/navigation-menu.blade.php'));
|
||||
copy(__DIR__.'/../../stubs/livewire/resources/views/terms.blade.php', resource_path('views/terms.blade.php'));
|
||||
copy(__DIR__.'/../../stubs/livewire/resources/views/policy.blade.php', resource_path('views/policy.blade.php'));
|
||||
|
||||
// Other Views...
|
||||
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/livewire/resources/views/api', resource_path('views/api'));
|
||||
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/livewire/resources/views/profile', resource_path('views/profile'));
|
||||
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/livewire/resources/views/auth', resource_path('views/auth'));
|
||||
|
||||
if (! Str::contains(file_get_contents(base_path('routes/web.php')), "'/dashboard'")) {
|
||||
(new Filesystem)->append(base_path('routes/web.php'), $this->livewireRouteDefinition());
|
||||
}
|
||||
|
||||
// Assets...
|
||||
copy(__DIR__.'/../../stubs/resources/css/app.css', resource_path('css/app.css'));
|
||||
|
||||
// Tests...
|
||||
$stubs = $this->getTestStubsPath();
|
||||
|
||||
copy($stubs.'/livewire/ApiTokenPermissionsTest.php', base_path('tests/Feature/ApiTokenPermissionsTest.php'));
|
||||
copy($stubs.'/livewire/BrowserSessionsTest.php', base_path('tests/Feature/BrowserSessionsTest.php'));
|
||||
copy($stubs.'/livewire/CreateApiTokenTest.php', base_path('tests/Feature/CreateApiTokenTest.php'));
|
||||
copy($stubs.'/livewire/DeleteAccountTest.php', base_path('tests/Feature/DeleteAccountTest.php'));
|
||||
copy($stubs.'/livewire/DeleteApiTokenTest.php', base_path('tests/Feature/DeleteApiTokenTest.php'));
|
||||
copy($stubs.'/livewire/ProfileInformationTest.php', base_path('tests/Feature/ProfileInformationTest.php'));
|
||||
copy($stubs.'/livewire/TwoFactorAuthenticationSettingsTest.php', base_path('tests/Feature/TwoFactorAuthenticationSettingsTest.php'));
|
||||
copy($stubs.'/livewire/UpdatePasswordTest.php', base_path('tests/Feature/UpdatePasswordTest.php'));
|
||||
|
||||
// Teams...
|
||||
if ($this->option('teams')) {
|
||||
$this->installLivewireTeamStack();
|
||||
}
|
||||
|
||||
if (! $this->option('dark')) {
|
||||
$this->removeDarkClasses((new Finder)
|
||||
->in(resource_path('views'))
|
||||
->name('*.blade.php')
|
||||
->filter(fn ($file) => $file->getPathname() !== resource_path('views/welcome.blade.php'))
|
||||
);
|
||||
}
|
||||
|
||||
if (file_exists(base_path('pnpm-lock.yaml'))) {
|
||||
$this->runCommands(['pnpm install', 'pnpm run build']);
|
||||
} elseif (file_exists(base_path('yarn.lock'))) {
|
||||
$this->runCommands(['yarn install', 'yarn run build']);
|
||||
} elseif (file_exists(base_path('bun.lockb'))) {
|
||||
$this->runCommands(['bun install', 'bun run build']);
|
||||
} else {
|
||||
$this->runCommands(['npm install', 'npm run build']);
|
||||
}
|
||||
|
||||
$this->line('');
|
||||
$this->runDatabaseMigrations();
|
||||
|
||||
$this->components->info('Livewire scaffolding installed successfully.');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the Livewire team stack into the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function installLivewireTeamStack()
|
||||
{
|
||||
// Directories...
|
||||
(new Filesystem)->ensureDirectoryExists(resource_path('views/teams'));
|
||||
|
||||
// Other Views...
|
||||
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/livewire/resources/views/teams', resource_path('views/teams'));
|
||||
|
||||
// Tests...
|
||||
$stubs = $this->getTestStubsPath();
|
||||
|
||||
copy($stubs.'/livewire/CreateTeamTest.php', base_path('tests/Feature/CreateTeamTest.php'));
|
||||
copy($stubs.'/livewire/DeleteTeamTest.php', base_path('tests/Feature/DeleteTeamTest.php'));
|
||||
copy($stubs.'/livewire/InviteTeamMemberTest.php', base_path('tests/Feature/InviteTeamMemberTest.php'));
|
||||
copy($stubs.'/livewire/LeaveTeamTest.php', base_path('tests/Feature/LeaveTeamTest.php'));
|
||||
copy($stubs.'/livewire/RemoveTeamMemberTest.php', base_path('tests/Feature/RemoveTeamMemberTest.php'));
|
||||
copy($stubs.'/livewire/UpdateTeamMemberRoleTest.php', base_path('tests/Feature/UpdateTeamMemberRoleTest.php'));
|
||||
copy($stubs.'/livewire/UpdateTeamNameTest.php', base_path('tests/Feature/UpdateTeamNameTest.php'));
|
||||
|
||||
$this->ensureApplicationIsTeamCompatible();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the route definition(s) that should be installed for Livewire.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function livewireRouteDefinition()
|
||||
{
|
||||
return <<<'EOF'
|
||||
|
||||
Route::middleware([
|
||||
'auth:sanctum',
|
||||
config('jetstream.auth_session'),
|
||||
'verified',
|
||||
])->group(function () {
|
||||
Route::get('/dashboard', function () {
|
||||
return view('dashboard');
|
||||
})->name('dashboard');
|
||||
});
|
||||
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the Inertia stack into the application.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function installInertiaStack()
|
||||
{
|
||||
// Install Inertia...
|
||||
if (! $this->requireComposerPackages('inertiajs/inertia-laravel:^2.0', 'tightenco/ziggy:^2.0')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->call('install:api', [
|
||||
'--without-migration-prompt' => true,
|
||||
]);
|
||||
|
||||
// Install NPM packages...
|
||||
$this->updateNodePackages(function ($packages) {
|
||||
return [
|
||||
'@inertiajs/vue3' => '^2.0',
|
||||
'@tailwindcss/forms' => '^0.5.7',
|
||||
'@tailwindcss/typography' => '^0.5.10',
|
||||
'@vitejs/plugin-vue' => '^5.0.0',
|
||||
'autoprefixer' => '^10.4.16',
|
||||
'postcss' => '^8.4.32',
|
||||
'tailwindcss' => '^3.4.0',
|
||||
'vue' => '^3.3.13',
|
||||
] + $packages;
|
||||
});
|
||||
|
||||
// Tailwind Configuration...
|
||||
copy(__DIR__.'/../../stubs/inertia/tailwind.config.js', base_path('tailwind.config.js'));
|
||||
copy(__DIR__.'/../../stubs/inertia/postcss.config.js', base_path('postcss.config.js'));
|
||||
copy(__DIR__.'/../../stubs/inertia/vite.config.js', base_path('vite.config.js'));
|
||||
|
||||
// jsconfig.json...
|
||||
copy(__DIR__.'/../../stubs/inertia/jsconfig.json', base_path('jsconfig.json'));
|
||||
|
||||
// Directories...
|
||||
(new Filesystem)->ensureDirectoryExists(app_path('Actions/Fortify'));
|
||||
(new Filesystem)->ensureDirectoryExists(app_path('Actions/Jetstream'));
|
||||
(new Filesystem)->ensureDirectoryExists(resource_path('css'));
|
||||
(new Filesystem)->ensureDirectoryExists(resource_path('js/Components'));
|
||||
(new Filesystem)->ensureDirectoryExists(resource_path('js/Layouts'));
|
||||
(new Filesystem)->ensureDirectoryExists(resource_path('js/Pages'));
|
||||
(new Filesystem)->ensureDirectoryExists(resource_path('js/Pages/API'));
|
||||
(new Filesystem)->ensureDirectoryExists(resource_path('js/Pages/Auth'));
|
||||
(new Filesystem)->ensureDirectoryExists(resource_path('js/Pages/Profile'));
|
||||
(new Filesystem)->ensureDirectoryExists(resource_path('views'));
|
||||
(new Filesystem)->ensureDirectoryExists(resource_path('markdown'));
|
||||
|
||||
(new Filesystem)->deleteDirectory(resource_path('sass'));
|
||||
|
||||
// Terms Of Service / Privacy Policy...
|
||||
copy(__DIR__.'/../../stubs/resources/markdown/terms.md', resource_path('markdown/terms.md'));
|
||||
copy(__DIR__.'/../../stubs/resources/markdown/policy.md', resource_path('markdown/policy.md'));
|
||||
|
||||
// Service Providers...
|
||||
copy(__DIR__.'/../../stubs/app/Providers/JetstreamServiceProvider.php', app_path('Providers/JetstreamServiceProvider.php'));
|
||||
ServiceProvider::addProviderToBootstrapFile('App\Providers\JetstreamServiceProvider');
|
||||
|
||||
// Middleware...
|
||||
(new Filesystem)->ensureDirectoryExists(app_path('Http/Middleware'));
|
||||
(new Process([$this->phpBinary(), 'artisan', 'inertia:middleware', 'HandleInertiaRequests', '--force'], base_path()))
|
||||
->setTimeout(null)
|
||||
->run(function ($type, $output) {
|
||||
$this->output->write($output);
|
||||
});
|
||||
|
||||
$this->installMiddleware([
|
||||
'\App\Http\Middleware\HandleInertiaRequests::class',
|
||||
'\Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class',
|
||||
]);
|
||||
|
||||
// Models...
|
||||
copy(__DIR__.'/../../stubs/app/Models/User.php', app_path('Models/User.php'));
|
||||
|
||||
// Factories...
|
||||
copy(__DIR__.'/../../database/factories/UserFactory.php', base_path('database/factories/UserFactory.php'));
|
||||
|
||||
// Actions...
|
||||
copy(__DIR__.'/../../stubs/app/Actions/Fortify/CreateNewUser.php', app_path('Actions/Fortify/CreateNewUser.php'));
|
||||
copy(__DIR__.'/../../stubs/app/Actions/Fortify/UpdateUserProfileInformation.php', app_path('Actions/Fortify/UpdateUserProfileInformation.php'));
|
||||
copy(__DIR__.'/../../stubs/app/Actions/Jetstream/DeleteUser.php', app_path('Actions/Jetstream/DeleteUser.php'));
|
||||
|
||||
// Blade Views...
|
||||
copy(__DIR__.'/../../stubs/inertia/resources/views/app.blade.php', resource_path('views/app.blade.php'));
|
||||
|
||||
if (file_exists(resource_path('views/welcome.blade.php'))) {
|
||||
unlink(resource_path('views/welcome.blade.php'));
|
||||
}
|
||||
|
||||
// Inertia Pages...
|
||||
copy(__DIR__.'/../../stubs/inertia/resources/js/Pages/Dashboard.vue', resource_path('js/Pages/Dashboard.vue'));
|
||||
copy(__DIR__.'/../../stubs/inertia/resources/js/Pages/PrivacyPolicy.vue', resource_path('js/Pages/PrivacyPolicy.vue'));
|
||||
copy(__DIR__.'/../../stubs/inertia/resources/js/Pages/TermsOfService.vue', resource_path('js/Pages/TermsOfService.vue'));
|
||||
copy(__DIR__.'/../../stubs/inertia/resources/js/Pages/Welcome.vue', resource_path('js/Pages/Welcome.vue'));
|
||||
|
||||
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/inertia/resources/js/Components', resource_path('js/Components'));
|
||||
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/inertia/resources/js/Layouts', resource_path('js/Layouts'));
|
||||
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/inertia/resources/js/Pages/API', resource_path('js/Pages/API'));
|
||||
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/inertia/resources/js/Pages/Auth', resource_path('js/Pages/Auth'));
|
||||
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/inertia/resources/js/Pages/Profile', resource_path('js/Pages/Profile'));
|
||||
|
||||
copy(__DIR__.'/../../stubs/inertia/routes/web.php', base_path('routes/web.php'));
|
||||
|
||||
// Assets...
|
||||
copy(__DIR__.'/../../stubs/resources/css/app.css', resource_path('css/app.css'));
|
||||
copy(__DIR__.'/../../stubs/inertia/resources/js/app.js', resource_path('js/app.js'));
|
||||
|
||||
// Tests...
|
||||
$stubs = $this->getTestStubsPath();
|
||||
|
||||
copy($stubs.'/inertia/ApiTokenPermissionsTest.php', base_path('tests/Feature/ApiTokenPermissionsTest.php'));
|
||||
copy($stubs.'/inertia/BrowserSessionsTest.php', base_path('tests/Feature/BrowserSessionsTest.php'));
|
||||
copy($stubs.'/inertia/CreateApiTokenTest.php', base_path('tests/Feature/CreateApiTokenTest.php'));
|
||||
copy($stubs.'/inertia/DeleteAccountTest.php', base_path('tests/Feature/DeleteAccountTest.php'));
|
||||
copy($stubs.'/inertia/DeleteApiTokenTest.php', base_path('tests/Feature/DeleteApiTokenTest.php'));
|
||||
copy($stubs.'/inertia/ProfileInformationTest.php', base_path('tests/Feature/ProfileInformationTest.php'));
|
||||
copy($stubs.'/inertia/TwoFactorAuthenticationSettingsTest.php', base_path('tests/Feature/TwoFactorAuthenticationSettingsTest.php'));
|
||||
copy($stubs.'/inertia/UpdatePasswordTest.php', base_path('tests/Feature/UpdatePasswordTest.php'));
|
||||
|
||||
// Teams...
|
||||
if ($this->option('teams')) {
|
||||
$this->installInertiaTeamStack();
|
||||
}
|
||||
|
||||
if ($this->option('ssr')) {
|
||||
$this->installInertiaSsrStack();
|
||||
}
|
||||
|
||||
if (! $this->option('dark')) {
|
||||
$this->removeDarkClasses((new Finder)
|
||||
->in(resource_path('js'))
|
||||
->name('*.vue')
|
||||
->notPath('Pages/Welcome.vue')
|
||||
);
|
||||
}
|
||||
|
||||
if (file_exists(base_path('pnpm-lock.yaml'))) {
|
||||
$this->runCommands(['pnpm install', 'pnpm run build']);
|
||||
} elseif (file_exists(base_path('yarn.lock'))) {
|
||||
$this->runCommands(['yarn install', 'yarn run build']);
|
||||
} elseif (file_exists(base_path('bun.lockb'))) {
|
||||
$this->runCommands(['bun install', 'bun run build']);
|
||||
} else {
|
||||
$this->runCommands(['npm install', 'npm run build']);
|
||||
}
|
||||
|
||||
$this->line('');
|
||||
$this->runDatabaseMigrations();
|
||||
|
||||
$this->components->info('Inertia scaffolding installed successfully.');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the Inertia team stack into the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function installInertiaTeamStack()
|
||||
{
|
||||
// Directories...
|
||||
(new Filesystem)->ensureDirectoryExists(resource_path('js/Pages/Profile'));
|
||||
|
||||
// Pages...
|
||||
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/inertia/resources/js/Pages/Teams', resource_path('js/Pages/Teams'));
|
||||
|
||||
// Tests...
|
||||
$stubs = $this->getTestStubsPath();
|
||||
|
||||
copy($stubs.'/inertia/CreateTeamTest.php', base_path('tests/Feature/CreateTeamTest.php'));
|
||||
copy($stubs.'/inertia/DeleteTeamTest.php', base_path('tests/Feature/DeleteTeamTest.php'));
|
||||
copy($stubs.'/inertia/InviteTeamMemberTest.php', base_path('tests/Feature/InviteTeamMemberTest.php'));
|
||||
copy($stubs.'/inertia/LeaveTeamTest.php', base_path('tests/Feature/LeaveTeamTest.php'));
|
||||
copy($stubs.'/inertia/RemoveTeamMemberTest.php', base_path('tests/Feature/RemoveTeamMemberTest.php'));
|
||||
copy($stubs.'/inertia/UpdateTeamMemberRoleTest.php', base_path('tests/Feature/UpdateTeamMemberRoleTest.php'));
|
||||
copy($stubs.'/inertia/UpdateTeamNameTest.php', base_path('tests/Feature/UpdateTeamNameTest.php'));
|
||||
|
||||
$this->ensureApplicationIsTeamCompatible();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the installed user model is ready for team usage.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function ensureApplicationIsTeamCompatible()
|
||||
{
|
||||
// Publish Team Migrations...
|
||||
$this->callSilent('vendor:publish', ['--tag' => 'jetstream-team-migrations', '--force' => true]);
|
||||
|
||||
// Configuration...
|
||||
$this->replaceInFile('// Features::teams([\'invitations\' => true])', 'Features::teams([\'invitations\' => true])', config_path('jetstream.php'));
|
||||
|
||||
// Directories...
|
||||
(new Filesystem)->ensureDirectoryExists(app_path('Actions/Jetstream'));
|
||||
(new Filesystem)->ensureDirectoryExists(app_path('Events'));
|
||||
(new Filesystem)->ensureDirectoryExists(app_path('Policies'));
|
||||
|
||||
// Service Providers...
|
||||
copy(__DIR__.'/../../stubs/app/Providers/JetstreamWithTeamsServiceProvider.php', app_path('Providers/JetstreamServiceProvider.php'));
|
||||
|
||||
// Models...
|
||||
copy(__DIR__.'/../../stubs/app/Models/Membership.php', app_path('Models/Membership.php'));
|
||||
copy(__DIR__.'/../../stubs/app/Models/Team.php', app_path('Models/Team.php'));
|
||||
copy(__DIR__.'/../../stubs/app/Models/TeamInvitation.php', app_path('Models/TeamInvitation.php'));
|
||||
copy(__DIR__.'/../../stubs/app/Models/UserWithTeams.php', app_path('Models/User.php'));
|
||||
|
||||
// Actions...
|
||||
copy(__DIR__.'/../../stubs/app/Actions/Jetstream/AddTeamMember.php', app_path('Actions/Jetstream/AddTeamMember.php'));
|
||||
copy(__DIR__.'/../../stubs/app/Actions/Jetstream/CreateTeam.php', app_path('Actions/Jetstream/CreateTeam.php'));
|
||||
copy(__DIR__.'/../../stubs/app/Actions/Jetstream/DeleteTeam.php', app_path('Actions/Jetstream/DeleteTeam.php'));
|
||||
copy(__DIR__.'/../../stubs/app/Actions/Jetstream/DeleteUserWithTeams.php', app_path('Actions/Jetstream/DeleteUser.php'));
|
||||
copy(__DIR__.'/../../stubs/app/Actions/Jetstream/InviteTeamMember.php', app_path('Actions/Jetstream/InviteTeamMember.php'));
|
||||
copy(__DIR__.'/../../stubs/app/Actions/Jetstream/RemoveTeamMember.php', app_path('Actions/Jetstream/RemoveTeamMember.php'));
|
||||
copy(__DIR__.'/../../stubs/app/Actions/Jetstream/UpdateTeamName.php', app_path('Actions/Jetstream/UpdateTeamName.php'));
|
||||
|
||||
copy(__DIR__.'/../../stubs/app/Actions/Fortify/CreateNewUserWithTeams.php', app_path('Actions/Fortify/CreateNewUser.php'));
|
||||
|
||||
// Policies...
|
||||
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/app/Policies', app_path('Policies'));
|
||||
|
||||
// Factories...
|
||||
copy(__DIR__.'/../../database/factories/UserFactory.php', base_path('database/factories/UserFactory.php'));
|
||||
copy(__DIR__.'/../../database/factories/TeamFactory.php', base_path('database/factories/TeamFactory.php'));
|
||||
|
||||
// Seeders...
|
||||
copy(__DIR__.'/../../database/seeders/DatabaseSeeder.php', base_path('database/seeders/DatabaseSeeder.php'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the Inertia SSR stack into the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function installInertiaSsrStack()
|
||||
{
|
||||
$this->updateNodePackages(function ($packages) {
|
||||
return [
|
||||
'@vue/server-renderer' => '^3.3.13',
|
||||
] + $packages;
|
||||
});
|
||||
|
||||
copy(__DIR__.'/../../stubs/inertia/resources/js/ssr.js', resource_path('js/ssr.js'));
|
||||
$this->replaceInFile("input: 'resources/js/app.js',", "input: 'resources/js/app.js',".PHP_EOL." ssr: 'resources/js/ssr.js',", base_path('vite.config.js'));
|
||||
|
||||
(new Filesystem)->ensureDirectoryExists(app_path('Http/Middleware'));
|
||||
copy(__DIR__.'/../../stubs/inertia/app/Http/Middleware/HandleInertiaRequests.php', app_path('Http/Middleware/HandleInertiaRequests.php'));
|
||||
|
||||
$this->replaceInFile('vite build', 'vite build && vite build --ssr', base_path('package.json'));
|
||||
$this->replaceInFile('/node_modules', '/bootstrap/ssr'.PHP_EOL.'/node_modules', base_path('.gitignore'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the given middleware names into the application.
|
||||
*
|
||||
* @param array|string $name
|
||||
* @param string $group
|
||||
* @param string $modifier
|
||||
* @return void
|
||||
*/
|
||||
protected function installMiddleware($names, $group = 'web', $modifier = 'append')
|
||||
{
|
||||
$bootstrapApp = file_get_contents(base_path('bootstrap/app.php'));
|
||||
|
||||
$names = collect(Arr::wrap($names))
|
||||
->filter(fn ($name) => ! Str::contains($bootstrapApp, $name))
|
||||
->whenNotEmpty(function ($names) use ($bootstrapApp, $group, $modifier) {
|
||||
$names = $names->map(fn ($name) => "$name")->implode(','.PHP_EOL.' ');
|
||||
|
||||
$stubs = [
|
||||
'->withMiddleware(function (Middleware $middleware) {',
|
||||
'->withMiddleware(function (Middleware $middleware): void {',
|
||||
];
|
||||
|
||||
$bootstrapApp = str_replace(
|
||||
$stubs,
|
||||
collect($stubs)->transform(fn ($stub) => $stub
|
||||
.PHP_EOL." \$middleware->$group($modifier: ["
|
||||
.PHP_EOL." $names,"
|
||||
.PHP_EOL.' ]);'
|
||||
.PHP_EOL
|
||||
)->all(),
|
||||
$bootstrapApp,
|
||||
);
|
||||
|
||||
file_put_contents(base_path('bootstrap/app.php'), $bootstrapApp);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the correct test stubs.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getTestStubsPath()
|
||||
{
|
||||
return $this->option('pest') || $this->isUsingPest()
|
||||
? __DIR__.'/../../stubs/pest-tests'
|
||||
: __DIR__.'/../../stubs/tests';
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given Composer package is installed.
|
||||
*
|
||||
* @param string $package
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasComposerPackage($package)
|
||||
{
|
||||
$packages = json_decode(file_get_contents(base_path('composer.json')), true);
|
||||
|
||||
return array_key_exists($package, $packages['require'] ?? [])
|
||||
|| array_key_exists($package, $packages['require-dev'] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs the given Composer Packages into the application.
|
||||
*
|
||||
* @param mixed $packages
|
||||
* @return bool
|
||||
*/
|
||||
protected function requireComposerPackages($packages)
|
||||
{
|
||||
$composer = $this->option('composer');
|
||||
|
||||
if ($composer !== 'global') {
|
||||
$command = [$this->phpBinary(), $composer, 'require'];
|
||||
}
|
||||
|
||||
$command = array_merge(
|
||||
$command ?? ['composer', 'require'],
|
||||
is_array($packages) ? $packages : func_get_args()
|
||||
);
|
||||
|
||||
return ! (new Process($command, base_path(), ['COMPOSER_MEMORY_LIMIT' => '-1']))
|
||||
->setTimeout(null)
|
||||
->run(function ($type, $output) {
|
||||
$this->output->write($output);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given Composer Packages as "dev" dependencies.
|
||||
*
|
||||
* @param mixed $packages
|
||||
* @return bool
|
||||
*/
|
||||
protected function removeComposerDevPackages($packages)
|
||||
{
|
||||
$composer = $this->option('composer');
|
||||
|
||||
if ($composer !== 'global') {
|
||||
$command = [$this->phpBinary(), $composer, 'remove', '--dev'];
|
||||
}
|
||||
|
||||
$command = array_merge(
|
||||
$command ?? ['composer', 'remove', '--dev'],
|
||||
is_array($packages) ? $packages : func_get_args()
|
||||
);
|
||||
|
||||
return (new Process($command, base_path(), ['COMPOSER_MEMORY_LIMIT' => '-1']))
|
||||
->setTimeout(null)
|
||||
->run(function ($type, $output) {
|
||||
$this->output->write($output);
|
||||
}) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the given Composer Packages as "dev" dependencies.
|
||||
*
|
||||
* @param mixed $packages
|
||||
* @return bool
|
||||
*/
|
||||
protected function requireComposerDevPackages($packages)
|
||||
{
|
||||
$composer = $this->option('composer');
|
||||
|
||||
if ($composer !== 'global') {
|
||||
$command = [$this->phpBinary(), $composer, 'require', '--dev'];
|
||||
}
|
||||
|
||||
$command = array_merge(
|
||||
$command ?? ['composer', 'require', '--dev'],
|
||||
is_array($packages) ? $packages : func_get_args()
|
||||
);
|
||||
|
||||
return (new Process($command, base_path(), ['COMPOSER_MEMORY_LIMIT' => '-1']))
|
||||
->setTimeout(null)
|
||||
->run(function ($type, $output) {
|
||||
$this->output->write($output);
|
||||
}) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the "package.json" file.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @param bool $dev
|
||||
* @return void
|
||||
*/
|
||||
protected static function updateNodePackages(callable $callback, $dev = true)
|
||||
{
|
||||
if (! file_exists(base_path('package.json'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
$configurationKey = $dev ? 'devDependencies' : 'dependencies';
|
||||
|
||||
$packages = json_decode(file_get_contents(base_path('package.json')), true);
|
||||
|
||||
$packages[$configurationKey] = $callback(
|
||||
array_key_exists($configurationKey, $packages) ? $packages[$configurationKey] : [],
|
||||
$configurationKey
|
||||
);
|
||||
|
||||
ksort($packages[$configurationKey]);
|
||||
|
||||
file_put_contents(
|
||||
base_path('package.json'),
|
||||
json_encode($packages, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT).PHP_EOL
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the database migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function runDatabaseMigrations()
|
||||
{
|
||||
if (confirm('New database migrations were added. Would you like to re-run your migrations?', true)) {
|
||||
(new Process([$this->phpBinary(), 'artisan', 'migrate:fresh', '--force'], base_path()))
|
||||
->setTimeout(null)
|
||||
->run(function ($type, $output) {
|
||||
$this->output->write($output);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a given string within a given file.
|
||||
*
|
||||
* @param string $replace
|
||||
* @param string|array $search
|
||||
* @param string $path
|
||||
* @return void
|
||||
*/
|
||||
protected function replaceInFile($search, $replace, $path)
|
||||
{
|
||||
file_put_contents($path, str_replace($search, $replace, file_get_contents($path)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove Tailwind dark classes from the given files.
|
||||
*
|
||||
* @param \Symfony\Component\Finder\Finder $finder
|
||||
* @return void
|
||||
*/
|
||||
protected function removeDarkClasses(Finder $finder)
|
||||
{
|
||||
foreach ($finder as $file) {
|
||||
file_put_contents($file->getPathname(), preg_replace('/\sdark:[^\s"\']+/', '', $file->getContents()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the appropriate PHP binary.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function phpBinary()
|
||||
{
|
||||
if (function_exists('Illuminate\Support\php_binary')) {
|
||||
return \Illuminate\Support\php_binary();
|
||||
}
|
||||
|
||||
return (new PhpExecutableFinder())->find(false) ?: 'php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the given commands.
|
||||
*
|
||||
* @param array $commands
|
||||
* @return void
|
||||
*/
|
||||
protected function runCommands($commands)
|
||||
{
|
||||
$process = Process::fromShellCommandline(implode(' && ', $commands), null, null, null, null);
|
||||
|
||||
if ('\\' !== DIRECTORY_SEPARATOR && file_exists('/dev/tty') && is_readable('/dev/tty')) {
|
||||
try {
|
||||
$process->setTty(true);
|
||||
} catch (RuntimeException $e) {
|
||||
$this->output->writeln(' <bg=yellow;fg=black> WARN </> '.$e->getMessage().PHP_EOL);
|
||||
}
|
||||
}
|
||||
|
||||
$process->run(function ($type, $line) {
|
||||
$this->output->write(' '.$line);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt for missing input arguments using the returned questions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function promptForMissingArgumentsUsing()
|
||||
{
|
||||
return [
|
||||
'stack' => fn () => select(
|
||||
label: 'Which Jetstream stack would you like to install?',
|
||||
options: [
|
||||
'inertia' => 'Vue with Inertia',
|
||||
'livewire' => 'Livewire',
|
||||
]
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Interact further with the user if they were prompted for missing arguments.
|
||||
*
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input
|
||||
* @param \Symfony\Component\Console\Output\OutputInterface $output
|
||||
* @return void
|
||||
*/
|
||||
protected function afterPromptingForMissingArguments(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
collect(multiselect(
|
||||
label: 'Would you like any optional features?',
|
||||
options: collect([
|
||||
'teams' => 'Team support',
|
||||
'api' => 'API support',
|
||||
'verification' => 'Email verification',
|
||||
'dark' => 'Dark mode',
|
||||
])->when(
|
||||
$input->getArgument('stack') === 'inertia',
|
||||
fn ($options) => $options->put('ssr', 'Inertia SSR')
|
||||
)->sort()->all(),
|
||||
))->each(fn ($option) => $input->setOption($option, true));
|
||||
|
||||
$input->setOption('pest', select(
|
||||
label: 'Which testing framework do you prefer?',
|
||||
options: ['Pest', 'PHPUnit'],
|
||||
default: 'Pest',
|
||||
) === 'Pest');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the project is already using Pest.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isUsingPest()
|
||||
{
|
||||
return class_exists(\Pest\TestSuite::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Contracts;
|
||||
|
||||
/**
|
||||
* @method void add(\Illuminate\Foundation\Auth\User $user, \Illuminate\Database\Eloquent\Model $team, string $email, string $role = null)
|
||||
*/
|
||||
interface AddsTeamMembers
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Contracts;
|
||||
|
||||
/**
|
||||
* @method \Illuminate\Database\Eloquent\Model create(\Illuminate\Foundation\Auth\User $user, array $input)
|
||||
*/
|
||||
interface CreatesTeams
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Contracts;
|
||||
|
||||
/**
|
||||
* @method void delete(\Illuminate\Database\Eloquent\Model $team)
|
||||
*/
|
||||
interface DeletesTeams
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Contracts;
|
||||
|
||||
/**
|
||||
* @method void delete(\Illuminate\Foundation\Auth\User $user)
|
||||
*/
|
||||
interface DeletesUsers
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Contracts;
|
||||
|
||||
/**
|
||||
* @method void invite(\Illuminate\Foundation\Auth\User $user, \Illuminate\Database\Eloquent\Model $team, string $email, string $role = null)
|
||||
*/
|
||||
interface InvitesTeamMembers
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Contracts;
|
||||
|
||||
/**
|
||||
* @method void remove(\Illuminate\Foundation\Auth\User $user, \Illuminate\Database\Eloquent\Model $team, \Illuminate\Foundation\Auth\User $teamMember)
|
||||
*/
|
||||
interface RemovesTeamMembers
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Contracts;
|
||||
|
||||
/**
|
||||
* @method void update(\Illuminate\Foundation\Auth\User $user, \Illuminate\Database\Eloquent\Model $team, array $input)
|
||||
*/
|
||||
interface UpdatesTeamNames
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Events;
|
||||
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
|
||||
class AddingTeam
|
||||
{
|
||||
use Dispatchable;
|
||||
|
||||
/**
|
||||
* The team owner.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $owner;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param mixed $owner
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($owner)
|
||||
{
|
||||
$this->owner = $owner;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Events;
|
||||
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
|
||||
class AddingTeamMember
|
||||
{
|
||||
use Dispatchable;
|
||||
|
||||
/**
|
||||
* The team instance.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $team;
|
||||
|
||||
/**
|
||||
* The team member being added.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param mixed $team
|
||||
* @param mixed $user
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($team, $user)
|
||||
{
|
||||
$this->team = $team;
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Events;
|
||||
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
|
||||
class InvitingTeamMember
|
||||
{
|
||||
use Dispatchable;
|
||||
|
||||
/**
|
||||
* The team instance.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $team;
|
||||
|
||||
/**
|
||||
* The email address of the invitee.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $email;
|
||||
|
||||
/**
|
||||
* The role of the invitee.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $role;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param mixed $team
|
||||
* @param mixed $email
|
||||
* @param mixed $role
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($team, $email, $role)
|
||||
{
|
||||
$this->team = $team;
|
||||
$this->email = $email;
|
||||
$this->role = $role;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Events;
|
||||
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
|
||||
class RemovingTeamMember
|
||||
{
|
||||
use Dispatchable;
|
||||
|
||||
/**
|
||||
* The team instance.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $team;
|
||||
|
||||
/**
|
||||
* The team member being removed.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param mixed $team
|
||||
* @param mixed $user
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($team, $user)
|
||||
{
|
||||
$this->team = $team;
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Events;
|
||||
|
||||
class TeamCreated extends TeamEvent
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Events;
|
||||
|
||||
class TeamDeleted extends TeamEvent
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Events;
|
||||
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
abstract class TeamEvent
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
/**
|
||||
* The team instance.
|
||||
*
|
||||
* @var \App\Models\Team
|
||||
*/
|
||||
public $team;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param \App\Models\Team $team
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($team)
|
||||
{
|
||||
$this->team = $team;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Events;
|
||||
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
|
||||
class TeamMemberAdded
|
||||
{
|
||||
use Dispatchable;
|
||||
|
||||
/**
|
||||
* The team instance.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $team;
|
||||
|
||||
/**
|
||||
* The team member that was added.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param mixed $team
|
||||
* @param mixed $user
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($team, $user)
|
||||
{
|
||||
$this->team = $team;
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Events;
|
||||
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
|
||||
class TeamMemberRemoved
|
||||
{
|
||||
use Dispatchable;
|
||||
|
||||
/**
|
||||
* The team instance.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $team;
|
||||
|
||||
/**
|
||||
* The team member that was removed.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param mixed $team
|
||||
* @param mixed $user
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($team, $user)
|
||||
{
|
||||
$this->team = $team;
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Events;
|
||||
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
|
||||
class TeamMemberUpdated
|
||||
{
|
||||
use Dispatchable;
|
||||
|
||||
/**
|
||||
* The team instance.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $team;
|
||||
|
||||
/**
|
||||
* The team member that was updated.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param mixed $team
|
||||
* @param mixed $user
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($team, $user)
|
||||
{
|
||||
$this->team = $team;
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Events;
|
||||
|
||||
class TeamUpdated extends TeamEvent
|
||||
{
|
||||
//
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream;
|
||||
|
||||
class Features
|
||||
{
|
||||
/**
|
||||
* Determine if the given feature is enabled.
|
||||
*
|
||||
* @param string $feature
|
||||
* @return bool
|
||||
*/
|
||||
public static function enabled(string $feature)
|
||||
{
|
||||
return in_array($feature, config('jetstream.features', []));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the feature is enabled and has a given option enabled.
|
||||
*
|
||||
* @param string $feature
|
||||
* @param string $option
|
||||
* @return bool
|
||||
*/
|
||||
public static function optionEnabled(string $feature, string $option)
|
||||
{
|
||||
return static::enabled($feature) &&
|
||||
config("jetstream-options.{$feature}.{$option}") === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the application is allowing profile photo uploads.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function managesProfilePhotos()
|
||||
{
|
||||
return static::enabled(static::profilePhotos());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the application is using any API features.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasApiFeatures()
|
||||
{
|
||||
return static::enabled(static::api());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the application is using any team features.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasTeamFeatures()
|
||||
{
|
||||
return static::enabled(static::teams());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if invitations are sent to team members.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function sendsTeamInvitations()
|
||||
{
|
||||
return static::optionEnabled(static::teams(), 'invitations');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the application has terms of service / privacy policy confirmation enabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasTermsAndPrivacyPolicyFeature()
|
||||
{
|
||||
return static::enabled(static::termsAndPrivacyPolicy());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the application is using any account deletion features.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasAccountDeletionFeatures()
|
||||
{
|
||||
return static::enabled(static::accountDeletion());
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the profile photo upload feature.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function profilePhotos()
|
||||
{
|
||||
return 'profile-photos';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the API feature.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function api()
|
||||
{
|
||||
return 'api';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the teams feature.
|
||||
*
|
||||
* @param array $options
|
||||
* @return string
|
||||
*/
|
||||
public static function teams(array $options = [])
|
||||
{
|
||||
if (! empty($options)) {
|
||||
config(['jetstream-options.teams' => $options]);
|
||||
}
|
||||
|
||||
return 'teams';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the terms of service and privacy policy feature.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function termsAndPrivacyPolicy()
|
||||
{
|
||||
return 'terms';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the account deletion feature.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function accountDeletion()
|
||||
{
|
||||
return 'account-deletion';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
trait HasProfilePhoto
|
||||
{
|
||||
/**
|
||||
* Update the user's profile photo.
|
||||
*
|
||||
* @param \Illuminate\Http\UploadedFile $photo
|
||||
* @param string $storagePath
|
||||
* @return void
|
||||
*/
|
||||
public function updateProfilePhoto(UploadedFile $photo, $storagePath = 'profile-photos')
|
||||
{
|
||||
tap($this->profile_photo_path, function ($previous) use ($photo, $storagePath) {
|
||||
$this->forceFill([
|
||||
'profile_photo_path' => $photo->storePublicly(
|
||||
$storagePath, ['disk' => $this->profilePhotoDisk()]
|
||||
),
|
||||
])->save();
|
||||
|
||||
if ($previous) {
|
||||
Storage::disk($this->profilePhotoDisk())->delete($previous);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the user's profile photo.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function deleteProfilePhoto()
|
||||
{
|
||||
if (! Features::managesProfilePhotos()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_null($this->profile_photo_path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Storage::disk($this->profilePhotoDisk())->delete($this->profile_photo_path);
|
||||
|
||||
$this->forceFill([
|
||||
'profile_photo_path' => null,
|
||||
])->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL to the user's profile photo.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Casts\Attribute
|
||||
*/
|
||||
protected function profilePhotoUrl(): Attribute
|
||||
{
|
||||
return Attribute::get(function (): string {
|
||||
return $this->profile_photo_path
|
||||
? Storage::disk($this->profilePhotoDisk())->url($this->profile_photo_path)
|
||||
: $this->defaultProfilePhotoUrl();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default profile photo URL if no profile photo has been uploaded.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function defaultProfilePhotoUrl()
|
||||
{
|
||||
$name = trim(collect(explode(' ', $this->name))->map(function ($segment) {
|
||||
return mb_substr($segment, 0, 1);
|
||||
})->join(' '));
|
||||
|
||||
return 'https://ui-avatars.com/api/?name='.urlencode($name).'&color=7F9CF5&background=EBF4FF';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the disk that profile photos should be stored on.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function profilePhotoDisk()
|
||||
{
|
||||
return isset($_ENV['VAPOR_ARTIFACT_NAME']) ? 's3' : config('jetstream.profile_photo_disk', 'public');
|
||||
}
|
||||
}
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
|
||||
trait HasTeams
|
||||
{
|
||||
/**
|
||||
* Determine if the given team is the current team.
|
||||
*
|
||||
* @param mixed $team
|
||||
* @return bool
|
||||
*/
|
||||
public function isCurrentTeam($team)
|
||||
{
|
||||
return $team->id === $this->currentTeam->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current team of the user's context.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function currentTeam()
|
||||
{
|
||||
if (is_null($this->current_team_id) && $this->id) {
|
||||
$this->switchTeam($this->personalTeam());
|
||||
}
|
||||
|
||||
return $this->belongsTo(Jetstream::teamModel(), 'current_team_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch the user's context to the given team.
|
||||
*
|
||||
* @param mixed $team
|
||||
* @return bool
|
||||
*/
|
||||
public function switchTeam($team)
|
||||
{
|
||||
if (! $this->belongsToTeam($team)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->forceFill([
|
||||
'current_team_id' => $team->id,
|
||||
])->save();
|
||||
|
||||
$this->setRelation('currentTeam', $team);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the teams the user owns or belongs to.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function allTeams()
|
||||
{
|
||||
return $this->ownedTeams->merge($this->teams)->sortBy('name');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the teams the user owns.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function ownedTeams()
|
||||
{
|
||||
return $this->hasMany(Jetstream::teamModel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the teams the user belongs to.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
*/
|
||||
public function teams()
|
||||
{
|
||||
return $this->belongsToMany(Jetstream::teamModel(), Jetstream::membershipModel())
|
||||
->withPivot('role')
|
||||
->withTimestamps()
|
||||
->as('membership');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user's "personal" team.
|
||||
*
|
||||
* @return \App\Models\Team
|
||||
*/
|
||||
public function personalTeam()
|
||||
{
|
||||
return $this->ownedTeams->where('personal_team', true)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user owns the given team.
|
||||
*
|
||||
* @param mixed $team
|
||||
* @return bool
|
||||
*/
|
||||
public function ownsTeam($team)
|
||||
{
|
||||
if (is_null($team)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->id == $team->{$this->getForeignKey()};
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user belongs to the given team.
|
||||
*
|
||||
* @param mixed $team
|
||||
* @return bool
|
||||
*/
|
||||
public function belongsToTeam($team)
|
||||
{
|
||||
if (is_null($team)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->ownsTeam($team) || $this->teams->contains(function ($t) use ($team) {
|
||||
return $t->id === $team->id;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the role that the user has on the team.
|
||||
*
|
||||
* @param mixed $team
|
||||
* @return \Laravel\Jetstream\Role|null
|
||||
*/
|
||||
public function teamRole($team)
|
||||
{
|
||||
if ($this->ownsTeam($team)) {
|
||||
return new OwnerRole;
|
||||
}
|
||||
|
||||
if (! $this->belongsToTeam($team)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$role = $team->users
|
||||
->where('id', $this->id)
|
||||
->first()
|
||||
->membership
|
||||
->role;
|
||||
|
||||
return $role ? Jetstream::findRole($role) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user has the given role on the given team.
|
||||
*
|
||||
* @param mixed $team
|
||||
* @param string $role
|
||||
* @return bool
|
||||
*/
|
||||
public function hasTeamRole($team, string $role)
|
||||
{
|
||||
if ($this->ownsTeam($team)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->belongsToTeam($team) && optional(Jetstream::findRole($team->users->where(
|
||||
'id', $this->id
|
||||
)->first()->membership->role))->key === $role;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user's permissions for the given team.
|
||||
*
|
||||
* @param mixed $team
|
||||
* @return array
|
||||
*/
|
||||
public function teamPermissions($team)
|
||||
{
|
||||
if ($this->ownsTeam($team)) {
|
||||
return ['*'];
|
||||
}
|
||||
|
||||
if (! $this->belongsToTeam($team)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return (array) optional($this->teamRole($team))->permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user has the given permission on the given team.
|
||||
*
|
||||
* @param mixed $team
|
||||
* @param string $permission
|
||||
* @return bool
|
||||
*/
|
||||
public function hasTeamPermission($team, string $permission)
|
||||
{
|
||||
if ($this->ownsTeam($team)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (! $this->belongsToTeam($team)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (in_array(HasApiTokens::class, class_uses_recursive($this)) &&
|
||||
! $this->tokenCan($permission) &&
|
||||
$this->currentAccessToken() !== null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$permissions = $this->teamPermissions($team);
|
||||
|
||||
return in_array($permission, $permissions) ||
|
||||
in_array('*', $permissions) ||
|
||||
(Str::endsWith($permission, ':create') && in_array('*:create', $permissions)) ||
|
||||
(Str::endsWith($permission, ':update') && in_array('*:update', $permissions));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Laravel\Jetstream\Jetstream;
|
||||
|
||||
class CurrentTeamController extends Controller
|
||||
{
|
||||
/**
|
||||
* Update the authenticated user's current team.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function update(Request $request)
|
||||
{
|
||||
$team = Jetstream::newTeamModel()->findOrFail($request->team_id);
|
||||
|
||||
if (! $request->user()->switchTeam($team)) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
return redirect(config('fortify.home'), 303);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Controllers\Inertia;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Laravel\Jetstream\Jetstream;
|
||||
|
||||
class ApiTokenController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the user API token screen.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Inertia\Response
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
return Jetstream::inertia()->render($request, 'API/Index', [
|
||||
'tokens' => $request->user()->tokens->map(function ($token) {
|
||||
return $token->toArray() + [
|
||||
'last_used_ago' => optional($token->last_used_at)->diffForHumans(),
|
||||
];
|
||||
}),
|
||||
'availablePermissions' => Jetstream::$permissions,
|
||||
'defaultPermissions' => Jetstream::$defaultPermissions,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new API token.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
]);
|
||||
|
||||
$token = $request->user()->createToken(
|
||||
$request->name,
|
||||
Jetstream::validPermissions($request->input('permissions', []))
|
||||
);
|
||||
|
||||
return back()->with('flash', [
|
||||
'token' => explode('|', $token->plainTextToken, 2)[1],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the given API token's permissions.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param string $tokenId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function update(Request $request, $tokenId)
|
||||
{
|
||||
$request->validate([
|
||||
'permissions' => 'array',
|
||||
'permissions.*' => 'string',
|
||||
]);
|
||||
|
||||
$token = $request->user()->tokens()->where('id', $tokenId)->firstOrFail();
|
||||
|
||||
$token->forceFill([
|
||||
'abilities' => Jetstream::validPermissions($request->input('permissions', [])),
|
||||
])->save();
|
||||
|
||||
return back(303);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the given API token.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param string $tokenId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function destroy(Request $request, $tokenId)
|
||||
{
|
||||
$request->user()->tokens()->where('id', $tokenId)->first()->delete();
|
||||
|
||||
return back(303);
|
||||
}
|
||||
}
|
||||
Vendored
+84
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Controllers\Inertia\Concerns;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Laravel\Fortify\Actions\DisableTwoFactorAuthentication;
|
||||
use Laravel\Fortify\Features;
|
||||
|
||||
trait ConfirmsTwoFactorAuthentication
|
||||
{
|
||||
/**
|
||||
* Validate the two factor authentication state for the request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request
|
||||
* @return void
|
||||
*/
|
||||
protected function validateTwoFactorAuthenticationState(Request $request)
|
||||
{
|
||||
if (! Features::optionEnabled(Features::twoFactorAuthentication(), 'confirm')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$currentTime = time();
|
||||
|
||||
// Notate totally disabled state in session...
|
||||
if ($this->twoFactorAuthenticationDisabled($request)) {
|
||||
$request->session()->put('two_factor_empty_at', $currentTime);
|
||||
}
|
||||
|
||||
// If was previously totally disabled this session but is now confirming, notate time...
|
||||
if ($this->hasJustBegunConfirmingTwoFactorAuthentication($request)) {
|
||||
$request->session()->put('two_factor_confirming_at', $currentTime);
|
||||
}
|
||||
|
||||
// If the profile is reloaded and is not confirmed but was previously in confirming state, disable...
|
||||
if ($this->neverFinishedConfirmingTwoFactorAuthentication($request, $currentTime)) {
|
||||
app(DisableTwoFactorAuthentication::class)(Auth::user());
|
||||
|
||||
$request->session()->put('two_factor_empty_at', $currentTime);
|
||||
$request->session()->remove('two_factor_confirming_at');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if two factor authenticatoin is totally disabled.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return bool
|
||||
*/
|
||||
protected function twoFactorAuthenticationDisabled(Request $request)
|
||||
{
|
||||
return is_null($request->user()->two_factor_secret) &&
|
||||
is_null($request->user()->two_factor_confirmed_at);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if two factor authentication is just now being confirmed within the last request cycle.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasJustBegunConfirmingTwoFactorAuthentication(Request $request)
|
||||
{
|
||||
return ! is_null($request->user()->two_factor_secret) &&
|
||||
is_null($request->user()->two_factor_confirmed_at) &&
|
||||
$request->session()->has('two_factor_empty_at') &&
|
||||
is_null($request->session()->get('two_factor_confirming_at'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if two factor authentication was never totally confirmed once confirmation started.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $currentTime
|
||||
* @return bool
|
||||
*/
|
||||
protected function neverFinishedConfirmingTwoFactorAuthentication(Request $request, $currentTime)
|
||||
{
|
||||
return ! array_key_exists('code', $request->session()->getOldInput()) &&
|
||||
is_null($request->user()->two_factor_confirmed_at) &&
|
||||
$request->session()->get('two_factor_confirming_at', 0) != $currentTime;
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Controllers\Inertia;
|
||||
|
||||
use Illuminate\Contracts\Auth\StatefulGuard;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Inertia\Inertia;
|
||||
use Laravel\Fortify\Actions\ConfirmPassword;
|
||||
use Laravel\Jetstream\Contracts\DeletesUsers;
|
||||
|
||||
class CurrentUserController extends Controller
|
||||
{
|
||||
/**
|
||||
* Delete the current user.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Illuminate\Contracts\Auth\StatefulGuard $guard
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy(Request $request, StatefulGuard $guard)
|
||||
{
|
||||
$confirmed = app(ConfirmPassword::class)(
|
||||
$guard, $request->user(), $request->password
|
||||
);
|
||||
|
||||
if (! $confirmed) {
|
||||
throw ValidationException::withMessages([
|
||||
'password' => __('The password is incorrect.'),
|
||||
]);
|
||||
}
|
||||
|
||||
app(DeletesUsers::class)->delete($request->user()->fresh());
|
||||
|
||||
$guard->logout();
|
||||
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return Inertia::location(url('/'));
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Controllers\Inertia;
|
||||
|
||||
use Illuminate\Contracts\Auth\StatefulGuard;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Laravel\Fortify\Actions\ConfirmPassword;
|
||||
|
||||
class OtherBrowserSessionsController extends Controller
|
||||
{
|
||||
/**
|
||||
* Log out from other browser sessions.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Illuminate\Contracts\Auth\StatefulGuard $guard
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function destroy(Request $request, StatefulGuard $guard)
|
||||
{
|
||||
$confirmed = app(ConfirmPassword::class)(
|
||||
$guard, $request->user(), $request->password
|
||||
);
|
||||
|
||||
if (! $confirmed) {
|
||||
throw ValidationException::withMessages([
|
||||
'password' => __('The password is incorrect.'),
|
||||
]);
|
||||
}
|
||||
|
||||
$guard->logoutOtherDevices($request->password);
|
||||
|
||||
$this->deleteOtherSessionRecords($request);
|
||||
|
||||
return back(303);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the other browser session records from storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return void
|
||||
*/
|
||||
protected function deleteOtherSessionRecords(Request $request)
|
||||
{
|
||||
if (config('session.driver') !== 'database') {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::connection(config('session.connection'))->table(config('session.table', 'sessions'))
|
||||
->where('user_id', $request->user()->getAuthIdentifier())
|
||||
->where('id', '!=', $request->session()->getId())
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Controllers\Inertia;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Illuminate\Support\Str;
|
||||
use Inertia\Inertia;
|
||||
use Laravel\Jetstream\Jetstream;
|
||||
|
||||
class PrivacyPolicyController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the privacy policy for the application.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Inertia\Response
|
||||
*/
|
||||
public function show(Request $request)
|
||||
{
|
||||
$policyFile = Jetstream::localizedMarkdownPath('policy.md');
|
||||
|
||||
return Inertia::render('PrivacyPolicy', [
|
||||
'policy' => Str::markdown(file_get_contents($policyFile)),
|
||||
]);
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Controllers\Inertia;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
|
||||
class ProfilePhotoController extends Controller
|
||||
{
|
||||
/**
|
||||
* Delete the current user's profile photo.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function destroy(Request $request)
|
||||
{
|
||||
$request->user()->deleteProfilePhoto();
|
||||
|
||||
return back(303)->with('status', 'profile-photo-deleted');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Controllers\Inertia;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Laravel\Jetstream\Actions\ValidateTeamDeletion;
|
||||
use Laravel\Jetstream\Contracts\CreatesTeams;
|
||||
use Laravel\Jetstream\Contracts\DeletesTeams;
|
||||
use Laravel\Jetstream\Contracts\UpdatesTeamNames;
|
||||
use Laravel\Jetstream\Jetstream;
|
||||
use Laravel\Jetstream\RedirectsActions;
|
||||
|
||||
class TeamController extends Controller
|
||||
{
|
||||
use RedirectsActions;
|
||||
|
||||
/**
|
||||
* Show the team management screen.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $teamId
|
||||
* @return \Inertia\Response
|
||||
*/
|
||||
public function show(Request $request, $teamId)
|
||||
{
|
||||
$team = Jetstream::newTeamModel()->findOrFail($teamId);
|
||||
|
||||
Gate::authorize('view', $team);
|
||||
|
||||
return Jetstream::inertia()->render($request, 'Teams/Show', [
|
||||
'team' => $team->load('owner', 'users', 'teamInvitations'),
|
||||
'availableRoles' => array_values(Jetstream::$roles),
|
||||
'availablePermissions' => Jetstream::$permissions,
|
||||
'defaultPermissions' => Jetstream::$defaultPermissions,
|
||||
'permissions' => [
|
||||
'canAddTeamMembers' => Gate::check('addTeamMember', $team),
|
||||
'canDeleteTeam' => Gate::check('delete', $team),
|
||||
'canRemoveTeamMembers' => Gate::check('removeTeamMember', $team),
|
||||
'canUpdateTeam' => Gate::check('update', $team),
|
||||
'canUpdateTeamMembers' => Gate::check('updateTeamMember', $team),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the team creation screen.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Inertia\Response
|
||||
*/
|
||||
public function create(Request $request)
|
||||
{
|
||||
Gate::authorize('create', Jetstream::newTeamModel());
|
||||
|
||||
return Jetstream::inertia()->render($request, 'Teams/Create');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new team.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$creator = app(CreatesTeams::class);
|
||||
|
||||
$creator->create($request->user(), $request->all());
|
||||
|
||||
return $this->redirectPath($creator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the given team's name.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $teamId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function update(Request $request, $teamId)
|
||||
{
|
||||
$team = Jetstream::newTeamModel()->findOrFail($teamId);
|
||||
|
||||
app(UpdatesTeamNames::class)->update($request->user(), $team, $request->all());
|
||||
|
||||
return back(303);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the given team.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $teamId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function destroy(Request $request, $teamId)
|
||||
{
|
||||
$team = Jetstream::newTeamModel()->findOrFail($teamId);
|
||||
|
||||
app(ValidateTeamDeletion::class)->validate($request->user(), $team);
|
||||
|
||||
$deleter = app(DeletesTeams::class);
|
||||
|
||||
$deleter->delete($team);
|
||||
|
||||
return $this->redirectPath($deleter);
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Controllers\Inertia;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Laravel\Jetstream\Actions\UpdateTeamMemberRole;
|
||||
use Laravel\Jetstream\Contracts\AddsTeamMembers;
|
||||
use Laravel\Jetstream\Contracts\InvitesTeamMembers;
|
||||
use Laravel\Jetstream\Contracts\RemovesTeamMembers;
|
||||
use Laravel\Jetstream\Features;
|
||||
use Laravel\Jetstream\Jetstream;
|
||||
|
||||
class TeamMemberController extends Controller
|
||||
{
|
||||
/**
|
||||
* Add a new team member to a team.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $teamId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function store(Request $request, $teamId)
|
||||
{
|
||||
$team = Jetstream::newTeamModel()->findOrFail($teamId);
|
||||
|
||||
if (Features::sendsTeamInvitations()) {
|
||||
app(InvitesTeamMembers::class)->invite(
|
||||
$request->user(),
|
||||
$team,
|
||||
$request->email ?: '',
|
||||
$request->role
|
||||
);
|
||||
} else {
|
||||
app(AddsTeamMembers::class)->add(
|
||||
$request->user(),
|
||||
$team,
|
||||
$request->email ?: '',
|
||||
$request->role
|
||||
);
|
||||
}
|
||||
|
||||
return back(303);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the given team member's role.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $teamId
|
||||
* @param int $userId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function update(Request $request, $teamId, $userId)
|
||||
{
|
||||
app(UpdateTeamMemberRole::class)->update(
|
||||
$request->user(),
|
||||
Jetstream::newTeamModel()->findOrFail($teamId),
|
||||
$userId,
|
||||
$request->role
|
||||
);
|
||||
|
||||
return back(303);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given user from the given team.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $teamId
|
||||
* @param int $userId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function destroy(Request $request, $teamId, $userId)
|
||||
{
|
||||
$team = Jetstream::newTeamModel()->findOrFail($teamId);
|
||||
|
||||
app(RemovesTeamMembers::class)->remove(
|
||||
$request->user(),
|
||||
$team,
|
||||
$user = Jetstream::findUserByIdOrFail($userId)
|
||||
);
|
||||
|
||||
if ($request->user()->id === $user->id) {
|
||||
return redirect(config('fortify.home'));
|
||||
}
|
||||
|
||||
return back(303);
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Controllers\Inertia;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Illuminate\Support\Str;
|
||||
use Inertia\Inertia;
|
||||
use Laravel\Jetstream\Jetstream;
|
||||
|
||||
class TermsOfServiceController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the terms of service for the application.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Inertia\Response
|
||||
*/
|
||||
public function show(Request $request)
|
||||
{
|
||||
$termsFile = Jetstream::localizedMarkdownPath('terms.md');
|
||||
|
||||
return Inertia::render('TermsOfService', [
|
||||
'terms' => Str::markdown(file_get_contents($termsFile)),
|
||||
]);
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Controllers\Inertia;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Laravel\Fortify\Features;
|
||||
use Laravel\Jetstream\Agent;
|
||||
use Laravel\Jetstream\Jetstream;
|
||||
|
||||
class UserProfileController extends Controller
|
||||
{
|
||||
use Concerns\ConfirmsTwoFactorAuthentication;
|
||||
|
||||
/**
|
||||
* Show the general profile settings screen.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Inertia\Response
|
||||
*/
|
||||
public function show(Request $request)
|
||||
{
|
||||
$this->validateTwoFactorAuthenticationState($request);
|
||||
|
||||
return Jetstream::inertia()->render($request, 'Profile/Show', [
|
||||
'confirmsTwoFactorAuthentication' => Features::optionEnabled(Features::twoFactorAuthentication(), 'confirm'),
|
||||
'sessions' => $this->sessions($request)->all(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current sessions.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function sessions(Request $request)
|
||||
{
|
||||
if (config('session.driver') !== 'database') {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return collect(
|
||||
DB::connection(config('session.connection'))->table(config('session.table', 'sessions'))
|
||||
->where('user_id', $request->user()->getAuthIdentifier())
|
||||
->orderBy('last_activity', 'desc')
|
||||
->get()
|
||||
)->map(function ($session) use ($request) {
|
||||
$agent = $this->createAgent($session);
|
||||
|
||||
return (object) [
|
||||
'agent' => [
|
||||
'is_desktop' => $agent->isDesktop(),
|
||||
'platform' => $agent->platform(),
|
||||
'browser' => $agent->browser(),
|
||||
],
|
||||
'ip_address' => $session->ip_address,
|
||||
'is_current_device' => $session->id === $request->session()->getId(),
|
||||
'last_active' => Carbon::createFromTimestamp($session->last_activity)->diffForHumans(),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new agent instance from the given session.
|
||||
*
|
||||
* @param mixed $session
|
||||
* @return \Laravel\Jetstream\Agent
|
||||
*/
|
||||
protected function createAgent($session)
|
||||
{
|
||||
return tap(new Agent(), fn ($agent) => $agent->setUserAgent($session->user_agent));
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Controllers\Livewire;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
|
||||
class ApiTokenController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the user API token screen.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
return view('api.index', [
|
||||
'request' => $request,
|
||||
'user' => $request->user(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Controllers\Livewire;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Jetstream\Jetstream;
|
||||
|
||||
class PrivacyPolicyController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the privacy policy for the application.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function show(Request $request)
|
||||
{
|
||||
$policyFile = Jetstream::localizedMarkdownPath('policy.md');
|
||||
|
||||
return view('policy', [
|
||||
'policy' => Str::markdown(file_get_contents($policyFile)),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Controllers\Livewire;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Laravel\Jetstream\Jetstream;
|
||||
|
||||
class TeamController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the team management screen.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $teamId
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function show(Request $request, $teamId)
|
||||
{
|
||||
$team = Jetstream::newTeamModel()->findOrFail($teamId);
|
||||
|
||||
if (Gate::denies('view', $team)) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
return view('teams.show', [
|
||||
'user' => $request->user(),
|
||||
'team' => $team,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the team creation screen.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function create(Request $request)
|
||||
{
|
||||
Gate::authorize('create', Jetstream::newTeamModel());
|
||||
|
||||
return view('teams.create', [
|
||||
'user' => $request->user(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Controllers\Livewire;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Jetstream\Jetstream;
|
||||
|
||||
class TermsOfServiceController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the terms of service for the application.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function show(Request $request)
|
||||
{
|
||||
$termsFile = Jetstream::localizedMarkdownPath('terms.md');
|
||||
|
||||
return view('terms', [
|
||||
'terms' => Str::markdown(file_get_contents($termsFile)),
|
||||
]);
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Controllers\Livewire;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
|
||||
class UserProfileController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the user profile screen.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function show(Request $request)
|
||||
{
|
||||
return view('profile.show', [
|
||||
'request' => $request,
|
||||
'user' => $request->user(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Controllers;
|
||||
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Laravel\Jetstream\Contracts\AddsTeamMembers;
|
||||
use Laravel\Jetstream\Jetstream;
|
||||
|
||||
class TeamInvitationController extends Controller
|
||||
{
|
||||
/**
|
||||
* Accept a team invitation.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $invitationId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function accept(Request $request, $invitationId)
|
||||
{
|
||||
$model = Jetstream::teamInvitationModel();
|
||||
|
||||
$invitation = $model::whereKey($invitationId)->firstOrFail();
|
||||
|
||||
app(AddsTeamMembers::class)->add(
|
||||
$invitation->team->owner,
|
||||
$invitation->team,
|
||||
$invitation->email,
|
||||
$invitation->role
|
||||
);
|
||||
|
||||
$invitation->delete();
|
||||
|
||||
return redirect(config('fortify.home'))->banner(
|
||||
__('Great! You have accepted the invitation to join the :team team.', ['team' => $invitation->team->name]),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the given team invitation.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $invitationId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function destroy(Request $request, $invitationId)
|
||||
{
|
||||
$model = Jetstream::teamInvitationModel();
|
||||
|
||||
$invitation = $model::whereKey($invitationId)->firstOrFail();
|
||||
|
||||
if (! Gate::forUser($request->user())->check('removeTeamMember', $invitation->team)) {
|
||||
throw new AuthorizationException;
|
||||
}
|
||||
|
||||
$invitation->delete();
|
||||
|
||||
return back(303);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Livewire;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Laravel\Jetstream\Jetstream;
|
||||
use Livewire\Component;
|
||||
|
||||
class ApiTokenManager extends Component
|
||||
{
|
||||
/**
|
||||
* The create API token form state.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $createApiTokenForm = [
|
||||
'name' => '',
|
||||
'permissions' => [],
|
||||
];
|
||||
|
||||
/**
|
||||
* Indicates if the plain text token is being displayed to the user.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $displayingToken = false;
|
||||
|
||||
/**
|
||||
* The plain text token value.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $plainTextToken;
|
||||
|
||||
/**
|
||||
* Indicates if the user is currently managing an API token's permissions.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $managingApiTokenPermissions = false;
|
||||
|
||||
/**
|
||||
* The token that is currently having its permissions managed.
|
||||
*
|
||||
* @var \Laravel\Sanctum\PersonalAccessToken|null
|
||||
*/
|
||||
public $managingPermissionsFor;
|
||||
|
||||
/**
|
||||
* The update API token form state.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $updateApiTokenForm = [
|
||||
'permissions' => [],
|
||||
];
|
||||
|
||||
/**
|
||||
* Indicates if the application is confirming if an API token should be deleted.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $confirmingApiTokenDeletion = false;
|
||||
|
||||
/**
|
||||
* The ID of the API token being deleted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $apiTokenIdBeingDeleted;
|
||||
|
||||
/**
|
||||
* Mount the component.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function mount()
|
||||
{
|
||||
$this->createApiTokenForm['permissions'] = Jetstream::$defaultPermissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new API token.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function createApiToken()
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
|
||||
Validator::make([
|
||||
'name' => $this->createApiTokenForm['name'],
|
||||
], [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
])->validateWithBag('createApiToken');
|
||||
|
||||
$this->displayTokenValue($this->user->createToken(
|
||||
$this->createApiTokenForm['name'],
|
||||
Jetstream::validPermissions($this->createApiTokenForm['permissions'])
|
||||
));
|
||||
|
||||
$this->createApiTokenForm['name'] = '';
|
||||
$this->createApiTokenForm['permissions'] = Jetstream::$defaultPermissions;
|
||||
|
||||
$this->dispatch('created');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the token value to the user.
|
||||
*
|
||||
* @param \Laravel\Sanctum\NewAccessToken $token
|
||||
* @return void
|
||||
*/
|
||||
protected function displayTokenValue($token)
|
||||
{
|
||||
$this->displayingToken = true;
|
||||
|
||||
$this->plainTextToken = explode('|', $token->plainTextToken, 2)[1];
|
||||
|
||||
$this->dispatch('showing-token-modal');
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow the given token's permissions to be managed.
|
||||
*
|
||||
* @param int $tokenId
|
||||
* @return void
|
||||
*/
|
||||
public function manageApiTokenPermissions($tokenId)
|
||||
{
|
||||
$this->managingApiTokenPermissions = true;
|
||||
|
||||
$this->managingPermissionsFor = $this->user->tokens()->where(
|
||||
'id', $tokenId
|
||||
)->firstOrFail();
|
||||
|
||||
$this->updateApiTokenForm['permissions'] = $this->managingPermissionsFor->abilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the API token's permissions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updateApiToken()
|
||||
{
|
||||
$this->managingPermissionsFor->forceFill([
|
||||
'abilities' => Jetstream::validPermissions($this->updateApiTokenForm['permissions']),
|
||||
])->save();
|
||||
|
||||
$this->managingApiTokenPermissions = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm that the given API token should be deleted.
|
||||
*
|
||||
* @param int $tokenId
|
||||
* @return void
|
||||
*/
|
||||
public function confirmApiTokenDeletion($tokenId)
|
||||
{
|
||||
$this->confirmingApiTokenDeletion = true;
|
||||
|
||||
$this->apiTokenIdBeingDeleted = $tokenId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the API token.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function deleteApiToken()
|
||||
{
|
||||
$this->user->tokens()->where('id', $this->apiTokenIdBeingDeleted)->first()->delete();
|
||||
|
||||
$this->user->load('tokens');
|
||||
|
||||
$this->confirmingApiTokenDeletion = false;
|
||||
|
||||
$this->managingPermissionsFor = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current user of the application.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getUserProperty()
|
||||
{
|
||||
return Auth::user();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the component.
|
||||
*
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return view('api.api-token-manager');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Livewire;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Laravel\Jetstream\Contracts\CreatesTeams;
|
||||
use Laravel\Jetstream\RedirectsActions;
|
||||
use Livewire\Component;
|
||||
|
||||
class CreateTeamForm extends Component
|
||||
{
|
||||
use RedirectsActions;
|
||||
|
||||
/**
|
||||
* The component's state.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $state = [];
|
||||
|
||||
/**
|
||||
* Create a new team.
|
||||
*
|
||||
* @param \Laravel\Jetstream\Contracts\CreatesTeams $creator
|
||||
* @return mixed
|
||||
*/
|
||||
public function createTeam(CreatesTeams $creator)
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
|
||||
$creator->create(Auth::user(), $this->state);
|
||||
|
||||
return $this->redirectPath($creator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current user of the application.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getUserProperty()
|
||||
{
|
||||
return Auth::user();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the component.
|
||||
*
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return view('teams.create-team-form');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Livewire;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Laravel\Jetstream\Actions\ValidateTeamDeletion;
|
||||
use Laravel\Jetstream\Contracts\DeletesTeams;
|
||||
use Laravel\Jetstream\RedirectsActions;
|
||||
use Livewire\Component;
|
||||
|
||||
class DeleteTeamForm extends Component
|
||||
{
|
||||
use RedirectsActions;
|
||||
|
||||
/**
|
||||
* The team instance.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $team;
|
||||
|
||||
/**
|
||||
* Indicates if team deletion is being confirmed.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $confirmingTeamDeletion = false;
|
||||
|
||||
/**
|
||||
* Mount the component.
|
||||
*
|
||||
* @param mixed $team
|
||||
* @return void
|
||||
*/
|
||||
public function mount($team)
|
||||
{
|
||||
$this->team = $team;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the team.
|
||||
*
|
||||
* @param \Laravel\Jetstream\Actions\ValidateTeamDeletion $validator
|
||||
* @param \Laravel\Jetstream\Contracts\DeletesTeams $deleter
|
||||
* @return mixed
|
||||
*/
|
||||
public function deleteTeam(ValidateTeamDeletion $validator, DeletesTeams $deleter)
|
||||
{
|
||||
$validator->validate(Auth::user(), $this->team);
|
||||
|
||||
$deleter->delete($this->team);
|
||||
|
||||
$this->team = null;
|
||||
|
||||
return $this->redirectPath($deleter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the component.
|
||||
*
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return view('teams.delete-team-form');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Livewire;
|
||||
|
||||
use Illuminate\Contracts\Auth\StatefulGuard;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Laravel\Jetstream\Contracts\DeletesUsers;
|
||||
use Livewire\Component;
|
||||
|
||||
class DeleteUserForm extends Component
|
||||
{
|
||||
/**
|
||||
* Indicates if user deletion is being confirmed.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $confirmingUserDeletion = false;
|
||||
|
||||
/**
|
||||
* The user's current password.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $password = '';
|
||||
|
||||
/**
|
||||
* Confirm that the user would like to delete their account.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function confirmUserDeletion()
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
|
||||
$this->password = '';
|
||||
|
||||
$this->dispatch('confirming-delete-user');
|
||||
|
||||
$this->confirmingUserDeletion = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the current user.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Laravel\Jetstream\Contracts\DeletesUsers $deleter
|
||||
* @param \Illuminate\Contracts\Auth\StatefulGuard $auth
|
||||
* @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function deleteUser(Request $request, DeletesUsers $deleter, StatefulGuard $auth)
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
|
||||
if (! Hash::check($this->password, Auth::user()->password)) {
|
||||
throw ValidationException::withMessages([
|
||||
'password' => [__('This password does not match our records.')],
|
||||
]);
|
||||
}
|
||||
|
||||
$deleter->delete(Auth::user()->fresh());
|
||||
|
||||
$auth->logout();
|
||||
|
||||
if ($request->hasSession()) {
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
}
|
||||
|
||||
return redirect(config('fortify.redirects.logout') ?? '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the component.
|
||||
*
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return view('profile.delete-user-form');
|
||||
}
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Livewire;
|
||||
|
||||
use Illuminate\Contracts\Auth\StatefulGuard;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Laravel\Jetstream\Agent;
|
||||
use Livewire\Component;
|
||||
|
||||
class LogoutOtherBrowserSessionsForm extends Component
|
||||
{
|
||||
/**
|
||||
* Indicates if logout is being confirmed.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $confirmingLogout = false;
|
||||
|
||||
/**
|
||||
* The user's current password.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $password = '';
|
||||
|
||||
/**
|
||||
* Confirm that the user would like to log out from other browser sessions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function confirmLogout()
|
||||
{
|
||||
$this->password = '';
|
||||
|
||||
$this->dispatch('confirming-logout-other-browser-sessions');
|
||||
|
||||
$this->confirmingLogout = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log out from other browser sessions.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Auth\StatefulGuard $guard
|
||||
* @return void
|
||||
*/
|
||||
public function logoutOtherBrowserSessions(StatefulGuard $guard)
|
||||
{
|
||||
if (config('session.driver') !== 'database') {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->resetErrorBag();
|
||||
|
||||
if (! Hash::check($this->password, Auth::user()->password)) {
|
||||
throw ValidationException::withMessages([
|
||||
'password' => [__('This password does not match our records.')],
|
||||
]);
|
||||
}
|
||||
|
||||
$guard->logoutOtherDevices($this->password);
|
||||
|
||||
$this->deleteOtherSessionRecords();
|
||||
|
||||
request()->session()->put([
|
||||
'password_hash_'.Auth::getDefaultDriver() => Auth::user()->getAuthPassword(),
|
||||
]);
|
||||
|
||||
$this->confirmingLogout = false;
|
||||
|
||||
$this->dispatch('loggedOut');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the other browser session records from storage.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function deleteOtherSessionRecords()
|
||||
{
|
||||
if (config('session.driver') !== 'database') {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::connection(config('session.connection'))->table(config('session.table', 'sessions'))
|
||||
->where('user_id', Auth::user()->getAuthIdentifier())
|
||||
->where('id', '!=', request()->session()->getId())
|
||||
->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current sessions.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function getSessionsProperty()
|
||||
{
|
||||
if (config('session.driver') !== 'database') {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return collect(
|
||||
DB::connection(config('session.connection'))->table(config('session.table', 'sessions'))
|
||||
->where('user_id', Auth::user()->getAuthIdentifier())
|
||||
->orderBy('last_activity', 'desc')
|
||||
->get()
|
||||
)->map(function ($session) {
|
||||
return (object) [
|
||||
'agent' => $this->createAgent($session),
|
||||
'ip_address' => $session->ip_address,
|
||||
'is_current_device' => $session->id === request()->session()->getId(),
|
||||
'last_active' => Carbon::createFromTimestamp($session->last_activity)->diffForHumans(),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new agent instance from the given session.
|
||||
*
|
||||
* @param mixed $session
|
||||
* @return \Laravel\Jetstream\Agent
|
||||
*/
|
||||
protected function createAgent($session)
|
||||
{
|
||||
return tap(new Agent(), fn ($agent) => $agent->setUserAgent($session->user_agent));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the component.
|
||||
*
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return view('profile.logout-other-browser-sessions-form');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Livewire;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class NavigationMenu extends Component
|
||||
{
|
||||
/**
|
||||
* The component's listeners.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $listeners = [
|
||||
'refresh-navigation-menu' => '$refresh',
|
||||
];
|
||||
|
||||
/**
|
||||
* Render the component.
|
||||
*
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return view('navigation-menu');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Livewire;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Laravel\Jetstream\Actions\UpdateTeamMemberRole;
|
||||
use Laravel\Jetstream\Contracts\AddsTeamMembers;
|
||||
use Laravel\Jetstream\Contracts\InvitesTeamMembers;
|
||||
use Laravel\Jetstream\Contracts\RemovesTeamMembers;
|
||||
use Laravel\Jetstream\Features;
|
||||
use Laravel\Jetstream\Jetstream;
|
||||
use Laravel\Jetstream\Role;
|
||||
use Livewire\Component;
|
||||
|
||||
class TeamMemberManager extends Component
|
||||
{
|
||||
/**
|
||||
* The team instance.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $team;
|
||||
|
||||
/**
|
||||
* Indicates if a user's role is currently being managed.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $currentlyManagingRole = false;
|
||||
|
||||
/**
|
||||
* The user that is having their role managed.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $managingRoleFor;
|
||||
|
||||
/**
|
||||
* The current role for the user that is having their role managed.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $currentRole;
|
||||
|
||||
/**
|
||||
* Indicates if the application is confirming if a user wishes to leave the current team.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $confirmingLeavingTeam = false;
|
||||
|
||||
/**
|
||||
* Indicates if the application is confirming if a team member should be removed.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $confirmingTeamMemberRemoval = false;
|
||||
|
||||
/**
|
||||
* The ID of the team member being removed.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
public $teamMemberIdBeingRemoved = null;
|
||||
|
||||
/**
|
||||
* The "add team member" form state.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $addTeamMemberForm = [
|
||||
'email' => '',
|
||||
'role' => null,
|
||||
];
|
||||
|
||||
/**
|
||||
* Mount the component.
|
||||
*
|
||||
* @param mixed $team
|
||||
* @return void
|
||||
*/
|
||||
public function mount($team)
|
||||
{
|
||||
$this->team = $team;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new team member to a team.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addTeamMember()
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
|
||||
if (Features::sendsTeamInvitations()) {
|
||||
app(InvitesTeamMembers::class)->invite(
|
||||
$this->user,
|
||||
$this->team,
|
||||
$this->addTeamMemberForm['email'],
|
||||
$this->addTeamMemberForm['role']
|
||||
);
|
||||
} else {
|
||||
app(AddsTeamMembers::class)->add(
|
||||
$this->user,
|
||||
$this->team,
|
||||
$this->addTeamMemberForm['email'],
|
||||
$this->addTeamMemberForm['role']
|
||||
);
|
||||
}
|
||||
|
||||
$this->addTeamMemberForm = [
|
||||
'email' => '',
|
||||
'role' => null,
|
||||
];
|
||||
|
||||
$this->team = $this->team->fresh();
|
||||
|
||||
$this->dispatch('saved');
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel a pending team member invitation.
|
||||
*
|
||||
* @param int $invitationId
|
||||
* @return void
|
||||
*/
|
||||
public function cancelTeamInvitation($invitationId)
|
||||
{
|
||||
if (! empty($invitationId)) {
|
||||
$model = Jetstream::teamInvitationModel();
|
||||
|
||||
$model::whereKey($invitationId)
|
||||
->where('team_id', $this->team->id)
|
||||
->delete();
|
||||
}
|
||||
|
||||
$this->team = $this->team->fresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow the given user's role to be managed.
|
||||
*
|
||||
* @param int $userId
|
||||
* @return void
|
||||
*/
|
||||
public function manageRole($userId)
|
||||
{
|
||||
$this->currentlyManagingRole = true;
|
||||
$this->managingRoleFor = Jetstream::findUserByIdOrFail($userId);
|
||||
$this->currentRole = $this->managingRoleFor->teamRole($this->team)->key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the role for the user being managed.
|
||||
*
|
||||
* @param \Laravel\Jetstream\Actions\UpdateTeamMemberRole $updater
|
||||
* @return void
|
||||
*/
|
||||
public function updateRole(UpdateTeamMemberRole $updater)
|
||||
{
|
||||
$updater->update(
|
||||
$this->user,
|
||||
$this->team,
|
||||
$this->managingRoleFor->id,
|
||||
$this->currentRole
|
||||
);
|
||||
|
||||
$this->team = $this->team->fresh();
|
||||
|
||||
$this->stopManagingRole();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop managing the role of a given user.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function stopManagingRole()
|
||||
{
|
||||
$this->currentlyManagingRole = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the currently authenticated user from the team.
|
||||
*
|
||||
* @param \Laravel\Jetstream\Contracts\RemovesTeamMembers $remover
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function leaveTeam(RemovesTeamMembers $remover)
|
||||
{
|
||||
$remover->remove(
|
||||
$this->user,
|
||||
$this->team,
|
||||
$this->user
|
||||
);
|
||||
|
||||
$this->confirmingLeavingTeam = false;
|
||||
|
||||
$this->team = $this->team->fresh();
|
||||
|
||||
return redirect(config('fortify.home'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm that the given team member should be removed.
|
||||
*
|
||||
* @param int $userId
|
||||
* @return void
|
||||
*/
|
||||
public function confirmTeamMemberRemoval($userId)
|
||||
{
|
||||
$this->confirmingTeamMemberRemoval = true;
|
||||
|
||||
$this->teamMemberIdBeingRemoved = $userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a team member from the team.
|
||||
*
|
||||
* @param \Laravel\Jetstream\Contracts\RemovesTeamMembers $remover
|
||||
* @return void
|
||||
*/
|
||||
public function removeTeamMember(RemovesTeamMembers $remover)
|
||||
{
|
||||
$remover->remove(
|
||||
$this->user,
|
||||
$this->team,
|
||||
$user = Jetstream::findUserByIdOrFail($this->teamMemberIdBeingRemoved)
|
||||
);
|
||||
|
||||
$this->confirmingTeamMemberRemoval = false;
|
||||
|
||||
$this->teamMemberIdBeingRemoved = null;
|
||||
|
||||
$this->team = $this->team->fresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current user of the application.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getUserProperty()
|
||||
{
|
||||
return Auth::user();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the available team member roles.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getRolesProperty()
|
||||
{
|
||||
return collect(Jetstream::$roles)->transform(function ($role) {
|
||||
return with($role->jsonSerialize(), function ($data) {
|
||||
return (new Role(
|
||||
$data['key'],
|
||||
$data['name'],
|
||||
$data['permissions']
|
||||
))->description($data['description']);
|
||||
});
|
||||
})->values()->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the component.
|
||||
*
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return view('teams.team-member-manager');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Livewire;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Laravel\Fortify\Actions\ConfirmTwoFactorAuthentication;
|
||||
use Laravel\Fortify\Actions\DisableTwoFactorAuthentication;
|
||||
use Laravel\Fortify\Actions\EnableTwoFactorAuthentication;
|
||||
use Laravel\Fortify\Actions\GenerateNewRecoveryCodes;
|
||||
use Laravel\Fortify\Features;
|
||||
use Laravel\Jetstream\ConfirmsPasswords;
|
||||
use Livewire\Component;
|
||||
|
||||
class TwoFactorAuthenticationForm extends Component
|
||||
{
|
||||
use ConfirmsPasswords;
|
||||
|
||||
/**
|
||||
* Indicates if two factor authentication QR code is being displayed.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $showingQrCode = false;
|
||||
|
||||
/**
|
||||
* Indicates if the two factor authentication confirmation input and button are being displayed.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $showingConfirmation = false;
|
||||
|
||||
/**
|
||||
* Indicates if two factor authentication recovery codes are being displayed.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $showingRecoveryCodes = false;
|
||||
|
||||
/**
|
||||
* The OTP code for confirming two factor authentication.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $code;
|
||||
|
||||
/**
|
||||
* Mount the component.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function mount()
|
||||
{
|
||||
if (Features::optionEnabled(Features::twoFactorAuthentication(), 'confirm') &&
|
||||
is_null(Auth::user()->two_factor_confirmed_at)) {
|
||||
app(DisableTwoFactorAuthentication::class)(Auth::user());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable two factor authentication for the user.
|
||||
*
|
||||
* @param \Laravel\Fortify\Actions\EnableTwoFactorAuthentication $enable
|
||||
* @return void
|
||||
*/
|
||||
public function enableTwoFactorAuthentication(EnableTwoFactorAuthentication $enable)
|
||||
{
|
||||
if (Features::optionEnabled(Features::twoFactorAuthentication(), 'confirmPassword')) {
|
||||
$this->ensurePasswordIsConfirmed();
|
||||
}
|
||||
|
||||
$enable(Auth::user());
|
||||
|
||||
$this->showingQrCode = true;
|
||||
|
||||
if (Features::optionEnabled(Features::twoFactorAuthentication(), 'confirm')) {
|
||||
$this->showingConfirmation = true;
|
||||
} else {
|
||||
$this->showingRecoveryCodes = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm two factor authentication for the user.
|
||||
*
|
||||
* @param \Laravel\Fortify\Actions\ConfirmTwoFactorAuthentication $confirm
|
||||
* @return void
|
||||
*/
|
||||
public function confirmTwoFactorAuthentication(ConfirmTwoFactorAuthentication $confirm)
|
||||
{
|
||||
if (Features::optionEnabled(Features::twoFactorAuthentication(), 'confirmPassword')) {
|
||||
$this->ensurePasswordIsConfirmed();
|
||||
}
|
||||
|
||||
$confirm(Auth::user(), $this->code);
|
||||
|
||||
$this->showingQrCode = false;
|
||||
$this->showingConfirmation = false;
|
||||
$this->showingRecoveryCodes = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the user's recovery codes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function showRecoveryCodes()
|
||||
{
|
||||
if (Features::optionEnabled(Features::twoFactorAuthentication(), 'confirmPassword')) {
|
||||
$this->ensurePasswordIsConfirmed();
|
||||
}
|
||||
|
||||
$this->showingRecoveryCodes = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate new recovery codes for the user.
|
||||
*
|
||||
* @param \Laravel\Fortify\Actions\GenerateNewRecoveryCodes $generate
|
||||
* @return void
|
||||
*/
|
||||
public function regenerateRecoveryCodes(GenerateNewRecoveryCodes $generate)
|
||||
{
|
||||
if (Features::optionEnabled(Features::twoFactorAuthentication(), 'confirmPassword')) {
|
||||
$this->ensurePasswordIsConfirmed();
|
||||
}
|
||||
|
||||
$generate(Auth::user());
|
||||
|
||||
$this->showingRecoveryCodes = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable two factor authentication for the user.
|
||||
*
|
||||
* @param \Laravel\Fortify\Actions\DisableTwoFactorAuthentication $disable
|
||||
* @return void
|
||||
*/
|
||||
public function disableTwoFactorAuthentication(DisableTwoFactorAuthentication $disable)
|
||||
{
|
||||
if (Features::optionEnabled(Features::twoFactorAuthentication(), 'confirmPassword')) {
|
||||
$this->ensurePasswordIsConfirmed();
|
||||
}
|
||||
|
||||
$disable(Auth::user());
|
||||
|
||||
$this->showingQrCode = false;
|
||||
$this->showingConfirmation = false;
|
||||
$this->showingRecoveryCodes = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current user of the application.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getUserProperty()
|
||||
{
|
||||
return Auth::user();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if two factor authentication is enabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getEnabledProperty()
|
||||
{
|
||||
return ! empty($this->user->two_factor_secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the component.
|
||||
*
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return view('profile.two-factor-authentication-form');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Livewire;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Laravel\Fortify\Contracts\UpdatesUserPasswords;
|
||||
use Livewire\Component;
|
||||
|
||||
class UpdatePasswordForm extends Component
|
||||
{
|
||||
/**
|
||||
* The component's state.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $state = [
|
||||
'current_password' => '',
|
||||
'password' => '',
|
||||
'password_confirmation' => '',
|
||||
];
|
||||
|
||||
/**
|
||||
* Update the user's password.
|
||||
*
|
||||
* @param \Laravel\Fortify\Contracts\UpdatesUserPasswords $updater
|
||||
* @return void
|
||||
*/
|
||||
public function updatePassword(UpdatesUserPasswords $updater)
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
|
||||
$updater->update(Auth::user(), $this->state);
|
||||
|
||||
if (request()->hasSession()) {
|
||||
request()->session()->put([
|
||||
'password_hash_'.Auth::getDefaultDriver() => Auth::user()->getAuthPassword(),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->state = [
|
||||
'current_password' => '',
|
||||
'password' => '',
|
||||
'password_confirmation' => '',
|
||||
];
|
||||
|
||||
$this->dispatch('saved');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current user of the application.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getUserProperty()
|
||||
{
|
||||
return Auth::user();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the component.
|
||||
*
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return view('profile.update-password-form');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Livewire;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
|
||||
class UpdateProfileInformationForm extends Component
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
/**
|
||||
* The component's state.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $state = [];
|
||||
|
||||
/**
|
||||
* The new avatar for the user.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $photo;
|
||||
|
||||
/**
|
||||
* Determine if the verification email was sent.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $verificationLinkSent = false;
|
||||
|
||||
/**
|
||||
* Prepare the component.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function mount()
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$this->state = array_merge([
|
||||
'email' => $user->email,
|
||||
], $user->withoutRelations()->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's profile information.
|
||||
*
|
||||
* @param \Laravel\Fortify\Contracts\UpdatesUserProfileInformation $updater
|
||||
* @return \Illuminate\Http\RedirectResponse|null
|
||||
*/
|
||||
public function updateProfileInformation(UpdatesUserProfileInformation $updater)
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
|
||||
$updater->update(
|
||||
Auth::user(),
|
||||
$this->photo
|
||||
? array_merge($this->state, ['photo' => $this->photo])
|
||||
: $this->state
|
||||
);
|
||||
|
||||
if (isset($this->photo)) {
|
||||
return redirect()->route('profile.show');
|
||||
}
|
||||
|
||||
$this->dispatch('saved');
|
||||
|
||||
$this->dispatch('refresh-navigation-menu');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete user's profile photo.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function deleteProfilePhoto()
|
||||
{
|
||||
Auth::user()->deleteProfilePhoto();
|
||||
|
||||
$this->dispatch('refresh-navigation-menu');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sent the email verification.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function sendEmailVerification()
|
||||
{
|
||||
Auth::user()->sendEmailVerificationNotification();
|
||||
|
||||
$this->verificationLinkSent = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current user of the application.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getUserProperty()
|
||||
{
|
||||
return Auth::user();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the component.
|
||||
*
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return view('profile.update-profile-information-form');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Livewire;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Laravel\Jetstream\Contracts\UpdatesTeamNames;
|
||||
use Livewire\Component;
|
||||
|
||||
class UpdateTeamNameForm extends Component
|
||||
{
|
||||
/**
|
||||
* The team instance.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $team;
|
||||
|
||||
/**
|
||||
* The component's state.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $state = [];
|
||||
|
||||
/**
|
||||
* Mount the component.
|
||||
*
|
||||
* @param mixed $team
|
||||
* @return void
|
||||
*/
|
||||
public function mount($team)
|
||||
{
|
||||
$this->team = $team;
|
||||
|
||||
$this->state = $team->withoutRelations()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the team's name.
|
||||
*
|
||||
* @param \Laravel\Jetstream\Contracts\UpdatesTeamNames $updater
|
||||
* @return void
|
||||
*/
|
||||
public function updateTeamName(UpdatesTeamNames $updater)
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
|
||||
$updater->update($this->user, $this->team, $this->state);
|
||||
|
||||
$this->dispatch('saved');
|
||||
|
||||
$this->dispatch('refresh-navigation-menu');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current user of the application.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getUserProperty()
|
||||
{
|
||||
return Auth::user();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the component.
|
||||
*
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return view('teams.update-team-name-form');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Middleware;
|
||||
|
||||
use Illuminate\Contracts\Auth\StatefulGuard;
|
||||
use Illuminate\Session\Middleware\AuthenticateSession as BaseAuthenticateSession;
|
||||
|
||||
class AuthenticateSession extends BaseAuthenticateSession
|
||||
{
|
||||
/**
|
||||
* Get the guard instance that should be used by the middleware.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Auth\Factory|\Illuminate\Contracts\Auth\Guard
|
||||
*/
|
||||
protected function guard()
|
||||
{
|
||||
return app(StatefulGuard::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Http\Middleware;
|
||||
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Inertia\Inertia;
|
||||
use Laravel\Fortify\Features;
|
||||
use Laravel\Jetstream\Jetstream;
|
||||
|
||||
class ShareInertiaData
|
||||
{
|
||||
/**
|
||||
* Handle the incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param callable $next
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function handle($request, $next)
|
||||
{
|
||||
Inertia::share(array_filter([
|
||||
'jetstream' => function () use ($request) {
|
||||
$user = $request->user();
|
||||
|
||||
return [
|
||||
'canCreateTeams' => $user &&
|
||||
Jetstream::userHasTeamFeatures($user) &&
|
||||
Gate::forUser($user)->check('create', Jetstream::newTeamModel()),
|
||||
'canManageTwoFactorAuthentication' => Features::canManageTwoFactorAuthentication(),
|
||||
'canUpdatePassword' => Features::enabled(Features::updatePasswords()),
|
||||
'canUpdateProfileInformation' => Features::canUpdateProfileInformation(),
|
||||
'hasEmailVerification' => Features::enabled(Features::emailVerification()),
|
||||
'flash' => $request->session()->get('flash', []),
|
||||
'hasAccountDeletionFeatures' => Jetstream::hasAccountDeletionFeatures(),
|
||||
'hasApiFeatures' => Jetstream::hasApiFeatures(),
|
||||
'hasTeamFeatures' => Jetstream::hasTeamFeatures(),
|
||||
'hasTermsAndPrivacyPolicyFeature' => Jetstream::hasTermsAndPrivacyPolicyFeature(),
|
||||
'managesProfilePhotos' => Jetstream::managesProfilePhotos(),
|
||||
];
|
||||
},
|
||||
'auth' => [
|
||||
'user' => function () use ($request) {
|
||||
if (! $user = $request->user()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$userHasTeamFeatures = Jetstream::userHasTeamFeatures($user);
|
||||
|
||||
if ($user && $userHasTeamFeatures) {
|
||||
$user->currentTeam;
|
||||
}
|
||||
|
||||
return array_merge($user->toArray(), array_filter([
|
||||
'all_teams' => $userHasTeamFeatures ? $user->allTeams()->values() : null,
|
||||
]), [
|
||||
'two_factor_enabled' => Features::enabled(Features::twoFactorAuthentication())
|
||||
&& ! is_null($user->two_factor_secret),
|
||||
]);
|
||||
},
|
||||
],
|
||||
'errorBags' => function () {
|
||||
return collect(optional(Session::get('errors'))->getBags() ?: [])->mapWithKeys(function ($bag, $key) {
|
||||
return [$key => $bag->messages()];
|
||||
})->all();
|
||||
},
|
||||
]));
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class InertiaManager
|
||||
{
|
||||
/**
|
||||
* The registered rendering callbacks.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $renderingCallbacks = [];
|
||||
|
||||
/**
|
||||
* Render the given Inertia page.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param string $page
|
||||
* @param array $data
|
||||
* @return \Inertia\Response
|
||||
*/
|
||||
public function render(Request $request, string $page, array $data = [])
|
||||
{
|
||||
if (isset($this->renderingCallbacks[$page])) {
|
||||
foreach ($this->renderingCallbacks[$page] as $callback) {
|
||||
$data = $callback($request, $data);
|
||||
}
|
||||
}
|
||||
|
||||
return Inertia::render($page, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a rendering callback.
|
||||
*
|
||||
* @param string $page
|
||||
* @param callable $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function whenRendering(string $page, callable $callback)
|
||||
{
|
||||
$this->renderingCallbacks[$page][] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream;
|
||||
|
||||
trait InteractsWithBanner
|
||||
{
|
||||
/**
|
||||
* Update the banner message.
|
||||
*
|
||||
* @param string $message
|
||||
* @return void
|
||||
*/
|
||||
protected function banner($message)
|
||||
{
|
||||
$this->dispatch('banner-message',
|
||||
style: 'success',
|
||||
message: $message,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the banner message with a warning message.
|
||||
*
|
||||
* @param string $message
|
||||
* @return void
|
||||
*/
|
||||
protected function warningBanner($message)
|
||||
{
|
||||
$this->dispatch('banner-message',
|
||||
style: 'warning',
|
||||
message: $message,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the banner message with a danger / error message.
|
||||
*
|
||||
* @param string $message
|
||||
* @return void
|
||||
*/
|
||||
protected function dangerBanner($message)
|
||||
{
|
||||
$this->dispatch('banner-message',
|
||||
style: 'danger',
|
||||
message: $message,
|
||||
);
|
||||
}
|
||||
}
|
||||
+490
@@ -0,0 +1,490 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Arr;
|
||||
use Laravel\Jetstream\Contracts\AddsTeamMembers;
|
||||
use Laravel\Jetstream\Contracts\CreatesTeams;
|
||||
use Laravel\Jetstream\Contracts\DeletesTeams;
|
||||
use Laravel\Jetstream\Contracts\DeletesUsers;
|
||||
use Laravel\Jetstream\Contracts\InvitesTeamMembers;
|
||||
use Laravel\Jetstream\Contracts\RemovesTeamMembers;
|
||||
use Laravel\Jetstream\Contracts\UpdatesTeamNames;
|
||||
|
||||
class Jetstream
|
||||
{
|
||||
/**
|
||||
* Indicates if Jetstream routes will be registered.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $registersRoutes = true;
|
||||
|
||||
/**
|
||||
* The roles that are available to assign to users.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $roles = [];
|
||||
|
||||
/**
|
||||
* The permissions that exist within the application.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $permissions = [];
|
||||
|
||||
/**
|
||||
* The default permissions that should be available to new entities.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $defaultPermissions = [];
|
||||
|
||||
/**
|
||||
* The user model that should be used by Jetstream.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $userModel = 'App\\Models\\User';
|
||||
|
||||
/**
|
||||
* The team model that should be used by Jetstream.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $teamModel = 'App\\Models\\Team';
|
||||
|
||||
/**
|
||||
* The membership model that should be used by Jetstream.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $membershipModel = 'App\\Models\\Membership';
|
||||
|
||||
/**
|
||||
* The team invitation model that should be used by Jetstream.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $teamInvitationModel = 'App\\Models\\TeamInvitation';
|
||||
|
||||
/**
|
||||
* The Inertia manager instance.
|
||||
*
|
||||
* @var \Laravel\Jetstream\InertiaManager
|
||||
*/
|
||||
public static $inertiaManager;
|
||||
|
||||
/**
|
||||
* Determine if Jetstream has registered roles.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasRoles()
|
||||
{
|
||||
return count(static::$roles) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the role with the given key.
|
||||
*
|
||||
* @param string $key
|
||||
* @return \Laravel\Jetstream\Role|null
|
||||
*/
|
||||
public static function findRole(string $key)
|
||||
{
|
||||
return static::$roles[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a role.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $name
|
||||
* @param array $permissions
|
||||
* @return \Laravel\Jetstream\Role
|
||||
*/
|
||||
public static function role(string $key, string $name, array $permissions)
|
||||
{
|
||||
static::$permissions = collect(array_merge(static::$permissions, $permissions))
|
||||
->unique()
|
||||
->sort()
|
||||
->values()
|
||||
->all();
|
||||
|
||||
return tap(new Role($key, $name, $permissions), function ($role) use ($key) {
|
||||
static::$roles[$key] = $role;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if any permissions have been registered with Jetstream.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasPermissions()
|
||||
{
|
||||
return count(static::$permissions) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the available API token permissions.
|
||||
*
|
||||
* @param array $permissions
|
||||
* @return static
|
||||
*/
|
||||
public static function permissions(array $permissions)
|
||||
{
|
||||
static::$permissions = $permissions;
|
||||
|
||||
return new static;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the default permissions that should be available to new API tokens.
|
||||
*
|
||||
* @param array $permissions
|
||||
* @return static
|
||||
*/
|
||||
public static function defaultApiTokenPermissions(array $permissions)
|
||||
{
|
||||
static::$defaultPermissions = $permissions;
|
||||
|
||||
return new static;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the permissions in the given list that are actually defined permissions for the application.
|
||||
*
|
||||
* @param array $permissions
|
||||
* @return array
|
||||
*/
|
||||
public static function validPermissions(array $permissions)
|
||||
{
|
||||
return array_values(array_intersect($permissions, static::$permissions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if Jetstream is managing profile photos.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function managesProfilePhotos()
|
||||
{
|
||||
return Features::managesProfilePhotos();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if Jetstream is supporting API features.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasApiFeatures()
|
||||
{
|
||||
return Features::hasApiFeatures();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if Jetstream is supporting team features.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasTeamFeatures()
|
||||
{
|
||||
return Features::hasTeamFeatures();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a given user model utilizes the "HasTeams" trait.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model
|
||||
* @return bool
|
||||
*/
|
||||
public static function userHasTeamFeatures($user)
|
||||
{
|
||||
return (array_key_exists(HasTeams::class, class_uses_recursive($user)) ||
|
||||
method_exists($user, 'currentTeam')) &&
|
||||
static::hasTeamFeatures();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the application is using the terms confirmation feature.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasTermsAndPrivacyPolicyFeature()
|
||||
{
|
||||
return Features::hasTermsAndPrivacyPolicyFeature();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the application is using any account deletion features.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasAccountDeletionFeatures()
|
||||
{
|
||||
return Features::hasAccountDeletionFeatures();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a user instance by the given ID.
|
||||
*
|
||||
* @param int $id
|
||||
* @return mixed
|
||||
*/
|
||||
public static function findUserByIdOrFail($id)
|
||||
{
|
||||
return static::newUserModel()->where('id', $id)->firstOrFail();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a user instance by the given email address or fail.
|
||||
*
|
||||
* @param string $email
|
||||
* @return mixed
|
||||
*/
|
||||
public static function findUserByEmailOrFail(string $email)
|
||||
{
|
||||
return static::newUserModel()->where('email', $email)->firstOrFail();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the user model used by the application.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function userModel()
|
||||
{
|
||||
return static::$userModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new instance of the user model.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function newUserModel()
|
||||
{
|
||||
$model = static::userModel();
|
||||
|
||||
return new $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the user model that should be used by Jetstream.
|
||||
*
|
||||
* @param string $model
|
||||
* @return static
|
||||
*/
|
||||
public static function useUserModel(string $model)
|
||||
{
|
||||
static::$userModel = $model;
|
||||
|
||||
return new static;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the team model used by the application.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function teamModel()
|
||||
{
|
||||
return static::$teamModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new instance of the team model.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function newTeamModel()
|
||||
{
|
||||
$model = static::teamModel();
|
||||
|
||||
return new $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the team model that should be used by Jetstream.
|
||||
*
|
||||
* @param string $model
|
||||
* @return static
|
||||
*/
|
||||
public static function useTeamModel(string $model)
|
||||
{
|
||||
static::$teamModel = $model;
|
||||
|
||||
return new static;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the membership model used by the application.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function membershipModel()
|
||||
{
|
||||
return static::$membershipModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the membership model that should be used by Jetstream.
|
||||
*
|
||||
* @param string $model
|
||||
* @return static
|
||||
*/
|
||||
public static function useMembershipModel(string $model)
|
||||
{
|
||||
static::$membershipModel = $model;
|
||||
|
||||
return new static;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the team invitation model used by the application.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function teamInvitationModel()
|
||||
{
|
||||
return static::$teamInvitationModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the team invitation model that should be used by Jetstream.
|
||||
*
|
||||
* @param string $model
|
||||
* @return static
|
||||
*/
|
||||
public static function useTeamInvitationModel(string $model)
|
||||
{
|
||||
static::$teamInvitationModel = $model;
|
||||
|
||||
return new static;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a class / callback that should be used to create teams.
|
||||
*
|
||||
* @param string $class
|
||||
* @return void
|
||||
*/
|
||||
public static function createTeamsUsing(string $class)
|
||||
{
|
||||
return app()->singleton(CreatesTeams::class, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a class / callback that should be used to update team names.
|
||||
*
|
||||
* @param string $class
|
||||
* @return void
|
||||
*/
|
||||
public static function updateTeamNamesUsing(string $class)
|
||||
{
|
||||
return app()->singleton(UpdatesTeamNames::class, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a class / callback that should be used to add team members.
|
||||
*
|
||||
* @param string $class
|
||||
* @return void
|
||||
*/
|
||||
public static function addTeamMembersUsing(string $class)
|
||||
{
|
||||
return app()->singleton(AddsTeamMembers::class, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a class / callback that should be used to add team members.
|
||||
*
|
||||
* @param string $class
|
||||
* @return void
|
||||
*/
|
||||
public static function inviteTeamMembersUsing(string $class)
|
||||
{
|
||||
return app()->singleton(InvitesTeamMembers::class, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a class / callback that should be used to remove team members.
|
||||
*
|
||||
* @param string $class
|
||||
* @return void
|
||||
*/
|
||||
public static function removeTeamMembersUsing(string $class)
|
||||
{
|
||||
return app()->singleton(RemovesTeamMembers::class, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a class / callback that should be used to delete teams.
|
||||
*
|
||||
* @param string $class
|
||||
* @return void
|
||||
*/
|
||||
public static function deleteTeamsUsing(string $class)
|
||||
{
|
||||
return app()->singleton(DeletesTeams::class, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a class / callback that should be used to delete users.
|
||||
*
|
||||
* @param string $class
|
||||
* @return void
|
||||
*/
|
||||
public static function deleteUsersUsing(string $class)
|
||||
{
|
||||
return app()->singleton(DeletesUsers::class, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage Jetstream's Inertia settings.
|
||||
*
|
||||
* @return \Laravel\Jetstream\InertiaManager
|
||||
*/
|
||||
public static function inertia()
|
||||
{
|
||||
if (is_null(static::$inertiaManager)) {
|
||||
static::$inertiaManager = new InertiaManager;
|
||||
}
|
||||
|
||||
return static::$inertiaManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the path to a localized Markdown resource.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string|null
|
||||
*/
|
||||
public static function localizedMarkdownPath($name)
|
||||
{
|
||||
$localName = preg_replace('#(\.md)$#i', '.'.app()->getLocale().'$1', $name);
|
||||
|
||||
return Arr::first([
|
||||
resource_path('markdown/'.$localName),
|
||||
resource_path('markdown/'.$name),
|
||||
], function ($path) {
|
||||
return file_exists($path);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure Jetstream to not register its routes.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function ignoreRoutes()
|
||||
{
|
||||
static::$registersRoutes = false;
|
||||
|
||||
return new static;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream;
|
||||
|
||||
use App\Http\Middleware\HandleInertiaRequests;
|
||||
use Illuminate\Contracts\Http\Kernel;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\View\Compilers\BladeCompiler;
|
||||
use Inertia\Inertia;
|
||||
use Laravel\Fortify\Events\PasswordUpdatedViaController;
|
||||
use Laravel\Fortify\Fortify;
|
||||
use Laravel\Jetstream\Http\Livewire\ApiTokenManager;
|
||||
use Laravel\Jetstream\Http\Livewire\CreateTeamForm;
|
||||
use Laravel\Jetstream\Http\Livewire\DeleteTeamForm;
|
||||
use Laravel\Jetstream\Http\Livewire\DeleteUserForm;
|
||||
use Laravel\Jetstream\Http\Livewire\LogoutOtherBrowserSessionsForm;
|
||||
use Laravel\Jetstream\Http\Livewire\NavigationMenu;
|
||||
use Laravel\Jetstream\Http\Livewire\TeamMemberManager;
|
||||
use Laravel\Jetstream\Http\Livewire\TwoFactorAuthenticationForm;
|
||||
use Laravel\Jetstream\Http\Livewire\UpdatePasswordForm;
|
||||
use Laravel\Jetstream\Http\Livewire\UpdateProfileInformationForm;
|
||||
use Laravel\Jetstream\Http\Livewire\UpdateTeamNameForm;
|
||||
use Laravel\Jetstream\Http\Middleware\ShareInertiaData;
|
||||
use Livewire\Livewire;
|
||||
|
||||
class JetstreamServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->mergeConfigFrom(__DIR__.'/../config/jetstream.php', 'jetstream');
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
Fortify::viewPrefix('auth.');
|
||||
|
||||
$this->configurePublishing();
|
||||
$this->configureRoutes();
|
||||
$this->configureCommands();
|
||||
|
||||
RedirectResponse::macro('banner', function ($message): RedirectResponse {
|
||||
/** @var \Illuminate\Http\RedirectResponse $this */
|
||||
return $this->with('flash', [
|
||||
'bannerStyle' => 'success',
|
||||
'banner' => $message,
|
||||
]);
|
||||
});
|
||||
|
||||
RedirectResponse::macro('warningBanner', function ($message): RedirectResponse {
|
||||
/** @var \Illuminate\Http\RedirectResponse $this */
|
||||
return $this->with('flash', [
|
||||
'bannerStyle' => 'warning',
|
||||
'banner' => $message,
|
||||
]);
|
||||
});
|
||||
|
||||
RedirectResponse::macro('dangerBanner', function ($message): RedirectResponse {
|
||||
/** @var \Illuminate\Http\RedirectResponse $this */
|
||||
return $this->with('flash', [
|
||||
'bannerStyle' => 'danger',
|
||||
'banner' => $message,
|
||||
]);
|
||||
});
|
||||
|
||||
if (config('jetstream.stack') === 'inertia' && class_exists(Inertia::class)) {
|
||||
$this->bootInertia();
|
||||
}
|
||||
|
||||
if (config('jetstream.stack') === 'livewire' && class_exists(Livewire::class)) {
|
||||
Livewire::component('navigation-menu', NavigationMenu::class);
|
||||
Livewire::component('profile.update-profile-information-form', UpdateProfileInformationForm::class);
|
||||
Livewire::component('profile.update-password-form', UpdatePasswordForm::class);
|
||||
Livewire::component('profile.two-factor-authentication-form', TwoFactorAuthenticationForm::class);
|
||||
Livewire::component('profile.logout-other-browser-sessions-form', LogoutOtherBrowserSessionsForm::class);
|
||||
Livewire::component('profile.delete-user-form', DeleteUserForm::class);
|
||||
|
||||
if (Features::hasApiFeatures()) {
|
||||
Livewire::component('api.api-token-manager', ApiTokenManager::class);
|
||||
}
|
||||
|
||||
if (Features::hasTeamFeatures()) {
|
||||
Livewire::component('teams.create-team-form', CreateTeamForm::class);
|
||||
Livewire::component('teams.update-team-name-form', UpdateTeamNameForm::class);
|
||||
Livewire::component('teams.team-member-manager', TeamMemberManager::class);
|
||||
Livewire::component('teams.delete-team-form', DeleteTeamForm::class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure publishing for the package.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function configurePublishing()
|
||||
{
|
||||
if (! $this->app->runningInConsole()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->publishes([
|
||||
__DIR__.'/../stubs/config/jetstream.php' => config_path('jetstream.php'),
|
||||
], 'jetstream-config');
|
||||
|
||||
$this->publishes([
|
||||
__DIR__.'/../database/migrations/0001_01_01_000000_create_users_table.php' => database_path('migrations/0001_01_01_000000_create_users_table.php'),
|
||||
], 'jetstream-migrations');
|
||||
|
||||
$this->publishesMigrations([
|
||||
__DIR__.'/../database/migrations/2020_05_21_100000_create_teams_table.php' => database_path('migrations/2020_05_21_100000_create_teams_table.php'),
|
||||
__DIR__.'/../database/migrations/2020_05_21_200000_create_team_user_table.php' => database_path('migrations/2020_05_21_200000_create_team_user_table.php'),
|
||||
__DIR__.'/../database/migrations/2020_05_21_300000_create_team_invitations_table.php' => database_path('migrations/2020_05_21_300000_create_team_invitations_table.php'),
|
||||
], 'jetstream-team-migrations');
|
||||
|
||||
$this->publishes([
|
||||
__DIR__.'/../routes/'.config('jetstream.stack').'.php' => base_path('routes/jetstream.php'),
|
||||
], 'jetstream-routes');
|
||||
|
||||
$this->publishes([
|
||||
__DIR__.'/../stubs/inertia/resources/js/Pages/Auth' => resource_path('js/Pages/Auth'),
|
||||
__DIR__.'/../stubs/inertia/resources/js/Components/AuthenticationCard.vue' => resource_path('js/Components/AuthenticationCard.vue'),
|
||||
__DIR__.'/../stubs/inertia/resources/js/Components/AuthenticationCardLogo.vue' => resource_path('js/Components/AuthenticationCardLogo.vue'),
|
||||
__DIR__.'/../stubs/inertia/resources/js/Components/Checkbox.vue' => resource_path('js/Components/Checkbox.vue'),
|
||||
], 'jetstream-inertia-auth-pages');
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the routes offered by the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function configureRoutes()
|
||||
{
|
||||
if (Jetstream::$registersRoutes) {
|
||||
Route::group([
|
||||
'namespace' => 'Laravel\Jetstream\Http\Controllers',
|
||||
'domain' => config('jetstream.domain', null),
|
||||
'prefix' => config('jetstream.prefix', config('jetstream.path')),
|
||||
], function () {
|
||||
$this->loadRoutesFrom(__DIR__.'/../routes/'.config('jetstream.stack').'.php');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the commands offered by the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function configureCommands()
|
||||
{
|
||||
if (! $this->app->runningInConsole()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->commands([
|
||||
Console\InstallCommand::class,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot any Inertia related services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function bootInertia()
|
||||
{
|
||||
$kernel = $this->app->make(Kernel::class);
|
||||
|
||||
$kernel->appendMiddlewareToGroup('web', ShareInertiaData::class);
|
||||
$kernel->appendToMiddlewarePriority(ShareInertiaData::class);
|
||||
|
||||
if (class_exists(HandleInertiaRequests::class)) {
|
||||
$kernel->appendToMiddlewarePriority(HandleInertiaRequests::class);
|
||||
}
|
||||
|
||||
Event::listen(function (PasswordUpdatedViaController $event) {
|
||||
if (request()->hasSession()) {
|
||||
request()->session()->put(['password_hash_sanctum' => Auth::user()->getAuthPassword()]);
|
||||
}
|
||||
});
|
||||
|
||||
Fortify::loginView(function () {
|
||||
return Inertia::render('Auth/Login', [
|
||||
'canResetPassword' => Route::has('password.request'),
|
||||
'status' => session('status'),
|
||||
]);
|
||||
});
|
||||
|
||||
Fortify::requestPasswordResetLinkView(function () {
|
||||
return Inertia::render('Auth/ForgotPassword', [
|
||||
'status' => session('status'),
|
||||
]);
|
||||
});
|
||||
|
||||
Fortify::resetPasswordView(function (Request $request) {
|
||||
return Inertia::render('Auth/ResetPassword', [
|
||||
'email' => $request->input('email'),
|
||||
'token' => $request->route('token'),
|
||||
]);
|
||||
});
|
||||
|
||||
Fortify::registerView(function () {
|
||||
return Inertia::render('Auth/Register');
|
||||
});
|
||||
|
||||
Fortify::verifyEmailView(function () {
|
||||
return Inertia::render('Auth/VerifyEmail', [
|
||||
'status' => session('status'),
|
||||
]);
|
||||
});
|
||||
|
||||
Fortify::twoFactorChallengeView(function () {
|
||||
return Inertia::render('Auth/TwoFactorChallenge');
|
||||
});
|
||||
|
||||
Fortify::confirmPasswordView(function () {
|
||||
return Inertia::render('Auth/ConfirmPassword');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Mail;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Laravel\Jetstream\TeamInvitation as TeamInvitationModel;
|
||||
|
||||
class TeamInvitation extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* The team invitation instance.
|
||||
*
|
||||
* @var \Laravel\Jetstream\TeamInvitation
|
||||
*/
|
||||
public $invitation;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @param \Laravel\Jetstream\TeamInvitation $invitation
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(TeamInvitationModel $invitation)
|
||||
{
|
||||
$this->invitation = $invitation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
return $this->markdown('emails.team-invitation', ['acceptUrl' => URL::signedRoute('team-invitations.accept', [
|
||||
'invitation' => $this->invitation,
|
||||
])])->subject(__('Team Invitation'));
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\Pivot;
|
||||
|
||||
abstract class Membership extends Pivot
|
||||
{
|
||||
/**
|
||||
* The table associated with the pivot model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'team_user';
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream;
|
||||
|
||||
class OwnerRole extends Role
|
||||
{
|
||||
/**
|
||||
* Create a new role instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('owner', 'Owner', ['*']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream;
|
||||
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
trait RedirectsActions
|
||||
{
|
||||
/**
|
||||
* Get the redirect response for the given action.
|
||||
*
|
||||
* @param mixed $action
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function redirectPath($action)
|
||||
{
|
||||
if (method_exists($action, 'redirectTo')) {
|
||||
$response = $action->redirectTo();
|
||||
} else {
|
||||
$response = property_exists($action, 'redirectTo')
|
||||
? $action->redirectTo
|
||||
: config('fortify.home');
|
||||
}
|
||||
|
||||
return $response instanceof Response ? $response : redirect($response);
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream;
|
||||
|
||||
use JsonSerializable;
|
||||
|
||||
class Role implements JsonSerializable
|
||||
{
|
||||
/**
|
||||
* The key identifier for the role.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $key;
|
||||
|
||||
/**
|
||||
* The name of the role.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* The role's permissions.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $permissions;
|
||||
|
||||
/**
|
||||
* The role's description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $description;
|
||||
|
||||
/**
|
||||
* Create a new role instance.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $name
|
||||
* @param array $permissions
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(string $key, string $name, array $permissions)
|
||||
{
|
||||
$this->key = $key;
|
||||
$this->name = $name;
|
||||
$this->permissions = $permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe the role.
|
||||
*
|
||||
* @param string $description
|
||||
* @return $this
|
||||
*/
|
||||
public function description(string $description)
|
||||
{
|
||||
$this->description = $description;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the JSON serializable representation of the object.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return [
|
||||
'key' => $this->key,
|
||||
'name' => __($this->name),
|
||||
'description' => __($this->description),
|
||||
'permissions' => $this->permissions,
|
||||
];
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream\Rules;
|
||||
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
use Laravel\Jetstream\Jetstream;
|
||||
|
||||
class Role implements Rule
|
||||
{
|
||||
/**
|
||||
* Determine if the validation rule passes.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
public function passes($attribute, $value)
|
||||
{
|
||||
return in_array($value, array_keys(Jetstream::$roles));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation error message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function message()
|
||||
{
|
||||
return __('The :attribute must be a valid role.');
|
||||
}
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
abstract class Team extends Model
|
||||
{
|
||||
/**
|
||||
* Get the owner of the team.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function owner()
|
||||
{
|
||||
return $this->belongsTo(Jetstream::userModel(), 'user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the team's users including its owner.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function allUsers()
|
||||
{
|
||||
return $this->users->merge([$this->owner]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the users that belong to the team.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
*/
|
||||
public function users()
|
||||
{
|
||||
return $this->belongsToMany(Jetstream::userModel(), Jetstream::membershipModel())
|
||||
->withPivot('role')
|
||||
->withTimestamps()
|
||||
->as('membership');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given user belongs to the team.
|
||||
*
|
||||
* @param \App\Models\User $user
|
||||
* @return bool
|
||||
*/
|
||||
public function hasUser($user)
|
||||
{
|
||||
return $this->users->contains($user) || $user->ownsTeam($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given email address belongs to a user on the team.
|
||||
*
|
||||
* @param string $email
|
||||
* @return bool
|
||||
*/
|
||||
public function hasUserWithEmail(string $email)
|
||||
{
|
||||
return $this->allUsers()->contains(function ($user) use ($email) {
|
||||
return $user->email === $email;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given user has the given permission on the team.
|
||||
*
|
||||
* @param \App\Models\User $user
|
||||
* @param string $permission
|
||||
* @return bool
|
||||
*/
|
||||
public function userHasPermission($user, $permission)
|
||||
{
|
||||
return $user->hasTeamPermission($this, $permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the pending user invitations for the team.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function teamInvitations()
|
||||
{
|
||||
return $this->hasMany(Jetstream::teamInvitationModel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given user from the team.
|
||||
*
|
||||
* @param \App\Models\User $user
|
||||
* @return void
|
||||
*/
|
||||
public function removeUser($user)
|
||||
{
|
||||
if ($user->current_team_id === $this->id) {
|
||||
$user->forceFill([
|
||||
'current_team_id' => null,
|
||||
])->save();
|
||||
}
|
||||
|
||||
$this->users()->detach($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge all of the team's resources.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function purge()
|
||||
{
|
||||
$this->owner()->where('current_team_id', $this->id)
|
||||
->update(['current_team_id' => null]);
|
||||
|
||||
$this->users()->where('current_team_id', $this->id)
|
||||
->update(['current_team_id' => null]);
|
||||
|
||||
$this->users()->detach();
|
||||
|
||||
$this->delete();
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Jetstream;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class TeamInvitation extends Model
|
||||
{
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'email',
|
||||
'role',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the team that the invitation belongs to.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function team()
|
||||
{
|
||||
return $this->belongsTo(Jetstream::teamModel());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user