diff --git a/.php_cs b/.php_cs index 1e37fcb13..fdb94601d 100644 --- a/.php_cs +++ b/.php_cs @@ -33,6 +33,7 @@ return PhpCsFixer\Config::create() 'new_with_braces' => false, 'no_alias_functions' => true, 'no_multiline_whitespace_before_semicolons' => true, + 'no_superfluous_phpdoc_tags' => false, 'no_unreachable_default_argument_value' => true, 'no_useless_return' => true, 'not_operator_with_successor_space' => true, diff --git a/app/Http/Controllers/Admin/Servers/ServerTransferController.php b/app/Http/Controllers/Admin/Servers/ServerTransferController.php new file mode 100644 index 000000000..323ffb7bc --- /dev/null +++ b/app/Http/Controllers/Admin/Servers/ServerTransferController.php @@ -0,0 +1,177 @@ +alert = $alert; + $this->allocationRepository = $allocationRepository; + $this->repository = $repository; + $this->locationRepository = $locationRepository; + $this->nodeRepository = $nodeRepository; + $this->suspensionService = $suspensionService; + $this->transferService = $transferService; + $this->daemonConfigurationRepository = $daemonConfigurationRepository; + } + + /** + * Starts a transfer of a server to a new node. + * + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Server $server + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Throwable + */ + public function transfer(Request $request, Server $server) + { + $validatedData = $request->validate([ + 'node_id' => 'required|exists:nodes,id', + 'allocation_id' => 'required|bail|unique:servers|exists:allocations,id', + 'allocation_additional' => 'nullable', + ]); + + $node_id = $validatedData['node_id']; + $allocation_id = intval($validatedData['allocation_id']); + $additional_allocations = array_map('intval', $validatedData['allocation_additional'] ?? []); + + // Check if the node is viable for the transfer. + $node = $this->nodeRepository->getNodeWithResourceUsage($node_id); + if ($node->isViable($server->memory, $server->disk)) { + // Check if the selected daemon is online. + $this->daemonConfigurationRepository->setNode($node)->getSystemInformation(); + + // Suspend the server and request an archive to be created. + $this->suspensionService->toggle($server, 'suspend'); + + // Create a new ServerTransfer entry. + $transfer = new ServerTransfer; + + $transfer->server_id = $server->id; + $transfer->old_node = $server->node_id; + $transfer->new_node = $node_id; + $transfer->old_allocation = $server->allocation_id; + $transfer->new_allocation = $allocation_id; + $transfer->old_additional_allocations = json_encode($server->allocations->where('id', '!=', $server->allocation_id)->pluck('id')); + $transfer->new_additional_allocations = json_encode($additional_allocations); + + $transfer->save(); + + // Add the allocations to the server so they cannot be automatically assigned while the transfer is in progress. + $this->assignAllocationsToServer($server, $node_id, $allocation_id, $additional_allocations); + + // Request an archive from the server's current daemon. (this also checks if the daemon is online) + $this->transferService->requestArchive($server); + + $this->alert->success(trans('admin/server.alerts.transfer_started'))->flash(); + } else { + $this->alert->danger(trans('admin/server.alerts.transfer_not_viable'))->flash(); + } + + return redirect()->route('admin.servers.view.manage', $server->id); + } + + /** + * Assigns the specified allocations to the specified server. + * + * @param Server $server + * @param int $node_id + * @param int $allocation_id + * @param array $additional_allocations + */ + private function assignAllocationsToServer(Server $server, int $node_id, int $allocation_id, array $additional_allocations) + { + $allocations = $additional_allocations; + array_push($allocations, $allocation_id); + + $unassigned = $this->allocationRepository->getUnassignedAllocationIds($node_id); + + $updateIds = []; + foreach ($allocations as $allocation) { + if (! in_array($allocation, $unassigned)) { + continue; + } + + $updateIds[] = $allocation; + } + + if (! empty($updateIds)) { + $this->allocationRepository->updateWhereIn('id', $updateIds, ['server_id' => $server->id]); + } + } +} diff --git a/app/Http/Controllers/Admin/Servers/ServerViewController.php b/app/Http/Controllers/Admin/Servers/ServerViewController.php index de78ca17f..25e198592 100644 --- a/app/Http/Controllers/Admin/Servers/ServerViewController.php +++ b/app/Http/Controllers/Admin/Servers/ServerViewController.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Http\Controllers\Admin\Servers; +use JavaScript; use Illuminate\Http\Request; use Pterodactyl\Models\Nest; use Pterodactyl\Models\Server; @@ -9,8 +10,10 @@ use Illuminate\Contracts\View\Factory; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Repositories\Eloquent\NestRepository; +use Pterodactyl\Repositories\Eloquent\NodeRepository; use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Traits\Controllers\JavascriptInjection; +use Pterodactyl\Repositories\Eloquent\LocationRepository; use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository; class ServerViewController extends Controller @@ -37,17 +40,31 @@ class ServerViewController extends Controller */ private $nestRepository; + /** + * @var \Pterodactyl\Repositories\Eloquent\LocationRepository + */ + private $locationRepository; + + /** + * @var \Pterodactyl\Repositories\Eloquent\NodeRepository + */ + private $nodeRepository; + /** * ServerViewController constructor. * * @param \Pterodactyl\Repositories\Eloquent\DatabaseHostRepository $databaseHostRepository * @param \Pterodactyl\Repositories\Eloquent\NestRepository $nestRepository + * @param \Pterodactyl\Repositories\Eloquent\LocationRepository $locationRepository + * @param \Pterodactyl\Repositories\Eloquent\NodeRepository $nodeRepository * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository * @param \Illuminate\Contracts\View\Factory $view */ public function __construct( DatabaseHostRepository $databaseHostRepository, NestRepository $nestRepository, + LocationRepository $locationRepository, + NodeRepository $nodeRepository, ServerRepository $repository, Factory $view ) { @@ -55,6 +72,8 @@ class ServerViewController extends Controller $this->databaseHostRepository = $databaseHostRepository; $this->repository = $repository; $this->nestRepository = $nestRepository; + $this->nodeRepository = $nodeRepository; + $this->locationRepository = $locationRepository; } /** @@ -150,6 +169,7 @@ class ServerViewController extends Controller * @return \Illuminate\Contracts\View\View * * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function manage(Request $request, Server $server) { @@ -159,7 +179,22 @@ class ServerViewController extends Controller ); } - return $this->view->make('admin.servers.view.manage', compact('server')); + // Check if the panel doesn't have at least 2 nodes configured. + $nodes = $this->nodeRepository->all(); + $canTransfer = false; + if (count($nodes) >= 2) { + $canTransfer = true; + } + + Javascript::put([ + 'nodeData' => $this->nodeRepository->getNodesForServerCreation(), + ]); + + return $this->view->make('admin.servers.view.manage', [ + 'server' => $server, + 'locations' => $this->locationRepository->all(), + 'canTransfer' => $canTransfer, + ]); } /** diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index 8bcb10390..6f8e106ef 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -262,7 +262,7 @@ class ServersController extends Controller { $this->buildModificationService->handle($server, $request->only([ 'allocation_id', 'add_allocations', 'remove_allocations', - 'memory', 'swap', 'io', 'cpu', 'disk', + 'memory', 'swap', 'io', 'cpu', 'threads', 'disk', 'database_limit', 'allocation_limit', 'oom_disabled', ])); $this->alert->success(trans('admin/server.alerts.build_updated'))->flash(); diff --git a/app/Http/Controllers/Api/Client/ClientController.php b/app/Http/Controllers/Api/Client/ClientController.php index f11d1ac15..b673ac5bf 100644 --- a/app/Http/Controllers/Api/Client/ClientController.php +++ b/app/Http/Controllers/Api/Client/ClientController.php @@ -3,7 +3,6 @@ namespace Pterodactyl\Http\Controllers\Api\Client; use Pterodactyl\Models\User; -use Illuminate\Support\Collection; use Pterodactyl\Models\Permission; use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Transformers\Api\Client\ServerTransformer; @@ -72,16 +71,10 @@ class ClientController extends ClientApiController */ public function permissions() { - $permissions = Permission::permissions()->map(function ($values, $key) { - return Collection::make($values)->map(function ($permission) use ($key) { - return $key . '.' . $permission; - })->values()->toArray(); - })->flatten(); - return [ 'object' => 'system_permissions', 'attributes' => [ - 'permissions' => $permissions, + 'permissions' => Permission::permissions(), ], ]; } diff --git a/app/Http/Controllers/Api/Client/Servers/BackupController.php b/app/Http/Controllers/Api/Client/Servers/BackupController.php new file mode 100644 index 000000000..64703dd85 --- /dev/null +++ b/app/Http/Controllers/Api/Client/Servers/BackupController.php @@ -0,0 +1,77 @@ +initiateBackupService = $initiateBackupService; + } + + /** + * Returns all of the backups for a given server instance in a paginated + * result set. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Backups\GetBackupsRequest $request + * @param \Pterodactyl\Models\Server $server + * @return array + */ + public function index(GetBackupsRequest $request, Server $server) + { + return $this->fractal->collection($server->backups()->paginate(20)) + ->transformWith($this->getTransformer(BackupTransformer::class)) + ->toArray(); + } + + /** + * Starts the backup process for a server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Backups\StoreBackupRequest $request + * @param \Pterodactyl\Models\Server $server + * @return array + * + * @throws \Exception + */ + public function store(StoreBackupRequest $request, Server $server) + { + $backup = $this->initiateBackupService + ->setIgnoredFiles($request->input('ignored')) + ->handle($server, $request->input('name')); + + return $this->fractal->item($backup) + ->transformWith($this->getTransformer(BackupTransformer::class)) + ->toArray(); + } + + public function view() + { + } + + public function update() + { + } + + public function delete() + { + } +} diff --git a/app/Http/Controllers/Api/Client/Servers/DownloadBackupController.php b/app/Http/Controllers/Api/Client/Servers/DownloadBackupController.php new file mode 100644 index 000000000..22c7ea6fb --- /dev/null +++ b/app/Http/Controllers/Api/Client/Servers/DownloadBackupController.php @@ -0,0 +1,80 @@ +daemonBackupRepository = $daemonBackupRepository; + $this->responseFactory = $responseFactory; + } + + /** + * Download the backup for a given server instance. For daemon local files, the file + * will be streamed back through the Panel. For AWS S3 files, a signed URL will be generated + * which the user is redirected to. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Backups\DownloadBackupRequest $request + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Backup $backup + * @return \Illuminate\Http\RedirectResponse + */ + public function __invoke(DownloadBackupRequest $request, Server $server, Backup $backup) + { + $signer = new Sha256; + $now = CarbonImmutable::now(); + + $token = (new Builder)->issuedBy(config('app.url')) + ->permittedFor($server->node->getConnectionAddress()) + ->identifiedBy(hash('sha256', $request->user()->id . $server->uuid), true) + ->issuedAt($now->getTimestamp()) + ->canOnlyBeUsedAfter($now->subMinutes(5)->getTimestamp()) + ->expiresAt($now->addMinutes(15)->getTimestamp()) + ->withClaim('unique_id', Str::random(16)) + ->withClaim('backup_uuid', $backup->uuid) + ->withClaim('server_uuid', $server->uuid) + ->getToken($signer, new Key($server->node->daemonSecret)); + + $location = sprintf( + '%s/download/backup?token=%s', + $server->node->getConnectionAddress(), + $token->__toString() + ); + + return RedirectResponse::create($location); + } +} diff --git a/app/Http/Controllers/Api/Client/Servers/SettingsController.php b/app/Http/Controllers/Api/Client/Servers/SettingsController.php index 5b457fa9c..64c73a8de 100644 --- a/app/Http/Controllers/Api/Client/Servers/SettingsController.php +++ b/app/Http/Controllers/Api/Client/Servers/SettingsController.php @@ -4,9 +4,12 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; use Illuminate\Http\Response; use Pterodactyl\Models\Server; +use Illuminate\Http\JsonResponse; use Pterodactyl\Repositories\Eloquent\ServerRepository; +use Pterodactyl\Services\Servers\ReinstallServerService; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Pterodactyl\Http\Requests\Api\Client\Servers\Settings\RenameServerRequest; +use Pterodactyl\Http\Requests\Api\Client\Servers\Settings\ReinstallServerRequest; class SettingsController extends ClientApiController { @@ -15,16 +18,25 @@ class SettingsController extends ClientApiController */ private $repository; + /** + * @var \Pterodactyl\Services\Servers\ReinstallServerService + */ + private $reinstallServerService; + /** * SettingsController constructor. * * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository + * @param \Pterodactyl\Services\Servers\ReinstallServerService $reinstallServerService */ - public function __construct(ServerRepository $repository) - { + public function __construct( + ServerRepository $repository, + ReinstallServerService $reinstallServerService + ) { parent::__construct(); $this->repository = $repository; + $this->reinstallServerService = $reinstallServerService; } /** @@ -32,7 +44,7 @@ class SettingsController extends ClientApiController * * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Settings\RenameServerRequest $request * @param \Pterodactyl\Models\Server $server - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException @@ -43,6 +55,22 @@ class SettingsController extends ClientApiController 'name' => $request->input('name'), ]); - return Response::create('', Response::HTTP_NO_CONTENT); + return JsonResponse::create([], Response::HTTP_NO_CONTENT); + } + + /** + * Reinstalls the server on the daemon. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Settings\ReinstallServerRequest $request + * @param \Pterodactyl\Models\Server $server + * @return \Illuminate\Http\JsonResponse + * + * @throws \Throwable + */ + public function reinstall(ReinstallServerRequest $request, Server $server) + { + $this->reinstallServerService->reinstall($server); + + return JsonResponse::create([], Response::HTTP_ACCEPTED); } } diff --git a/app/Http/Controllers/Api/Client/Servers/SubuserController.php b/app/Http/Controllers/Api/Client/Servers/SubuserController.php index 3b3746b04..ccfea5e65 100644 --- a/app/Http/Controllers/Api/Client/Servers/SubuserController.php +++ b/app/Http/Controllers/Api/Client/Servers/SubuserController.php @@ -2,11 +2,17 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; +use Illuminate\Http\Request; use Pterodactyl\Models\Server; +use Illuminate\Http\JsonResponse; use Pterodactyl\Repositories\Eloquent\SubuserRepository; +use Pterodactyl\Services\Subusers\SubuserCreationService; use Pterodactyl\Transformers\Api\Client\SubuserTransformer; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\GetSubuserRequest; +use Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\StoreSubuserRequest; +use Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\DeleteSubuserRequest; +use Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\UpdateSubuserRequest; class SubuserController extends ClientApiController { @@ -15,16 +21,25 @@ class SubuserController extends ClientApiController */ private $repository; + /** + * @var \Pterodactyl\Services\Subusers\SubuserCreationService + */ + private $creationService; + /** * SubuserController constructor. * * @param \Pterodactyl\Repositories\Eloquent\SubuserRepository $repository + * @param \Pterodactyl\Services\Subusers\SubuserCreationService $creationService */ - public function __construct(SubuserRepository $repository) - { + public function __construct( + SubuserRepository $repository, + SubuserCreationService $creationService + ) { parent::__construct(); $this->repository = $repository; + $this->creationService = $creationService; } /** @@ -36,10 +51,78 @@ class SubuserController extends ClientApiController */ public function index(GetSubuserRequest $request, Server $server) { - $server->subusers->load('user'); - return $this->fractal->collection($server->subusers) ->transformWith($this->getTransformer(SubuserTransformer::class)) ->toArray(); } + + /** + * Create a new subuser for the given server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\StoreSubuserRequest $request + * @param \Pterodactyl\Models\Server $server + * @return array + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException + * @throws \Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException + * @throws \Throwable + */ + public function store(StoreSubuserRequest $request, Server $server) + { + $response = $this->creationService->handle( + $server, $request->input('email'), $this->getDefaultPermissions($request) + ); + + return $this->fractal->item($response) + ->transformWith($this->getTransformer(SubuserTransformer::class)) + ->toArray(); + } + + /** + * Update a given subuser in the system for the server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\UpdateSubuserRequest $request + * @param \Pterodactyl\Models\Server $server + * @return array + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(UpdateSubuserRequest $request, Server $server): array + { + $subuser = $request->endpointSubuser(); + $this->repository->update($subuser->id, [ + 'permissions' => $this->getDefaultPermissions($request), + ]); + + return $this->fractal->item($subuser->refresh()) + ->transformWith($this->getTransformer(SubuserTransformer::class)) + ->toArray(); + } + + /** + * Removes a subusers from a server's assignment. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\DeleteSubuserRequest $request + * @param \Pterodactyl\Models\Server $server + * @return \Illuminate\Http\JsonResponse + */ + public function delete(DeleteSubuserRequest $request, Server $server) + { + $this->repository->delete($request->endpointSubuser()->id); + + return JsonResponse::create([], JsonResponse::HTTP_NO_CONTENT); + } + + /** + * Returns the default permissions for all subusers to ensure none are ever removed wrongly. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + protected function getDefaultPermissions(Request $request): array + { + return array_unique(array_merge($request->input('permissions') ?? [], ['websocket.*'])); + } } diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerBackupController.php b/app/Http/Controllers/Api/Remote/Servers/ServerBackupController.php new file mode 100644 index 000000000..b4599ee0a --- /dev/null +++ b/app/Http/Controllers/Api/Remote/Servers/ServerBackupController.php @@ -0,0 +1,70 @@ +repository = $repository; + $this->serverRepository = $serverRepository; + } + + /** + * Updates a server backup's state in the database depending on wether or not + * it was successful. + * + * @param \Pterodactyl\Http\Requests\Api\Remote\ReportBackupCompleteRequest $request + * @param string $uuid + * @param string $backup + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function __invoke(ReportBackupCompleteRequest $request, string $uuid, string $backup) + { + $server = $this->serverRepository->getByUuid($uuid); + + $where = [ + ['uuid', '=', $backup], + ['server_id', '=', $server->id], + ]; + + if ($request->input('successful')) { + $this->repository->updateWhere($where, [ + 'sha256_hash' => $request->input('sha256_hash'), + 'bytes' => $request->input('file_size'), + 'completed_at' => Carbon::now(), + ]); + } else { + $this->repository->deleteWhere($where); + } + + return JsonResponse::create([], JsonResponse::HTTP_NO_CONTENT); + } +} diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php new file mode 100644 index 000000000..1d2e723ee --- /dev/null +++ b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php @@ -0,0 +1,238 @@ +connection = $connection; + $this->repository = $repository; + $this->allocationRepository = $allocationRepository; + $this->nodeRepository = $nodeRepository; + $this->daemonServerRepository = $daemonServerRepository; + $this->daemonTransferRepository = $daemonTransferRepository; + $this->configurationStructureService = $configurationStructureService; + $this->suspensionService = $suspensionService; + $this->writer = $writer; + } + + /** + * The daemon notifies us about the archive status. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Throwable + */ + public function archive(Request $request, string $uuid) + { + $server = $this->repository->getByUuid($uuid); + + // Unsuspend the server and don't continue the transfer. + if (! $request->input('successful')) { + $this->suspensionService->toggle($server, 'unsuspend'); + + return JsonResponse::create([], Response::HTTP_NO_CONTENT); + } + + $server->node_id = $server->transfer->new_node; + + $data = $this->configurationStructureService->handle($server); + $data['suspended'] = false; + $data['service']['skip_scripts'] = true; + + $allocations = $server->getAllocationMappings(); + $data['allocations']['default']['ip'] = array_key_first($allocations); + $data['allocations']['default']['port'] = $allocations[$data['allocations']['default']['ip']][0]; + + $now = Chronos::now(); + $signer = new Sha256; + + $token = (new Builder)->issuedBy(config('app.url')) + ->permittedFor($server->node->getConnectionAddress()) + ->identifiedBy(hash('sha256', $server->uuid), true) + ->issuedAt($now->getTimestamp()) + ->canOnlyBeUsedAfter($now->getTimestamp()) + ->expiresAt($now->addMinutes(15)->getTimestamp()) + ->relatedTo($server->uuid, true) + ->getToken($signer, new Key($server->node->daemonSecret)); + + // On the daemon transfer repository, make sure to set the node after the server + // because setServer() tells the repository to use the server's node and not the one + // we want to specify. + try { + $this->daemonTransferRepository + ->setServer($server) + ->setNode($this->nodeRepository->find($server->transfer->new_node)) + ->notify($server, $data, $server->node, $token->__toString()); + } catch (DaemonConnectionException $exception) { + throw $exception; + } + + return JsonResponse::create([], Response::HTTP_NO_CONTENT); + } + + /** + * The daemon notifies us about a transfer failure. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\Http\JsonResponse + * + * @throws \Throwable + */ + public function failure(string $uuid) + { + $server = $this->repository->getByUuid($uuid); + $transfer = $server->transfer; + + $allocationIds = json_decode($transfer->new_additional_allocations); + array_push($allocationIds, $transfer->new_allocation); + + // Remove the new allocations. + $this->allocationRepository->updateWhereIn('id', $allocationIds, ['server_id' => null]); + + // Unsuspend the server. + $this->suspensionService->toggle($server, 'unsuspend'); + + return JsonResponse::create([], Response::HTTP_NO_CONTENT); + } + + /** + * The daemon notifies us about a transfer success. + * + * @param string $uuid + * @return \Illuminate\Http\JsonResponse + * + * @throws \Throwable + */ + public function success(string $uuid) + { + $server = $this->repository->getByUuid($uuid); + $transfer = $server->transfer; + + $allocationIds = json_decode($transfer->old_additional_allocations); + array_push($allocationIds, $transfer->old_allocation); + + // Begin a transaction. + $this->connection->beginTransaction(); + + // Remove the old allocations. + $this->allocationRepository->updateWhereIn('id', $allocationIds, ['server_id' => null]); + + // Update the server's allocation_id and node_id. + $server->allocation_id = $transfer->new_allocation; + $server->node_id = $transfer->new_node; + $server->save(); + + // Mark the transfer as successful. + $transfer->successful = true; + $transfer->save(); + + // Commit the transaction. + $this->connection->commit(); + + // Delete the server from the old node + try { + $this->daemonServerRepository->setServer($server)->delete(); + } catch (DaemonConnectionException $exception) { + $this->writer->warning($exception); + } + + // Unsuspend the server + $server->load('node'); + $this->suspensionService->toggle($server, $this->suspensionService::ACTION_UNSUSPEND); + + return JsonResponse::create([], Response::HTTP_NO_CONTENT); + } +} diff --git a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php index 800add18c..fe0ca5610 100644 --- a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php +++ b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php @@ -42,6 +42,16 @@ class AuthenticateServerAccess throw new NotFoundHttpException(trans('exceptions.api.resource_not_found')); } + // At the very least, ensure that the user trying to make this request is the + // server owner, a subuser, or a root admin. We'll leave it up to the controllers + // to authenticate more detailed permissions if needed. + if ($request->user()->id !== $server->owner_id && ! $request->user()->root_admin) { + // Check for subuser status. + if (! $server->subusers->contains('user_id', $request->user()->id)) { + throw new NotFoundHttpException(trans('exceptions.api.resource_not_found')); + } + } + if ($server->suspended) { throw new AccessDeniedHttpException('Cannot access a server that is marked as being suspended.'); } diff --git a/app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php b/app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php index 40c26c538..81ed6b401 100644 --- a/app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php +++ b/app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Http\Middleware\Api\Client; use Closure; +use Pterodactyl\Models\Backup; use Illuminate\Container\Container; use Pterodactyl\Contracts\Extensions\HashidsInterface; use Pterodactyl\Http\Middleware\Api\ApiSubstituteBindings; @@ -55,6 +56,10 @@ class SubstituteClientApiBindings extends ApiSubstituteBindings } }); + $this->router->model('backup', Backup::class, function ($value) { + return Backup::query()->where('uuid', $value)->firstOrFail(); + }); + return parent::handle($request, $next); } } diff --git a/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php b/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php index d386f5851..32f68bd23 100644 --- a/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php +++ b/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php @@ -49,6 +49,7 @@ class StoreServerRequest extends ApplicationApiRequest 'limits.swap' => $rules['swap'], 'limits.disk' => $rules['disk'], 'limits.io' => $rules['io'], + 'limits.threads' => $rules['threads'], 'limits.cpu' => $rules['cpu'], // Application Resource Limits @@ -96,6 +97,7 @@ class StoreServerRequest extends ApplicationApiRequest 'disk' => array_get($data, 'limits.disk'), 'io' => array_get($data, 'limits.io'), 'cpu' => array_get($data, 'limits.cpu'), + 'threads' => array_get($data, 'limits.threads'), 'skip_scripts' => array_get($data, 'skip_scripts', false), 'allocation_id' => array_get($data, 'allocation.default'), 'allocation_additional' => array_get($data, 'allocation.additional'), diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php index 9d5a3bb0a..3f0e1c8ca 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php @@ -25,6 +25,7 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest 'limits.swap' => $this->requiredToOptional('swap', $rules['swap'], true), 'limits.io' => $this->requiredToOptional('io', $rules['io'], true), 'limits.cpu' => $this->requiredToOptional('cpu', $rules['cpu'], true), + 'limits.threads' => $this->requiredToOptional('threads', $rules['threads'], true), 'limits.disk' => $this->requiredToOptional('disk', $rules['disk'], true), // Legacy rules to maintain backwards compatable API support without requiring @@ -35,6 +36,7 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest 'swap' => $this->requiredToOptional('swap', $rules['swap']), 'io' => $this->requiredToOptional('io', $rules['io']), 'cpu' => $this->requiredToOptional('cpu', $rules['cpu']), + 'threads' => $this->requiredToOptional('threads', $rules['threads']), 'disk' => $this->requiredToOptional('disk', $rules['disk']), 'add_allocations' => 'bail|array', diff --git a/app/Http/Requests/Api/Client/Servers/Backups/DownloadBackupRequest.php b/app/Http/Requests/Api/Client/Servers/Backups/DownloadBackupRequest.php new file mode 100644 index 000000000..cfa38e688 --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Backups/DownloadBackupRequest.php @@ -0,0 +1,41 @@ +route()->parameter('server'); + /** @var \Pterodactyl\Models\Backup|mixed $backup */ + $backup = $this->route()->parameter('backup'); + + if ($server instanceof Server && $backup instanceof Backup) { + if ($server->exists && $backup->exists && $server->id === $backup->server_id) { + return true; + } + } + + return false; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Backups/GetBackupsRequest.php b/app/Http/Requests/Api/Client/Servers/Backups/GetBackupsRequest.php new file mode 100644 index 000000000..f938906d1 --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Backups/GetBackupsRequest.php @@ -0,0 +1,17 @@ + 'nullable|string|max:255', + 'ignore' => 'nullable|string', + ]; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/SendPowerRequest.php b/app/Http/Requests/Api/Client/Servers/SendPowerRequest.php index 8686b4f67..ea7e00fcc 100644 --- a/app/Http/Requests/Api/Client/Servers/SendPowerRequest.php +++ b/app/Http/Requests/Api/Client/Servers/SendPowerRequest.php @@ -18,11 +18,10 @@ class SendPowerRequest extends ClientApiRequest case 'start': return Permission::ACTION_CONTROL_START; case 'stop': + case 'kill': return Permission::ACTION_CONTROL_STOP; case 'restart': return Permission::ACTION_CONTROL_RESTART; - case 'kill': - return Permission::ACTION_CONTROL_KILL; } return '__invalid'; diff --git a/app/Http/Requests/Api/Client/Servers/Settings/ReinstallServerRequest.php b/app/Http/Requests/Api/Client/Servers/Settings/ReinstallServerRequest.php new file mode 100644 index 000000000..9edc8ad1c --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Settings/ReinstallServerRequest.php @@ -0,0 +1,17 @@ + 'required|email', + 'permissions' => 'required|array', + 'permissions.*' => 'string', + ]; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Subusers/SubuserRequest.php b/app/Http/Requests/Api/Client/Servers/Subusers/SubuserRequest.php new file mode 100644 index 000000000..d8c07936d --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Subusers/SubuserRequest.php @@ -0,0 +1,131 @@ +route()->hasParameter('subuser')) { + if ($this->endpointSubuser()->user_id === $this->user()->id) { + return false; + } + } + + // If this is a POST request, validate that the user can even assign the permissions they + // have selected to assign. + if ($this->method() === Request::METHOD_POST && $this->has('permissions')) { + $this->validatePermissionsCanBeAssigned( + $this->input('permissions') ?? [] + ); + } + + return true; + } + + /** + * Validates that the permissions we are trying to assign can actually be assigned + * by the user making the request. + * + * @param array $permissions + */ + protected function validatePermissionsCanBeAssigned(array $permissions) + { + $user = $this->user(); + /** @var \Pterodactyl\Models\Server $server */ + $server = $this->route()->parameter('server'); + + // If we are a root admin or the server owner, no need to perform these checks. + if ($user->root_admin || $user->id === $server->owner_id) { + return; + } + + // Otherwise, get the current subuser's permission set, and ensure that the + // permissions they are trying to assign are not _more_ than the ones they + // already have. + if (count(array_diff($permissions, $this->currentUserPermissions())) > 0) { + throw new HttpForbiddenException( + 'Cannot assign permissions to a subuser that your account does not actively possess.' + ); + } + } + + /** + * Returns the currently authenticated user's permissions. + * + * @return array + */ + public function currentUserPermissions(): array + { + /** @var \Pterodactyl\Repositories\Eloquent\SubuserRepository $repository */ + $repository = $this->container->make(SubuserRepository::class); + + /* @var \Pterodactyl\Models\Subuser $model */ + try { + $model = $repository->findFirstWhere([ + ['server_id', $this->route()->parameter('server')->id], + ['user_id' => $this->user()->id], + ]); + } catch (RecordNotFoundException $exception) { + return []; + } + + return $model->permissions; + } + + /** + * Return the subuser model for the given request which can then be validated. If + * required request parameters are missing a 404 error will be returned, otherwise + * a model exception will be returned if the model is not found. + * + * This returns the subuser based on the endpoint being hit, not the actual subuser + * for the account making the request. + * + * @return \Pterodactyl\Models\Subuser + * + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public function endpointSubuser() + { + /** @var \Pterodactyl\Repositories\Eloquent\SubuserRepository $repository */ + $repository = $this->container->make(SubuserRepository::class); + + $parameters = $this->route()->parameters(); + if ( + ! isset($parameters['server'], $parameters['server']) + || ! is_string($parameters['subuser']) + || ! $parameters['server'] instanceof Server + ) { + throw new NotFoundHttpException; + } + + return $this->model ?: $this->model = $repository->getUserForServer( + $parameters['server']->id, $parameters['subuser'] + ); + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Subusers/UpdateSubuserRequest.php b/app/Http/Requests/Api/Client/Servers/Subusers/UpdateSubuserRequest.php new file mode 100644 index 000000000..3a84a0278 --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Subusers/UpdateSubuserRequest.php @@ -0,0 +1,27 @@ + 'required|array', + 'permissions.*' => 'string', + ]; + } +} diff --git a/app/Http/Requests/Api/Remote/ReportBackupCompleteRequest.php b/app/Http/Requests/Api/Remote/ReportBackupCompleteRequest.php new file mode 100644 index 000000000..b0383b963 --- /dev/null +++ b/app/Http/Requests/Api/Remote/ReportBackupCompleteRequest.php @@ -0,0 +1,20 @@ + 'boolean', + 'sha256_hash' => 'string|required_if:successful,true', + 'file_size' => 'numeric|required_if:successful,true', + ]; + } +} diff --git a/app/Models/Allocation.php b/app/Models/Allocation.php index 00d02de80..5f2435624 100644 --- a/app/Models/Allocation.php +++ b/app/Models/Allocation.php @@ -18,7 +18,7 @@ namespace Pterodactyl\Models; * @property \Pterodactyl\Models\Server|null $server * @property \Pterodactyl\Models\Node $node */ -class Allocation extends Validable +class Allocation extends Model { /** * The resource name for this model when it is transformed into an @@ -75,7 +75,7 @@ class Allocation extends Validable /** * Accessor to automatically provide the IP alias if defined. * - * @param null|string $value + * @param string|null $value * @return string */ public function getAliasAttribute($value) @@ -86,7 +86,7 @@ class Allocation extends Validable /** * Accessor to quickly determine if this allocation has an alias. * - * @param null|string $value + * @param string|null $value * @return bool */ public function getHasAliasAttribute($value) diff --git a/app/Models/ApiKey.php b/app/Models/ApiKey.php index 6fb8a0e82..59517621a 100644 --- a/app/Models/ApiKey.php +++ b/app/Models/ApiKey.php @@ -16,7 +16,7 @@ use Pterodactyl\Services\Acl\Api\AdminAcl; * @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $updated_at */ -class ApiKey extends Validable +class ApiKey extends Model { const RESOURCE_NAME = 'api_key'; @@ -53,7 +53,7 @@ class ApiKey extends Validable * @var array */ protected $casts = [ - 'allowed_ips' => 'json', + 'allowed_ips' => 'array', 'user_id' => 'int', 'r_' . AdminAcl::RESOURCE_USERS => 'int', 'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'int', @@ -99,7 +99,8 @@ class ApiKey extends Validable 'identifier' => 'required|string|size:16|unique:api_keys,identifier', 'token' => 'required|string', 'memo' => 'required|nullable|string|max:500', - 'allowed_ips' => 'nullable|json', + 'allowed_ips' => 'nullable|array', + 'allowed_ips.*' => 'string', 'last_used_at' => 'nullable|date', 'r_' . AdminAcl::RESOURCE_USERS => 'integer|min:0|max:3', 'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'integer|min:0|max:3', diff --git a/app/Models/Backup.php b/app/Models/Backup.php new file mode 100644 index 000000000..6259f11c6 --- /dev/null +++ b/app/Models/Backup.php @@ -0,0 +1,85 @@ + 'int', + 'bytes' => 'int', + ]; + + /** + * @var array + */ + protected $dates = [ + 'completed_at', + ]; + + /** + * @var array + */ + protected $attributes = [ + 'sha256_hash' => null, + 'bytes' => 0, + ]; + + /** + * @var array + */ + public static $validationRules = [ + 'server_id' => 'bail|required|numeric|exists:servers,id', + 'uuid' => 'required|uuid', + 'name' => 'required|string', + 'ignored_files' => 'string', + 'disk' => 'required|string', + 'sha256_hash' => 'nullable|string', + 'bytes' => 'numeric', + ]; + + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function server() + { + return $this->belongsTo(Server::class); + } +} diff --git a/app/Models/DaemonKey.php b/app/Models/DaemonKey.php index 9f5384d54..fa5bb6a91 100644 --- a/app/Models/DaemonKey.php +++ b/app/Models/DaemonKey.php @@ -4,7 +4,7 @@ namespace Pterodactyl\Models; use Znck\Eloquent\Traits\BelongsToThrough; -class DaemonKey extends Validable +class DaemonKey extends Model { use BelongsToThrough; diff --git a/app/Models/Database.php b/app/Models/Database.php index 08ef3e566..2db45734d 100644 --- a/app/Models/Database.php +++ b/app/Models/Database.php @@ -2,7 +2,7 @@ namespace Pterodactyl\Models; -class Database extends Validable +class Database extends Model { /** * The resource name for this model when it is transformed into an diff --git a/app/Models/DatabaseHost.php b/app/Models/DatabaseHost.php index 88d6dcde5..6fafce2f0 100644 --- a/app/Models/DatabaseHost.php +++ b/app/Models/DatabaseHost.php @@ -2,7 +2,7 @@ namespace Pterodactyl\Models; -class DatabaseHost extends Validable +class DatabaseHost extends Model { /** * The resource name for this model when it is transformed into an diff --git a/app/Models/Egg.php b/app/Models/Egg.php index 8db406799..1bdb48ca7 100644 --- a/app/Models/Egg.php +++ b/app/Models/Egg.php @@ -39,7 +39,7 @@ namespace Pterodactyl\Models; * @property \Pterodactyl\Models\Egg|null $scriptFrom * @property \Pterodactyl\Models\Egg|null $configFrom */ -class Egg extends Validable +class Egg extends Model { /** * The resource name for this model when it is transformed into an diff --git a/app/Models/EggVariable.php b/app/Models/EggVariable.php index 727a67b9f..2db891dc9 100644 --- a/app/Models/EggVariable.php +++ b/app/Models/EggVariable.php @@ -2,7 +2,7 @@ namespace Pterodactyl\Models; -class EggVariable extends Validable +class EggVariable extends Model { /** * The resource name for this model when it is transformed into an diff --git a/app/Models/Location.php b/app/Models/Location.php index 20a8e4c38..e0871edf6 100644 --- a/app/Models/Location.php +++ b/app/Models/Location.php @@ -2,7 +2,7 @@ namespace Pterodactyl\Models; -class Location extends Validable +class Location extends Model { /** * The resource name for this model when it is transformed into an diff --git a/app/Models/Validable.php b/app/Models/Model.php similarity index 77% rename from app/Models/Validable.php rename to app/Models/Model.php index e7c5c5665..095fe7adc 100644 --- a/app/Models/Validable.php +++ b/app/Models/Model.php @@ -5,11 +5,18 @@ namespace Pterodactyl\Models; use Illuminate\Support\Str; use Illuminate\Validation\Rule; use Illuminate\Container\Container; -use Illuminate\Database\Eloquent\Model; use Illuminate\Contracts\Validation\Factory; +use Illuminate\Database\Eloquent\Model as IlluminateModel; -abstract class Validable extends Model +abstract class Model extends IlluminateModel { + /** + * Set to true to return immutable Carbon date instances from the model. + * + * @var bool + */ + protected $immutableDates = false; + /** * Determines if the model should undergo data validation before it is saved * to the database. @@ -47,7 +54,7 @@ abstract class Validable extends Model static::$validatorFactory = Container::getInstance()->make(Factory::class); - static::saving(function (Validable $model) { + static::saving(function (Model $model) { return $model->validate(); }); } @@ -140,7 +147,27 @@ abstract class Validable extends Model } return $this->getValidator()->setData( - $this->getAttributes() + // Trying to do self::toArray() here will leave out keys based on the whitelist/blacklist + // for that model. Doing this will return all of the attributes in a format that can + // properly be validated. + $this->addCastAttributesToArray( + $this->getAttributes(), $this->getMutatedAttributes() + ) )->passes(); } + + /** + * Return a timestamp as DateTime object. + * + * @param mixed $value + * @return \Illuminate\Support\Carbon|\Carbon\CarbonImmutable + */ + protected function asDateTime($value) + { + if (! $this->immutableDates) { + return parent::asDateTime($value); + } + + return parent::asDateTime($value)->toImmutable(); + } } diff --git a/app/Models/Nest.php b/app/Models/Nest.php index af77dc461..d2839919d 100644 --- a/app/Models/Nest.php +++ b/app/Models/Nest.php @@ -15,7 +15,7 @@ namespace Pterodactyl\Models; * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Egg[] $eggs * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Pack[] $packs */ -class Nest extends Validable +class Nest extends Model { /** * The resource name for this model when it is transformed into an diff --git a/app/Models/Node.php b/app/Models/Node.php index 81cd99383..9518084c5 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -32,9 +32,10 @@ use Pterodactyl\Models\Traits\Searchable; * @property \Pterodactyl\Models\Server[]|\Illuminate\Database\Eloquent\Collection $servers * @property \Pterodactyl\Models\Allocation[]|\Illuminate\Database\Eloquent\Collection $allocations */ -class Node extends Validable +class Node extends Model { - use Notifiable, Searchable; + use Notifiable; + use Searchable; /** * The resource name for this model when it is transformed into an @@ -170,6 +171,7 @@ class Node extends Validable ], 'system' => [ 'data' => $this->daemonBase, + 'archive_directory' => $this->daemonBase . '/.archives', 'username' => 'pterodactyl', 'timezone_path' => '/etc/timezone', 'set_permissions_on_boot' => true, @@ -235,4 +237,19 @@ class Node extends Validable { return $this->hasMany(Allocation::class); } + + /** + * Returns a boolean if the node is viable for an additional server to be placed on it. + * + * @param int $memory + * @param int $disk + * @return bool + */ + public function isViable(int $memory, int $disk): bool + { + $memoryLimit = $this->memory * (1 + ($this->memory_overallocate / 100)); + $diskLimit = $this->disk * (1 + ($this->disk_overallocate / 100)); + + return ($this->sum_memory + $memory) <= $memoryLimit && ($this->sum_disk + $disk) <= $diskLimit; + } } diff --git a/app/Models/Pack.php b/app/Models/Pack.php index 092f1cbf2..3846d74eb 100644 --- a/app/Models/Pack.php +++ b/app/Models/Pack.php @@ -20,7 +20,7 @@ use Pterodactyl\Models\Traits\Searchable; * @property \Pterodactyl\Models\Egg|null $egg * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Server[] $servers */ -class Pack extends Validable +class Pack extends Model { use Searchable; diff --git a/app/Models/Permission.php b/app/Models/Permission.php index 11db34545..15d70c4ee 100644 --- a/app/Models/Permission.php +++ b/app/Models/Permission.php @@ -4,7 +4,7 @@ namespace Pterodactyl\Models; use Illuminate\Support\Collection; -class Permission extends Validable +class Permission extends Model { /** * The resource name for this model when it is transformed into an @@ -20,7 +20,6 @@ class Permission extends Validable const ACTION_CONTROL_START = 'control.start'; const ACTION_CONTROL_STOP = 'control.stop'; const ACTION_CONTROL_RESTART = 'control.restart'; - const ACTION_CONTROL_KILL = 'control.kill'; const ACTION_DATABASE_READ = 'database.read'; const ACTION_DATABASE_CREATE = 'database.create'; @@ -38,6 +37,12 @@ class Permission extends Validable const ACTION_USER_UPDATE = 'user.update'; const ACTION_USER_DELETE = 'user.delete'; + const ACTION_BACKUP_READ = 'backup.read'; + const ACTION_BACKUP_CREATE = 'backup.create'; + const ACTION_BACKUP_UPDATE = 'backup.update'; + const ACTION_BACKUP_DELETE = 'backup.delete'; + const ACTION_BACKUP_DOWNLOAD = 'backup.download'; + const ACTION_ALLOCATION_READ = 'allocation.read'; const ACTION_ALLOCIATION_UPDATE = 'allocation.update'; @@ -98,105 +103,100 @@ class Permission extends Validable */ protected static $permissions = [ 'websocket' => [ - // Allows the user to connect to the server websocket, this will give them - // access to view the console output as well as realtime server stats (CPU - // and Memory usage). - '*', + 'description' => 'Allows the user to connect to the server websocket, giving them access to view console output and realtime server stats.', + 'keys' => [ + '*' => 'Gives user full read access to the websocket.', + ], ], 'control' => [ - // Allows the user to send data to the server console process. A user with this - // permission will not be able to stop the server directly by issuing the specified - // stop command for the Egg, however depending on plugins and server configuration - // they may still be able to control the server power state. - 'console', // power.send-command - - // Allows the user to start/stop/restart/kill the server process. - 'start', // power.power-start - 'stop', // power.power-stop - 'restart', // power.power-restart - 'kill', // power.power-kill + 'description' => 'Permissions that control a user\'s ability to control the power state of a server, or send commands.', + 'keys' => [ + 'console' => 'Allows a user to send commands to the server instance via the console.', + 'start' => 'Allows a user to start the server if it is stopped.', + 'stop' => 'Allows a user to stop a server if it is running.', + 'restart' => 'Allows a user to perform a server restart. This allows them to start the server if it is offline, but not put the server in a completely stopped state.', + ], ], 'user' => [ - // Allows a user to create a new user assigned to the server. They will not be able - // to assign any permissions they do not already have on their account as well. - 'create', // subuser.create-subuser - 'read', // subuser.list-subusers, subuser.view-subuser - 'update', // subuser.edit-subuser - 'delete', // subuser.delete-subuser + 'description' => 'Permissions that allow a user to manage other subusers on a server. They will never be able to edit their own account, or assign permissions they do not have themselves.', + 'keys' => [ + 'create' => 'Allows a user to create new subusers for the server.', + 'read' => 'Allows the user to view subusers and their permissions for the server.', + 'update' => 'Allows a user to modify other subusers.', + 'delete' => 'Allows a user to delete a subuser from the server.', + ], ], 'file' => [ - // Allows a user to create additional files and folders either via the Panel, - // or via a direct upload. - 'create', // files.create-files, files.upload-files, files.copy-files, files.move-files + 'description' => 'Permissions that control a user\'s ability to modify the filesystem for this server.', + 'keys' => [ + 'create' => 'Allows a user to create additional files and folders via the Panel or direct upload.', + 'read' => 'Allows a user to view the contents of a directory and read the contents of a file. Users with this permission can also download files.', + 'update' => 'Allows a user to update the contents of an existing file or directory.', + 'delete' => 'Allows a user to delete files or directories.', + 'archive' => 'Allows a user to archive the contents of a directory as well as decompress existing archives on the system.', + 'sftp' => 'Allows a user to connect to SFTP and manage server files using the other assigned file permissions.', + ], + ], - // Allows a user to view the contents of a directory as well as read the contents - // of a given file. A user with this permission will be able to download files - // as well. - 'read', // files.list-files, files.download-files - - // Allows a user to update the contents of an existing file or directory. - 'update', // files.edit-files, files.save-files - - // Allows a user to delete a file or directory. - 'delete', // files.delete-files - - // Allows a user to archive the contents of a directory as well as decompress existing - // archives on the system. - 'archive', // files.compress-files, files.decompress-files - - // Allows the user to connect and manage server files using their account - // credentials and a SFTP client. - 'sftp', // files.access-sftp + 'backup' => [ + 'description' => 'Permissions that control a user\'s ability to generate and manage server backups.', + 'keys' => [ + 'create' => 'Allows a user to create new backups for this server.', + 'read' => 'Allows a user to view all backups that exist for this server.', + 'update' => '', + 'delete' => 'Allows a user to remove backups from the system.', + 'download' => 'Allows a user to download backups.', + ], ], // Controls permissions for editing or viewing a server's allocations. 'allocation' => [ - 'read', // server.view-allocations - 'update', // server.edit-allocation + 'description' => 'Permissions that control a user\'s ability to modify the port allocations for this server.', + 'keys' => [ + 'read' => 'Allows a user to view the allocations assigned to this server.', + 'update' => 'Allows a user to modify the allocations assigned to this server.', + ], ], // Controls permissions for editing or viewing a server's startup parameters. 'startup' => [ - 'read', // server.view-startup - 'update', // server.edit-startup + 'description' => 'Permissions that control a user\'s ability to view this server\'s startup parameters.', + 'keys' => [ + 'read' => '', + 'update' => '', + ], ], 'database' => [ - // Allows a user to create a new database for a server. - 'create', // database.create-database - - // Allows a user to view the databases associated with the server. If they do not also - // have the view_password permission they will only be able to see the connection address - // and the name of the user. - 'read', // database.view-databases - - // Allows a user to rotate the password on a database instance. If the user does not - // alow have the view_password permission they will not be able to see the updated password - // anywhere, but it will still be rotated. - 'update', // database.reset-db-password - - // Allows a user to delete a database instance. - 'delete', // database.delete-database - - // Allows a user to view the password associated with a database instance for the - // server. Note that a user without this permission may still be able to access these - // credentials by viewing files or the console. - 'view_password', // database.reset-db-password + 'description' => 'Permissions that control a user\'s access to the database management for this server.', + 'keys' => [ + 'create' => 'Allows a user to create a new database for this server.', + 'read' => 'Allows a user to view the database associated with this server.', + 'update' => 'Allows a user to rotate the password on a database instance. If the user does not have the view_password permission they will not see the updated password.', + 'delete' => 'Allows a user to remove a database instance from this server.', + 'view_password' => 'Allows a user to view the password associated with a database instance for this server.', + ], ], 'schedule' => [ - 'create', // task.create-schedule - 'read', // task.view-schedule, task.list-schedules - 'update', // task.edit-schedule, task.queue-schedule, task.toggle-schedule - 'delete', // task.delete-schedule + 'description' => 'Permissions that control a user\'s access to the schedule management for this server.', + 'keys' => [ + 'create' => '', // task.create-schedule + 'read' => '', // task.view-schedule, task.list-schedules + 'update' => '', // task.edit-schedule, task.queue-schedule, task.toggle-schedule + 'delete' => '', // task.delete-schedule + ], ], 'settings' => [ - 'rename', - 'reinstall', + 'description' => 'Permissions that control a user\'s access to the settings for this server.', + 'keys' => [ + 'rename' => '', + 'reinstall' => '', + ], ], ]; diff --git a/app/Models/Schedule.php b/app/Models/Schedule.php index 21cad0e59..384d354ad 100644 --- a/app/Models/Schedule.php +++ b/app/Models/Schedule.php @@ -25,7 +25,7 @@ use Pterodactyl\Contracts\Extensions\HashidsInterface; * @property \Pterodactyl\Models\Server $server * @property \Pterodactyl\Models\Task[]|\Illuminate\Support\Collection $tasks */ -class Schedule extends Validable +class Schedule extends Model { /** * The resource name for this model when it is transformed into an diff --git a/app/Models/Server.php b/app/Models/Server.php index 9bdb19ff1..97fea38a8 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -23,6 +23,7 @@ use Znck\Eloquent\Traits\BelongsToThrough; * @property int $disk * @property int $io * @property int $cpu + * @property string $threads * @property bool $oom_disabled * @property int $allocation_id * @property int $nest_id @@ -50,10 +51,14 @@ use Znck\Eloquent\Traits\BelongsToThrough; * @property \Pterodactyl\Models\Location $location * @property \Pterodactyl\Models\DaemonKey $key * @property \Pterodactyl\Models\DaemonKey[]|\Illuminate\Database\Eloquent\Collection $keys + * @property \Pterodactyl\Models\ServerTransfer $transfer + * @property \Pterodactyl\Models\Backup[]|\Illuminate\Database\Eloquent\Collection $backups */ -class Server extends Validable +class Server extends Model { - use BelongsToThrough, Notifiable, Searchable; + use BelongsToThrough; + use Notifiable; + use Searchable; /** * The resource name for this model when it is transformed into an @@ -61,6 +66,10 @@ class Server extends Validable */ const RESOURCE_NAME = 'server'; + const STATUS_INSTALLING = 0; + const STATUS_INSTALLED = 1; + const STATUS_INSTALL_FAILED = 2; + /** * The table associated with the model. * @@ -105,6 +114,7 @@ class Server extends Validable 'swap' => 'required|numeric|min:-1', 'io' => 'required|numeric|between:10,1000', 'cpu' => 'required|numeric|min:0', + 'threads' => 'nullable|regex:/^[0-9-,]+$/', 'oom_disabled' => 'sometimes|boolean', 'disk' => 'required|numeric|min:0', 'allocation_id' => 'required|bail|unique:servers|exists:allocations,id', @@ -177,7 +187,7 @@ class Server extends Validable */ public function getAllocationMappings(): array { - return $this->allocations->groupBy('ip')->map(function ($item) { + return $this->allocations->where('node_id', $this->node_id)->groupBy('ip')->map(function ($item) { return $item->pluck('port'); })->toArray(); } @@ -331,4 +341,22 @@ class Server extends Validable { return $this->hasMany(DaemonKey::class); } + + /** + * Returns the associated server transfer. + * + * @return \Illuminate\Database\Eloquent\Relations\HasOne + */ + public function transfer() + { + return $this->hasOne(ServerTransfer::class)->orderByDesc('id'); + } + + /** + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function backups() + { + return $this->hasMany(Backup::class); + } } diff --git a/app/Models/ServerTransfer.php b/app/Models/ServerTransfer.php new file mode 100644 index 000000000..94072bf0c --- /dev/null +++ b/app/Models/ServerTransfer.php @@ -0,0 +1,81 @@ + 'int', + 'old_node' => 'int', + 'new_node' => 'int', + 'old_allocation' => 'int', + 'new_allocation' => 'int', + 'old_additional_allocations' => 'string', + 'new_additional_allocations' => 'string', + 'successful' => 'bool', + ]; + + /** + * @var array + */ + public static $validationRules = [ + 'server_id' => 'required|numeric|exists:servers,id', + 'old_node' => 'required|numeric', + 'new_node' => 'required|numeric', + 'old_allocation' => 'required|numeric', + 'new_allocation' => 'required|numeric', + 'old_additional_allocations' => 'nullable', + 'new_additional_allocations' => 'nullable', + 'successful' => 'sometimes|boolean', + ]; + + /** + * Gets the server associated with a server transfer. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function server() + { + return $this->belongsTo(Server::class); + } +} diff --git a/app/Models/Setting.php b/app/Models/Setting.php index c23afb6e8..1a91a578e 100644 --- a/app/Models/Setting.php +++ b/app/Models/Setting.php @@ -2,7 +2,7 @@ namespace Pterodactyl\Models; -class Setting extends Validable +class Setting extends Model { /** * The table associated with the model. diff --git a/app/Models/Subuser.php b/app/Models/Subuser.php index e5e8e318e..d75bbe9ab 100644 --- a/app/Models/Subuser.php +++ b/app/Models/Subuser.php @@ -15,7 +15,7 @@ use Illuminate\Notifications\Notifiable; * @property \Pterodactyl\Models\User $user * @property \Pterodactyl\Models\Server $server */ -class Subuser extends Validable +class Subuser extends Model { use Notifiable; diff --git a/app/Models/Task.php b/app/Models/Task.php index 83d4119ff..f5a26b78a 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -22,7 +22,7 @@ use Pterodactyl\Contracts\Extensions\HashidsInterface; * @property \Pterodactyl\Models\Schedule $schedule * @property \Pterodactyl\Models\Server $server */ -class Task extends Validable +class Task extends Model { use BelongsToThrough; diff --git a/app/Models/User.php b/app/Models/User.php index 0a37311d3..c8efc7bd6 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -37,17 +37,20 @@ use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; * * @property string $name * @property \Pterodactyl\Models\ApiKey[]|\Illuminate\Database\Eloquent\Collection $apiKeys - * @property \Pterodactyl\Models\Permission[]|\Illuminate\Database\Eloquent\Collection $permissions * @property \Pterodactyl\Models\Server[]|\Illuminate\Database\Eloquent\Collection $servers - * @property \Pterodactyl\Models\Subuser[]|\Illuminate\Database\Eloquent\Collection $subuserOf * @property \Pterodactyl\Models\DaemonKey[]|\Illuminate\Database\Eloquent\Collection $keys */ -class User extends Validable implements +class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract { - use Authenticatable, Authorizable, AvailableLanguages, CanResetPassword, Notifiable, Searchable; + use Authenticatable; + use Authorizable; + use AvailableLanguages; + use CanResetPassword; + use Notifiable; + use Searchable; const USER_LEVEL_USER = 0; const USER_LEVEL_ADMIN = 1; @@ -220,16 +223,6 @@ class User extends Validable implements return trim($this->name_first . ' ' . $this->name_last); } - /** - * Returns all permissions that a user has. - * - * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough - */ - public function permissions() - { - return $this->hasManyThrough(Permission::class, Subuser::class); - } - /** * Returns all servers that a user owns. * @@ -240,16 +233,6 @@ class User extends Validable implements return $this->hasMany(Server::class, 'owner_id'); } - /** - * Return all servers that user is listed as a subuser of directly. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function subuserOf() - { - return $this->hasMany(Subuser::class); - } - /** * Return all of the daemon keys that a user belongs to. * diff --git a/app/Policies/ServerPolicy.php b/app/Policies/ServerPolicy.php index ac89be673..bc3fa7aca 100644 --- a/app/Policies/ServerPolicy.php +++ b/app/Policies/ServerPolicy.php @@ -1,21 +1,29 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Policies; -use Cache; -use Carbon; +use Carbon\Carbon; use Pterodactyl\Models\User; use Pterodactyl\Models\Server; +use Illuminate\Contracts\Cache\Repository as CacheRepository; class ServerPolicy { + /** + * @var \Illuminate\Contracts\Cache\Repository + */ + private $cache; + + /** + * ServerPolicy constructor. + * + * @param \Illuminate\Contracts\Cache\Repository $cache + */ + public function __construct(CacheRepository $cache) + { + $this->cache = $cache; + } + /** * Checks if the user has the given permission on/for the server. * @@ -26,13 +34,16 @@ class ServerPolicy */ protected function checkPermission(User $user, Server $server, $permission) { - $permissions = Cache::remember('ServerPolicy.' . $user->uuid . $server->uuid, Carbon::now()->addSeconds(5), function () use ($user, $server) { - return $user->permissions()->server($server)->get()->transform(function ($item) { - return $item->permission; - })->values(); + $key = sprintf('ServerPolicy.%s.%s', $user->uuid, $server->uuid); + + $permissions = $this->cache->remember($key, Carbon::now()->addSeconds(5), function () use ($user, $server) { + /** @var \Pterodactyl\Models\Subuser|null $subuser */ + $subuser = $server->subusers()->where('user_id', $user->id)->first(); + + return $subuser ? $subuser->permissions : []; }); - return $permissions->search($permission, true) !== false; + return in_array($permission, $permissions); } /** diff --git a/app/Repositories/Eloquent/BackupRepository.php b/app/Repositories/Eloquent/BackupRepository.php new file mode 100644 index 000000000..2ff206325 --- /dev/null +++ b/app/Repositories/Eloquent/BackupRepository.php @@ -0,0 +1,16 @@ +withFresh) ? $instance->fresh() : $saved; } + /** + * Update a model using the attributes passed. + * + * @param array|\Closure $attributes + * @param array $values + * @return int + */ + public function updateWhere($attributes, array $values) + { + return $this->getBuilder()->where($attributes)->update($values); + } + /** * Perform a mass update where matching records are updated using whereIn. * This does not perform any model data validation. diff --git a/app/Repositories/Eloquent/NodeRepository.php b/app/Repositories/Eloquent/NodeRepository.php index 89abbeeb3..6f317bc81 100644 --- a/app/Repositories/Eloquent/NodeRepository.php +++ b/app/Repositories/Eloquent/NodeRepository.php @@ -174,6 +174,23 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa })->values(); } + /** + * Returns a node with the given id with the Node's resource usage. + * + * @param int $node_id + * @return Node + */ + public function getNodeWithResourceUsage(int $node_id): Node + { + $instance = $this->getBuilder() + ->select(['nodes.id', 'nodes.fqdn', 'nodes.scheme', 'nodes.daemonSecret', 'nodes.daemonListen', 'nodes.memory', 'nodes.disk', 'nodes.memory_overallocate', 'nodes.disk_overallocate']) + ->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk') + ->leftJoin('servers', 'servers.node_id', '=', 'nodes.id') + ->where('nodes.id', $node_id); + + return $instance->first(); + } + /** * Return the IDs of all nodes that exist in the provided locations and have the space * available to support the additional disk and memory provided. diff --git a/app/Repositories/Eloquent/SubuserRepository.php b/app/Repositories/Eloquent/SubuserRepository.php index 4636f7b37..e00d825e7 100644 --- a/app/Repositories/Eloquent/SubuserRepository.php +++ b/app/Repositories/Eloquent/SubuserRepository.php @@ -3,7 +3,6 @@ namespace Pterodactyl\Repositories\Eloquent; use Pterodactyl\Models\Subuser; -use Illuminate\Support\Collection; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; @@ -20,19 +19,27 @@ class SubuserRepository extends EloquentRepository implements SubuserRepositoryI } /** - * Returns the subusers for the given server instance with the associated user - * and permission relationships pre-loaded. + * Returns a subuser model for the given user and server combination. If no record + * exists an exception will be thrown. * * @param int $server - * @return \Illuminate\Support\Collection + * @param string $uuid + * @return \Pterodactyl\Models\Subuser + * + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ - public function getSubusersForServer(int $server): Collection + public function getUserForServer(int $server, string $uuid): Subuser { - return $this->getBuilder() - ->with('user', 'permissions') - ->where('server_id', $server) - ->get() - ->toBase(); + /** @var \Pterodactyl\Models\Subuser $model */ + $model = $this->getBuilder() + ->with('server', 'user') + ->select('subusers.*') + ->join('users', 'users.id', '=', 'subusers.user_id') + ->where('subusers.server_id', $server) + ->where('users.uuid', $uuid) + ->firstOrFail(); + + return $model; } /** diff --git a/app/Repositories/Eloquent/UserRepository.php b/app/Repositories/Eloquent/UserRepository.php index b69df198c..1ed6c5b74 100644 --- a/app/Repositories/Eloquent/UserRepository.php +++ b/app/Repositories/Eloquent/UserRepository.php @@ -29,7 +29,7 @@ class UserRepository extends EloquentRepository implements UserRepositoryInterfa */ public function getAllUsersWithCounts(): LengthAwarePaginator { - return $this->getBuilder()->withCount('servers', 'subuserOf') + return $this->getBuilder()->withCount('servers') ->search($this->getSearchTerm()) ->paginate(50, $this->getColumns()); } diff --git a/app/Repositories/Wings/DaemonBackupRepository.php b/app/Repositories/Wings/DaemonBackupRepository.php new file mode 100644 index 000000000..7657c53f7 --- /dev/null +++ b/app/Repositories/Wings/DaemonBackupRepository.php @@ -0,0 +1,63 @@ +server, Server::class); + + try { + return $this->getHttpClient()->post( + sprintf('/api/servers/%s/backup', $this->server->uuid), + [ + 'json' => [ + 'uuid' => $backup->uuid, + 'ignored_files' => explode(PHP_EOL, $backup->ignored_files), + ], + ] + ); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + } + + /** + * Returns a stream of a backup's contents from the Wings instance so that we + * do not need to send the user directly to the Daemon. + * + * @param string $backup + * @return \Psr\Http\Message\ResponseInterface + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function getBackup(string $backup): ResponseInterface + { + Assert::isInstanceOf($this->server, Server::class); + + try { + return $this->getHttpClient()->get( + sprintf('/api/servers/%s/backup/%s', $this->server->uuid, $backup), + ['stream' => true] + ); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + } +} diff --git a/app/Repositories/Wings/DaemonServerRepository.php b/app/Repositories/Wings/DaemonServerRepository.php index ae4d1333a..b41c75483 100644 --- a/app/Repositories/Wings/DaemonServerRepository.php +++ b/app/Repositories/Wings/DaemonServerRepository.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Repositories\Wings; -use BadMethodCallException; use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; use GuzzleHttp\Exception\TransferException; @@ -13,7 +12,6 @@ class DaemonServerRepository extends DaemonRepository /** * Returns details about a server from the Daemon instance. * - * @return array * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException */ public function getDetails(): array @@ -89,10 +87,20 @@ class DaemonServerRepository extends DaemonRepository /** * Reinstall a server on the daemon. + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException */ public function reinstall(): void { - throw new BadMethodCallException('Method is not implemented.'); + Assert::isInstanceOf($this->server, Server::class); + + try { + $this->getHttpClient()->post(sprintf( + '/api/servers/%s/reinstall', $this->server->uuid + )); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } } /** @@ -116,4 +124,24 @@ class DaemonServerRepository extends DaemonRepository throw new DaemonConnectionException($exception); } } + + /** + * Requests the daemon to create a full archive of the server. + * Once the daemon is finished they will send a POST request to + * "/api/remote/servers/{uuid}/archive" with a boolean. + * + * @throws DaemonConnectionException + */ + public function requestArchive(): void + { + Assert::isInstanceOf($this->server, Server::class); + + try { + $this->getHttpClient()->post(sprintf( + '/api/servers/%s/archive', $this->server->uuid + )); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + } } diff --git a/app/Repositories/Wings/DaemonTransferRepository.php b/app/Repositories/Wings/DaemonTransferRepository.php new file mode 100644 index 000000000..e8a514196 --- /dev/null +++ b/app/Repositories/Wings/DaemonTransferRepository.php @@ -0,0 +1,35 @@ +getHttpClient()->post('/api/transfer', [ + 'json' => [ + 'server_id' => $server->uuid, + 'url' => $node->getConnectionAddress() . sprintf('/api/servers/%s/archive', $server->uuid), + 'token' => 'Bearer ' . $token, + 'server' => $data, + ], + ]); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + } +} diff --git a/app/Services/Backups/InitiateBackupService.php b/app/Services/Backups/InitiateBackupService.php new file mode 100644 index 000000000..f9c4de4b5 --- /dev/null +++ b/app/Services/Backups/InitiateBackupService.php @@ -0,0 +1,91 @@ +repository = $repository; + $this->connection = $connection; + $this->daemonBackupRepository = $daemonBackupRepository; + } + + /** + * Sets the files to be ignored by this backup. + * + * @param string|null $ignored + * @return $this + */ + public function setIgnoredFiles(?string $ignored) + { + $this->ignoredFiles = $ignored; + + return $this; + } + + /** + * Initiates the backup process for a server on the daemon. + * + * @param \Pterodactyl\Models\Server $server + * @param string|null $name + * @return \Pterodactyl\Models\Backup + * + * @throws \Throwable + */ + public function handle(Server $server, string $name = null): Backup + { + return $this->connection->transaction(function () use ($server, $name) { + /** @var \Pterodactyl\Models\Backup $backup */ + $backup = $this->repository->create([ + 'server_id' => $server->id, + 'uuid' => Uuid::uuid4()->toString(), + 'name' => trim($name) ?: sprintf('Backup at %s', CarbonImmutable::now()->toDateTimeString()), + 'ignored_files' => $this->ignoredFiles ?? '', + 'disk' => 'local', + ], true, true); + + $this->daemonBackupRepository->setServer($server)->backup($backup); + + return $backup; + }); + } +} diff --git a/app/Services/Servers/BuildModificationService.php b/app/Services/Servers/BuildModificationService.php index 0e3085dc5..0a010b545 100644 --- a/app/Services/Servers/BuildModificationService.php +++ b/app/Services/Servers/BuildModificationService.php @@ -98,6 +98,7 @@ class BuildModificationService 'swap' => array_get($data, 'swap'), 'io' => array_get($data, 'io'), 'cpu' => array_get($data, 'cpu'), + 'threads' => array_get($data, 'threads'), 'disk' => array_get($data, 'disk'), 'allocation_id' => array_get($data, 'allocation_id'), 'database_limit' => array_get($data, 'database_limit'), diff --git a/app/Services/Servers/ReinstallServerService.php b/app/Services/Servers/ReinstallServerService.php index 6242eeea2..27955c475 100644 --- a/app/Services/Servers/ReinstallServerService.php +++ b/app/Services/Servers/ReinstallServerService.php @@ -3,11 +3,9 @@ namespace Pterodactyl\Services\Servers; use Pterodactyl\Models\Server; -use GuzzleHttp\Exception\RequestException; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Repositories\Wings\DaemonServerRepository; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; class ReinstallServerService { @@ -44,28 +42,23 @@ class ReinstallServerService } /** - * @param int|\Pterodactyl\Models\Server $server + * Reinstall a server on the remote daemon. * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @param \Pterodactyl\Models\Server $server + * @return \Pterodactyl\Models\Server + * + * @throws \Throwable */ - public function reinstall($server) + public function reinstall(Server $server) { - if (! $server instanceof Server) { - $server = $this->repository->find($server); - } + $this->database->transaction(function () use ($server) { + $this->repository->withoutFreshModel()->update($server->id, [ + 'installed' => Server::STATUS_INSTALLING, + ]); - $this->database->beginTransaction(); - $this->repository->withoutFreshModel()->update($server->id, [ - 'installed' => 0, - ], true, true); - - try { $this->daemonServerRepository->setServer($server)->reinstall(); - $this->database->commit(); - } catch (RequestException $exception) { - throw new DaemonConnectionException($exception); - } + }); + + return $server->refresh(); } } diff --git a/app/Services/Servers/ServerConfigurationStructureService.php b/app/Services/Servers/ServerConfigurationStructureService.php index 9984e3a98..69950b230 100644 --- a/app/Services/Servers/ServerConfigurationStructureService.php +++ b/app/Services/Servers/ServerConfigurationStructureService.php @@ -81,6 +81,7 @@ class ServerConfigurationStructureService 'swap' => $server->swap, 'io_weight' => $server->io, 'cpu_limit' => $server->cpu, + 'threads' => $server->threads, 'disk_space' => $server->disk, ], 'service' => [ @@ -130,6 +131,7 @@ class ServerConfigurationStructureService 'swap' => (int) $server->swap, 'io' => (int) $server->io, 'cpu' => (int) $server->cpu, + 'threads' => $server->threads, 'disk' => (int) $server->disk, 'image' => $server->image, ], diff --git a/app/Services/Servers/ServerCreationService.php b/app/Services/Servers/ServerCreationService.php index 9cc1535a4..cf0900282 100644 --- a/app/Services/Servers/ServerCreationService.php +++ b/app/Services/Servers/ServerCreationService.php @@ -4,7 +4,6 @@ namespace Pterodactyl\Services\Servers; use Ramsey\Uuid\Uuid; use Illuminate\Support\Arr; -use Pterodactyl\Models\Node; use Pterodactyl\Models\User; use Pterodactyl\Models\Server; use Illuminate\Support\Collection; @@ -242,6 +241,7 @@ class ServerCreationService 'disk' => Arr::get($data, 'disk'), 'io' => Arr::get($data, 'io'), 'cpu' => Arr::get($data, 'cpu'), + 'threads' => Arr::get($data, 'threads'), 'oom_disabled' => Arr::get($data, 'oom_disabled', true), 'allocation_id' => Arr::get($data, 'allocation_id'), 'nest_id' => Arr::get($data, 'nest_id'), diff --git a/app/Services/Servers/TransferService.php b/app/Services/Servers/TransferService.php new file mode 100644 index 000000000..b45a78dc8 --- /dev/null +++ b/app/Services/Servers/TransferService.php @@ -0,0 +1,46 @@ +repository = $repository; + $this->daemonServerRepository = $daemonServerRepository; + } + + /** + * Requests an archive from the daemon. + * + * @param int|\Pterodactyl\Models\Server $server + * + * @throws \Throwable + */ + public function requestArchive(Server $server) + { + $this->daemonServerRepository->setServer($server)->requestArchive(); + } +} diff --git a/app/Services/Subusers/PermissionCreationService.php b/app/Services/Subusers/PermissionCreationService.php deleted file mode 100644 index 328485ee1..000000000 --- a/app/Services/Subusers/PermissionCreationService.php +++ /dev/null @@ -1,63 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Services\Subusers; - -use Webmozart\Assert\Assert; -use Pterodactyl\Models\Permission; -use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface; - -class PermissionCreationService -{ - /** - * @var \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface - */ - protected $repository; - - /** - * PermissionCreationService constructor. - * - * @param \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface $repository - */ - public function __construct(PermissionRepositoryInterface $repository) - { - $this->repository = $repository; - } - - /** - * Assign permissions to a given subuser. - * - * @param int $subuser - * @param array $permissions - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - */ - public function handle($subuser, array $permissions) - { - Assert::integerish($subuser, 'First argument passed to handle must be an integer, received %s.'); - - $permissionMappings = Permission::getPermissions(true); - $insertPermissions = []; - - foreach ($permissions as $permission) { - if (array_key_exists($permission, $permissionMappings)) { - Assert::stringNotEmpty($permission, 'Permission argument provided must be a non-empty string, received %s.'); - - array_push($insertPermissions, [ - 'subuser_id' => $subuser, - 'permission' => $permission, - ]); - } - } - - if (! empty($insertPermissions)) { - $this->repository->withoutFreshModel()->insert($insertPermissions); - } - } -} diff --git a/app/Services/Subusers/SubuserCreationService.php b/app/Services/Subusers/SubuserCreationService.php index 9c6b7c42e..e31e8ca8b 100644 --- a/app/Services/Subusers/SubuserCreationService.php +++ b/app/Services/Subusers/SubuserCreationService.php @@ -1,22 +1,14 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Subusers; use Pterodactyl\Models\Server; +use Pterodactyl\Models\Subuser; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Services\Users\UserCreationService; +use Pterodactyl\Repositories\Eloquent\SubuserRepository; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; -use Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; use Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException; use Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException; @@ -25,113 +17,87 @@ class SubuserCreationService /** * @var \Illuminate\Database\ConnectionInterface */ - protected $connection; + private $connection; /** - * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService + * @var \Pterodactyl\Repositories\Eloquent\SubuserRepository */ - protected $keyCreationService; - - /** - * @var \Pterodactyl\Services\Subusers\PermissionCreationService - */ - protected $permissionService; - - /** - * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface - */ - protected $subuserRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $serverRepository; + private $subuserRepository; /** * @var \Pterodactyl\Services\Users\UserCreationService */ - protected $userCreationService; + private $userCreationService; /** * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface */ - protected $userRepository; + private $userRepository; /** * SubuserCreationService constructor. * * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService $keyCreationService - * @param \Pterodactyl\Services\Subusers\PermissionCreationService $permissionService - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository - * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $subuserRepository + * @param \Pterodactyl\Repositories\Eloquent\SubuserRepository $subuserRepository * @param \Pterodactyl\Services\Users\UserCreationService $userCreationService * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $userRepository */ public function __construct( ConnectionInterface $connection, - DaemonKeyCreationService $keyCreationService, - PermissionCreationService $permissionService, - ServerRepositoryInterface $serverRepository, - SubuserRepositoryInterface $subuserRepository, + SubuserRepository $subuserRepository, UserCreationService $userCreationService, UserRepositoryInterface $userRepository ) { $this->connection = $connection; - $this->keyCreationService = $keyCreationService; - $this->permissionService = $permissionService; - $this->serverRepository = $serverRepository; $this->subuserRepository = $subuserRepository; $this->userRepository = $userRepository; $this->userCreationService = $userCreationService; } /** - * @param int|\Pterodactyl\Models\Server $server + * Creates a new user on the system and assigns them access to the provided server. + * If the email address already belongs to a user on the system a new user will not + * be created. + * + * @param \Pterodactyl\Models\Server $server * @param string $email * @param array $permissions * @return \Pterodactyl\Models\Subuser * - * @throws \Exception * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException * @throws \Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException + * @throws \Throwable */ - public function handle($server, $email, array $permissions) + public function handle(Server $server, string $email, array $permissions): Subuser { - if (! $server instanceof Server) { - $server = $this->serverRepository->find($server); - } + return $this->connection->transaction(function () use ($server, $email, $permissions) { + try { + $user = $this->userRepository->findFirstWhere([['email', '=', $email]]); - $this->connection->beginTransaction(); - try { - $user = $this->userRepository->findFirstWhere([['email', '=', $email]]); + if ($server->owner_id === $user->id) { + throw new UserIsServerOwnerException(trans('exceptions.subusers.user_is_owner')); + } - if ($server->owner_id === $user->id) { - throw new UserIsServerOwnerException(trans('exceptions.subusers.user_is_owner')); + $subuserCount = $this->subuserRepository->findCountWhere([['user_id', '=', $user->id], ['server_id', '=', $server->id]]); + if ($subuserCount !== 0) { + throw new ServerSubuserExistsException(trans('exceptions.subusers.subuser_exists')); + } + } catch (RecordNotFoundException $exception) { + $user = $this->userCreationService->handle([ + 'email' => $email, + 'username' => preg_replace('/([^\w\.-]+)/', '', strtok($email, '@')) . str_random(3), + 'name_first' => 'Server', + 'name_last' => 'Subuser', + 'root_admin' => false, + ]); } - $subuserCount = $this->subuserRepository->findCountWhere([['user_id', '=', $user->id], ['server_id', '=', $server->id]]); - if ($subuserCount !== 0) { - throw new ServerSubuserExistsException(trans('exceptions.subusers.subuser_exists')); - } - } catch (RecordNotFoundException $exception) { - $username = preg_replace('/([^\w\.-]+)/', '', strtok($email, '@')); - $user = $this->userCreationService->handle([ - 'email' => $email, - 'username' => $username . str_random(3), - 'name_first' => 'Server', - 'name_last' => 'Subuser', - 'root_admin' => false, + return $this->subuserRepository->create([ + 'user_id' => $user->id, + 'server_id' => $server->id, + 'permissions' => array_unique($permissions), ]); - } - - $subuser = $this->subuserRepository->create(['user_id' => $user->id, 'server_id' => $server->id]); - $this->keyCreationService->handle($server->id, $user->id); - $this->permissionService->handle($subuser->id, $permissions); - $this->connection->commit(); - - return $subuser; + }); } } diff --git a/app/Services/Subusers/SubuserDeletionService.php b/app/Services/Subusers/SubuserDeletionService.php deleted file mode 100644 index 6bc35ae3b..000000000 --- a/app/Services/Subusers/SubuserDeletionService.php +++ /dev/null @@ -1,35 +0,0 @@ -repository = $repository; - } - - /** - * Delete a subuser and their associated permissions from the Panel and Daemon. - * - * @param \Pterodactyl\Models\Subuser $subuser - */ - public function handle(Subuser $subuser) - { - $this->repository->delete($subuser->id); - } -} diff --git a/app/Services/Subusers/SubuserUpdateService.php b/app/Services/Subusers/SubuserUpdateService.php deleted file mode 100644 index 64d7f0b38..000000000 --- a/app/Services/Subusers/SubuserUpdateService.php +++ /dev/null @@ -1,107 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Services\Subusers; - -use Pterodactyl\Models\Subuser; -use GuzzleHttp\Exception\RequestException; -use Illuminate\Database\ConnectionInterface; -use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; -use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; -use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface; -use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; -use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; - -class SubuserUpdateService -{ - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface - */ - private $daemonRepository; - - /** - * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService - */ - private $keyProviderService; - - /** - * @var \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface - */ - private $permissionRepository; - - /** - * @var \Pterodactyl\Services\Subusers\PermissionCreationService - */ - private $permissionService; - - /** - * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface - */ - private $repository; - - /** - * SubuserUpdateService constructor. - * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService - * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository - * @param \Pterodactyl\Services\Subusers\PermissionCreationService $permissionService - * @param \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface $permissionRepository - * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository - */ - public function __construct( - ConnectionInterface $connection, - DaemonKeyProviderService $keyProviderService, - DaemonServerRepositoryInterface $daemonRepository, - PermissionCreationService $permissionService, - PermissionRepositoryInterface $permissionRepository, - SubuserRepositoryInterface $repository - ) { - $this->connection = $connection; - $this->daemonRepository = $daemonRepository; - $this->keyProviderService = $keyProviderService; - $this->permissionRepository = $permissionRepository; - $this->permissionService = $permissionService; - $this->repository = $repository; - } - - /** - * Update permissions for a given subuser. - * - * @param \Pterodactyl\Models\Subuser $subuser - * @param array $permissions - * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function handle(Subuser $subuser, array $permissions) - { - $subuser = $this->repository->loadServerAndUserRelations($subuser); - - $this->connection->beginTransaction(); - $this->permissionRepository->deleteWhere([['subuser_id', '=', $subuser->id]]); - $this->permissionService->handle($subuser->id, $permissions); - - try { - $token = $this->keyProviderService->handle($subuser->getRelation('server'), $subuser->getRelation('user'), false); - $this->daemonRepository->setServer($subuser->getRelation('server'))->revokeAccessKey($token); - } catch (RequestException $exception) { - $this->connection->rollBack(); - throw new DaemonConnectionException($exception); - } - - $this->connection->commit(); - } -} diff --git a/app/Transformers/Api/Application/ServerTransformer.php b/app/Transformers/Api/Application/ServerTransformer.php index 70dc185d6..4fb31212d 100644 --- a/app/Transformers/Api/Application/ServerTransformer.php +++ b/app/Transformers/Api/Application/ServerTransformer.php @@ -75,6 +75,7 @@ class ServerTransformer extends BaseTransformer 'disk' => $server->disk, 'io' => $server->io, 'cpu' => $server->cpu, + 'threads' => $server->threads, ], 'feature_limits' => [ 'databases' => $server->database_limit, diff --git a/app/Transformers/Api/Client/BackupTransformer.php b/app/Transformers/Api/Client/BackupTransformer.php new file mode 100644 index 000000000..53966fc77 --- /dev/null +++ b/app/Transformers/Api/Client/BackupTransformer.php @@ -0,0 +1,33 @@ + $backup->uuid, + 'name' => $backup->name, + 'ignored_files' => $backup->ignored_files, + 'sha256_hash' => $backup->sha256_hash, + 'bytes' => $backup->bytes, + 'created_at' => $backup->created_at->toIso8601String(), + 'completed_at' => $backup->completed_at ? $backup->completed_at->toIso8601String() : null, + ]; + } +} diff --git a/app/Transformers/Api/Client/SubuserTransformer.php b/app/Transformers/Api/Client/SubuserTransformer.php index e0b165553..d2e7ce0ff 100644 --- a/app/Transformers/Api/Client/SubuserTransformer.php +++ b/app/Transformers/Api/Client/SubuserTransformer.php @@ -2,16 +2,10 @@ namespace Pterodactyl\Transformers\Api\Client; -use Pterodactyl\Models\User; use Pterodactyl\Models\Subuser; class SubuserTransformer extends BaseClientTransformer { - /** - * @var array - */ - protected $defaultIncludes = ['user']; - /** * Return the resource name for the JSONAPI output. * @@ -27,23 +21,13 @@ class SubuserTransformer extends BaseClientTransformer * * @param \Pterodactyl\Models\Subuser $model * @return array|void + * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function transform(Subuser $model) { - return [ - 'permissions' => $model->permissions->pluck('permission'), - ]; - } - - /** - * Include the permissions associated with this subuser. - * - * @param \Pterodactyl\Models\Subuser $model - * @return \League\Fractal\Resource\Item - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException - */ - public function includeUser(Subuser $model) - { - return $this->item($model->user, $this->makeTransformer(UserTransformer::class), User::RESOURCE_NAME); + return array_merge( + $this->makeTransformer(UserTransformer::class)->transform($model->user), + ['permissions' => $model->permissions] + ); } } diff --git a/composer.json b/composer.json index 0bae14398..f582e060a 100644 --- a/composer.json +++ b/composer.json @@ -15,16 +15,16 @@ "ext-mbstring": "*", "ext-pdo_mysql": "*", "ext-zip": "*", - "appstract/laravel-blade-directives": "^1.6", - "aws/aws-sdk-php": "^3.110", - "cakephp/chronos": "^1.2", - "doctrine/dbal": "^2.9", + "appstract/laravel-blade-directives": "^1.8", + "aws/aws-sdk-php": "^3.134", + "cakephp/chronos": "^1.3", + "doctrine/dbal": "^2.10", "fideloper/proxy": "^4.2", - "guzzlehttp/guzzle": "^6.3", + "guzzlehttp/guzzle": "^6.5", "hashids/hashids": "^4.0", - "laracasts/utilities": "^3.0", - "laravel/framework": "^6.0.0", - "laravel/helpers": "^1.1", + "laracasts/utilities": "^3.1", + "laravel/framework": "^6.18", + "laravel/helpers": "^1.2", "laravel/tinker": "^1.0", "lcobucci/jwt": "^3.3", "matriphe/iso-639": "^1.2", @@ -32,18 +32,18 @@ "predis/predis": "^1.1", "prologue/alerts": "^0.4", "s1lentium/iptools": "^1.1", - "spatie/laravel-fractal": "^5.6", - "staudenmeir/belongs-to-through": "^2.6", - "symfony/yaml": "^4.0", - "webmozart/assert": "^1.5" + "spatie/laravel-fractal": "^5.7", + "staudenmeir/belongs-to-through": "^2.9", + "symfony/yaml": "^4.4", + "webmozart/assert": "^1.7" }, "require-dev": { "barryvdh/laravel-debugbar": "^3.2", "barryvdh/laravel-ide-helper": "^2.6", "codedungeon/phpunit-result-printer": "0.25.1", - "friendsofphp/php-cs-fixer": "^2.15.1", - "laravel/dusk": "^5.5", - "php-mock/php-mock-phpunit": "^2.4", + "friendsofphp/php-cs-fixer": "^2.16.1", + "laravel/dusk": "^5.11", + "php-mock/php-mock-phpunit": "^2.6", "phpunit/phpunit": "^7" }, "autoload": { diff --git a/composer.lock b/composer.lock index 4c8759161..6cc218722 100644 --- a/composer.lock +++ b/composer.lock @@ -4,24 +4,24 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "39fbdca3eac026ce6a99684706ffa03b", + "content-hash": "1d03ca0a7151c6594255643eaaa8f527", "packages": [ { "name": "appstract/laravel-blade-directives", - "version": "1.6.0", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/appstract/laravel-blade-directives.git", - "reference": "72dbfce8c0a8f421293e211b17cef13e7fa17fc7" + "reference": "ac9958e5499d21b12c317a43d96e41c00eaa3fba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appstract/laravel-blade-directives/zipball/72dbfce8c0a8f421293e211b17cef13e7fa17fc7", - "reference": "72dbfce8c0a8f421293e211b17cef13e7fa17fc7", + "url": "https://api.github.com/repos/appstract/laravel-blade-directives/zipball/ac9958e5499d21b12c317a43d96e41c00eaa3fba", + "reference": "ac9958e5499d21b12c317a43d96e41c00eaa3fba", "shasum": "" }, "require": { - "laravel/framework": "^5.7|^6.0", + "laravel/framework": "^5.7|^6.0|^7.0", "php": "^7.1.3" }, "require-dev": { @@ -48,8 +48,8 @@ "authors": [ { "name": "Gijs Jorissen", - "email": "hello@appstract.team", - "homepage": "https://appstract.team", + "email": "gijs@appstract.nl", + "homepage": "https://appstract.nl", "role": "Developer" } ], @@ -59,30 +59,30 @@ "appstract", "laravel-blade-directives" ], - "time": "2019-09-04T09:05:25+00:00" + "time": "2020-03-04T08:57:34+00:00" }, { "name": "aws/aws-sdk-php", - "version": "3.110.9", + "version": "3.134.3", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "25fb32317e4dbd2b34caca01ed940cce12f7362c" + "reference": "3de2711a47e7c3f5e93a5c83f019188fd23f852f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/25fb32317e4dbd2b34caca01ed940cce12f7362c", - "reference": "25fb32317e4dbd2b34caca01ed940cce12f7362c", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/3de2711a47e7c3f5e93a5c83f019188fd23f852f", + "reference": "3de2711a47e7c3f5e93a5c83f019188fd23f852f", "shasum": "" }, "require": { "ext-json": "*", "ext-pcre": "*", "ext-simplexml": "*", - "guzzlehttp/guzzle": "^5.3.3|^6.2.1", - "guzzlehttp/promises": "~1.0", + "guzzlehttp/guzzle": "^5.3.3|^6.2.1|^7.0", + "guzzlehttp/promises": "^1.0", "guzzlehttp/psr7": "^1.4.1", - "mtdowling/jmespath.php": "~2.2", + "mtdowling/jmespath.php": "^2.5", "php": ">=5.5" }, "require-dev": { @@ -97,7 +97,8 @@ "nette/neon": "^2.3", "phpunit/phpunit": "^4.8.35|^5.4.3", "psr/cache": "^1.0", - "psr/simple-cache": "^1.0" + "psr/simple-cache": "^1.0", + "sebastian/comparator": "^1.2.3" }, "suggest": { "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", @@ -142,30 +143,29 @@ "s3", "sdk" ], - "time": "2019-09-04T18:17:50+00:00" + "time": "2020-04-03T18:11:51+00:00" }, { "name": "cakephp/chronos", - "version": "1.2.8", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/cakephp/chronos.git", - "reference": "0292f06e8cc23fc82f0574889da2d8bf27b613c1" + "reference": "ba2bab98849e7bf29b02dd634ada49ab36472959" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cakephp/chronos/zipball/0292f06e8cc23fc82f0574889da2d8bf27b613c1", - "reference": "0292f06e8cc23fc82f0574889da2d8bf27b613c1", + "url": "https://api.github.com/repos/cakephp/chronos/zipball/ba2bab98849e7bf29b02dd634ada49ab36472959", + "reference": "ba2bab98849e7bf29b02dd634ada49ab36472959", "shasum": "" }, "require": { - "php": "^5.5.9|^7" + "php": ">=5.6" }, "require-dev": { "athletic/athletic": "~0.1", "cakephp/cakephp-codesniffer": "^3.0", "phpbench/phpbench": "@dev", - "phpstan/phpstan": "^0.6.4", "phpunit/phpunit": "<6.0 || ^7.0" }, "type": "library", @@ -199,29 +199,29 @@ "datetime", "time" ], - "time": "2019-06-17T15:19:18+00:00" + "time": "2019-11-30T02:33:19+00:00" }, { "name": "dnoegel/php-xdg-base-dir", - "version": "0.1", + "version": "v0.1.1", "source": { "type": "git", "url": "https://github.com/dnoegel/php-xdg-base-dir.git", - "reference": "265b8593498b997dc2d31e75b89f053b5cc9621a" + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/265b8593498b997dc2d31e75b89f053b5cc9621a", - "reference": "265b8593498b997dc2d31e75b89f053b5cc9621a", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", "shasum": "" }, "require": { "php": ">=5.3.2" }, "require-dev": { - "phpunit/phpunit": "@stable" + "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" }, - "type": "project", + "type": "library", "autoload": { "psr-4": { "XdgBaseDir\\": "src/" @@ -232,20 +232,20 @@ "MIT" ], "description": "implementation of xdg base directory specification for php", - "time": "2014-10-24T07:27:01+00:00" + "time": "2019-12-04T15:06:13+00:00" }, { "name": "doctrine/cache", - "version": "v1.8.0", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/doctrine/cache.git", - "reference": "d768d58baee9a4862ca783840eca1b9add7a7f57" + "reference": "382e7f4db9a12dc6c19431743a2b096041bcdd62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/d768d58baee9a4862ca783840eca1b9add7a7f57", - "reference": "d768d58baee9a4862ca783840eca1b9add7a7f57", + "url": "https://api.github.com/repos/doctrine/cache/zipball/382e7f4db9a12dc6c19431743a2b096041bcdd62", + "reference": "382e7f4db9a12dc6c19431743a2b096041bcdd62", "shasum": "" }, "require": { @@ -256,7 +256,7 @@ }, "require-dev": { "alcaeus/mongo-php-adapter": "^1.1", - "doctrine/coding-standard": "^4.0", + "doctrine/coding-standard": "^6.0", "mongodb/mongodb": "^1.1", "phpunit/phpunit": "^7.0", "predis/predis": "~1.0" @@ -267,7 +267,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8.x-dev" + "dev-master": "1.9.x-dev" } }, "autoload": { @@ -280,6 +280,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -288,10 +292,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -301,41 +301,47 @@ "email": "schmittjoh@gmail.com" } ], - "description": "Caching library offering an object-oriented API for many cache backends", - "homepage": "https://www.doctrine-project.org", + "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", + "homepage": "https://www.doctrine-project.org/projects/cache.html", "keywords": [ + "abstraction", + "apcu", "cache", - "caching" + "caching", + "couchdb", + "memcached", + "php", + "redis", + "xcache" ], - "time": "2018-08-21T18:01:43+00:00" + "time": "2019-11-29T15:36:20+00:00" }, { "name": "doctrine/dbal", - "version": "v2.9.2", + "version": "v2.10.1", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9" + "reference": "c2b8e6e82732a64ecde1cddf9e1e06cb8556e3d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9", - "reference": "22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/c2b8e6e82732a64ecde1cddf9e1e06cb8556e3d8", + "reference": "c2b8e6e82732a64ecde1cddf9e1e06cb8556e3d8", "shasum": "" }, "require": { "doctrine/cache": "^1.0", "doctrine/event-manager": "^1.0", "ext-pdo": "*", - "php": "^7.1" + "php": "^7.2" }, "require-dev": { - "doctrine/coding-standard": "^5.0", - "jetbrains/phpstorm-stubs": "^2018.1.2", - "phpstan/phpstan": "^0.10.1", - "phpunit/phpunit": "^7.4", - "symfony/console": "^2.0.5|^3.0|^4.0", - "symfony/phpunit-bridge": "^3.4.5|^4.0.5" + "doctrine/coding-standard": "^6.0", + "jetbrains/phpstorm-stubs": "^2019.1", + "phpstan/phpstan": "^0.11.3", + "phpunit/phpunit": "^8.4.1", + "symfony/console": "^2.0.5|^3.0|^4.0|^5.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -346,7 +352,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.9.x-dev", + "dev-master": "2.10.x-dev", "dev-develop": "3.0.x-dev" } }, @@ -360,6 +366,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -368,10 +378,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -382,27 +388,38 @@ "keywords": [ "abstraction", "database", + "db2", "dbal", + "mariadb", + "mssql", "mysql", - "persistence", + "oci8", + "oracle", + "pdo", "pgsql", - "php", - "queryobject" + "postgresql", + "queryobject", + "sasql", + "sql", + "sqlanywhere", + "sqlite", + "sqlserver", + "sqlsrv" ], - "time": "2018-12-31T03:27:51+00:00" + "time": "2020-01-04T12:56:21+00:00" }, { "name": "doctrine/event-manager", - "version": "v1.0.0", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/event-manager.git", - "reference": "a520bc093a0170feeb6b14e9d83f3a14452e64b3" + "reference": "629572819973f13486371cb611386eb17851e85c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/a520bc093a0170feeb6b14e9d83f3a14452e64b3", - "reference": "a520bc093a0170feeb6b14e9d83f3a14452e64b3", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/629572819973f13486371cb611386eb17851e85c", + "reference": "629572819973f13486371cb611386eb17851e85c", "shasum": "" }, "require": { @@ -412,7 +429,7 @@ "doctrine/common": "<2.9@dev" }, "require-dev": { - "doctrine/coding-standard": "^4.0", + "doctrine/coding-standard": "^6.0", "phpunit/phpunit": "^7.0" }, "type": "library", @@ -431,6 +448,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -439,10 +460,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -456,27 +473,29 @@ "email": "ocramius@gmail.com" } ], - "description": "Doctrine Event Manager component", + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", "homepage": "https://www.doctrine-project.org/projects/event-manager.html", "keywords": [ "event", - "eventdispatcher", - "eventmanager" + "event dispatcher", + "event manager", + "event system", + "events" ], - "time": "2018-06-11T11:59:03+00:00" + "time": "2019-11-10T09:48:07+00:00" }, { "name": "doctrine/inflector", - "version": "v1.3.0", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "5527a48b7313d15261292c149e55e26eae771b0a" + "reference": "ec3a55242203ffa6a4b27c58176da97ff0a7aec1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/5527a48b7313d15261292c149e55e26eae771b0a", - "reference": "5527a48b7313d15261292c149e55e26eae771b0a", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/ec3a55242203ffa6a4b27c58176da97ff0a7aec1", + "reference": "ec3a55242203ffa6a4b27c58176da97ff0a7aec1", "shasum": "" }, "require": { @@ -501,6 +520,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -509,10 +532,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -530,20 +549,20 @@ "singularize", "string" ], - "time": "2018-01-09T20:05:19+00:00" + "time": "2019-10-30T19:59:35+00:00" }, { "name": "doctrine/lexer", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "e17f069ede36f7534b95adec71910ed1b49c74ea" + "reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/e17f069ede36f7534b95adec71910ed1b49c74ea", - "reference": "e17f069ede36f7534b95adec71910ed1b49c74ea", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6", + "reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6", "shasum": "" }, "require": { @@ -557,7 +576,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { @@ -592,7 +611,7 @@ "parser", "php" ], - "time": "2019-07-30T19:33:28+00:00" + "time": "2019-10-30T14:39:59+00:00" }, { "name": "dragonmantank/cron-expression", @@ -650,27 +669,27 @@ }, { "name": "egulias/email-validator", - "version": "2.1.11", + "version": "2.1.17", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "92dd169c32f6f55ba570c309d83f5209cefb5e23" + "reference": "ade6887fd9bd74177769645ab5c474824f8a418a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/92dd169c32f6f55ba570c309d83f5209cefb5e23", - "reference": "92dd169c32f6f55ba570c309d83f5209cefb5e23", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/ade6887fd9bd74177769645ab5c474824f8a418a", + "reference": "ade6887fd9bd74177769645ab5c474824f8a418a", "shasum": "" }, "require": { "doctrine/lexer": "^1.0.1", - "php": ">= 5.5" + "php": ">=5.5", + "symfony/polyfill-intl-idn": "^1.10" }, "require-dev": { - "dominicsayers/isemail": "dev-master", - "phpunit/phpunit": "^4.8.35||^5.7||^6.0", - "satooshi/php-coveralls": "^1.0.1", - "symfony/phpunit-bridge": "^4.4@dev" + "dominicsayers/isemail": "^3.0.7", + "phpunit/phpunit": "^4.8.36|^7.5.15", + "satooshi/php-coveralls": "^1.0.1" }, "suggest": { "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" @@ -704,74 +723,28 @@ "validation", "validator" ], - "time": "2019-08-13T17:33:27+00:00" - }, - { - "name": "erusev/parsedown", - "version": "1.7.3", - "source": { - "type": "git", - "url": "https://github.com/erusev/parsedown.git", - "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/6d893938171a817f4e9bc9e86f2da1e370b7bcd7", - "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35" - }, - "type": "library", - "autoload": { - "psr-0": { - "Parsedown": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Emanuil Rusev", - "email": "hello@erusev.com", - "homepage": "http://erusev.com" - } - ], - "description": "Parser for Markdown.", - "homepage": "http://parsedown.org", - "keywords": [ - "markdown", - "parser" - ], - "time": "2019-03-17T18:48:37+00:00" + "time": "2020-02-13T22:36:52+00:00" }, { "name": "fideloper/proxy", - "version": "4.2.1", + "version": "4.3.0", "source": { "type": "git", "url": "https://github.com/fideloper/TrustedProxy.git", - "reference": "03085e58ec7bee24773fa5a8850751a6e61a7e8a" + "reference": "ec38ad69ee378a1eec04fb0e417a97cfaf7ed11a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/03085e58ec7bee24773fa5a8850751a6e61a7e8a", - "reference": "03085e58ec7bee24773fa5a8850751a6e61a7e8a", + "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/ec38ad69ee378a1eec04fb0e417a97cfaf7ed11a", + "reference": "ec38ad69ee378a1eec04fb0e417a97cfaf7ed11a", "shasum": "" }, "require": { - "illuminate/contracts": "^5.0|^6.0|^7.0", + "illuminate/contracts": "^5.0|^6.0|^7.0|^8.0", "php": ">=5.4.0" }, "require-dev": { - "illuminate/http": "^5.0|^6.0|^7.0", + "illuminate/http": "^5.0|^6.0|^7.0|^8.0", "mockery/mockery": "^1.0", "phpunit/phpunit": "^6.0" }, @@ -804,48 +777,50 @@ "proxy", "trusted proxy" ], - "time": "2019-09-03T16:45:42+00:00" + "time": "2020-02-22T01:51:47+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "6.3.3", + "version": "6.5.2", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" + "reference": "43ece0e75098b7ecd8d13918293029e555a50f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba", - "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/43ece0e75098b7ecd8d13918293029e555a50f82", + "reference": "43ece0e75098b7ecd8d13918293029e555a50f82", "shasum": "" }, "require": { + "ext-json": "*", "guzzlehttp/promises": "^1.0", - "guzzlehttp/psr7": "^1.4", + "guzzlehttp/psr7": "^1.6.1", "php": ">=5.5" }, "require-dev": { "ext-curl": "*", "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", - "psr/log": "^1.0" + "psr/log": "^1.1" }, "suggest": { + "ext-intl": "Required for Internationalized Domain Name (IDN) support", "psr/log": "Required for using the Log middleware" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "6.3-dev" + "dev-master": "6.5-dev" } }, "autoload": { - "files": [ - "src/functions_include.php" - ], "psr-4": { "GuzzleHttp\\": "src/" - } + }, + "files": [ + "src/functions_include.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -869,7 +844,7 @@ "rest", "web service" ], - "time": "2018-04-22T15:46:56+00:00" + "time": "2019-12-23T11:57:10+00:00" }, { "name": "guzzlehttp/promises", @@ -1100,6 +1075,7 @@ "email": "jakub.onderka@gmail.com" } ], + "abandoned": "php-parallel-lint/php-console-color", "time": "2018-09-29T17:23:10+00:00" }, { @@ -1146,25 +1122,26 @@ } ], "description": "Highlight PHP code in terminal", + "abandoned": "php-parallel-lint/php-console-highlighter", "time": "2018-09-29T18:48:56+00:00" }, { "name": "laracasts/utilities", - "version": "3.0.1", + "version": "3.1", "source": { "type": "git", "url": "https://github.com/laracasts/PHP-Vars-To-Js-Transformer.git", - "reference": "490c6cf4d23c26c94e01867265c84921863a5f19" + "reference": "7c5eb11221de608eef8c70c2d3540c8cd80466e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laracasts/PHP-Vars-To-Js-Transformer/zipball/490c6cf4d23c26c94e01867265c84921863a5f19", - "reference": "490c6cf4d23c26c94e01867265c84921863a5f19", + "url": "https://api.github.com/repos/laracasts/PHP-Vars-To-Js-Transformer/zipball/7c5eb11221de608eef8c70c2d3540c8cd80466e3", + "reference": "7c5eb11221de608eef8c70c2d3540c8cd80466e3", "shasum": "" }, "require": { - "illuminate/support": "^5.0|^6.0", - "php": ">=5.5.0" + "illuminate/support": "^5.0|^6.0|^7.0", + "php": ">=5.5.0|>=7.2.5" }, "require-dev": { "phpspec/phpspec": "~2.0" @@ -1203,30 +1180,30 @@ "javascript", "laravel" ], - "time": "2019-09-04T12:55:27+00:00" + "time": "2020-03-03T16:07:08+00:00" }, { "name": "laravel/framework", - "version": "v6.0.0", + "version": "v6.18.3", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "89c81d4dc37714d82521d05dc003b26b2a86defc" + "reference": "4e48acfaba87f08320a2764d36c3b6a4a4112ccf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/89c81d4dc37714d82521d05dc003b26b2a86defc", - "reference": "89c81d4dc37714d82521d05dc003b26b2a86defc", + "url": "https://api.github.com/repos/laravel/framework/zipball/4e48acfaba87f08320a2764d36c3b6a4a4112ccf", + "reference": "4e48acfaba87f08320a2764d36c3b6a4a4112ccf", "shasum": "" }, "require": { "doctrine/inflector": "^1.1", "dragonmantank/cron-expression": "^2.0", "egulias/email-validator": "^2.1.10", - "erusev/parsedown": "^1.7", "ext-json": "*", "ext-mbstring": "*", "ext-openssl": "*", + "league/commonmark": "^1.3", "league/flysystem": "^1.0.8", "monolog/monolog": "^1.12|^2.0", "nesbot/carbon": "^2.0", @@ -1284,16 +1261,15 @@ "aws/aws-sdk-php": "^3.0", "doctrine/dbal": "^2.6", "filp/whoops": "^2.4", - "guzzlehttp/guzzle": "^6.3", + "guzzlehttp/guzzle": "^6.3|^7.0", "league/flysystem-cached-adapter": "^1.0", - "mockery/mockery": "^1.2.3", + "mockery/mockery": "^1.3.1", "moontoast/math": "^1.1", "orchestra/testbench-core": "^4.0", "pda/pheanstalk": "^4.0", - "phpunit/phpunit": "^8.3", + "phpunit/phpunit": "^7.5.15|^8.4|^9.0", "predis/predis": "^1.1.1", - "symfony/cache": "^4.3", - "true/punycode": "^2.1" + "symfony/cache": "^4.3.4" }, "suggest": { "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.0).", @@ -1302,19 +1278,21 @@ "ext-memcached": "Required to use the memcache cache driver.", "ext-pcntl": "Required to use all features of the queue worker.", "ext-posix": "Required to use all features of the queue worker.", - "ext-redis": "Required to use the Redis cache and queue drivers.", + "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).", "filp/whoops": "Required for friendly error pages in development (^2.4).", - "fzaninotto/faker": "Required to use the eloquent factory builder (^1.4).", - "guzzlehttp/guzzle": "Required to use the Mailgun mail driver and the ping methods on schedules (^6.0).", - "laravel/tinker": "Required to use the tinker console command (^1.0).", + "fzaninotto/faker": "Required to use the eloquent factory builder (^1.9.1).", + "guzzlehttp/guzzle": "Required to use the Mailgun mail driver and the ping methods on schedules (^6.0|^7.0).", + "laravel/tinker": "Required to use the tinker console command (^2.0).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).", "league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).", "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0).", "moontoast/math": "Required to use ordered UUIDs (^1.1).", + "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).", - "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^3.0).", - "symfony/cache": "Required to PSR-6 cache bridge (^4.3).", - "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^1.1).", + "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^4.0).", + "symfony/cache": "Required to PSR-6 cache bridge (^4.3.4).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^1.2).", "wildbit/swiftmailer-postmark": "Required to use Postmark mail driver (^3.0)." }, "type": "library", @@ -1348,24 +1326,24 @@ "framework", "laravel" ], - "time": "2019-09-03T13:09:57+00:00" + "time": "2020-03-24T16:37:50+00:00" }, { "name": "laravel/helpers", - "version": "v1.1.1", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/laravel/helpers.git", - "reference": "b8eae9ddd461e89d0296f74fd069c413bf83b6fa" + "reference": "1f978fc5dad9f7f906b18242c654252615201de4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/helpers/zipball/b8eae9ddd461e89d0296f74fd069c413bf83b6fa", - "reference": "b8eae9ddd461e89d0296f74fd069c413bf83b6fa", + "url": "https://api.github.com/repos/laravel/helpers/zipball/1f978fc5dad9f7f906b18242c654252615201de4", + "reference": "1f978fc5dad9f7f906b18242c654252615201de4", "shasum": "" }, "require": { - "illuminate/support": "~5.8.0|^6.0", + "illuminate/support": "~5.8.0|^6.0|^7.0", "php": ">=7.1.3" }, "require-dev": { @@ -1401,7 +1379,7 @@ "helpers", "laravel" ], - "time": "2019-07-30T15:25:31+00:00" + "time": "2020-03-03T13:52:16+00:00" }, { "name": "laravel/tinker", @@ -1522,17 +1500,91 @@ "time": "2019-05-24T18:30:49+00:00" }, { - "name": "league/flysystem", - "version": "1.0.55", + "name": "league/commonmark", + "version": "1.3.2", "source": { "type": "git", - "url": "https://github.com/thephpleague/flysystem.git", - "reference": "33c91155537c6dc899eacdc54a13ac6303f156e6" + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "75542a366ccbe1896ed79fcf3e8e68206d6c4257" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/33c91155537c6dc899eacdc54a13ac6303f156e6", - "reference": "33c91155537c6dc899eacdc54a13ac6303f156e6", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/75542a366ccbe1896ed79fcf3e8e68206d6c4257", + "reference": "75542a366ccbe1896ed79fcf3e8e68206d6c4257", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1" + }, + "conflict": { + "scrutinizer/ocular": "1.7.*" + }, + "require-dev": { + "cebe/markdown": "~1.0", + "commonmark/commonmark.js": "0.29.1", + "erusev/parsedown": "~1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "~1.4", + "mikehaertl/php-shellcommand": "^1.4", + "phpstan/phpstan-shim": "^0.11.5", + "phpunit/phpunit": "^7.5", + "scrutinizer/ocular": "^1.5", + "symfony/finder": "^4.2" + }, + "bin": [ + "bin/commonmark" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and Github-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "time": "2020-03-25T19:55:28+00:00" + }, + { + "name": "league/flysystem", + "version": "1.0.66", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "021569195e15f8209b1c4bebb78bd66aa4f08c21" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/021569195e15f8209b1c4bebb78bd66aa4f08c21", + "reference": "021569195e15f8209b1c4bebb78bd66aa4f08c21", "shasum": "" }, "require": { @@ -1544,7 +1596,7 @@ }, "require-dev": { "phpspec/phpspec": "^3.4", - "phpunit/phpunit": "^5.7.10" + "phpunit/phpunit": "^5.7.26" }, "suggest": { "ext-fileinfo": "Required for MimeType", @@ -1603,20 +1655,26 @@ "sftp", "storage" ], - "time": "2019-08-24T11:17:19+00:00" + "funding": [ + { + "url": "https://offset.earth/frankdejonge", + "type": "other" + } + ], + "time": "2020-03-17T18:58:12+00:00" }, { "name": "league/fractal", - "version": "0.18.0", + "version": "0.19.2", "source": { "type": "git", "url": "https://github.com/thephpleague/fractal.git", - "reference": "4e553dae1a9402adbe11c81430a64675dc97b4fc" + "reference": "06dc15f6ba38f2dde2f919d3095d13b571190a7c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/fractal/zipball/4e553dae1a9402adbe11c81430a64675dc97b4fc", - "reference": "4e553dae1a9402adbe11c81430a64675dc97b4fc", + "url": "https://api.github.com/repos/thephpleague/fractal/zipball/06dc15f6ba38f2dde2f919d3095d13b571190a7c", + "reference": "06dc15f6ba38f2dde2f919d3095d13b571190a7c", "shasum": "" }, "require": { @@ -1628,7 +1686,7 @@ "mockery/mockery": "~0.9", "pagerfanta/pagerfanta": "~1.0.0", "phpunit/phpunit": "^4.8.35 || ^7.5", - "squizlabs/php_codesniffer": "~1.5", + "squizlabs/php_codesniffer": "~1.5|~2.0|~3.4", "zendframework/zend-paginator": "~2.3" }, "suggest": { @@ -1667,7 +1725,7 @@ "league", "rest" ], - "time": "2019-05-10T02:16:43+00:00" + "time": "2020-01-24T23:17:29+00:00" }, { "name": "matriphe/iso-639", @@ -1715,16 +1773,16 @@ }, { "name": "monolog/monolog", - "version": "2.0.0", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "68545165e19249013afd1d6f7485aecff07a2d22" + "reference": "c861fcba2ca29404dc9e617eedd9eff4616986b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/68545165e19249013afd1d6f7485aecff07a2d22", - "reference": "68545165e19249013afd1d6f7485aecff07a2d22", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/c861fcba2ca29404dc9e617eedd9eff4616986b8", + "reference": "c861fcba2ca29404dc9e617eedd9eff4616986b8", "shasum": "" }, "require": { @@ -1792,27 +1850,29 @@ "logging", "psr-3" ], - "time": "2019-08-30T09:56:44+00:00" + "time": "2019-12-20T14:22:59+00:00" }, { "name": "mtdowling/jmespath.php", - "version": "2.4.0", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "adcc9531682cf87dfda21e1fd5d0e7a41d292fac" + "reference": "52168cb9472de06979613d365c7f1ab8798be895" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/adcc9531682cf87dfda21e1fd5d0e7a41d292fac", - "reference": "adcc9531682cf87dfda21e1fd5d0e7a41d292fac", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/52168cb9472de06979613d365c7f1ab8798be895", + "reference": "52168cb9472de06979613d365c7f1ab8798be895", "shasum": "" }, "require": { - "php": ">=5.4.0" + "php": ">=5.4.0", + "symfony/polyfill-mbstring": "^1.4" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "composer/xdebug-handler": "^1.2", + "phpunit/phpunit": "^4.8.36|^7.5.15" }, "bin": [ "bin/jp.php" @@ -1820,7 +1880,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.5-dev" } }, "autoload": { @@ -1847,31 +1907,32 @@ "json", "jsonpath" ], - "time": "2016-12-03T22:08:25+00:00" + "time": "2019-12-30T18:03:34+00:00" }, { "name": "nesbot/carbon", - "version": "2.24.0", + "version": "2.32.2", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "934459c5ac0658bc765ad1e53512c7c77adcac29" + "reference": "f10e22cf546704fab1db4ad4b9dedbc5c797a0dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/934459c5ac0658bc765ad1e53512c7c77adcac29", - "reference": "934459c5ac0658bc765ad1e53512c7c77adcac29", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/f10e22cf546704fab1db4ad4b9dedbc5c797a0dc", + "reference": "f10e22cf546704fab1db4ad4b9dedbc5c797a0dc", "shasum": "" }, "require": { "ext-json": "*", "php": "^7.1.8 || ^8.0", - "symfony/translation": "^3.4 || ^4.0" + "symfony/translation": "^3.4 || ^4.0 || ^5.0" }, "require-dev": { + "doctrine/orm": "^2.7", "friendsofphp/php-cs-fixer": "^2.14 || ^3.0", "kylekatarnls/multi-tester": "^1.1", - "phpmd/phpmd": "dev-php-7.1-compatibility", + "phpmd/phpmd": "^2.8", "phpstan/phpstan": "^0.11", "phpunit/phpunit": "^7.5 || ^8.0", "squizlabs/php_codesniffer": "^3.4" @@ -1881,6 +1942,9 @@ ], "type": "library", "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + }, "laravel": { "providers": [ "Carbon\\Laravel\\ServiceProvider" @@ -1907,27 +1971,37 @@ "homepage": "http://github.com/kylekatarnls" } ], - "description": "A API extension for DateTime that supports 281 different languages.", + "description": "An API extension for DateTime that supports 281 different languages.", "homepage": "http://carbon.nesbot.com", "keywords": [ "date", "datetime", "time" ], - "time": "2019-08-31T16:37:55+00:00" + "funding": [ + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2020-03-31T13:43:19+00:00" }, { "name": "nikic/php-parser", - "version": "v4.2.4", + "version": "v4.3.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "97e59c7a16464196a8b9c77c47df68e4a39a45c4" + "reference": "9a9981c347c5c49d6dfe5cf826bb882b824080dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/97e59c7a16464196a8b9c77c47df68e4a39a45c4", - "reference": "97e59c7a16464196a8b9c77c47df68e4a39a45c4", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/9a9981c347c5c49d6dfe5cf826bb882b824080dc", + "reference": "9a9981c347c5c49d6dfe5cf826bb882b824080dc", "shasum": "" }, "require": { @@ -1935,6 +2009,7 @@ "php": ">=7.0" }, "require-dev": { + "ircmaxell/php-yacc": "0.0.5", "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0" }, "bin": [ @@ -1943,7 +2018,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -1965,20 +2040,20 @@ "parser", "php" ], - "time": "2019-09-01T07:51:21+00:00" + "time": "2019-11-08T13:50:10+00:00" }, { "name": "opis/closure", - "version": "3.4.0", + "version": "3.5.1", "source": { "type": "git", "url": "https://github.com/opis/closure.git", - "reference": "60a97fff133b1669a5b1776aa8ab06db3f3962b7" + "reference": "93ebc5712cdad8d5f489b500c59d122df2e53969" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/60a97fff133b1669a5b1776aa8ab06db3f3962b7", - "reference": "60a97fff133b1669a5b1776aa8ab06db3f3962b7", + "url": "https://api.github.com/repos/opis/closure/zipball/93ebc5712cdad8d5f489b500c59d122df2e53969", + "reference": "93ebc5712cdad8d5f489b500c59d122df2e53969", "shasum": "" }, "require": { @@ -1991,7 +2066,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3.x-dev" + "dev-master": "3.5.x-dev" } }, "autoload": { @@ -2026,28 +2101,28 @@ "serialization", "serialize" ], - "time": "2019-09-02T21:07:33+00:00" + "time": "2019-11-29T22:36:02+00:00" }, { "name": "paragonie/constant_time_encoding", - "version": "v2.2.3", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "55af0dc01992b4d0da7f6372e2eac097bbbaffdb" + "reference": "47a1cedd2e4d52688eb8c96469c05ebc8fd28fa2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/55af0dc01992b4d0da7f6372e2eac097bbbaffdb", - "reference": "55af0dc01992b4d0da7f6372e2eac097bbbaffdb", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/47a1cedd2e4d52688eb8c96469c05ebc8fd28fa2", + "reference": "47a1cedd2e4d52688eb8c96469c05ebc8fd28fa2", "shasum": "" }, "require": { - "php": "^7" + "php": "^7|^8" }, "require-dev": { "phpunit/phpunit": "^6|^7", - "vimeo/psalm": "^1|^2" + "vimeo/psalm": "^1|^2|^3" }, "type": "library", "autoload": { @@ -2088,7 +2163,7 @@ "hex2bin", "rfc4648" ], - "time": "2019-01-03T20:26:31+00:00" + "time": "2019-11-06T19:20:29+00:00" }, { "name": "paragonie/random_compat", @@ -2137,43 +2212,48 @@ }, { "name": "phpoption/phpoption", - "version": "1.5.0", + "version": "1.7.3", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed" + "reference": "4acfd6a4b33a509d8c88f50e5222f734b6aeebae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/94e644f7d2051a5f0fcf77d81605f152eecff0ed", - "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/4acfd6a4b33a509d8c88f50e5222f734b6aeebae", + "reference": "4acfd6a4b33a509d8c88f50e5222f734b6aeebae", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^5.5.9 || ^7.0 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "4.7.*" + "bamarni/composer-bin-plugin": "^1.3", + "phpunit/phpunit": "^4.8.35 || ^5.0 || ^6.0 || ^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-master": "1.7-dev" } }, "autoload": { - "psr-0": { - "PhpOption\\": "src/" + "psr-4": { + "PhpOption\\": "src/PhpOption/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache2" + "Apache-2.0" ], "authors": [ { "name": "Johannes M. Schmitt", "email": "schmittjoh@gmail.com" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com" } ], "description": "Option Type for PHP", @@ -2183,7 +2263,7 @@ "php", "type" ], - "time": "2015-07-25T16:39:46+00:00" + "time": "2020-03-21T18:07:53+00:00" }, { "name": "pragmarx/google2fa", @@ -2293,22 +2373,22 @@ }, { "name": "prologue/alerts", - "version": "0.4.5", + "version": "0.4.6", "source": { "type": "git", "url": "https://github.com/prologuephp/alerts.git", - "reference": "b4357dfa8abd5c58e41b8bb1fddc0781184d35da" + "reference": "4c621a541a7f16631deda9d1b2b075c182d251d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/prologuephp/alerts/zipball/b4357dfa8abd5c58e41b8bb1fddc0781184d35da", - "reference": "b4357dfa8abd5c58e41b8bb1fddc0781184d35da", + "url": "https://api.github.com/repos/prologuephp/alerts/zipball/4c621a541a7f16631deda9d1b2b075c182d251d1", + "reference": "4c621a541a7f16631deda9d1b2b075c182d251d1", "shasum": "" }, "require": { - "illuminate/config": "~5|~6", - "illuminate/session": "~5|~6", - "illuminate/support": "~5|~6", + "illuminate/config": "~5|~6|~7", + "illuminate/session": "~5|~6|~7", + "illuminate/support": "~5|~6|~7", "php": ">=5.4.0" }, "require-dev": { @@ -2355,7 +2435,7 @@ "laravel", "messages" ], - "time": "2019-09-04T04:10:03+00:00" + "time": "2020-03-03T08:33:38+00:00" }, { "name": "psr/container", @@ -2458,16 +2538,16 @@ }, { "name": "psr/log", - "version": "1.1.0", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", - "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", "shasum": "" }, "require": { @@ -2476,7 +2556,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { @@ -2501,7 +2581,7 @@ "psr", "psr-3" ], - "time": "2018-11-20T15:27:04+00:00" + "time": "2020-03-23T09:12:05+00:00" }, { "name": "psr/simple-cache", @@ -2553,27 +2633,27 @@ }, { "name": "psy/psysh", - "version": "v0.9.9", + "version": "v0.9.12", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "9aaf29575bb8293206bb0420c1e1c87ff2ffa94e" + "reference": "90da7f37568aee36b116a030c5f99c915267edd4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/9aaf29575bb8293206bb0420c1e1c87ff2ffa94e", - "reference": "9aaf29575bb8293206bb0420c1e1c87ff2ffa94e", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/90da7f37568aee36b116a030c5f99c915267edd4", + "reference": "90da7f37568aee36b116a030c5f99c915267edd4", "shasum": "" }, "require": { - "dnoegel/php-xdg-base-dir": "0.1", + "dnoegel/php-xdg-base-dir": "0.1.*", "ext-json": "*", "ext-tokenizer": "*", "jakub-onderka/php-console-highlighter": "0.3.*|0.4.*", "nikic/php-parser": "~1.3|~2.0|~3.0|~4.0", "php": ">=5.4.0", - "symfony/console": "~2.3.10|^2.4.2|~3.0|~4.0", - "symfony/var-dumper": "~2.7|~3.0|~4.0" + "symfony/console": "~2.3.10|^2.4.2|~3.0|~4.0|~5.0", + "symfony/var-dumper": "~2.7|~3.0|~4.0|~5.0" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.2", @@ -2623,7 +2703,7 @@ "interactive", "shell" ], - "time": "2018-10-13T15:16:03+00:00" + "time": "2019-12-06T14:19:43+00:00" }, { "name": "ralouphie/getallheaders", @@ -2667,44 +2747,46 @@ }, { "name": "ramsey/uuid", - "version": "3.8.0", + "version": "3.9.3", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3" + "reference": "7e1633a6964b48589b142d60542f9ed31bd37a92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/d09ea80159c1929d75b3f9c60504d613aeb4a1e3", - "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/7e1633a6964b48589b142d60542f9ed31bd37a92", + "reference": "7e1633a6964b48589b142d60542f9ed31bd37a92", "shasum": "" }, "require": { - "paragonie/random_compat": "^1.0|^2.0|9.99.99", - "php": "^5.4 || ^7.0", + "ext-json": "*", + "paragonie/random_compat": "^1 | ^2 | 9.99.99", + "php": "^5.4 | ^7 | ^8", "symfony/polyfill-ctype": "^1.8" }, "replace": { "rhumsaa/uuid": "self.version" }, "require-dev": { - "codeception/aspect-mock": "^1.0 | ~2.0.0", - "doctrine/annotations": "~1.2.0", - "goaop/framework": "1.0.0-alpha.2 | ^1.0 | ~2.1.0", - "ircmaxell/random-lib": "^1.1", - "jakub-onderka/php-parallel-lint": "^0.9.0", - "mockery/mockery": "^0.9.9", + "codeception/aspect-mock": "^1 | ^2", + "doctrine/annotations": "^1.2", + "goaop/framework": "1.0.0-alpha.2 | ^1 | ^2.1", + "jakub-onderka/php-parallel-lint": "^1", + "mockery/mockery": "^0.9.11 | ^1", "moontoast/math": "^1.1", - "php-mock/php-mock-phpunit": "^0.3|^1.1", - "phpunit/phpunit": "^4.7|^5.0|^6.5", - "squizlabs/php_codesniffer": "^2.3" + "paragonie/random-lib": "^2", + "php-mock/php-mock-phpunit": "^0.3 | ^1.1", + "phpunit/phpunit": "^4.8 | ^5.4 | ^6.5", + "squizlabs/php_codesniffer": "^3.5" }, "suggest": { "ext-ctype": "Provides support for PHP Ctype functions", "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", + "ext-openssl": "Provides the OpenSSL extension for use with the OpenSslGenerator", "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", - "ircmaxell/random-lib": "Provides RandomLib for use with the RandomLibAdapter", "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." }, @@ -2717,13 +2799,21 @@ "autoload": { "psr-4": { "Ramsey\\Uuid\\": "src/" - } + }, + "files": [ + "src/functions.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + }, { "name": "Marijn Huizendveld", "email": "marijn.huizendveld@gmail.com" @@ -2731,11 +2821,6 @@ { "name": "Thibaud Fabre", "email": "thibaud@aztech.io" - }, - { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" } ], "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", @@ -2745,7 +2830,7 @@ "identifier", "uuid" ], - "time": "2018-07-19T23:38:55+00:00" + "time": "2020-02-21T04:36:14+00:00" }, { "name": "s1lentium/iptools", @@ -2800,20 +2885,20 @@ }, { "name": "spatie/fractalistic", - "version": "2.8.0", + "version": "2.9.0", "source": { "type": "git", "url": "https://github.com/spatie/fractalistic.git", - "reference": "9d29952f4552d6fc117e1f44f89d8dfdd36c393d" + "reference": "9d7594c4e9ed2657eb017db93ab6dbb58f56e049" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/fractalistic/zipball/9d29952f4552d6fc117e1f44f89d8dfdd36c393d", - "reference": "9d29952f4552d6fc117e1f44f89d8dfdd36c393d", + "url": "https://api.github.com/repos/spatie/fractalistic/zipball/9d7594c4e9ed2657eb017db93ab6dbb58f56e049", + "reference": "9d7594c4e9ed2657eb017db93ab6dbb58f56e049", "shasum": "" }, "require": { - "league/fractal": "^0.18.0", + "league/fractal": "^0.19.0", "php": "^7.0" }, "require-dev": { @@ -2833,9 +2918,9 @@ "authors": [ { "name": "Freek Van der Herten", - "role": "Developer", "email": "freek@spatie.be", - "homepage": "https://spatie.be" + "homepage": "https://spatie.be", + "role": "Developer" } ], "description": "A developer friendly wrapper around Fractal", @@ -2847,31 +2932,31 @@ "spatie", "transform" ], - "time": "2019-05-13T07:08:06+00:00" + "time": "2020-01-31T11:54:45+00:00" }, { "name": "spatie/laravel-fractal", - "version": "5.6.0", + "version": "5.7.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-fractal.git", - "reference": "21704e4a2cf62d9c2b981395fa5b15bd06848ed5" + "reference": "482b73a7942d14087167b32c6681351c81afaeb3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-fractal/zipball/21704e4a2cf62d9c2b981395fa5b15bd06848ed5", - "reference": "21704e4a2cf62d9c2b981395fa5b15bd06848ed5", + "url": "https://api.github.com/repos/spatie/laravel-fractal/zipball/482b73a7942d14087167b32c6681351c81afaeb3", + "reference": "482b73a7942d14087167b32c6681351c81afaeb3", "shasum": "" }, "require": { - "illuminate/contracts": "~5.7.0|~5.8.0|^6.0", - "illuminate/support": "~5.7.0|~5.8.0|^6.0", + "illuminate/contracts": "~5.8.0|^6.0|^7.0", + "illuminate/support": "~5.8.0|^6.0|^7.0", "php": "^7.2", "spatie/fractalistic": "^2.5" }, "require-dev": { "dms/phpunit-arraysubset-asserts": "^0.1.0", - "orchestra/testbench": "~3.7.0|~3.8.0|^4.0" + "orchestra/testbench": "~3.8.0|^4.0|^5.0" }, "type": "library", "extra": { @@ -2915,20 +3000,26 @@ "spatie", "transform" ], - "time": "2019-09-04T07:02:37+00:00" + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + } + ], + "time": "2020-03-02T18:40:49+00:00" }, { "name": "staudenmeir/belongs-to-through", - "version": "v2.6", + "version": "v2.9", "source": { "type": "git", "url": "https://github.com/staudenmeir/belongs-to-through.git", - "reference": "1f961dcc05e23aba291eb9f3da33b405b48a06ce" + "reference": "8f16bb7b51d081d90d9b093ba6f380f71a96d79f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/staudenmeir/belongs-to-through/zipball/1f961dcc05e23aba291eb9f3da33b405b48a06ce", - "reference": "1f961dcc05e23aba291eb9f3da33b405b48a06ce", + "url": "https://api.github.com/repos/staudenmeir/belongs-to-through/zipball/8f16bb7b51d081d90d9b093ba6f380f71a96d79f", + "reference": "8f16bb7b51d081d90d9b093ba6f380f71a96d79f", "shasum": "" }, "require": { @@ -2959,20 +3050,20 @@ } ], "description": "Laravel Eloquent BelongsToThrough relationship", - "time": "2019-08-25T23:38:38+00:00" + "time": "2019-12-29T10:58:12+00:00" }, { "name": "swiftmailer/swiftmailer", - "version": "v6.2.1", + "version": "v6.2.3", "source": { "type": "git", "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "5397cd05b0a0f7937c47b0adcb4c60e5ab936b6a" + "reference": "149cfdf118b169f7840bbe3ef0d4bc795d1780c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/5397cd05b0a0f7937c47b0adcb4c60e5ab936b6a", - "reference": "5397cd05b0a0f7937c47b0adcb4c60e5ab936b6a", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/149cfdf118b169f7840bbe3ef0d4bc795d1780c9", + "reference": "149cfdf118b169f7840bbe3ef0d4bc795d1780c9", "shasum": "" }, "require": { @@ -3021,31 +3112,32 @@ "mail", "mailer" ], - "time": "2019-04-21T09:21:45+00:00" + "time": "2019-11-12T09:31:26+00:00" }, { "name": "symfony/console", - "version": "v4.3.4", + "version": "v4.4.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "de63799239b3881b8a08f8481b22348f77ed7b36" + "reference": "10bb3ee3c97308869d53b3e3d03f6ac23ff985f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/de63799239b3881b8a08f8481b22348f77ed7b36", - "reference": "de63799239b3881b8a08f8481b22348f77ed7b36", + "url": "https://api.github.com/repos/symfony/console/zipball/10bb3ee3c97308869d53b3e3d03f6ac23ff985f7", + "reference": "10bb3ee3c97308869d53b3e3d03f6ac23ff985f7", "shasum": "" }, "require": { "php": "^7.1.3", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php73": "^1.8", - "symfony/service-contracts": "^1.1" + "symfony/service-contracts": "^1.1|^2" }, "conflict": { "symfony/dependency-injection": "<3.4", - "symfony/event-dispatcher": "<4.3", + "symfony/event-dispatcher": "<4.3|>=5", + "symfony/lock": "<4.4", "symfony/process": "<3.3" }, "provide": { @@ -3053,12 +3145,12 @@ }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", "symfony/event-dispatcher": "^4.3", - "symfony/lock": "~3.4|~4.0", - "symfony/process": "~3.4|~4.0", - "symfony/var-dumper": "^4.3" + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^3.4|^4.0|^5.0", + "symfony/var-dumper": "^4.3|^5.0" }, "suggest": { "psr/log": "For using the console logger", @@ -3069,7 +3161,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -3096,29 +3188,43 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2019-08-26T08:26:39+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-03-30T11:41:10+00:00" }, { "name": "symfony/css-selector", - "version": "v4.3.4", + "version": "v5.0.7", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "c6e5e2a00db768c92c3ae131532af4e1acc7bd03" + "reference": "5f8d5271303dad260692ba73dfa21777d38e124e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/c6e5e2a00db768c92c3ae131532af4e1acc7bd03", - "reference": "c6e5e2a00db768c92c3ae131532af4e1acc7bd03", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/5f8d5271303dad260692ba73dfa21777d38e124e", + "reference": "5f8d5271303dad260692ba73dfa21777d38e124e", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.2.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -3149,20 +3255,34 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2019-08-20T14:07:54+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-03-27T16:56:45+00:00" }, { "name": "symfony/debug", - "version": "v4.3.4", + "version": "v4.4.7", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "afcdea44a2e399c1e4b52246ec8d54c715393ced" + "reference": "346636d2cae417992ecfd761979b2ab98b339a45" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/afcdea44a2e399c1e4b52246ec8d54c715393ced", - "reference": "afcdea44a2e399c1e4b52246ec8d54c715393ced", + "url": "https://api.github.com/repos/symfony/debug/zipball/346636d2cae417992ecfd761979b2ab98b339a45", + "reference": "346636d2cae417992ecfd761979b2ab98b339a45", "shasum": "" }, "require": { @@ -3173,12 +3293,12 @@ "symfony/http-kernel": "<3.4" }, "require-dev": { - "symfony/http-kernel": "~3.4|~4.0" + "symfony/http-kernel": "^3.4|^4.0|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -3205,20 +3325,104 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2019-08-20T14:27:59+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-03-27T16:54:36+00:00" }, { - "name": "symfony/event-dispatcher", - "version": "v4.3.4", + "name": "symfony/error-handler", + "version": "v4.4.7", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "429d0a1451d4c9c4abe1959b2986b88794b9b7d2" + "url": "https://github.com/symfony/error-handler.git", + "reference": "7e9828fc98aa1cf27b422fe478a84f5b0abb7358" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/429d0a1451d4c9c4abe1959b2986b88794b9b7d2", - "reference": "429d0a1451d4c9c4abe1959b2986b88794b9b7d2", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/7e9828fc98aa1cf27b422fe478a84f5b0abb7358", + "reference": "7e9828fc98aa1cf27b422fe478a84f5b0abb7358", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "psr/log": "~1.0", + "symfony/debug": "^4.4.5", + "symfony/var-dumper": "^4.4|^5.0" + }, + "require-dev": { + "symfony/http-kernel": "^4.4|^5.0", + "symfony/serializer": "^4.4|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony ErrorHandler Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-03-30T14:07:33+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v4.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "abc8e3618bfdb55e44c8c6a00abd333f831bbfed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/abc8e3618bfdb55e44c8c6a00abd333f831bbfed", + "reference": "abc8e3618bfdb55e44c8c6a00abd333f831bbfed", "shasum": "" }, "require": { @@ -3234,12 +3438,12 @@ }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/expression-language": "~3.4|~4.0", - "symfony/http-foundation": "^3.4|^4.0", - "symfony/service-contracts": "^1.1", - "symfony/stopwatch": "~3.4|~4.0" + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^3.4|^4.0|^5.0" }, "suggest": { "symfony/dependency-injection": "", @@ -3248,7 +3452,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -3275,20 +3479,34 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2019-08-26T08:55:16+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-03-27T16:54:36+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v1.1.5", + "version": "v1.1.7", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "c61766f4440ca687de1084a5c00b08e167a2575c" + "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c61766f4440ca687de1084a5c00b08e167a2575c", - "reference": "c61766f4440ca687de1084a5c00b08e167a2575c", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c43ab685673fb6c8d84220c77897b1d6cdbe1d18", + "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18", "shasum": "" }, "require": { @@ -3333,20 +3551,20 @@ "interoperability", "standards" ], - "time": "2019-06-20T06:46:26+00:00" + "time": "2019-09-17T09:54:03+00:00" }, { "name": "symfony/finder", - "version": "v4.3.4", + "version": "v4.4.7", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "86c1c929f0a4b24812e1eb109262fc3372c8e9f2" + "reference": "5729f943f9854c5781984ed4907bbb817735776b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/86c1c929f0a4b24812e1eb109262fc3372c8e9f2", - "reference": "86c1c929f0a4b24812e1eb109262fc3372c8e9f2", + "url": "https://api.github.com/repos/symfony/finder/zipball/5729f943f9854c5781984ed4907bbb817735776b", + "reference": "5729f943f9854c5781984ed4907bbb817735776b", "shasum": "" }, "require": { @@ -3355,7 +3573,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -3382,35 +3600,49 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2019-08-14T12:26:46+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-03-27T16:54:36+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.3.4", + "version": "v4.4.7", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "d804bea118ff340a12e22a79f9c7e7eb56b35adc" + "reference": "62f92509c9abfd1f73e17b8cf1b72c0bdac6611b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d804bea118ff340a12e22a79f9c7e7eb56b35adc", - "reference": "d804bea118ff340a12e22a79f9c7e7eb56b35adc", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/62f92509c9abfd1f73e17b8cf1b72c0bdac6611b", + "reference": "62f92509c9abfd1f73e17b8cf1b72c0bdac6611b", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/mime": "^4.3", + "symfony/mime": "^4.3|^5.0", "symfony/polyfill-mbstring": "~1.1" }, "require-dev": { "predis/predis": "~1.0", - "symfony/expression-language": "~3.4|~4.0" + "symfony/expression-language": "^3.4|^4.0|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -3437,37 +3669,51 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2019-08-26T08:55:16+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-03-30T14:07:33+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.3.4", + "version": "v4.4.7", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "5e0fc71be03d52cd00c423061cfd300bd6f92a52" + "reference": "f356a489e51856b99908005eb7f2c51a1dfc95dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/5e0fc71be03d52cd00c423061cfd300bd6f92a52", - "reference": "5e0fc71be03d52cd00c423061cfd300bd6f92a52", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f356a489e51856b99908005eb7f2c51a1dfc95dc", + "reference": "f356a489e51856b99908005eb7f2c51a1dfc95dc", "shasum": "" }, "require": { "php": "^7.1.3", "psr/log": "~1.0", - "symfony/debug": "~3.4|~4.0", - "symfony/event-dispatcher": "^4.3", - "symfony/http-foundation": "^4.1.1", - "symfony/polyfill-ctype": "~1.8", + "symfony/error-handler": "^4.4", + "symfony/event-dispatcher": "^4.4", + "symfony/http-foundation": "^4.4|^5.0", + "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-php73": "^1.9" }, "conflict": { "symfony/browser-kit": "<4.3", "symfony/config": "<3.4", + "symfony/console": ">=5", "symfony/dependency-injection": "<4.3", "symfony/translation": "<4.2", - "symfony/var-dumper": "<4.1.1", "twig/twig": "<1.34|<2.4,>=2" }, "provide": { @@ -3475,34 +3721,32 @@ }, "require-dev": { "psr/cache": "~1.0", - "symfony/browser-kit": "^4.3", - "symfony/config": "~3.4|~4.0", - "symfony/console": "~3.4|~4.0", - "symfony/css-selector": "~3.4|~4.0", - "symfony/dependency-injection": "^4.3", - "symfony/dom-crawler": "~3.4|~4.0", - "symfony/expression-language": "~3.4|~4.0", - "symfony/finder": "~3.4|~4.0", - "symfony/process": "~3.4|~4.0", - "symfony/routing": "~3.4|~4.0", - "symfony/stopwatch": "~3.4|~4.0", - "symfony/templating": "~3.4|~4.0", - "symfony/translation": "~4.2", - "symfony/translation-contracts": "^1.1", - "symfony/var-dumper": "^4.1.1", - "twig/twig": "^1.34|^2.4" + "symfony/browser-kit": "^4.3|^5.0", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/console": "^3.4|^4.0", + "symfony/css-selector": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^4.3|^5.0", + "symfony/dom-crawler": "^3.4|^4.0|^5.0", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/finder": "^3.4|^4.0|^5.0", + "symfony/process": "^3.4|^4.0|^5.0", + "symfony/routing": "^3.4|^4.0|^5.0", + "symfony/stopwatch": "^3.4|^4.0|^5.0", + "symfony/templating": "^3.4|^4.0|^5.0", + "symfony/translation": "^4.2|^5.0", + "symfony/translation-contracts": "^1.1|^2", + "twig/twig": "^1.34|^2.4|^3.0" }, "suggest": { "symfony/browser-kit": "", "symfony/config": "", "symfony/console": "", - "symfony/dependency-injection": "", - "symfony/var-dumper": "" + "symfony/dependency-injection": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -3529,35 +3773,52 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2019-08-26T16:47:42+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-03-30T14:59:15+00:00" }, { "name": "symfony/mime", - "version": "v4.3.4", + "version": "v5.0.7", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "987a05df1c6ac259b34008b932551353f4f408df" + "reference": "481b7d6da88922fb1e0d86a943987722b08f3955" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/987a05df1c6ac259b34008b932551353f4f408df", - "reference": "987a05df1c6ac259b34008b932551353f4f408df", + "url": "https://api.github.com/repos/symfony/mime/zipball/481b7d6da88922fb1e0d86a943987722b08f3955", + "reference": "481b7d6da88922fb1e0d86a943987722b08f3955", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": "^7.2.5", "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-mbstring": "^1.0" }, + "conflict": { + "symfony/mailer": "<4.4" + }, "require-dev": { "egulias/email-validator": "^2.1.10", - "symfony/dependency-injection": "~3.4|^4.1" + "symfony/dependency-injection": "^4.4|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -3588,20 +3849,34 @@ "mime", "mime-type" ], - "time": "2019-08-22T08:16:11+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-03-27T16:56:45+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.12.0", + "version": "v1.15.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "550ebaac289296ce228a706d0867afc34687e3f4" + "reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", - "reference": "550ebaac289296ce228a706d0867afc34687e3f4", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/4719fa9c18b0464d399f1a63bf624b42b6fa8d14", + "reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14", "shasum": "" }, "require": { @@ -3613,7 +3888,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.15-dev" } }, "autoload": { @@ -3646,20 +3921,34 @@ "polyfill", "portable" ], - "time": "2019-08-06T08:03:45+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-02-27T09:26:54+00:00" }, { "name": "symfony/polyfill-iconv", - "version": "v1.12.0", + "version": "v1.15.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "685968b11e61a347c18bf25db32effa478be610f" + "reference": "ad6d62792bfbcfc385dd34b424d4fcf9712a32c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/685968b11e61a347c18bf25db32effa478be610f", - "reference": "685968b11e61a347c18bf25db32effa478be610f", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/ad6d62792bfbcfc385dd34b424d4fcf9712a32c8", + "reference": "ad6d62792bfbcfc385dd34b424d4fcf9712a32c8", "shasum": "" }, "require": { @@ -3671,7 +3960,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.15-dev" } }, "autoload": { @@ -3705,26 +3994,40 @@ "portable", "shim" ], - "time": "2019-08-06T08:03:45+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-03-09T19:04:49+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.12.0", + "version": "v1.15.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "6af626ae6fa37d396dc90a399c0ff08e5cfc45b2" + "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/6af626ae6fa37d396dc90a399c0ff08e5cfc45b2", - "reference": "6af626ae6fa37d396dc90a399c0ff08e5cfc45b2", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf", + "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf", "shasum": "" }, "require": { "php": ">=5.3.3", "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php72": "^1.9" + "symfony/polyfill-php72": "^1.10" }, "suggest": { "ext-intl": "For best performance" @@ -3732,7 +4035,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.15-dev" } }, "autoload": { @@ -3767,20 +4070,34 @@ "portable", "shim" ], - "time": "2019-08-06T08:03:45+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-03-09T19:04:49+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.12.0", + "version": "v1.15.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17" + "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17", - "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/81ffd3a9c6d707be22e3012b827de1c9775fc5ac", + "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac", "shasum": "" }, "require": { @@ -3792,7 +4109,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.15-dev" } }, "autoload": { @@ -3826,20 +4143,34 @@ "portable", "shim" ], - "time": "2019-08-06T08:03:45+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-03-09T19:04:49+00:00" }, { "name": "symfony/polyfill-php56", - "version": "v1.12.0", + "version": "v1.15.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php56.git", - "reference": "0e3b212e96a51338639d8ce175c046d7729c3403" + "reference": "d51ec491c8ddceae7dca8dd6c7e30428f543f37d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/0e3b212e96a51338639d8ce175c046d7729c3403", - "reference": "0e3b212e96a51338639d8ce175c046d7729c3403", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/d51ec491c8ddceae7dca8dd6c7e30428f543f37d", + "reference": "d51ec491c8ddceae7dca8dd6c7e30428f543f37d", "shasum": "" }, "require": { @@ -3849,7 +4180,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.15-dev" } }, "autoload": { @@ -3882,20 +4213,34 @@ "portable", "shim" ], - "time": "2019-08-06T08:03:45+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-03-09T19:04:49+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.12.0", + "version": "v1.15.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "04ce3335667451138df4307d6a9b61565560199e" + "reference": "37b0976c78b94856543260ce09b460a7bc852747" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/04ce3335667451138df4307d6a9b61565560199e", - "reference": "04ce3335667451138df4307d6a9b61565560199e", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/37b0976c78b94856543260ce09b460a7bc852747", + "reference": "37b0976c78b94856543260ce09b460a7bc852747", "shasum": "" }, "require": { @@ -3904,7 +4249,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.15-dev" } }, "autoload": { @@ -3937,20 +4282,34 @@ "portable", "shim" ], - "time": "2019-08-06T08:03:45+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-02-27T09:26:54+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.12.0", + "version": "v1.15.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188" + "reference": "0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/2ceb49eaccb9352bff54d22570276bb75ba4a188", - "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7", + "reference": "0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7", "shasum": "" }, "require": { @@ -3959,7 +4318,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.15-dev" } }, "autoload": { @@ -3995,20 +4354,34 @@ "portable", "shim" ], - "time": "2019-08-06T08:03:45+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-02-27T09:26:54+00:00" }, { "name": "symfony/polyfill-util", - "version": "v1.12.0", + "version": "v1.15.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-util.git", - "reference": "4317de1386717b4c22caed7725350a8887ab205c" + "reference": "d8e76c104127675d0ea3df3be0f2ae24a8619027" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/4317de1386717b4c22caed7725350a8887ab205c", - "reference": "4317de1386717b4c22caed7725350a8887ab205c", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/d8e76c104127675d0ea3df3be0f2ae24a8619027", + "reference": "d8e76c104127675d0ea3df3be0f2ae24a8619027", "shasum": "" }, "require": { @@ -4017,7 +4390,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.15-dev" } }, "autoload": { @@ -4047,20 +4420,34 @@ "polyfill", "shim" ], - "time": "2019-08-06T08:03:45+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-03-02T11:55:35+00:00" }, { "name": "symfony/process", - "version": "v4.3.4", + "version": "v4.4.7", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "e89969c00d762349f078db1128506f7f3dcc0d4a" + "reference": "3e40e87a20eaf83a1db825e1fa5097ae89042db3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/e89969c00d762349f078db1128506f7f3dcc0d4a", - "reference": "e89969c00d762349f078db1128506f7f3dcc0d4a", + "url": "https://api.github.com/repos/symfony/process/zipball/3e40e87a20eaf83a1db825e1fa5097ae89042db3", + "reference": "3e40e87a20eaf83a1db825e1fa5097ae89042db3", "shasum": "" }, "require": { @@ -4069,7 +4456,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -4096,20 +4483,34 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2019-08-26T08:26:39+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-03-27T16:54:36+00:00" }, { "name": "symfony/routing", - "version": "v4.3.4", + "version": "v4.4.7", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "ff1049f6232dc5b6023b1ff1c6de56f82bcd264f" + "reference": "0f562fa613e288d7dbae6c63abbc9b33ed75a8f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/ff1049f6232dc5b6023b1ff1c6de56f82bcd264f", - "reference": "ff1049f6232dc5b6023b1ff1c6de56f82bcd264f", + "url": "https://api.github.com/repos/symfony/routing/zipball/0f562fa613e288d7dbae6c63abbc9b33ed75a8f8", + "reference": "0f562fa613e288d7dbae6c63abbc9b33ed75a8f8", "shasum": "" }, "require": { @@ -4123,11 +4524,11 @@ "require-dev": { "doctrine/annotations": "~1.2", "psr/log": "~1.0", - "symfony/config": "~4.2", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/expression-language": "~3.4|~4.0", - "symfony/http-foundation": "~3.4|~4.0", - "symfony/yaml": "~3.4|~4.0" + "symfony/config": "^4.2|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/yaml": "^3.4|^4.0|^5.0" }, "suggest": { "doctrine/annotations": "For using the annotation loader", @@ -4139,7 +4540,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -4172,24 +4573,38 @@ "uri", "url" ], - "time": "2019-08-26T08:26:39+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-03-30T11:41:10+00:00" }, { "name": "symfony/service-contracts", - "version": "v1.1.6", + "version": "v2.0.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "ea7263d6b6d5f798b56a45a5b8d686725f2719a3" + "reference": "144c5e51266b281231e947b51223ba14acf1a749" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/ea7263d6b6d5f798b56a45a5b8d686725f2719a3", - "reference": "ea7263d6b6d5f798b56a45a5b8d686725f2719a3", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/144c5e51266b281231e947b51223ba14acf1a749", + "reference": "144c5e51266b281231e947b51223ba14acf1a749", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": "^7.2.5", "psr/container": "^1.0" }, "suggest": { @@ -4198,7 +4613,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -4230,30 +4645,31 @@ "interoperability", "standards" ], - "time": "2019-08-20T14:44:19+00:00" + "time": "2019-11-18T17:27:11+00:00" }, { "name": "symfony/translation", - "version": "v4.3.4", + "version": "v4.4.7", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "28498169dd334095fa981827992f3a24d50fed0f" + "reference": "4e54d336f2eca5facad449d0b0118bb449375b76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/28498169dd334095fa981827992f3a24d50fed0f", - "reference": "28498169dd334095fa981827992f3a24d50fed0f", + "url": "https://api.github.com/repos/symfony/translation/zipball/4e54d336f2eca5facad449d0b0118bb449375b76", + "reference": "4e54d336f2eca5facad449d0b0118bb449375b76", "shasum": "" }, "require": { "php": "^7.1.3", "symfony/polyfill-mbstring": "~1.0", - "symfony/translation-contracts": "^1.1.6" + "symfony/translation-contracts": "^1.1.6|^2" }, "conflict": { "symfony/config": "<3.4", "symfony/dependency-injection": "<3.4", + "symfony/http-kernel": "<4.4", "symfony/yaml": "<3.4" }, "provide": { @@ -4261,15 +4677,14 @@ }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~3.4|~4.0", - "symfony/console": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/finder": "~2.8|~3.0|~4.0", - "symfony/http-kernel": "~3.4|~4.0", - "symfony/intl": "~3.4|~4.0", - "symfony/service-contracts": "^1.1.2", - "symfony/var-dumper": "~3.4|~4.0", - "symfony/yaml": "~3.4|~4.0" + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/console": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/finder": "~2.8|~3.0|~4.0|^5.0", + "symfony/http-kernel": "^4.4", + "symfony/intl": "^3.4|^4.0|^5.0", + "symfony/service-contracts": "^1.1.2|^2", + "symfony/yaml": "^3.4|^4.0|^5.0" }, "suggest": { "psr/log-implementation": "To use logging capability in translator", @@ -4279,7 +4694,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -4306,24 +4721,38 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2019-08-26T08:55:16+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-03-27T16:54:36+00:00" }, { "name": "symfony/translation-contracts", - "version": "v1.1.6", + "version": "v2.0.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "325b17c24f3ee23cbecfa63ba809c6d89b5fa04a" + "reference": "8cc682ac458d75557203b2f2f14b0b92e1c744ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/325b17c24f3ee23cbecfa63ba809c6d89b5fa04a", - "reference": "325b17c24f3ee23cbecfa63ba809c6d89b5fa04a", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/8cc682ac458d75557203b2f2f14b0b92e1c744ed", + "reference": "8cc682ac458d75557203b2f2f14b0b92e1c744ed", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.2.5" }, "suggest": { "symfony/translation-implementation": "" @@ -4331,7 +4760,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -4363,20 +4792,20 @@ "interoperability", "standards" ], - "time": "2019-08-02T12:15:04+00:00" + "time": "2019-11-18T17:27:11+00:00" }, { "name": "symfony/var-dumper", - "version": "v4.3.4", + "version": "v4.4.7", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "641043e0f3e615990a0f29479f9c117e8a6698c6" + "reference": "5a0c2d93006131a36cf6f767d10e2ca8333b0d4a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/641043e0f3e615990a0f29479f9c117e8a6698c6", - "reference": "641043e0f3e615990a0f29479f9c117e8a6698c6", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/5a0c2d93006131a36cf6f767d10e2ca8333b0d4a", + "reference": "5a0c2d93006131a36cf6f767d10e2ca8333b0d4a", "shasum": "" }, "require": { @@ -4390,9 +4819,9 @@ }, "require-dev": { "ext-iconv": "*", - "symfony/console": "~3.4|~4.0", - "symfony/process": "~3.4|~4.0", - "twig/twig": "~1.34|~2.4" + "symfony/console": "^3.4|^4.0|^5.0", + "symfony/process": "^4.4|^5.0", + "twig/twig": "^1.34|^2.4|^3.0" }, "suggest": { "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", @@ -4405,7 +4834,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -4439,20 +4868,34 @@ "debug", "dump" ], - "time": "2019-08-26T08:26:39+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-03-27T16:54:36+00:00" }, { "name": "symfony/yaml", - "version": "v4.4.1", + "version": "v4.4.7", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "76de473358fe802578a415d5bb43c296cf09d211" + "reference": "ef166890d821518106da3560086bfcbeb4fadfec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/76de473358fe802578a415d5bb43c296cf09d211", - "reference": "76de473358fe802578a415d5bb43c296cf09d211", + "url": "https://api.github.com/repos/symfony/yaml/zipball/ef166890d821518106da3560086bfcbeb4fadfec", + "reference": "ef166890d821518106da3560086bfcbeb4fadfec", "shasum": "" }, "require": { @@ -4498,25 +4941,41 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2019-11-12T14:51:11+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-03-30T11:41:10+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", - "version": "2.2.1", + "version": "2.2.2", "source": { "type": "git", "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", - "reference": "0ed4a2ea4e0902dac0489e6436ebcd5bbcae9757" + "reference": "dda2ee426acd6d801d5b7fd1001cde9b5f790e15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/0ed4a2ea4e0902dac0489e6436ebcd5bbcae9757", - "reference": "0ed4a2ea4e0902dac0489e6436ebcd5bbcae9757", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/dda2ee426acd6d801d5b7fd1001cde9b5f790e15", + "reference": "dda2ee426acd6d801d5b7fd1001cde9b5f790e15", "shasum": "" }, "require": { + "ext-dom": "*", + "ext-libxml": "*", "php": "^5.5 || ^7.0", - "symfony/css-selector": "^2.7 || ^3.0 || ^4.0" + "symfony/css-selector": "^2.7 || ^3.0 || ^4.0 || ^5.0" }, "require-dev": { "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" @@ -4545,20 +5004,20 @@ ], "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.", "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", - "time": "2017-11-27T11:13:29+00:00" + "time": "2019-10-24T08:53:34+00:00" }, { "name": "vlucas/phpdotenv", - "version": "v3.5.0", + "version": "v3.6.2", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "95cb0fa6c025f7f0db7fc60f81e9fb231eb2d222" + "reference": "786a947e57086cf236cefdee80784634224b99fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/95cb0fa6c025f7f0db7fc60f81e9fb231eb2d222", - "reference": "95cb0fa6c025f7f0db7fc60f81e9fb231eb2d222", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/786a947e57086cf236cefdee80784634224b99fa", + "reference": "786a947e57086cf236cefdee80784634224b99fa", "shasum": "" }, "require": { @@ -4567,12 +5026,18 @@ "symfony/polyfill-ctype": "^1.9" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.0 || ^6.0" + "ext-filter": "*", + "ext-pcre": "*", + "phpunit/phpunit": "^4.8.35 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator.", + "ext-pcre": "Required to use most of the library." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.5-dev" + "dev-master": "3.6-dev" } }, "autoload": { @@ -4602,35 +5067,39 @@ "env", "environment" ], - "time": "2019-08-27T17:00:38+00:00" + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2020-03-27T23:36:02+00:00" }, { "name": "webmozart/assert", - "version": "1.5.0", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4" + "reference": "aed98a490f9a8f78468232db345ab9cf606cf598" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/88e6d84706d09a236046d686bbea96f07b3a34f4", - "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4", + "url": "https://api.github.com/repos/webmozart/assert/zipball/aed98a490f9a8f78468232db345ab9cf606cf598", + "reference": "aed98a490f9a8f78468232db345ab9cf606cf598", "shasum": "" }, "require": { "php": "^5.3.3 || ^7.0", "symfony/polyfill-ctype": "^1.8" }, + "conflict": { + "vimeo/psalm": "<3.6.0" + }, "require-dev": { "phpunit/phpunit": "^4.8.36 || ^7.5.13" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3-dev" - } - }, "autoload": { "psr-4": { "Webmozart\\Assert\\": "src/" @@ -4652,7 +5121,7 @@ "check", "validate" ], - "time": "2019-08-24T08:43:50+00:00" + "time": "2020-02-14T12:15:55+00:00" } ], "packages-dev": [ @@ -4703,26 +5172,26 @@ }, { "name": "barryvdh/laravel-debugbar", - "version": "v3.2.8", + "version": "v3.2.9", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "18208d64897ab732f6c04a19b319fe8f1d57a9c0" + "reference": "42d5da5379a7860093f8e4032167e4cb5ebec180" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/18208d64897ab732f6c04a19b319fe8f1d57a9c0", - "reference": "18208d64897ab732f6c04a19b319fe8f1d57a9c0", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/42d5da5379a7860093f8e4032167e4cb5ebec180", + "reference": "42d5da5379a7860093f8e4032167e4cb5ebec180", "shasum": "" }, "require": { - "illuminate/routing": "^5.5|^6", - "illuminate/session": "^5.5|^6", - "illuminate/support": "^5.5|^6", - "maximebf/debugbar": "~1.15.0", + "illuminate/routing": "^5.5|^6|^7", + "illuminate/session": "^5.5|^6|^7", + "illuminate/support": "^5.5|^6|^7", + "maximebf/debugbar": "^1.15.1", "php": ">=7.0", - "symfony/debug": "^3|^4", - "symfony/finder": "^3|^4" + "symfony/debug": "^3|^4|^5", + "symfony/finder": "^3|^4|^5" }, "require-dev": { "laravel/framework": "5.5.x" @@ -4767,42 +5236,39 @@ "profiler", "webprofiler" ], - "time": "2019-08-29T07:01:03+00:00" + "time": "2020-02-25T20:42:23+00:00" }, { "name": "barryvdh/laravel-ide-helper", - "version": "v2.6.4", + "version": "v2.6.7", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "16eb4f65ee0d51b1f1182d56ae28ee00a70ce75a" + "reference": "edd69c5e0508972c81f1f7173236de2459c45814" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/16eb4f65ee0d51b1f1182d56ae28ee00a70ce75a", - "reference": "16eb4f65ee0d51b1f1182d56ae28ee00a70ce75a", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/edd69c5e0508972c81f1f7173236de2459c45814", + "reference": "edd69c5e0508972c81f1f7173236de2459c45814", "shasum": "" }, "require": { "barryvdh/reflection-docblock": "^2.0.6", "composer/composer": "^1.6", - "illuminate/console": "^5.5|^6", - "illuminate/filesystem": "^5.5|^6", - "illuminate/support": "^5.5|^6", - "php": ">=7" + "doctrine/dbal": "~2.3", + "illuminate/console": "^5.5|^6|^7", + "illuminate/filesystem": "^5.5|^6|^7", + "illuminate/support": "^5.5|^6|^7", + "php": ">=7.2" }, "require-dev": { - "doctrine/dbal": "~2.3", - "illuminate/config": "^5.5|^6", - "illuminate/view": "^5.5|^6", - "phpro/grumphp": "^0.14", - "phpunit/phpunit": "4.*", - "scrutinizer/ocular": "~1.1", + "illuminate/config": "^5.5|^6|^7", + "illuminate/view": "^5.5|^6|^7", + "mockery/mockery": "^1.3", + "orchestra/testbench": "^3|^4", + "phpro/grumphp": "^0.17.1", "squizlabs/php_codesniffer": "^3" }, - "suggest": { - "doctrine/dbal": "Load information from the database about models for phpdocs (~2.3)" - }, "type": "library", "extra": { "branch-alias": { @@ -4841,7 +5307,7 @@ "phpstorm", "sublime" ], - "time": "2019-09-03T17:51:13+00:00" + "time": "2020-02-25T20:41:32+00:00" }, { "name": "barryvdh/reflection-docblock", @@ -4894,16 +5360,16 @@ }, { "name": "codedungeon/php-cli-colors", - "version": "1.10.7", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/mikeerickson/php-cli-colors.git", - "reference": "5649ef76ec0c9ed626e95bf40fdfaf4b8efcf79b" + "reference": "9f60ac692cc790755dad47b01c1d607fe5f43b94" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mikeerickson/php-cli-colors/zipball/5649ef76ec0c9ed626e95bf40fdfaf4b8efcf79b", - "reference": "5649ef76ec0c9ed626e95bf40fdfaf4b8efcf79b", + "url": "https://api.github.com/repos/mikeerickson/php-cli-colors/zipball/9f60ac692cc790755dad47b01c1d607fe5f43b94", + "reference": "9f60ac692cc790755dad47b01c1d607fe5f43b94", "shasum": "" }, "require-dev": { @@ -4934,7 +5400,7 @@ "package", "php" ], - "time": "2018-05-17T01:34:14+00:00" + "time": "2019-12-29T22:29:29+00:00" }, { "name": "codedungeon/phpunit-result-printer", @@ -4991,16 +5457,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.2.4", + "version": "1.2.6", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527" + "reference": "47fe531de31fca4a1b997f87308e7d7804348f7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/10bb96592168a0f8e8f6dcde3532d9fa50b0b527", - "reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/47fe531de31fca4a1b997f87308e7d7804348f7e", + "reference": "47fe531de31fca4a1b997f87308e7d7804348f7e", "shasum": "" }, "require": { @@ -5011,7 +5477,7 @@ "require-dev": { "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8", "psr/log": "^1.0", - "symfony/process": "^2.5 || ^3.0 || ^4.0" + "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0" }, "type": "library", "extra": { @@ -5043,20 +5509,20 @@ "ssl", "tls" ], - "time": "2019-08-30T08:44:50+00:00" + "time": "2020-01-13T10:02:55+00:00" }, { "name": "composer/composer", - "version": "1.9.0", + "version": "1.10.1", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "314aa57fdcfc942065996f59fb73a8b3f74f3fa5" + "reference": "b912a45da3e2b22f5cb5a23e441b697a295ba011" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/314aa57fdcfc942065996f59fb73a8b3f74f3fa5", - "reference": "314aa57fdcfc942065996f59fb73a8b3f74f3fa5", + "url": "https://api.github.com/repos/composer/composer/zipball/b912a45da3e2b22f5cb5a23e441b697a295ba011", + "reference": "b912a45da3e2b22f5cb5a23e441b697a295ba011", "shasum": "" }, "require": { @@ -5069,17 +5535,17 @@ "psr/log": "^1.0", "seld/jsonlint": "^1.4", "seld/phar-utils": "^1.0", - "symfony/console": "^2.7 || ^3.0 || ^4.0", - "symfony/filesystem": "^2.7 || ^3.0 || ^4.0", - "symfony/finder": "^2.7 || ^3.0 || ^4.0", - "symfony/process": "^2.7 || ^3.0 || ^4.0" + "symfony/console": "^2.7 || ^3.0 || ^4.0 || ^5.0", + "symfony/filesystem": "^2.7 || ^3.0 || ^4.0 || ^5.0", + "symfony/finder": "^2.7 || ^3.0 || ^4.0 || ^5.0", + "symfony/process": "^2.7 || ^3.0 || ^4.0 || ^5.0" }, "conflict": { "symfony/console": "2.8.38" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7", - "phpunit/phpunit-mock-objects": "^2.3 || ^3.0" + "phpspec/prophecy": "^1.10", + "symfony/phpunit-bridge": "^3.4" }, "suggest": { "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", @@ -5092,7 +5558,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.10-dev" } }, "autoload": { @@ -5123,28 +5589,37 @@ "dependency", "package" ], - "time": "2019-08-02T18:55:33+00:00" + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2020-03-13T19:34:27+00:00" }, { "name": "composer/semver", - "version": "1.5.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e" + "reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/46d9139568ccb8d9e7cdd4539cab7347568a5e2e", - "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e", + "url": "https://api.github.com/repos/composer/semver/zipball/c6bea70230ef4dd483e6bbcab6005f682ed3a8de", + "reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "^4.5 || ^5.0.5", - "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" + "phpunit/phpunit": "^4.5 || ^5.0.5" }, "type": "library", "extra": { @@ -5185,20 +5660,20 @@ "validation", "versioning" ], - "time": "2019-03-19T17:25:45+00:00" + "time": "2020-01-13T12:06:48+00:00" }, { "name": "composer/spdx-licenses", - "version": "1.5.2", + "version": "1.5.3", "source": { "type": "git", "url": "https://github.com/composer/spdx-licenses.git", - "reference": "7ac1e6aec371357df067f8a688c3d6974df68fa5" + "reference": "0c3e51e1880ca149682332770e25977c70cf9dae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/7ac1e6aec371357df067f8a688c3d6974df68fa5", - "reference": "7ac1e6aec371357df067f8a688c3d6974df68fa5", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/0c3e51e1880ca149682332770e25977c70cf9dae", + "reference": "0c3e51e1880ca149682332770e25977c70cf9dae", "shasum": "" }, "require": { @@ -5245,28 +5720,28 @@ "spdx", "validator" ], - "time": "2019-07-29T10:31:59+00:00" + "time": "2020-02-14T07:44:31+00:00" }, { "name": "composer/xdebug-handler", - "version": "1.3.3", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f" + "reference": "1ab9842d69e64fb3a01be6b656501032d1b78cb7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/46867cbf8ca9fb8d60c506895449eb799db1184f", - "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/1ab9842d69e64fb3a01be6b656501032d1b78cb7", + "reference": "1ab9842d69e64fb3a01be6b656501032d1b78cb7", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0", + "php": "^5.3.2 || ^7.0 || ^8.0", "psr/log": "^1.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8" }, "type": "library", "autoload": { @@ -5284,39 +5759,46 @@ "email": "john-stevenson@blueyonder.co.uk" } ], - "description": "Restarts a process without xdebug.", + "description": "Restarts a process without Xdebug.", "keywords": [ "Xdebug", "performance" ], - "time": "2019-05-27T17:52:04+00:00" + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + } + ], + "time": "2020-03-01T12:26:26+00:00" }, { "name": "doctrine/annotations", - "version": "v1.7.0", + "version": "1.10.1", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "fa4c4e861e809d6a1103bd620cce63ed91aedfeb" + "reference": "5eb79f3dbdffed6544e1fc287572c0f462bd29bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/fa4c4e861e809d6a1103bd620cce63ed91aedfeb", - "reference": "fa4c4e861e809d6a1103bd620cce63ed91aedfeb", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/5eb79f3dbdffed6544e1fc287572c0f462bd29bb", + "reference": "5eb79f3dbdffed6544e1fc287572c0f462bd29bb", "shasum": "" }, "require": { "doctrine/lexer": "1.*", + "ext-tokenizer": "*", "php": "^7.1" }, "require-dev": { "doctrine/cache": "1.*", - "phpunit/phpunit": "^7.5@dev" + "phpunit/phpunit": "^7.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7.x-dev" + "dev-master": "1.9.x-dev" } }, "autoload": { @@ -5357,20 +5839,20 @@ "docblock", "parser" ], - "time": "2019-08-08T18:11:40+00:00" + "time": "2020-04-02T12:33:25+00:00" }, { "name": "doctrine/instantiator", - "version": "1.2.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "a2c590166b2133a4633738648b6b064edae0814a" + "reference": "ae466f726242e637cebdd526a7d991b9433bacf1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", - "reference": "a2c590166b2133a4633738648b6b064edae0814a", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/ae466f726242e637cebdd526a7d991b9433bacf1", + "reference": "ae466f726242e637cebdd526a7d991b9433bacf1", "shasum": "" }, "require": { @@ -5413,80 +5895,20 @@ "constructor", "instantiate" ], - "time": "2019-03-17T17:37:11+00:00" - }, - { - "name": "facebook/webdriver", - "version": "1.7.1", - "source": { - "type": "git", - "url": "https://github.com/facebook/php-webdriver.git", - "reference": "e43de70f3c7166169d0f14a374505392734160e5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/e43de70f3c7166169d0f14a374505392734160e5", - "reference": "e43de70f3c7166169d0f14a374505392734160e5", - "shasum": "" - }, - "require": { - "ext-curl": "*", - "ext-json": "*", - "ext-mbstring": "*", - "ext-zip": "*", - "php": "^5.6 || ~7.0", - "symfony/process": "^2.8 || ^3.1 || ^4.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.0", - "jakub-onderka/php-parallel-lint": "^0.9.2", - "php-coveralls/php-coveralls": "^2.0", - "php-mock/php-mock-phpunit": "^1.1", - "phpunit/phpunit": "^5.7", - "sebastian/environment": "^1.3.4 || ^2.0 || ^3.0", - "squizlabs/php_codesniffer": "^2.6", - "symfony/var-dumper": "^3.3 || ^4.0" - }, - "suggest": { - "ext-SimpleXML": "For Firefox profile creation" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-community": "1.5-dev" - } - }, - "autoload": { - "psr-4": { - "Facebook\\WebDriver\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "description": "A PHP client for Selenium WebDriver", - "homepage": "https://github.com/facebook/php-webdriver", - "keywords": [ - "facebook", - "php", - "selenium", - "webdriver" - ], - "time": "2019-06-13T08:02:18+00:00" + "time": "2019-10-21T16:45:58+00:00" }, { "name": "friendsofphp/php-cs-fixer", - "version": "v2.15.3", + "version": "v2.16.1", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "705490b0f282f21017d73561e9498d2b622ee34c" + "reference": "c8afb599858876e95e8ebfcd97812d383fa23f02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/705490b0f282f21017d73561e9498d2b622ee34c", - "reference": "705490b0f282f21017d73561e9498d2b622ee34c", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/c8afb599858876e95e8ebfcd97812d383fa23f02", + "reference": "c8afb599858876e95e8ebfcd97812d383fa23f02", "shasum": "" }, "require": { @@ -5497,15 +5919,15 @@ "ext-tokenizer": "*", "php": "^5.6 || ^7.0", "php-cs-fixer/diff": "^1.3", - "symfony/console": "^3.4.17 || ^4.1.6", - "symfony/event-dispatcher": "^3.0 || ^4.0", - "symfony/filesystem": "^3.0 || ^4.0", - "symfony/finder": "^3.0 || ^4.0", - "symfony/options-resolver": "^3.0 || ^4.0", + "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0", + "symfony/event-dispatcher": "^3.0 || ^4.0 || ^5.0", + "symfony/filesystem": "^3.0 || ^4.0 || ^5.0", + "symfony/finder": "^3.0 || ^4.0 || ^5.0", + "symfony/options-resolver": "^3.0 || ^4.0 || ^5.0", "symfony/polyfill-php70": "^1.0", "symfony/polyfill-php72": "^1.4", - "symfony/process": "^3.0 || ^4.0", - "symfony/stopwatch": "^3.0 || ^4.0" + "symfony/process": "^3.0 || ^4.0 || ^5.0", + "symfony/stopwatch": "^3.0 || ^4.0 || ^5.0" }, "require-dev": { "johnkary/phpunit-speedtrap": "^1.1 || ^2.0 || ^3.0", @@ -5518,8 +5940,8 @@ "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.1", "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.1", "phpunitgoodpractices/traits": "^1.8", - "symfony/phpunit-bridge": "^4.3", - "symfony/yaml": "^3.0 || ^4.0" + "symfony/phpunit-bridge": "^4.3 || ^5.0", + "symfony/yaml": "^3.0 || ^4.0 || ^5.0" }, "suggest": { "ext-mbstring": "For handling non-UTF8 characters in cache signature.", @@ -5562,7 +5984,7 @@ } ], "description": "A tool to automatically fix PHP code style", - "time": "2019-08-31T12:51:54+00:00" + "time": "2019-11-25T22:10:32+00:00" }, { "name": "hassankhan/config", @@ -5623,23 +6045,23 @@ }, { "name": "justinrainbow/json-schema", - "version": "5.2.8", + "version": "5.2.9", "source": { "type": "git", "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "dcb6e1006bb5fd1e392b4daa68932880f37550d4" + "reference": "44c6787311242a979fa15c704327c20e7221a0e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/dcb6e1006bb5fd1e392b4daa68932880f37550d4", - "reference": "dcb6e1006bb5fd1e392b4daa68932880f37550d4", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/44c6787311242a979fa15c704327c20e7221a0e4", + "reference": "44c6787311242a979fa15c704327c20e7221a0e4", "shasum": "" }, "require": { "php": ">=5.3.3" }, "require-dev": { - "friendsofphp/php-cs-fixer": "~2.2.20", + "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", "json-schema/json-schema-test-suite": "1.2.0", "phpunit/phpunit": "^4.8.35" }, @@ -5685,39 +6107,42 @@ "json", "schema" ], - "time": "2019-01-14T23:55:14+00:00" + "time": "2019-09-25T14:49:45+00:00" }, { "name": "laravel/dusk", - "version": "v5.5.0", + "version": "v5.11.0", "source": { "type": "git", "url": "https://github.com/laravel/dusk.git", - "reference": "525076aa34f6935b1f54d4c5a155f84182194bbb" + "reference": "e07cc46a1e39767739e8197189780b4c2639806d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/dusk/zipball/525076aa34f6935b1f54d4c5a155f84182194bbb", - "reference": "525076aa34f6935b1f54d4c5a155f84182194bbb", + "url": "https://api.github.com/repos/laravel/dusk/zipball/e07cc46a1e39767739e8197189780b4c2639806d", + "reference": "e07cc46a1e39767739e8197189780b4c2639806d", "shasum": "" }, "require": { "ext-json": "*", "ext-zip": "*", - "facebook/webdriver": "^1.7", - "illuminate/console": "~5.7.0|~5.8.0|^6.0", - "illuminate/support": "~5.7.0|~5.8.0|^6.0", + "illuminate/console": "~5.7.0|~5.8.0|^6.0|^7.0", + "illuminate/support": "~5.7.0|~5.8.0|^6.0|^7.0", "nesbot/carbon": "^1.20|^2.0", "php": ">=7.1.0", - "symfony/console": "^4.0", - "symfony/finder": "^4.0", - "symfony/process": "^4.0", - "vlucas/phpdotenv": "^2.2|^3.3" + "php-webdriver/webdriver": "^1.8.1", + "symfony/console": "^4.0|^5.0", + "symfony/finder": "^4.0|^5.0", + "symfony/process": "^4.0|^5.0", + "vlucas/phpdotenv": "^2.2|^3.0|^4.0" }, "require-dev": { "mockery/mockery": "^1.0", "phpunit/phpunit": "^7.5|^8.0" }, + "suggest": { + "ext-pcntl": "Used to gracefully terminate Dusk when tests are running." + }, "type": "library", "extra": { "branch-alias": { @@ -5750,29 +6175,29 @@ "testing", "webdriver" ], - "time": "2019-08-06T17:53:56+00:00" + "time": "2020-03-24T16:21:49+00:00" }, { "name": "maximebf/debugbar", - "version": "v1.15.0", + "version": "v1.16.1", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "30e7d60937ee5f1320975ca9bc7bcdd44d500f07" + "reference": "58998b818c6567fac01e35b8a4b70c1a64530556" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/30e7d60937ee5f1320975ca9bc7bcdd44d500f07", - "reference": "30e7d60937ee5f1320975ca9bc7bcdd44d500f07", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/58998b818c6567fac01e35b8a4b70c1a64530556", + "reference": "58998b818c6567fac01e35b8a4b70c1a64530556", "shasum": "" }, "require": { - "php": ">=5.3.0", + "php": "^7.1", "psr/log": "^1.0", - "symfony/var-dumper": "^2.6|^3.0|^4.0" + "symfony/var-dumper": "^2.6|^3|^4|^5" }, "require-dev": { - "phpunit/phpunit": "^4.0|^5.0" + "phpunit/phpunit": "^5" }, "suggest": { "kriswallsmith/assetic": "The best way to manage assets", @@ -5782,7 +6207,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.14-dev" + "dev-master": "1.16-dev" } }, "autoload": { @@ -5811,20 +6236,20 @@ "debug", "debugbar" ], - "time": "2017-12-15T11:13:46+00:00" + "time": "2019-11-24T09:46:11+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.9.3", + "version": "1.9.5", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea" + "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea", - "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/b2c28789e80a97badd14145fda39b545d83ca3ef", + "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef", "shasum": "" }, "require": { @@ -5859,7 +6284,7 @@ "object", "object graph" ], - "time": "2019-08-09T12:45:53+00:00" + "time": "2020-01-17T21:11:47+00:00" }, { "name": "phar-io/manifest", @@ -6016,33 +6441,36 @@ }, { "name": "php-mock/php-mock", - "version": "2.1.2", + "version": "2.2.1", "source": { "type": "git", "url": "https://github.com/php-mock/php-mock.git", - "reference": "35379d7b382b787215617f124662d9ead72c15e3" + "reference": "8ca7205ad5e73fbbffa9bde9f6bc90daf5e49702" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mock/php-mock/zipball/35379d7b382b787215617f124662d9ead72c15e3", - "reference": "35379d7b382b787215617f124662d9ead72c15e3", + "url": "https://api.github.com/repos/php-mock/php-mock/zipball/8ca7205ad5e73fbbffa9bde9f6bc90daf5e49702", + "reference": "8ca7205ad5e73fbbffa9bde9f6bc90daf5e49702", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "phpunit/php-text-template": "^1" + "phpunit/php-text-template": "^1 || ^2" }, "replace": { "malkusch/php-mock": "*" }, "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.0" + "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.0 || ^9.0" }, "suggest": { "php-mock/php-mock-phpunit": "Allows integration into PHPUnit testcase with the trait PHPMock." }, "type": "library", "autoload": { + "files": [ + "autoload.php" + ], "psr-4": { "phpmock\\": [ "classes/", @@ -6057,9 +6485,9 @@ "authors": [ { "name": "Markus Malkusch", - "role": "Developer", "email": "markus@malkusch.de", - "homepage": "http://markus.malkusch.de" + "homepage": "http://markus.malkusch.de", + "role": "Developer" } ], "description": "PHP-Mock can mock built-in PHP functions (e.g. time()). PHP-Mock relies on PHP's namespace fallback policy. No further extension is needed.", @@ -6073,29 +6501,29 @@ "test", "test double" ], - "time": "2019-06-05T20:10:01+00:00" + "time": "2020-02-08T14:50:32+00:00" }, { "name": "php-mock/php-mock-integration", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/php-mock/php-mock-integration.git", - "reference": "5a0d7d7755f823bc2a230cfa45058b40f9013bc4" + "reference": "003d585841e435958a02e9b986953907b8b7609b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mock/php-mock-integration/zipball/5a0d7d7755f823bc2a230cfa45058b40f9013bc4", - "reference": "5a0d7d7755f823bc2a230cfa45058b40f9013bc4", + "url": "https://api.github.com/repos/php-mock/php-mock-integration/zipball/003d585841e435958a02e9b986953907b8b7609b", + "reference": "003d585841e435958a02e9b986953907b8b7609b", "shasum": "" }, "require": { "php": ">=5.6", - "php-mock/php-mock": "^2", - "phpunit/php-text-template": "^1" + "php-mock/php-mock": "^2.2", + "phpunit/php-text-template": "^1 || ^2" }, "require-dev": { - "phpunit/phpunit": "^4|^5" + "phpunit/phpunit": "^5.7.27 || ^6 || ^7 || ^8 || ^9" }, "type": "library", "autoload": { @@ -6126,26 +6554,26 @@ "test", "test double" ], - "time": "2017-02-17T21:31:34+00:00" + "time": "2020-02-08T14:40:25+00:00" }, { "name": "php-mock/php-mock-phpunit", - "version": "2.4.0", + "version": "2.6.0", "source": { "type": "git", "url": "https://github.com/php-mock/php-mock-phpunit.git", - "reference": "04f78fe83df4855654373188aca8cccf8bf472ce" + "reference": "2877a0e58f12e91b64bf36ccd080a209dcbf6c30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mock/php-mock-phpunit/zipball/04f78fe83df4855654373188aca8cccf8bf472ce", - "reference": "04f78fe83df4855654373188aca8cccf8bf472ce", + "url": "https://api.github.com/repos/php-mock/php-mock-phpunit/zipball/2877a0e58f12e91b64bf36ccd080a209dcbf6c30", + "reference": "2877a0e58f12e91b64bf36ccd080a209dcbf6c30", "shasum": "" }, "require": { "php": ">=7", - "php-mock/php-mock-integration": "^2", - "phpunit/phpunit": "^6 || ^7 || ^8" + "php-mock/php-mock-integration": "^2.1", + "phpunit/phpunit": "^6 || ^7 || ^8 || ^9" }, "type": "library", "autoload": { @@ -6163,9 +6591,9 @@ "authors": [ { "name": "Markus Malkusch", - "role": "Developer", "email": "markus@malkusch.de", - "homepage": "http://markus.malkusch.de" + "homepage": "http://markus.malkusch.de", + "role": "Developer" } ], "description": "Mock built-in PHP functions (e.g. time()) with PHPUnit. This package relies on PHP's namespace fallback policy. No further extension is needed.", @@ -6180,39 +6608,102 @@ "test", "test double" ], - "time": "2019-06-07T12:26:51+00:00" + "time": "2020-02-08T15:44:47+00:00" }, { - "name": "phpdocumentor/reflection-common", - "version": "1.0.1", + "name": "php-webdriver/webdriver", + "version": "1.8.2", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + "url": "https://github.com/php-webdriver/php-webdriver.git", + "reference": "3308a70be084d6d7fd1ee5787b4c2e6eb4b70aab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/3308a70be084d6d7fd1ee5787b4c2e6eb4b70aab", + "reference": "3308a70be084d6d7fd1ee5787b4c2e6eb4b70aab", "shasum": "" }, "require": { - "php": ">=5.5" + "ext-curl": "*", + "ext-json": "*", + "ext-zip": "*", + "php": "^5.6 || ~7.0", + "symfony/polyfill-mbstring": "^1.12", + "symfony/process": "^2.8 || ^3.1 || ^4.0 || ^5.0" }, "require-dev": { - "phpunit/phpunit": "^4.6" + "friendsofphp/php-cs-fixer": "^2.0", + "jakub-onderka/php-parallel-lint": "^1.0", + "php-coveralls/php-coveralls": "^2.0", + "php-mock/php-mock-phpunit": "^1.1", + "phpunit/phpunit": "^5.7", + "sebastian/environment": "^1.3.4 || ^2.0 || ^3.0", + "sminnee/phpunit-mock-objects": "^3.4", + "squizlabs/php_codesniffer": "^3.5", + "symfony/var-dumper": "^3.3 || ^4.0 || ^5.0" + }, + "suggest": { + "ext-SimpleXML": "For Firefox profile creation" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.8.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src" - ] + "Facebook\\WebDriver\\": "lib/" + }, + "files": [ + "lib/Exception/TimeoutException.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.", + "homepage": "https://github.com/php-webdriver/php-webdriver", + "keywords": [ + "Chromedriver", + "geckodriver", + "php", + "selenium", + "webdriver" + ], + "time": "2020-03-04T14:40:12+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -6234,44 +6725,42 @@ "reflection", "static analysis" ], - "time": "2017-09-11T18:02:19+00:00" + "time": "2018-08-07T13:53:10+00:00" }, { "name": "phpdocumentor/reflection-docblock", - "version": "4.3.1", + "version": "5.1.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c" + "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", - "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", + "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", "shasum": "" }, "require": { - "php": "^7.0", - "phpdocumentor/reflection-common": "^1.0.0", - "phpdocumentor/type-resolver": "^0.4.0", - "webmozart/assert": "^1.0" + "ext-filter": "^7.1", + "php": "^7.2", + "phpdocumentor/reflection-common": "^2.0", + "phpdocumentor/type-resolver": "^1.0", + "webmozart/assert": "^1" }, "require-dev": { - "doctrine/instantiator": "~1.0.5", - "mockery/mockery": "^1.0", - "phpunit/phpunit": "^6.4" + "doctrine/instantiator": "^1", + "mockery/mockery": "^1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.x-dev" + "dev-master": "5.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -6282,44 +6771,46 @@ { "name": "Mike van Riel", "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2019-04-30T17:48:53+00:00" + "time": "2020-02-22T12:28:44+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "0.4.0", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + "reference": "7462d5f123dfc080dfdf26897032a6513644fc95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/7462d5f123dfc080dfdf26897032a6513644fc95", + "reference": "7462d5f123dfc080dfdf26897032a6513644fc95", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0", - "phpdocumentor/reflection-common": "^1.0" + "php": "^7.2", + "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^5.2||^4.8.24" + "ext-tokenizer": "^7.2", + "mockery/mockery": "~1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -6332,37 +6823,38 @@ "email": "me@mikevanriel.com" } ], - "time": "2017-07-14T14:27:02+00:00" + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "time": "2020-02-18T18:59:58+00:00" }, { "name": "phpspec/prophecy", - "version": "1.8.1", + "version": "v1.10.3", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76" + "reference": "451c3cd1418cf640de218914901e51b064abb093" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76", - "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", + "reference": "451c3cd1418cf640de218914901e51b064abb093", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", - "sebastian/comparator": "^1.1|^2.0|^3.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0" + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" }, "require-dev": { - "phpspec/phpspec": "^2.5|^3.2", + "phpspec/phpspec": "^2.5 || ^3.2", "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8.x-dev" + "dev-master": "1.10.x-dev" } }, "autoload": { @@ -6395,7 +6887,7 @@ "spy", "stub" ], - "time": "2019-06-13T12:50:23+00:00" + "time": "2020-03-05T15:02:03+00:00" }, { "name": "phpunit/php-code-coverage", @@ -6602,16 +7094,16 @@ }, { "name": "phpunit/php-token-stream", - "version": "3.1.0", + "version": "3.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "e899757bb3df5ff6e95089132f32cd59aac2220a" + "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e899757bb3df5ff6e95089132f32cd59aac2220a", - "reference": "e899757bb3df5ff6e95089132f32cd59aac2220a", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff", + "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff", "shasum": "" }, "require": { @@ -6647,20 +7139,20 @@ "keywords": [ "tokenizer" ], - "time": "2019-07-25T05:29:42+00:00" + "time": "2019-09-17T06:23:10+00:00" }, { "name": "phpunit/phpunit", - "version": "7.5.15", + "version": "7.5.20", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "d79c053d972856b8b941bb233e39dc521a5093f0" + "reference": "9467db479d1b0487c99733bb1e7944d32deded2c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d79c053d972856b8b941bb233e39dc521a5093f0", - "reference": "d79c053d972856b8b941bb233e39dc521a5093f0", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9467db479d1b0487c99733bb1e7944d32deded2c", + "reference": "9467db479d1b0487c99733bb1e7944d32deded2c", "shasum": "" }, "require": { @@ -6731,7 +7223,7 @@ "testing", "xunit" ], - "time": "2019-08-21T07:05:16+00:00" + "time": "2020-01-08T08:45:45+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -6900,16 +7392,16 @@ }, { "name": "sebastian/environment", - "version": "4.2.2", + "version": "4.2.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404" + "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/f2a2c8e1c97c11ace607a7a667d73d47c19fe404", - "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/464c90d7bdf5ad4e8a6aea15c091fec0603d4368", + "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368", "shasum": "" }, "require": { @@ -6949,20 +7441,20 @@ "environment", "hhvm" ], - "time": "2019-05-05T09:05:15+00:00" + "time": "2019-11-20T08:46:58+00:00" }, { "name": "sebastian/exporter", - "version": "3.1.1", + "version": "3.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "06a9a5947f47b3029d76118eb5c22802e5869687" + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/06a9a5947f47b3029d76118eb5c22802e5869687", - "reference": "06a9a5947f47b3029d76118eb5c22802e5869687", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", "shasum": "" }, "require": { @@ -7016,7 +7508,7 @@ "export", "exporter" ], - "time": "2019-08-11T12:43:14+00:00" + "time": "2019-09-14T09:02:43+00:00" }, { "name": "sebastian/global-state", @@ -7301,16 +7793,16 @@ }, { "name": "seld/jsonlint", - "version": "1.7.1", + "version": "1.7.2", "source": { "type": "git", "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "d15f59a67ff805a44c50ea0516d2341740f81a38" + "reference": "e2e5d290e4d2a4f0eb449f510071392e00e10d19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/d15f59a67ff805a44c50ea0516d2341740f81a38", - "reference": "d15f59a67ff805a44c50ea0516d2341740f81a38", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/e2e5d290e4d2a4f0eb449f510071392e00e10d19", + "reference": "e2e5d290e4d2a4f0eb449f510071392e00e10d19", "shasum": "" }, "require": { @@ -7346,20 +7838,20 @@ "parser", "validator" ], - "time": "2018-01-24T12:46:19+00:00" + "time": "2019-10-24T14:27:39+00:00" }, { "name": "seld/phar-utils", - "version": "1.0.1", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/Seldaek/phar-utils.git", - "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a" + "reference": "8800503d56b9867d43d9c303b9cbcc26016e82f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/7009b5139491975ef6486545a39f3e6dad5ac30a", - "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/8800503d56b9867d43d9c303b9cbcc26016e82f0", + "reference": "8800503d56b9867d43d9c303b9cbcc26016e82f0", "shasum": "" }, "require": { @@ -7388,32 +7880,32 @@ ], "description": "PHAR file format utilities, for when PHP phars you up", "keywords": [ - "phra" + "phar" ], - "time": "2015-10-13T18:44:15+00:00" + "time": "2020-02-14T15:25:33+00:00" }, { "name": "symfony/filesystem", - "version": "v4.3.4", + "version": "v5.0.7", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "9abbb7ef96a51f4d7e69627bc6f63307994e4263" + "reference": "ca3b87dd09fff9b771731637f5379965fbfab420" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/9abbb7ef96a51f4d7e69627bc6f63307994e4263", - "reference": "9abbb7ef96a51f4d7e69627bc6f63307994e4263", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/ca3b87dd09fff9b771731637f5379965fbfab420", + "reference": "ca3b87dd09fff9b771731637f5379965fbfab420", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": "^7.2.5", "symfony/polyfill-ctype": "~1.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -7440,29 +7932,43 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2019-08-20T14:07:54+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-03-27T16:56:45+00:00" }, { "name": "symfony/options-resolver", - "version": "v4.3.4", + "version": "v5.0.7", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "81c2e120522a42f623233968244baebd6b36cb6a" + "reference": "09dccfffd24b311df7f184aa80ee7b61ad61ed8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/81c2e120522a42f623233968244baebd6b36cb6a", - "reference": "81c2e120522a42f623233968244baebd6b36cb6a", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/09dccfffd24b311df7f184aa80ee7b61ad61ed8d", + "reference": "09dccfffd24b311df7f184aa80ee7b61ad61ed8d", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.2.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -7494,20 +8000,34 @@ "configuration", "options" ], - "time": "2019-08-08T09:29:19+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-03-27T16:56:45+00:00" }, { "name": "symfony/polyfill-php70", - "version": "v1.12.0", + "version": "v1.15.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "54b4c428a0054e254223797d2713c31e08610831" + "reference": "2a18e37a489803559284416df58c71ccebe50bf0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/54b4c428a0054e254223797d2713c31e08610831", - "reference": "54b4c428a0054e254223797d2713c31e08610831", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/2a18e37a489803559284416df58c71ccebe50bf0", + "reference": "2a18e37a489803559284416df58c71ccebe50bf0", "shasum": "" }, "require": { @@ -7517,7 +8037,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.15-dev" } }, "autoload": { @@ -7553,30 +8073,30 @@ "portable", "shim" ], - "time": "2019-08-06T08:03:45+00:00" + "time": "2020-02-27T09:26:54+00:00" }, { "name": "symfony/stopwatch", - "version": "v4.3.4", + "version": "v5.0.7", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "1e4ff456bd625be5032fac9be4294e60442e9b71" + "reference": "a1d86d30d4522423afc998f32404efa34fcf5a73" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/1e4ff456bd625be5032fac9be4294e60442e9b71", - "reference": "1e4ff456bd625be5032fac9be4294e60442e9b71", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/a1d86d30d4522423afc998f32404efa34fcf5a73", + "reference": "a1d86d30d4522423afc998f32404efa34fcf5a73", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/service-contracts": "^1.0" + "php": "^7.2.5", + "symfony/service-contracts": "^1.0|^2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -7603,7 +8123,21 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2019-08-07T11:52:19+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-03-27T16:56:45+00:00" }, { "name": "theseer/tokenizer", @@ -7657,5 +8191,6 @@ "ext-pdo_mysql": "*", "ext-zip": "*" }, - "platform-dev": [] + "platform-dev": [], + "plugin-api-version": "1.1.0" } diff --git a/config/backups.php b/config/backups.php new file mode 100644 index 000000000..57edfee30 --- /dev/null +++ b/config/backups.php @@ -0,0 +1,29 @@ + env('APP_BACKUP_DRIVER', 'local'), + + 'disks' => [ + // There is no configuration for the local disk for Wings. That configuration + // is determined by the Daemon configuration, and not the Panel. + 'local' => [], + + // Configuration for storing backups in Amazon S3. + 's3' => [ + 'region' => '', + 'access_key' => '', + 'access_secret_key' => '', + + // The S3 bucket to use for backups. + 'bucket' => '', + + // The location within the S3 bucket where backups will be stored. Backups + // are stored within a folder using the server's UUID as the name. Each + // backup for that server lives within that folder. + 'location' => '', + ], + ], +]; diff --git a/database/migrations/2020_04_03_203624_add_threads_column_to_servers_table.php b/database/migrations/2020_04_03_203624_add_threads_column_to_servers_table.php new file mode 100644 index 000000000..0371312de --- /dev/null +++ b/database/migrations/2020_04_03_203624_add_threads_column_to_servers_table.php @@ -0,0 +1,32 @@ +string('threads')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('servers', function (Blueprint $table) { + $table->dropColumn('threads'); + }); + } +} diff --git a/database/migrations/2020_04_03_230614_create_backups_table.php b/database/migrations/2020_04_03_230614_create_backups_table.php new file mode 100644 index 000000000..63dad39a0 --- /dev/null +++ b/database/migrations/2020_04_03_230614_create_backups_table.php @@ -0,0 +1,43 @@ +bigIncrements('id'); + $table->unsignedInteger('server_id'); + $table->char('uuid', 36); + $table->string('name'); + $table->text('ignored_files'); + $table->string('disk'); + $table->string('sha256_hash')->nullable(); + $table->integer('bytes')->default(0); + $table->timestamp('completed_at')->nullable(); + $table->timestamps(); + $table->softDeletes(); + + $table->unique('uuid'); + $table->foreign('server_id')->references('id')->on('servers')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('backups'); + } +} diff --git a/database/migrations/2020_04_04_131016_add_table_server_transfers.php b/database/migrations/2020_04_04_131016_add_table_server_transfers.php new file mode 100644 index 000000000..1ce37c26d --- /dev/null +++ b/database/migrations/2020_04_04_131016_add_table_server_transfers.php @@ -0,0 +1,44 @@ +increments('id'); + $table->integer('server_id')->unsigned(); + $table->integer('old_node')->unsigned(); + $table->integer('new_node')->unsigned(); + $table->integer('old_allocation')->unsigned(); + $table->integer('new_allocation')->unsigned(); + $table->string('old_additional_allocations')->nullable(); + $table->string('new_additional_allocations')->nullable(); + $table->timestamps(); + }); + + Schema::table('server_transfers', function (Blueprint $table) { + $table->foreign('server_id')->references('id')->on('servers'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('server_transfers'); + } +} diff --git a/database/migrations/2020_04_04_172331_add_successful_column_to_server_transfers.php b/database/migrations/2020_04_04_172331_add_successful_column_to_server_transfers.php new file mode 100644 index 000000000..83f183abf --- /dev/null +++ b/database/migrations/2020_04_04_172331_add_successful_column_to_server_transfers.php @@ -0,0 +1,32 @@ +tinyInteger('successful')->unsigned()->default(0); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('server_transfers', function (Blueprint $table) { + $table->dropColumn('successful'); + }); + } +} diff --git a/public/themes/pterodactyl/js/admin/server/transfer.js b/public/themes/pterodactyl/js/admin/server/transfer.js new file mode 100644 index 000000000..5c2664a86 --- /dev/null +++ b/public/themes/pterodactyl/js/admin/server/transfer.js @@ -0,0 +1,56 @@ +$(document).ready(function () { + $('#pNodeId').select2({ + placeholder: 'Select a Node', + }).change(); + + $('#pAllocation').select2({ + placeholder: 'Select a Default Allocation', + }); + + $('#pAllocationAdditional').select2({ + placeholder: 'Select Additional Allocations', + }); +}); + +$('#pNodeId').on('change', function () { + let currentNode = $(this).val(); + + $.each(Pterodactyl.nodeData, function (i, v) { + if (v.id == currentNode) { + $('#pAllocation').html('').select2({ + data: v.allocations, + placeholder: 'Select a Default Allocation', + }); + + updateAdditionalAllocations(); + } + }); +}); + +$('#pAllocation').on('change', function () { + updateAdditionalAllocations(); +}); + +function updateAdditionalAllocations() { + let currentAllocation = $('#pAllocation').val(); + let currentNode = $('#pNodeId').val(); + + $.each(Pterodactyl.nodeData, function (i, v) { + if (v.id == currentNode) { + let allocations = []; + + for (let i = 0; i < v.allocations.length; i++) { + const allocation = v.allocations[i]; + + if (allocation.id != currentAllocation) { + allocations.push(allocation); + } + } + + $('#pAllocationAdditional').html('').select2({ + data: allocations, + placeholder: 'Select Additional Allocations', + }); + } + }); +} diff --git a/resources/lang/en/admin/server.php b/resources/lang/en/admin/server.php index fa254c8d9..a697d4e9e 100644 --- a/resources/lang/en/admin/server.php +++ b/resources/lang/en/admin/server.php @@ -27,5 +27,8 @@ return [ 'details_updated' => 'Server details have been successfully updated.', 'docker_image_updated' => 'Successfully changed the default Docker image to use for this server. A reboot is required to apply this change.', 'node_required' => 'You must have at least one node configured before you can add a server to this panel.', + 'transfer_nodes_required' => 'You must have at least two nodes configured before you can transfer servers.', + 'transfer_started' => 'Server transfer has been started.', + 'transfer_not_viable' => 'The node you selected is not viable for this transfer.', ], ]; diff --git a/resources/scripts/api/getServers.ts b/resources/scripts/api/getServers.ts index b77440da2..c67322a78 100644 --- a/resources/scripts/api/getServers.ts +++ b/resources/scripts/api/getServers.ts @@ -1,9 +1,9 @@ import { rawDataToServerObject, Server } from '@/api/server/getServer'; import http, { getPaginationSet, PaginatedResult } from '@/api/http'; -export default (): Promise> => { +export default (query?: string): Promise> => { return new Promise((resolve, reject) => { - http.get(`/api/client`, { params: { include: [ 'allocation' ] } }) + http.get(`/api/client`, { params: { include: [ 'allocation' ], query } }) .then(({ data }) => resolve({ items: (data.data || []).map((datum: any) => rawDataToServerObject(datum.attributes)), pagination: getPaginationSet(data.meta.pagination), diff --git a/resources/scripts/api/getSystemPermissions.ts b/resources/scripts/api/getSystemPermissions.ts index 47016b3c9..69fb56797 100644 --- a/resources/scripts/api/getSystemPermissions.ts +++ b/resources/scripts/api/getSystemPermissions.ts @@ -1,7 +1,7 @@ -import { SubuserPermission } from '@/state/server/subusers'; +import { PanelPermissions } from '@/state/permissions'; import http from '@/api/http'; -export default (): Promise => { +export default (): Promise => { return new Promise((resolve, reject) => { http.get(`/api/client/permissions`) .then(({ data }) => resolve(data.attributes.permissions)) diff --git a/resources/scripts/api/server/backups/createServerBackup.ts b/resources/scripts/api/server/backups/createServerBackup.ts new file mode 100644 index 000000000..2616c2bf3 --- /dev/null +++ b/resources/scripts/api/server/backups/createServerBackup.ts @@ -0,0 +1,12 @@ +import { rawDataToServerBackup, ServerBackup } from '@/api/server/backups/getServerBackups'; +import http from '@/api/http'; + +export default (uuid: string, name?: string, ignore?: string): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/client/servers/${uuid}/backups`, { + name, ignore, + }) + .then(({ data }) => resolve(rawDataToServerBackup(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/backups/getServerBackups.ts b/resources/scripts/api/server/backups/getServerBackups.ts new file mode 100644 index 000000000..49f3aa24c --- /dev/null +++ b/resources/scripts/api/server/backups/getServerBackups.ts @@ -0,0 +1,32 @@ +import http, { FractalResponseData, getPaginationSet, PaginatedResult } from '@/api/http'; + +export interface ServerBackup { + uuid: string; + name: string; + ignoredFiles: string; + sha256Hash: string; + bytes: number; + createdAt: Date; + completedAt: Date | null; +} + +export const rawDataToServerBackup = ({ attributes }: FractalResponseData): ServerBackup => ({ + uuid: attributes.uuid, + name: attributes.name, + ignoredFiles: attributes.ignored_files, + sha256Hash: attributes.sha256_hash, + bytes: attributes.bytes, + createdAt: new Date(attributes.created_at), + completedAt: attributes.completed_at ? new Date(attributes.completed_at) : null, +}); + +export default (uuid: string, page?: number | string): Promise> => { + return new Promise((resolve, reject) => { + http.get(`/api/client/servers/${uuid}/backups`, { params: { page } }) + .then(({ data }) => resolve({ + items: (data.data || []).map(rawDataToServerBackup), + pagination: getPaginationSet(data.meta.pagination), + })) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/getServer.ts b/resources/scripts/api/server/getServer.ts index e6a89fd1a..31e70fcc5 100644 --- a/resources/scripts/api/server/getServer.ts +++ b/resources/scripts/api/server/getServer.ts @@ -24,6 +24,7 @@ export interface Server { disk: number; io: number; cpu: number; + threads: string; }; featureLimits: { databases: number; @@ -41,20 +42,23 @@ export const rawDataToServerObject = (data: any): Server => ({ port: data.sftp_details.port, }, description: data.description ? ((data.description.length > 0) ? data.description : null) : null, - allocations: [{ + allocations: [ { ip: data.allocation.ip, alias: null, port: data.allocation.port, default: true, - }], + } ], limits: { ...data.limits }, featureLimits: { ...data.feature_limits }, }); -export default (uuid: string): Promise => { +export default (uuid: string): Promise<[ Server, string[] ]> => { return new Promise((resolve, reject) => { http.get(`/api/client/servers/${uuid}`) - .then(response => resolve(rawDataToServerObject(response.data.attributes))) + .then(({ data }) => resolve([ + rawDataToServerObject(data.attributes), + data.meta?.is_server_owner ? ['*'] : (data.meta?.user_permissions || []), + ])) .catch(reject); }); }; diff --git a/resources/scripts/api/server/reinstallServer.ts b/resources/scripts/api/server/reinstallServer.ts new file mode 100644 index 000000000..e931d7991 --- /dev/null +++ b/resources/scripts/api/server/reinstallServer.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (uuid: string): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/client/servers/${uuid}/settings/reinstall`) + .then(() => resolve()) + .catch(reject); + }); +} diff --git a/resources/scripts/api/server/users/createOrUpdateSubuser.ts b/resources/scripts/api/server/users/createOrUpdateSubuser.ts new file mode 100644 index 000000000..fbcf78fe5 --- /dev/null +++ b/resources/scripts/api/server/users/createOrUpdateSubuser.ts @@ -0,0 +1,18 @@ +import http from '@/api/http'; +import { rawDataToServerSubuser } from '@/api/server/users/getServerSubusers'; +import { Subuser } from '@/state/server/subusers'; + +interface Params { + email: string; + permissions: string[]; +} + +export default (uuid: string, params: Params, subuser?: Subuser): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/client/servers/${uuid}/users${subuser ? `/${subuser.uuid}` : ''}`, { + ...params, + }) + .then(data => resolve(rawDataToServerSubuser(data.data))) + .catch(reject); + }); +} diff --git a/resources/scripts/api/server/users/deleteSubuser.ts b/resources/scripts/api/server/users/deleteSubuser.ts new file mode 100644 index 000000000..dccd98e69 --- /dev/null +++ b/resources/scripts/api/server/users/deleteSubuser.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (uuid: string, userId: string): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/client/servers/${uuid}/users/${userId}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/users/getServerSubusers.ts b/resources/scripts/api/server/users/getServerSubusers.ts index 0d290549d..177bde815 100644 --- a/resources/scripts/api/server/users/getServerSubusers.ts +++ b/resources/scripts/api/server/users/getServerSubusers.ts @@ -8,13 +8,13 @@ export const rawDataToServerSubuser = (data: FractalResponseData): Subuser => ({ image: data.attributes.image, twoFactorEnabled: data.attributes['2fa_enabled'], createdAt: new Date(data.attributes.created_at), - permissions: data.attributes.relationships!.permissions.attributes.permissions, - can: permission => data.attributes.relationships!.permissions.attributes.permissions.indexOf(permission) >= 0, + permissions: data.attributes.permissions || [], + can: permission => (data.attributes.permissions || []).indexOf(permission) >= 0, }); export default (uuid: string): Promise => { return new Promise((resolve, reject) => { - http.get(`/api/client/servers/${uuid}/users`, { params: { include: [ 'permissions' ] } }) + http.get(`/api/client/servers/${uuid}/users`) .then(({ data }) => resolve((data.data || []).map(rawDataToServerSubuser))) .catch(reject); }); diff --git a/resources/scripts/components/NavigationBar.tsx b/resources/scripts/components/NavigationBar.tsx index 0482069e5..7ff60bef0 100644 --- a/resources/scripts/components/NavigationBar.tsx +++ b/resources/scripts/components/NavigationBar.tsx @@ -8,6 +8,8 @@ import { faSwatchbook } from '@fortawesome/free-solid-svg-icons/faSwatchbook'; import { faCogs } from '@fortawesome/free-solid-svg-icons/faCogs'; import { useStoreState } from 'easy-peasy'; import { ApplicationStore } from '@/state'; +import { faSearch } from '@fortawesome/free-solid-svg-icons/faSearch'; +import SearchContainer from '@/components/dashboard/search/SearchContainer'; export default () => { const user = useStoreState((state: ApplicationStore) => state.user.data!); @@ -22,6 +24,7 @@ export default () => {
+ diff --git a/resources/scripts/components/auth/ForgotPasswordContainer.tsx b/resources/scripts/components/auth/ForgotPasswordContainer.tsx index 668875ca0..0f923eb38 100644 --- a/resources/scripts/components/auth/ForgotPasswordContainer.tsx +++ b/resources/scripts/components/auth/ForgotPasswordContainer.tsx @@ -4,25 +4,24 @@ import requestPasswordResetEmail from '@/api/auth/requestPasswordResetEmail'; import { httpErrorToHuman } from '@/api/http'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; import { Actions, useStoreActions } from 'easy-peasy'; -import FlashMessageRender from '@/components/FlashMessageRender'; import { ApplicationStore } from '@/state'; +import Field from '@/components/elements/Field'; +import { Formik, FormikHelpers } from 'formik'; +import { object, string } from 'yup'; + +interface Values { + email: string; +} export default () => { - const [ isSubmitting, setSubmitting ] = React.useState(false); - const [ email, setEmail ] = React.useState(''); - const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); - const handleFieldUpdate = (e: React.ChangeEvent) => setEmail(e.target.value); - - const handleSubmission = (e: React.FormEvent) => { - e.preventDefault(); - + const handleSubmission = ({ email }: Values, { setSubmitting, resetForm }: FormikHelpers) => { setSubmitting(true); clearFlashes(); requestPasswordResetEmail(email) .then(response => { - setEmail(''); + resetForm(); addFlash({ type: 'success', title: 'Success', message: response }); }) .catch(error => { @@ -33,46 +32,50 @@ export default () => { }; return ( -
-

- Request Password Reset -

- - - - -

- Enter your account email address to receive instructions on resetting your password. -

-
- -
-
- - Return to Login - -
-
-
+ + {({ isSubmitting }) => ( + + +
+ +
+
+ + Return to Login + +
+
+ )} +
); }; diff --git a/resources/scripts/components/auth/LoginContainer.tsx b/resources/scripts/components/auth/LoginContainer.tsx index 23c75b7cb..bebc6a059 100644 --- a/resources/scripts/components/auth/LoginContainer.tsx +++ b/resources/scripts/components/auth/LoginContainer.tsx @@ -2,7 +2,6 @@ import React, { useRef } from 'react'; import { Link, RouteComponentProps } from 'react-router-dom'; import login, { LoginData } from '@/api/auth/login'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; -import FlashMessageRender from '@/components/FlashMessageRender'; import { ActionCreator, Actions, useStoreActions, useStoreState } from 'easy-peasy'; import { ApplicationStore } from '@/state'; import { FormikProps, withFormik } from 'formik'; @@ -12,33 +11,12 @@ import { httpErrorToHuman } from '@/api/http'; import { FlashMessage } from '@/state/flashes'; import ReCAPTCHA from 'react-google-recaptcha'; import Spinner from '@/components/elements/Spinner'; -import styled from 'styled-components'; -import { breakpoint } from 'styled-components-breakpoint'; type OwnProps = RouteComponentProps & { clearFlashes: ActionCreator; addFlash: ActionCreator; } -const Container = styled.div` - ${breakpoint('sm')` - ${tw`w-4/5 mx-auto`} - `}; - - ${breakpoint('md')` - ${tw`p-10`} - `}; - - ${breakpoint('lg')` - ${tw`w-3/5`} - `}; - - ${breakpoint('xl')` - ${tw`w-full`} - max-width: 660px; - `}; -`; - const LoginContainer = ({ isSubmitting, setFieldValue, values, submitForm, handleSubmit }: OwnProps & FormikProps) => { const ref = useRef(null); const { enabled: recaptchaEnabled, siteKey } = useStoreState(state => state.settings.data!.recaptcha); @@ -56,66 +34,61 @@ const LoginContainer = ({ isSubmitting, setFieldValue, values, submitForm, handl return ( {ref.current && ref.current.render()} -

- Login to Continue -

- - - - + + + +
+ -
- - -
-
- -
- {recaptchaEnabled && - { - ref.current && ref.current.reset(); - setFieldValue('recaptchaData', token); - submitForm(); - }} - onExpired={() => setFieldValue('recaptchaData', null)} - /> - } -
- - Forgot password? - -
- - +
+
+ +
+ {recaptchaEnabled && + { + ref.current && ref.current.reset(); + setFieldValue('recaptchaData', token); + submitForm(); + }} + onExpired={() => setFieldValue('recaptchaData', null)} + /> + } +
+ + Forgot password? + +
+
); }; diff --git a/resources/scripts/components/auth/LoginFormContainer.tsx b/resources/scripts/components/auth/LoginFormContainer.tsx index bfdbe6782..5e291fb29 100644 --- a/resources/scripts/components/auth/LoginFormContainer.tsx +++ b/resources/scripts/components/auth/LoginFormContainer.tsx @@ -1,17 +1,47 @@ import React, { forwardRef } from 'react'; import { Form } from 'formik'; +import styled from 'styled-components'; +import { breakpoint } from 'styled-components-breakpoint'; +import FlashMessageRender from '@/components/FlashMessageRender'; -type Props = React.DetailedHTMLProps, HTMLFormElement>; +type Props = React.DetailedHTMLProps, HTMLFormElement> & { + title?: string; +} -export default forwardRef(({ ...props }, ref) => ( -
-
-
- +const Container = styled.div` + ${breakpoint('sm')` + ${tw`w-4/5 mx-auto`} + `}; + + ${breakpoint('md')` + ${tw`p-10`} + `}; + + ${breakpoint('lg')` + ${tw`w-3/5`} + `}; + + ${breakpoint('xl')` + ${tw`w-full`} + max-width: 700px; + `}; +`; + +export default forwardRef(({ title, ...props }, ref) => ( + + {title &&

+ {title} +

} + + +
+
+ +
+
+ {props.children} +
-
- {props.children} -
-
- + + )); diff --git a/resources/scripts/components/auth/ResetPasswordContainer.tsx b/resources/scripts/components/auth/ResetPasswordContainer.tsx index ec3b3ff6f..7350e725a 100644 --- a/resources/scripts/components/auth/ResetPasswordContainer.tsx +++ b/resources/scripts/components/auth/ResetPasswordContainer.tsx @@ -5,106 +5,109 @@ import { Link } from 'react-router-dom'; import performPasswordReset from '@/api/auth/performPasswordReset'; import { httpErrorToHuman } from '@/api/http'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; -import FlashMessageRender from '@/components/FlashMessageRender'; import { Actions, useStoreActions } from 'easy-peasy'; import { ApplicationStore } from '@/state'; import Spinner from '@/components/elements/Spinner'; +import { Formik, FormikHelpers } from 'formik'; +import { object, ref, string } from 'yup'; +import Field from '@/components/elements/Field'; type Props = Readonly & {}>; -export default (props: Props) => { - const [ isLoading, setIsLoading ] = useState(false); +interface Values { + password: string; + passwordConfirmation: string; +} + +export default ({ match, history, location }: Props) => { const [ email, setEmail ] = useState(''); - const [ password, setPassword ] = useState(''); - const [ passwordConfirm, setPasswordConfirm ] = useState(''); const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); - const parsed = parse(props.location.search); + const parsed = parse(location.search); if (email.length === 0 && parsed.email) { setEmail(parsed.email as string); } - const canSubmit = () => password && email && password.length >= 8 && password === passwordConfirm; - - const submit = (e: React.FormEvent) => { - e.preventDefault(); - - if (!password || !email || !passwordConfirm) { - return; - } - - setIsLoading(true); + const submit = ({ password, passwordConfirmation }: Values, { setSubmitting }: FormikHelpers) => { clearFlashes(); - - performPasswordReset(email, { - token: props.match.params.token, password, passwordConfirmation: passwordConfirm, - }) + performPasswordReset(email, { token: match.params.token, password, passwordConfirmation }) .then(() => { - addFlash({ type: 'success', message: 'Your password has been reset, please login to continue.' }); - props.history.push('/auth/login'); + // @ts-ignore + window.location = '/'; }) .catch(error => { console.error(error); + + setSubmitting(false); addFlash({ type: 'error', title: 'Error', message: httpErrorToHuman(error) }); - }) - .then(() => setIsLoading(false)); + }); }; return ( -
-

- Reset Password -

- - - - -
- - setPassword(e.target.value)} - /> -

- Passwords must be at least 8 characters in length. -

-
-
- - setPasswordConfirm(e.target.value)} - /> -
-
- -
-
- - Return to Login - -
-
-
+ + {({ isSubmitting }) => ( + +
+ + +
+
+ +
+
+ +
+
+ +
+
+ + Return to Login + +
+
+ )} +
); }; diff --git a/resources/scripts/components/dashboard/AccountApiContainer.tsx b/resources/scripts/components/dashboard/AccountApiContainer.tsx index 7fbe21626..51f1f7806 100644 --- a/resources/scripts/components/dashboard/AccountApiContainer.tsx +++ b/resources/scripts/components/dashboard/AccountApiContainer.tsx @@ -46,62 +46,67 @@ export default () => { }; return ( -
+
- - setKeys(s => ([...s!, key]))}/> - - - - {deleteIdentifier && - { - doDeletion(deleteIdentifier); - setDeleteIdentifier(''); - }} - onCanceled={() => setDeleteIdentifier('')} - > - Are you sure you wish to delete this API key? All requests using it will immediately be - invalidated and will fail. - - } - { - keys.length === 0 ? -

- {loading ? 'Loading...' : 'No API keys exist for this account.'} -

- : - keys.map(key => ( -
- -
-

{key.description}

-

- Last - used: {key.lastUsedAt ? format(key.lastUsedAt, 'MMM Do, YYYY HH:mm') : 'Never'} -

-
-

- - {key.identifier} - -

- -
- )) - } -
+ +
+

{key.description}

+

+ Last + used: {key.lastUsedAt ? format(key.lastUsedAt, 'MMM Do, YYYY HH:mm') : 'Never'} +

+
+

+ + {key.identifier} + +

+ +
+ )) + } + +
); }; diff --git a/resources/scripts/components/dashboard/search/SearchContainer.tsx b/resources/scripts/components/dashboard/search/SearchContainer.tsx new file mode 100644 index 000000000..475d65510 --- /dev/null +++ b/resources/scripts/components/dashboard/search/SearchContainer.tsx @@ -0,0 +1,32 @@ +import React, { useState } from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faSearch } from '@fortawesome/free-solid-svg-icons/faSearch'; +import useEventListener from '@/plugins/useEventListener'; +import SearchModal from '@/components/dashboard/search/SearchModal'; + +export default () => { + const [ visible, setVisible ] = useState(false); + + useEventListener('keydown', (e: KeyboardEvent) => { + if ([ 'input', 'textarea' ].indexOf(((e.target as HTMLElement).tagName || 'input').toLowerCase()) < 0) { + if (!visible && e.key.toLowerCase() === 'k') { + setVisible(true); + } + } + }); + + return ( + <> + {visible && + setVisible(false)} + /> + } +
setVisible(true)}> + +
+ + ); +}; diff --git a/resources/scripts/components/dashboard/search/SearchModal.tsx b/resources/scripts/components/dashboard/search/SearchModal.tsx new file mode 100644 index 000000000..af6f6fcca --- /dev/null +++ b/resources/scripts/components/dashboard/search/SearchModal.tsx @@ -0,0 +1,123 @@ +import React, { useEffect, useRef, useState } from 'react'; +import Modal, { RequiredModalProps } from '@/components/elements/Modal'; +import { Field, Form, Formik, FormikHelpers, useFormikContext } from 'formik'; +import { Actions, useStoreActions, useStoreState } from 'easy-peasy'; +import { object, string } from 'yup'; +import { debounce } from 'lodash-es'; +import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper'; +import InputSpinner from '@/components/elements/InputSpinner'; +import getServers from '@/api/getServers'; +import { Server } from '@/api/server/getServer'; +import { ApplicationStore } from '@/state'; +import { httpErrorToHuman } from '@/api/http'; +import { Link } from 'react-router-dom'; + +type Props = RequiredModalProps; + +interface Values { + term: string; +} + +const SearchWatcher = () => { + const { values, submitForm } = useFormikContext(); + + useEffect(() => { + if (values.term.length >= 3) { + submitForm(); + } + }, [ values.term ]); + + return null; +}; + +export default ({ ...props }: Props) => { + const ref = useRef(null); + const [ loading, setLoading ] = useState(false); + const [ servers, setServers ] = useState([]); + const isAdmin = useStoreState(state => state.user.data!.rootAdmin); + const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); + + const search = debounce(({ term }: Values, { setSubmitting }: FormikHelpers) => { + setLoading(true); + setSubmitting(false); + clearFlashes('search'); + getServers(term) + .then(servers => setServers(servers.items.filter((_, index) => index < 5))) + .catch(error => { + console.error(error); + addError({ key: 'search', message: httpErrorToHuman(error) }); + }) + .then(() => setLoading(false)); + }, 500); + + useEffect(() => { + if (props.visible) { + setTimeout(() => ref.current?.focus(), 250); + } + }, [ props.visible ]); + + return ( + + +
+ + + + + + +
+ {servers.length > 0 && +
+ { + servers.map(server => ( + props.onDismissed()} + > +
+

{server.name}

+

+ { + server.allocations.filter(alloc => alloc.default).map(allocation => ( + {allocation.alias || allocation.ip}:{allocation.port} + )) + } +

+
+
+ + {server.node} + +
+ + )) + } +
+ } +
+
+ ); +}; diff --git a/resources/scripts/components/elements/Can.tsx b/resources/scripts/components/elements/Can.tsx new file mode 100644 index 000000000..4ea140d3e --- /dev/null +++ b/resources/scripts/components/elements/Can.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { usePermissions } from '@/plugins/usePermissions'; + +interface Props { + action: string | string[]; + matchAny?: boolean; + renderOnError?: React.ReactNode | null; + children: React.ReactNode; +} + +const Can = ({ action, matchAny = false, renderOnError, children }: Props) => { + const can = usePermissions(action); + + return ( + <> + { + ((matchAny && can.filter(p => p).length > 0) || (!matchAny && can.every(p => p))) ? + children + : + renderOnError + } + + ); +}; + +export default Can; diff --git a/resources/scripts/components/elements/ConfirmationModal.tsx b/resources/scripts/components/elements/ConfirmationModal.tsx index 0796a5866..127a43faa 100644 --- a/resources/scripts/components/elements/ConfirmationModal.tsx +++ b/resources/scripts/components/elements/ConfirmationModal.tsx @@ -1,25 +1,25 @@ import React from 'react'; -import Modal from '@/components/elements/Modal'; +import Modal, { RequiredModalProps } from '@/components/elements/Modal'; -interface Props { +type Props = { title: string; buttonText: string; children: string; - visible: boolean; onConfirmed: () => void; - onCanceled: () => void; -} + showSpinnerOverlay?: boolean; +} & RequiredModalProps; -const ConfirmationModal = ({ title, children, visible, buttonText, onConfirmed, onCanceled }: Props) => ( +const ConfirmationModal = ({ title, appear, children, visible, buttonText, onConfirmed, showSpinnerOverlay, onDismissed }: Props) => ( onCanceled()} + showSpinnerOverlay={showSpinnerOverlay} + onDismissed={() => onDismissed()} >

{title}

{children}

- - - sendPowerCommand(action)}/> -
+ +
+ + + + + + + + sendPowerCommand(action)}/> + +
+
diff --git a/resources/scripts/components/server/backups/BackupContainer.tsx b/resources/scripts/components/server/backups/BackupContainer.tsx new file mode 100644 index 000000000..dee9342c9 --- /dev/null +++ b/resources/scripts/components/server/backups/BackupContainer.tsx @@ -0,0 +1,60 @@ +import React, { useEffect, useState } from 'react'; +import Spinner from '@/components/elements/Spinner'; +import getServerBackups, { ServerBackup } from '@/api/server/backups/getServerBackups'; +import useServer from '@/plugins/useServer'; +import useFlash from '@/plugins/useFlash'; +import { httpErrorToHuman } from '@/api/http'; +import Can from '@/components/elements/Can'; +import CreateBackupButton from '@/components/server/backups/CreateBackupButton'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import BackupRow from '@/components/server/backups/BackupRow'; + +export default () => { + const { uuid } = useServer(); + const { addError, clearFlashes } = useFlash(); + const [ loading, setLoading ] = useState(true); + const [ backups, setBackups ] = useState([]); + + useEffect(() => { + clearFlashes('backups'); + getServerBackups(uuid) + .then(data => { + setBackups(data.items); + }) + .catch(error => { + console.error(error); + addError({ key: 'backups', message: httpErrorToHuman(error) }); + }) + .then(() => setLoading(false)); + }, []); + + if (loading) { + return ; + } + + return ( +
+ + {!backups.length ? +

+ There are no backups stored for this server. +

+ : +
+ {backups.map((backup, index) => )} +
+ } + +
+ setBackups(s => [...s, backup])} + /> +
+
+
+ ); +}; diff --git a/resources/scripts/components/server/backups/BackupRow.tsx b/resources/scripts/components/server/backups/BackupRow.tsx new file mode 100644 index 000000000..406372e12 --- /dev/null +++ b/resources/scripts/components/server/backups/BackupRow.tsx @@ -0,0 +1,95 @@ +import React, { useState } from 'react'; +import { ServerBackup } from '@/api/server/backups/getServerBackups'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faArchive } from '@fortawesome/free-solid-svg-icons/faArchive'; +import format from 'date-fns/format'; +import distanceInWordsToNow from 'date-fns/distance_in_words_to_now'; +import Spinner from '@/components/elements/Spinner'; +import { faCloudDownloadAlt } from '@fortawesome/free-solid-svg-icons/faCloudDownloadAlt'; +import Modal, { RequiredModalProps } from '@/components/elements/Modal'; +import { bytesToHuman } from '@/helpers'; +import Can from '@/components/elements/Can'; +import { join } from "path"; +import useServer from '@/plugins/useServer'; + +interface Props { + backup: ServerBackup; + className?: string; +} + +const DownloadModal = ({ checksum, ...props }: RequiredModalProps & { checksum: string }) => ( + +

Verify file checksum

+

+ The SHA256 checksum of this file is: +

+
+            {checksum}
+        
+
+); + +export default ({ backup, className }: Props) => { + const { uuid } = useServer(); + const [ visible, setVisible ] = useState(false); + + return ( +
+ {visible && + setVisible(false)} + checksum={backup.sha256Hash} + /> + } +
+ {backup.completedAt ? + + : + + } +
+
+

+ {backup.name} + {backup.completedAt && + {bytesToHuman(backup.bytes)} + } +

+

+ {backup.uuid} +

+
+
+

+ {distanceInWordsToNow(backup.createdAt, { includeSeconds: true, addSuffix: true })} +

+

Created

+
+ + + +
+ ); +}; diff --git a/resources/scripts/components/server/backups/CreateBackupButton.tsx b/resources/scripts/components/server/backups/CreateBackupButton.tsx new file mode 100644 index 000000000..256a88325 --- /dev/null +++ b/resources/scripts/components/server/backups/CreateBackupButton.tsx @@ -0,0 +1,118 @@ +import React, { useEffect, useState } from 'react'; +import Modal, { RequiredModalProps } from '@/components/elements/Modal'; +import { Field as FormikField, Form, Formik, FormikHelpers, useFormikContext } from 'formik'; +import { object, string } from 'yup'; +import Field from '@/components/elements/Field'; +import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper'; +import useFlash from '@/plugins/useFlash'; +import useServer from '@/plugins/useServer'; +import createServerBackup from '@/api/server/backups/createServerBackup'; +import { httpErrorToHuman } from '@/api/http'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import { ServerBackup } from '@/api/server/backups/getServerBackups'; + +interface Values { + name: string; + ignored: string; +} + +interface Props { + onBackupGenerated: (backup: ServerBackup) => void; +} + +const ModalContent = ({ ...props }: RequiredModalProps) => { + const { isSubmitting } = useFormikContext(); + + return ( + +
+ +

Create server backup

+
+ +
+
+ + + +
+
+ +
+ +
+ ); +}; + +export default ({ onBackupGenerated }: Props) => { + const { uuid } = useServer(); + const { addError, clearFlashes } = useFlash(); + const [ visible, setVisible ] = useState(false); + + useEffect(() => { + clearFlashes('backups:create'); + }, [visible]); + + const submit = ({ name, ignored }: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('backups:create') + createServerBackup(uuid, name, ignored) + .then(backup => { + onBackupGenerated(backup); + setVisible(false); + }) + .catch(error => { + console.error(error); + addError({ key: 'backups:create', message: httpErrorToHuman(error) }); + setSubmitting(false); + }); + }; + + return ( + <> + {visible && + + setVisible(false)} + /> + + } + + + ); +}; diff --git a/resources/scripts/components/server/databases/DatabaseRow.tsx b/resources/scripts/components/server/databases/DatabaseRow.tsx index 4b4b1d3b0..1fd07774d 100644 --- a/resources/scripts/components/server/databases/DatabaseRow.tsx +++ b/resources/scripts/components/server/databases/DatabaseRow.tsx @@ -1,5 +1,4 @@ import React, { useState } from 'react'; -import { ServerDatabase } from '@/api/server/getServerDatabases'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faDatabase } from '@fortawesome/free-solid-svg-icons/faDatabase'; import { faTrashAlt } from '@fortawesome/free-solid-svg-icons/faTrashAlt'; @@ -16,6 +15,7 @@ import { ServerContext } from '@/state/server'; import deleteServerDatabase from '@/api/server/deleteServerDatabase'; import { httpErrorToHuman } from '@/api/http'; import RotatePasswordButton from '@/components/server/databases/RotatePasswordButton'; +import Can from '@/components/elements/Can'; interface Props { databaseId: string | number; @@ -24,10 +24,10 @@ interface Props { } export default ({ databaseId, className, onDelete }: Props) => { - const [visible, setVisible] = useState(false); + const [ visible, setVisible ] = useState(false); const database = ServerContext.useStoreState(state => state.databases.items.find(item => item.id === databaseId)); const appendDatabase = ServerContext.useStoreActions(actions => actions.databases.appendDatabase); - const [connectionVisible, setConnectionVisible] = useState(false); + const [ connectionVisible, setConnectionVisible ] = useState(false); const { addFlash, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); const server = ServerContext.useStoreState(state => state.server.data!); @@ -38,7 +38,7 @@ export default ({ databaseId, className, onDelete }: Props) => { const schema = object().shape({ confirm: string() .required('The database name must be provided.') - .oneOf([database.name.split('_', 2)[1], database.name], 'The database name must be provided.'), + .oneOf([ database.name.split('_', 2)[1], database.name ], 'The database name must be provided.'), }); const submit = (values: { confirm: string }, { setSubmitting }: FormikHelpers<{ confirm: string }>) => { @@ -73,7 +73,10 @@ export default ({ databaseId, className, onDelete }: Props) => { visible={visible} dismissable={!isSubmitting} showSpinnerOverlay={isSubmitting} - onDismissed={() => { setVisible(false); resetForm(); }} + onDismissed={() => { + setVisible(false); + resetForm(); + }} >

Confirm database deletion

@@ -113,10 +116,12 @@ export default ({ databaseId, className, onDelete }: Props) => { setConnectionVisible(false)}>

Database connection details

-
- - -
+ +
+ + +
+
{ />
- + + + @@ -156,9 +163,11 @@ export default ({ databaseId, className, onDelete }: Props) => { - + + +
diff --git a/resources/scripts/components/server/databases/DatabasesContainer.tsx b/resources/scripts/components/server/databases/DatabasesContainer.tsx index 30136423f..a5335f5c0 100644 --- a/resources/scripts/components/server/databases/DatabasesContainer.tsx +++ b/resources/scripts/components/server/databases/DatabasesContainer.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import getServerDatabases, { ServerDatabase } from '@/api/server/getServerDatabases'; +import getServerDatabases from '@/api/server/getServerDatabases'; import { ServerContext } from '@/state/server'; import { Actions, useStoreActions } from 'easy-peasy'; import { ApplicationStore } from '@/state'; @@ -9,6 +9,7 @@ import DatabaseRow from '@/components/server/databases/DatabaseRow'; import Spinner from '@/components/elements/Spinner'; import { CSSTransition } from 'react-transition-group'; import CreateDatabaseButton from '@/components/server/databases/CreateDatabaseButton'; +import Can from '@/components/elements/Can'; export default () => { const [ loading, setLoading ] = useState(true); @@ -41,7 +42,7 @@ export default () => { : - + <> {databases.length > 0 ? databases.map((database, index) => ( { :

{server.featureLimits.databases > 0 ? - `It looks like you have no databases. Click the button below to create one now.` + `It looks like you have no databases.` : `Databases cannot be created for this server.` }

} - {server.featureLimits.databases > 0 && -
- -
- } -
+ + {server.featureLimits.databases > 0 && +
+ +
+ } +
+
} diff --git a/resources/scripts/components/server/files/FileDropdownMenu.tsx b/resources/scripts/components/server/files/FileDropdownMenu.tsx index 07bec2912..5db5d90e6 100644 --- a/resources/scripts/components/server/files/FileDropdownMenu.tsx +++ b/resources/scripts/components/server/files/FileDropdownMenu.tsx @@ -13,7 +13,8 @@ import { join } from 'path'; import deleteFile from '@/api/server/files/deleteFile'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import copyFile from '@/api/server/files/copyFile'; -import http, { httpErrorToHuman } from '@/api/http'; +import { httpErrorToHuman } from '@/api/http'; +import Can from '@/components/elements/Can'; type ModalType = 'rename' | 'move'; @@ -118,30 +119,37 @@ export default ({ uuid }: { uuid: string }) => {
{ e.stopPropagation(); setMenuVisible(false); }} + onClick={e => { + e.stopPropagation(); + setMenuVisible(false); + }} className={'absolute bg-white p-2 rounded border border-neutral-700 shadow-lg text-neutral-500 min-w-48'} > -
setModal('rename')} - className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'} - > - - Rename -
-
setModal('move')} - className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'} - > - - Move -
-
doCopy()} - className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'} - > - - Copy -
+ +
setModal('rename')} + className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'} + > + + Rename +
+
setModal('move')} + className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'} + > + + Move +
+
+ +
doCopy()} + className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'} + > + + Copy +
+
doDownload()} @@ -149,13 +157,15 @@ export default ({ uuid }: { uuid: string }) => { Download
-
doDeletion()} - className={'hover:text-red-700 p-2 flex items-center hover:bg-red-100 rounded'} - > - - Delete -
+ +
doDeletion()} + className={'hover:text-red-700 p-2 flex items-center hover:bg-red-100 rounded'} + > + + Delete +
+
diff --git a/resources/scripts/components/server/files/FileEditContainer.tsx b/resources/scripts/components/server/files/FileEditContainer.tsx index 2d0de6576..a44698fd9 100644 --- a/resources/scripts/components/server/files/FileEditContainer.tsx +++ b/resources/scripts/components/server/files/FileEditContainer.tsx @@ -2,7 +2,7 @@ import React, { lazy, useEffect, useState } from 'react'; import { ServerContext } from '@/state/server'; import getFileContents from '@/api/server/files/getFileContents'; import useRouter from 'use-react-router'; -import { Actions, useStoreState } from 'easy-peasy'; +import { Actions, useStoreActions, useStoreState } from 'easy-peasy'; import { ApplicationStore } from '@/state'; import { httpErrorToHuman } from '@/api/http'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; @@ -10,6 +10,8 @@ import saveFileContents from '@/api/server/files/saveFileContents'; import FileManagerBreadcrumbs from '@/components/server/files/FileManagerBreadcrumbs'; import { useParams } from 'react-router'; import FileNameModal from '@/components/server/files/FileNameModal'; +import Can from '@/components/elements/Can'; +import FlashMessageRender from '@/components/FlashMessageRender'; const LazyAceEditor = lazy(() => import(/* webpackChunkName: "editor" */'@/components/elements/AceEditor')); @@ -21,15 +23,20 @@ export default () => { const [ modalVisible, setModalVisible ] = useState(false); const { id, uuid } = ServerContext.useStoreState(state => state.server.data!); - const addError = useStoreState((state: Actions) => state.flashes.addError); + const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); let fetchFileContent: null | (() => Promise) = null; if (action !== 'new') { useEffect(() => { + setLoading(true); + clearFlashes('files:view'); getFileContents(uuid, hash.replace(/^#/, '')) .then(setContent) - .catch(error => console.error(error)) + .catch(error => { + console.error(error); + addError({ key: 'files:view', message: httpErrorToHuman(error) }); + }) .then(() => setLoading(false)); }, [ uuid, hash ]); } @@ -40,10 +47,10 @@ export default () => { } setLoading(true); - fetchFileContent() - .then(content => { - return saveFileContents(uuid, name || hash.replace(/^#/, ''), content); - }) + clearFlashes('files:view'); + fetchFileContent().then(content => { + return saveFileContents(uuid, name || hash.replace(/^#/, ''), content); + }) .then(() => { if (name) { history.push(`/server/${id}/files/edit#/${name}`); @@ -54,13 +61,14 @@ export default () => { }) .catch(error => { console.error(error); - addError({ message: httpErrorToHuman(error), key: 'files' }); + addError({ message: httpErrorToHuman(error), key: 'files:view' }); }) .then(() => setLoading(false)); }; return (
+ {
{action === 'edit' ? - + + + : - + + + }
diff --git a/resources/scripts/components/server/files/FileManagerContainer.tsx b/resources/scripts/components/server/files/FileManagerContainer.tsx index bec0c2a31..41adcd8e2 100644 --- a/resources/scripts/components/server/files/FileManagerContainer.tsx +++ b/resources/scripts/components/server/files/FileManagerContainer.tsx @@ -11,6 +11,7 @@ import FileManagerBreadcrumbs from '@/components/server/files/FileManagerBreadcr import { FileObject } from '@/api/server/files/loadDirectory'; import NewDirectoryButton from '@/components/server/files/NewDirectoryButton'; import { Link } from 'react-router-dom'; +import Can from '@/components/elements/Can'; const sortFiles = (files: FileObject[]): FileObject[] => { return files.sort((a, b) => a.name.localeCompare(b.name)) @@ -34,7 +35,6 @@ export default () => { console.error(error.message, { error }); addError({ message: httpErrorToHuman(error), key: 'files' }); }); - // eslint-disable-next-line react-hooks/exhaustive-deps }, [ directory ]); return ( @@ -78,12 +78,17 @@ export default () => { } -
- - - New File - -
+ +
+ + + New File + +
+
} diff --git a/resources/scripts/components/server/schedules/ScheduleContainer.tsx b/resources/scripts/components/server/schedules/ScheduleContainer.tsx index 63f174a4c..2e771b1b9 100644 --- a/resources/scripts/components/server/schedules/ScheduleContainer.tsx +++ b/resources/scripts/components/server/schedules/ScheduleContainer.tsx @@ -9,6 +9,7 @@ import { httpErrorToHuman } from '@/api/http'; import { Actions, useStoreActions } from 'easy-peasy'; import { ApplicationStore } from '@/state'; import EditScheduleModal from '@/components/server/schedules/EditScheduleModal'; +import Can from '@/components/elements/Can'; export default ({ match, history }: RouteComponentProps) => { const { uuid } = ServerContext.useStoreState(state => state.server.data!); @@ -35,9 +36,8 @@ export default ({ match, history }: RouteComponentProps) => { <> { schedules.length === 0 ? -

- There are no schedules configured for this server. Click the button below to get - started. +

+ There are no schedules configured for this server.

: schedules.map(schedule => ( @@ -54,21 +54,23 @@ export default ({ match, history }: RouteComponentProps) => { )) } -
- {visible && setSchedules(s => [...(s || []), schedule])} - onDismissed={() => setVisible(false)} - />} - -
+ +
+ {visible && setSchedules(s => [ ...(s || []), schedule ])} + onDismissed={() => setVisible(false)} + />} + +
+
} diff --git a/resources/scripts/components/server/schedules/ScheduleEditContainer.tsx b/resources/scripts/components/server/schedules/ScheduleEditContainer.tsx index 77cfb9ee3..e601e8250 100644 --- a/resources/scripts/components/server/schedules/ScheduleEditContainer.tsx +++ b/resources/scripts/components/server/schedules/ScheduleEditContainer.tsx @@ -13,6 +13,7 @@ import ScheduleTaskRow from '@/components/server/schedules/ScheduleTaskRow'; import EditScheduleModal from '@/components/server/schedules/EditScheduleModal'; import NewTaskButton from '@/components/server/schedules/NewTaskButton'; import DeleteScheduleButton from '@/components/server/schedules/DeleteScheduleButton'; +import Can from '@/components/elements/Can'; interface Params { id: string; @@ -93,24 +94,27 @@ export default ({ match, history, location: { state } }: RouteComponentProps :

- There are no tasks configured for this schedule. Consider adding a new one using the - button below. + There are no tasks configured for this schedule.

}
- history.push(`/server/${id}/schedules`)} - /> - - setSchedule(s => ({ - ...s!, tasks: [ ...s!.tasks, task ], - }))} - /> + + history.push(`/server/${id}/schedules`)} + /> + + + + setSchedule(s => ({ + ...s!, tasks: [ ...s!.tasks, task ], + }))} + /> +
} diff --git a/resources/scripts/components/server/schedules/ScheduleTaskRow.tsx b/resources/scripts/components/server/schedules/ScheduleTaskRow.tsx index 773308e2c..93bd4d326 100644 --- a/resources/scripts/components/server/schedules/ScheduleTaskRow.tsx +++ b/resources/scripts/components/server/schedules/ScheduleTaskRow.tsx @@ -13,6 +13,7 @@ import { httpErrorToHuman } from '@/api/http'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import TaskDetailsModal from '@/components/server/schedules/TaskDetailsModal'; import { faPencilAlt } from '@fortawesome/free-solid-svg-icons/faPencilAlt'; +import Can from '@/components/elements/Can'; interface Props { schedule: number; @@ -75,22 +76,26 @@ export default ({ schedule, task, onTaskUpdated, onTaskRemoved }: Props) => {

} - - + + + + + + ); }; diff --git a/resources/scripts/components/server/settings/ReinstallServerBox.tsx b/resources/scripts/components/server/settings/ReinstallServerBox.tsx new file mode 100644 index 000000000..895901215 --- /dev/null +++ b/resources/scripts/components/server/settings/ReinstallServerBox.tsx @@ -0,0 +1,63 @@ +import React, { useState } from 'react'; +import { ServerContext } from '@/state/server'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import TitledGreyBox from '@/components/elements/TitledGreyBox'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import reinstallServer from '@/api/server/reinstallServer'; +import { Actions, useStoreActions } from 'easy-peasy'; +import { ApplicationStore } from '@/state'; +import { httpErrorToHuman } from '@/api/http'; + +export default () => { + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const [ isSubmitting, setIsSubmitting ] = useState(false); + const [ modalVisible, setModalVisible ] = useState(false); + const { addFlash, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); + + const reinstall = () => { + clearFlashes('settings'); + setIsSubmitting(true); + reinstallServer(uuid) + .then(() => { + addFlash({ key: 'settings', type: 'success', message: 'Your server has begun the reinstallation process.' }); + }) + .catch(error => { + console.error(error); + + addFlash({ key: 'settings', type: 'error', message: httpErrorToHuman(error) }); + }) + .then(() => { + setIsSubmitting(false); + setModalVisible(false); + }); + } + + return ( + + reinstall()} + showSpinnerOverlay={isSubmitting} + visible={modalVisible} + onDismissed={() => setModalVisible(false)} + > + Your server will be stopped and some files may be deleted or modified during this process, are you sure you wish to continue? + +

+ Reinstalling your server will stop it, and then re-run the installation script that initially + set it up.Some files may be deleted or modified during this process, + please back up your data before continuing. +

+
+ +
+
+ ); +}; diff --git a/resources/scripts/components/server/settings/RenameServerBox.tsx b/resources/scripts/components/server/settings/RenameServerBox.tsx index 5b1ed3181..83c0b2f6b 100644 --- a/resources/scripts/components/server/settings/RenameServerBox.tsx +++ b/resources/scripts/components/server/settings/RenameServerBox.tsx @@ -1,66 +1,69 @@ import React from 'react'; import { ServerContext } from '@/state/server'; import TitledGreyBox from '@/components/elements/TitledGreyBox'; -import { Form, FormikProps, withFormik } from 'formik'; -import { Server } from '@/api/server/getServer'; -import { ActionCreator } from 'easy-peasy'; +import { Form, Formik, FormikHelpers, useFormikContext } from 'formik'; +import { Actions, useStoreActions } from 'easy-peasy'; import renameServer from '@/api/server/renameServer'; import Field from '@/components/elements/Field'; import { object, string } from 'yup'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; - -interface OwnProps { - server: Server; - setServer: ActionCreator; -} +import { ApplicationStore } from '@/state'; +import { httpErrorToHuman } from '@/api/http'; interface Values { name: string; } -const RenameServerBox = ({ isSubmitting, ...props }: OwnProps & FormikProps) => ( - - -
- -
- -
- -
-); +const RenameServerBox = () => { + const { isSubmitting } = useFormikContext(); -const EnhancedForm = withFormik({ - displayName: 'RenameServerBoxForm', - - mapPropsToValues: props => ({ - name: props.server.name, - }), - - validationSchema: () => object().shape({ - name: string().required().min(1), - }), - - handleSubmit: (values, { props, setSubmitting }) => { - renameServer(props.server.uuid, values.name) - .then(() => props.setServer({ ...props.server, name: values.name })) - .catch(error => { - console.error(error); - }) - .then(() => setSubmitting(false)); - }, -})(RenameServerBox); + return ( + + +
+ +
+ +
+ +
+ ); +}; export default () => { const server = ServerContext.useStoreState(state => state.server.data!); const setServer = ServerContext.useStoreActions(actions => actions.server.setServer); + const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); - return ; + const submit = ({ name }: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('settings'); + renameServer(server.uuid, name) + .then(() => setServer({ ...server, name })) + .catch(error => { + console.error(error); + addError({ key: 'settings', message: httpErrorToHuman(error) }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + + + + ); }; diff --git a/resources/scripts/components/server/settings/SettingsContainer.tsx b/resources/scripts/components/server/settings/SettingsContainer.tsx index 104de803f..25766e80e 100644 --- a/resources/scripts/components/server/settings/SettingsContainer.tsx +++ b/resources/scripts/components/server/settings/SettingsContainer.tsx @@ -5,52 +5,69 @@ import { useStoreState } from 'easy-peasy'; import { ApplicationStore } from '@/state'; import { UserData } from '@/state/user'; import RenameServerBox from '@/components/server/settings/RenameServerBox'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import Can from '@/components/elements/Can'; +import ReinstallServerBox from '@/components/server/settings/ReinstallServerBox'; export default () => { const user = useStoreState(state => state.user.data!); const server = ServerContext.useStoreState(state => state.server.data!); return ( -
- -
- - -
-
- - -
-
-
-
-

- Your SFTP password is the same as the password you use to access this panel. -

+
+ +
+ +
+ +
+ + +
+
+ + +
+
+
+
+

+ Your SFTP password is the same as the password you use to access this panel. +

+
+
+ +
+
+
+
+
+ +
+
-
- + + + +
- -
-
); diff --git a/resources/scripts/components/server/users/AddSubuserButton.tsx b/resources/scripts/components/server/users/AddSubuserButton.tsx new file mode 100644 index 000000000..10b30024a --- /dev/null +++ b/resources/scripts/components/server/users/AddSubuserButton.tsx @@ -0,0 +1,21 @@ +import React, { useState } from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faUserPlus } from '@fortawesome/free-solid-svg-icons/faUserPlus'; +import EditSubuserModal from '@/components/server/users/EditSubuserModal'; + +export default () => { + const [ visible, setVisible ] = useState(false); + + return ( + <> + {visible && setVisible(false)} + />} + + + ); +}; diff --git a/resources/scripts/components/server/users/EditSubuserModal.tsx b/resources/scripts/components/server/users/EditSubuserModal.tsx new file mode 100644 index 000000000..bd6c0dfdb --- /dev/null +++ b/resources/scripts/components/server/users/EditSubuserModal.tsx @@ -0,0 +1,189 @@ +import React, { forwardRef, useRef } from 'react'; +import { Subuser } from '@/state/server/subusers'; +import { Form, Formik, FormikHelpers, useFormikContext } from 'formik'; +import { array, object, string } from 'yup'; +import Modal, { RequiredModalProps } from '@/components/elements/Modal'; +import Field from '@/components/elements/Field'; +import { Actions, useStoreActions, useStoreState } from 'easy-peasy'; +import { ApplicationStore } from '@/state'; +import TitledGreyBox from '@/components/elements/TitledGreyBox'; +import Checkbox from '@/components/elements/Checkbox'; +import styled from 'styled-components'; +import classNames from 'classnames'; +import createOrUpdateSubuser from '@/api/server/users/createOrUpdateSubuser'; +import { ServerContext } from '@/state/server'; +import { httpErrorToHuman } from '@/api/http'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import Can from '@/components/elements/Can'; +import { usePermissions } from '@/plugins/usePermissions'; + +type Props = { + subuser?: Subuser; +} & RequiredModalProps; + +interface Values { + email: string; + permissions: string[]; +} + +const PermissionLabel = styled.label` + ${tw`flex items-center border border-transparent rounded p-2`}; + text-transform: none; + + &:not(.disabled) { + ${tw`cursor-pointer`}; + + &:hover { + ${tw`border-neutral-500 bg-neutral-800`}; + } + } +`; + +const EditSubuserModal = forwardRef(({ subuser, ...props }, ref) => { + const { values, isSubmitting, setFieldValue } = useFormikContext(); + const [ canEditUser ] = usePermissions([ 'user.update' ]); + const permissions = useStoreState((state: ApplicationStore) => state.permissions.data); + + return ( + +

+ {subuser ? + `${canEditUser ? 'Modify' : 'View'} permissions for ${subuser.email}` + : + 'Create new subuser' + } +

+ + {!subuser && +
+ +
+ } +
+ {Object.keys(permissions).filter(key => key !== 'websocket').map((key, index) => ( + +

{key}

+ {canEditUser && + { + if (e.currentTarget.checked) { + setFieldValue('permissions', [ + ...values.permissions, + ...Object.keys(permissions[key].keys) + .map(pkey => `${key}.${pkey}`) + .filter(permission => values.permissions.indexOf(permission) === -1), + ]); + } else { + setFieldValue('permissions', [ + ...values.permissions.filter( + permission => Object.keys(permissions[key].keys) + .map(pkey => `${key}.${pkey}`) + .indexOf(permission) < 0, + ), + ]); + } + }} + /> + } +
+ } + className={index !== 0 ? 'mt-4' : undefined} + > +

+ {permissions[key].description} +

+ {Object.keys(permissions[key].keys).map((pkey, index) => ( + +
+ +
+
+ + {pkey} + + {permissions[key].keys[pkey].length > 0 && +

+ {permissions[key].keys[pkey]} +

+ } +
+
+ ))} + + ))} +
+ +
+ +
+
+ + ); +}); + +export default ({ subuser, ...props }: Props) => { + const ref = useRef(null); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const appendSubuser = ServerContext.useStoreActions(actions => actions.subusers.appendSubuser); + + const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); + + const submit = (values: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('user:edit'); + createOrUpdateSubuser(uuid, values, subuser) + .then(subuser => { + appendSubuser(subuser); + props.onDismissed(); + }) + .catch(error => { + console.error(error); + setSubmitting(false); + addError({ key: 'user:edit', message: httpErrorToHuman(error) }); + + if (ref.current) { + ref.current.scrollIntoView(); + } + }); + }; + + return ( + +
+ + +
+ ); +}; diff --git a/resources/scripts/components/server/users/PermissionEditor.tsx b/resources/scripts/components/server/users/PermissionEditor.tsx deleted file mode 100644 index bf72fe344..000000000 --- a/resources/scripts/components/server/users/PermissionEditor.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; -import { SubuserPermission } from '@/state/server/subusers'; -import { useStoreState } from 'easy-peasy'; -import { ApplicationStore } from '@/state'; -import { useTranslation } from 'react-i18next'; - -interface Props { - defaultPermissions: SubuserPermission[]; -} - -export default ({ defaultPermissions }: Props) => { - const { t } = useTranslation('server.users'); - const permissions = useStoreState((state: ApplicationStore) => state.permissions.data); - - return ( -
- { - permissions.map(permission => ( -
- = 0} - /> - -
- )) - } -
- -
-
- ); -}; diff --git a/resources/scripts/components/server/users/RemoveSubuserButton.tsx b/resources/scripts/components/server/users/RemoveSubuserButton.tsx new file mode 100644 index 000000000..28f58dc49 --- /dev/null +++ b/resources/scripts/components/server/users/RemoveSubuserButton.tsx @@ -0,0 +1,60 @@ +import React, { useState } from 'react'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import { ServerContext } from '@/state/server'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faTrashAlt } from '@fortawesome/free-solid-svg-icons/faTrashAlt'; +import { Subuser } from '@/state/server/subusers'; +import deleteSubuser from '@/api/server/users/deleteSubuser'; +import { Actions, useStoreActions } from 'easy-peasy'; +import { ApplicationStore } from '@/state'; +import { httpErrorToHuman } from '@/api/http'; + +export default ({ subuser }: { subuser: Subuser }) => { + const [ loading, setLoading ] = useState(false); + const [ showConfirmation, setShowConfirmation ] = useState(false); + + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const removeSubuser = ServerContext.useStoreActions(actions => actions.subusers.removeSubuser); + const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); + + const doDeletion = () => { + setLoading(true); + clearFlashes('users'); + deleteSubuser(uuid, subuser.uuid) + .then(() => { + setLoading(false); + removeSubuser(subuser.uuid); + }) + .catch(error => { + console.error(error); + addError({ key: 'users', message: httpErrorToHuman(error) }); + setShowConfirmation(false); + }); + } + + return ( + <> + {showConfirmation && + doDeletion()} + onDismissed={() => setShowConfirmation(false)} + > + Are you sure you wish to remove this subuser? They will have all access to this server revoked + immediately. + + } + + + ); +}; diff --git a/resources/scripts/components/server/users/UserRow.tsx b/resources/scripts/components/server/users/UserRow.tsx new file mode 100644 index 000000000..d12b7aef1 --- /dev/null +++ b/resources/scripts/components/server/users/UserRow.tsx @@ -0,0 +1,67 @@ +import React, { useState } from 'react'; +import { Subuser } from '@/state/server/subusers'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faPencilAlt } from '@fortawesome/free-solid-svg-icons/faPencilAlt'; +import RemoveSubuserButton from '@/components/server/users/RemoveSubuserButton'; +import EditSubuserModal from '@/components/server/users/EditSubuserModal'; +import { faUnlockAlt } from '@fortawesome/free-solid-svg-icons/faUnlockAlt'; +import { faUserLock } from '@fortawesome/free-solid-svg-icons/faUserLock'; +import classNames from 'classnames'; +import Can from '@/components/elements/Can'; + +interface Props { + subuser: Subuser; +} + +export default ({ subuser }: Props) => { + const [ visible, setVisible ] = useState(false); + + return ( +
+ {visible && + setVisible(false)} + /> + } +
+ +
+
+

{subuser.email}

+
+
+

+   + +   +

+

2FA Enabled

+
+
+

+ {subuser.permissions.filter(permission => permission !== 'websocket.*').length} +

+

Permissions

+
+ + + + +
+ ); +}; diff --git a/resources/scripts/components/server/users/UsersContainer.tsx b/resources/scripts/components/server/users/UsersContainer.tsx index 01e42f741..a6c863ea0 100644 --- a/resources/scripts/components/server/users/UsersContainer.tsx +++ b/resources/scripts/components/server/users/UsersContainer.tsx @@ -1,120 +1,64 @@ import React, { useEffect, useState } from 'react'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faUserPlus } from '@fortawesome/free-solid-svg-icons/faUserPlus'; import { ServerContext } from '@/state/server'; -import Spinner from '@/components/elements/Spinner'; -import { Subuser } from '@/state/server/subusers'; -import { CSSTransition } from 'react-transition-group'; -import classNames from 'classnames'; -import PermissionEditor from '@/components/server/users/PermissionEditor'; import { Actions, useStoreActions, useStoreState } from 'easy-peasy'; import { ApplicationStore } from '@/state'; -import { faArrowLeft } from '@fortawesome/free-solid-svg-icons/faArrowLeft'; +import Spinner from '@/components/elements/Spinner'; +import AddSubuserButton from '@/components/server/users/AddSubuserButton'; +import UserRow from '@/components/server/users/UserRow'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import getServerSubusers from '@/api/server/users/getServerSubusers'; +import { httpErrorToHuman } from '@/api/http'; +import Can from '@/components/elements/Can'; export default () => { const [ loading, setLoading ] = useState(true); - const [ editSubuser, setEditSubuser ] = useState(null); const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const subusers = ServerContext.useStoreState(state => state.subusers.data); - const getSubusers = ServerContext.useStoreActions(actions => actions.subusers.getSubusers); + const setSubusers = ServerContext.useStoreActions(actions => actions.subusers.setSubusers); const permissions = useStoreState((state: ApplicationStore) => state.permissions.data); const getPermissions = useStoreActions((actions: Actions) => actions.permissions.getPermissions); + const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); useEffect(() => { - if (!permissions.length) { - getPermissions().catch(error => console.error(error)); - } - }, [ permissions, getPermissions ]); + getPermissions().catch(error => console.error(error)); + }, []); useEffect(() => { - getSubusers(uuid) - .then(() => setLoading(false)) + clearFlashes('users'); + getServerSubusers(uuid) + .then(subusers => { + setSubusers(subusers); + setLoading(false); + }) .catch(error => { console.error(error); + addError({ key: 'users', message: httpErrorToHuman(error) }); }); - }, [ uuid, getSubusers ]); + }, []); - useEffect(() => { - if (subusers.length > 0) { - setLoading(false); - } - }, [ subusers ]); + if (loading || !Object.keys(permissions).length) { + return ; + } return ( -
-
-

Subusers

-
- {(loading || !permissions.length) ? -
- -
- : - !subusers.length ? -

It looks like you don't have any subusers.

- : - subusers.map(subuser => ( -
- -
-

{subuser.email}

-
-
- - -
-
- )) - } -
-
- -
-
- {editSubuser && - -
-

- setEditSubuser(null)}> - - - Edit {editSubuser.email} -

-
- - - -
-
-
+
+ + {!subusers.length ? +

+ It looks like you don't have any subusers. +

+ : + subusers.map(subuser => ( + + )) } + +
+ +
+
); }; diff --git a/resources/scripts/easy-peasy.d.ts b/resources/scripts/easy-peasy.d.ts new file mode 100644 index 000000000..939ad54cf --- /dev/null +++ b/resources/scripts/easy-peasy.d.ts @@ -0,0 +1,9 @@ +// noinspection ES6UnusedImports +import EasyPeasy from 'easy-peasy'; +import { ApplicationStore } from '@/state'; + +declare module 'easy-peasy' { + export function useStoreState( + mapState: (state: ApplicationStore) => Result, + ): Result; +} diff --git a/resources/scripts/helpers.ts b/resources/scripts/helpers.ts index 79048f778..9d531b52c 100644 --- a/resources/scripts/helpers.ts +++ b/resources/scripts/helpers.ts @@ -5,7 +5,7 @@ export function bytesToHuman (bytes: number): string { const i = Math.floor(Math.log(bytes) / Math.log(1000)); // @ts-ignore - return `${(bytes / Math.pow(1000, i)).toFixed(2) * 1} ${['Bytes', 'kB', 'MB', 'GB', 'TB'][i]}`; + return `${(bytes / Math.pow(1000, i)).toFixed(2) * 1} ${[ 'Bytes', 'kB', 'MB', 'GB', 'TB' ][i]}`; } export const bytesToMegabytes = (bytes: number) => Math.floor(bytes / 1000 / 1000); diff --git a/resources/scripts/plugins/useDeepMemo.ts b/resources/scripts/plugins/useDeepMemo.ts new file mode 100644 index 000000000..0ad6bb7df --- /dev/null +++ b/resources/scripts/plugins/useDeepMemo.ts @@ -0,0 +1,12 @@ +import { useRef } from 'react'; +import isEqual from 'lodash-es/isEqual'; + +export const useDeepMemo = (fn: () => T, key: K): T => { + const ref = useRef<{ key: K, value: T }>(); + + if (!ref.current || !isEqual(key, ref.current.key)) { + ref.current = { key, value: fn() }; + } + + return ref.current.value; +}; diff --git a/resources/scripts/plugins/useEventListener.ts b/resources/scripts/plugins/useEventListener.ts new file mode 100644 index 000000000..7cb14690a --- /dev/null +++ b/resources/scripts/plugins/useEventListener.ts @@ -0,0 +1,23 @@ +import { useEffect, useRef } from 'react'; + +export default (eventName: string, handler: any, element: any = window) => { + const savedHandler = useRef(null); + + useEffect(() => { + savedHandler.current = handler; + }, [handler]); + + useEffect( + () => { + const isSupported = element && element.addEventListener; + if (!isSupported) return; + + const eventListener = (event: any) => savedHandler.current(event); + element.addEventListener(eventName, eventListener); + return () => { + element.removeEventListener(eventName, eventListener); + }; + }, + [eventName, element], + ); +}; diff --git a/resources/scripts/plugins/useFlash.ts b/resources/scripts/plugins/useFlash.ts new file mode 100644 index 000000000..a55b87312 --- /dev/null +++ b/resources/scripts/plugins/useFlash.ts @@ -0,0 +1,9 @@ +import { Actions, useStoreActions } from 'easy-peasy'; +import { FlashStore } from '@/state/flashes'; +import { ApplicationStore } from '@/state'; + +const useFlash = (): Actions => { + return useStoreActions((actions: Actions) => actions.flashes); +}; + +export default useFlash; diff --git a/resources/scripts/plugins/usePermissions.ts b/resources/scripts/plugins/usePermissions.ts new file mode 100644 index 000000000..89dbd64bc --- /dev/null +++ b/resources/scripts/plugins/usePermissions.ts @@ -0,0 +1,25 @@ +import { ServerContext } from '@/state/server'; +import { useDeepMemo } from '@/plugins/useDeepMemo'; + +export const usePermissions = (action: string | string[]): boolean[] => { + const userPermissions = ServerContext.useStoreState(state => state.server.permissions); + + return useDeepMemo(() => { + if (userPermissions[0] === '*') { + return Array(Array.isArray(action) ? action.length : 1).fill(true); + } + + return (Array.isArray(action) ? action : [ action ]) + .map(permission => ( + // Allows checking for any permission matching a name, for example files.* + // will return if the user has any permission under the file.XYZ namespace. + ( + permission.endsWith('.*') && + permission !== 'websocket.*' && + userPermissions.filter(p => p.startsWith(permission.split('.')[0])).length > 0 + ) || + // Otherwise just check if the entire permission exists in the array or not. + userPermissions.indexOf(permission) >= 0 + )); + }, [ action, userPermissions ]); +}; diff --git a/resources/scripts/plugins/useServer.ts b/resources/scripts/plugins/useServer.ts new file mode 100644 index 000000000..40fd93da1 --- /dev/null +++ b/resources/scripts/plugins/useServer.ts @@ -0,0 +1,9 @@ +import { DependencyList } from 'react'; +import { ServerContext } from '@/state/server'; +import { Server } from '@/api/server/getServer'; + +const useServer = (dependencies?: DependencyList): Server => { + return ServerContext.useStoreState(state => state.server.data!, [ dependencies ]); +}; + +export default useServer; diff --git a/resources/scripts/routers/ServerRouter.tsx b/resources/scripts/routers/ServerRouter.tsx index 2998c6ee3..ce592dfeb 100644 --- a/resources/scripts/routers/ServerRouter.tsx +++ b/resources/scripts/routers/ServerRouter.tsx @@ -14,6 +14,9 @@ import FileEditContainer from '@/components/server/files/FileEditContainer'; import SettingsContainer from '@/components/server/settings/SettingsContainer'; import ScheduleContainer from '@/components/server/schedules/ScheduleContainer'; import ScheduleEditContainer from '@/components/server/schedules/ScheduleEditContainer'; +import UsersContainer from '@/components/server/users/UsersContainer'; +import Can from '@/components/elements/Can'; +import BackupContainer from '@/components/server/backups/BackupContainer'; const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) => { const server = ServerContext.useStoreState(state => state.server.data); @@ -33,11 +36,24 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>)
Console - File Manager - Databases - {/* User Management */} - Schedules - Settings + + File Manager + + + Databases + + + Schedules + + + Users + + + Backups + + + Settings +
@@ -62,9 +78,10 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) exact /> - {/* */} + + diff --git a/resources/scripts/state/permissions.ts b/resources/scripts/state/permissions.ts index e275e38f5..07a428002 100644 --- a/resources/scripts/state/permissions.ts +++ b/resources/scripts/state/permissions.ts @@ -1,15 +1,21 @@ -import { SubuserPermission } from '@/state/server/subusers'; import { action, Action, thunk, Thunk } from 'easy-peasy'; import getSystemPermissions from '@/api/getSystemPermissions'; +export interface PanelPermissions { + [key: string]: { + description: string; + keys: { [k: string]: string }; + }; +} + export interface GloablPermissionsStore { - data: SubuserPermission[]; - setPermissions: Action; + data: PanelPermissions; + setPermissions: Action; getPermissions: Thunk>; } const permissions: GloablPermissionsStore = { - data: [], + data: {}, setPermissions: action((state, payload) => { state.data = payload; diff --git a/resources/scripts/state/server/index.ts b/resources/scripts/state/server/index.ts index 8fc23aee5..fb26a7167 100644 --- a/resources/scripts/state/server/index.ts +++ b/resources/scripts/state/server/index.ts @@ -10,18 +10,30 @@ export type ServerStatus = 'offline' | 'starting' | 'stopping' | 'running'; interface ServerDataStore { data?: Server; + permissions: string[]; + getServer: Thunk>; setServer: Action; + setPermissions: Action; } const server: ServerDataStore = { + permissions: [], + getServer: thunk(async (actions, payload) => { - const server = await getServer(payload); + const [server, permissions] = await getServer(payload); + actions.setServer(server); + actions.setPermissions(permissions); }), + setServer: action((state, payload) => { state.data = payload; }), + + setPermissions: action((state, payload) => { + state.permissions = payload; + }), }; interface ServerStatusStore { @@ -75,9 +87,9 @@ export const ServerContext = createContextStore({ subusers, clearServerState: action(state => { state.server.data = undefined; + state.server.permissions = []; state.databases.items = []; state.subusers.data = []; - state.files.directory = '/'; state.files.contents = []; diff --git a/resources/scripts/state/server/subusers.ts b/resources/scripts/state/server/subusers.ts index 5d15149b7..5a6224853 100644 --- a/resources/scripts/state/server/subusers.ts +++ b/resources/scripts/state/server/subusers.ts @@ -1,9 +1,8 @@ -import { action, Action, thunk, Thunk } from 'easy-peasy'; -import getServerSubusers from '@/api/server/users/getServerSubusers'; +import { action, Action } from 'easy-peasy'; export type SubuserPermission = 'websocket.*' | - 'control.console' | 'control.start' | 'control.stop' | 'control.restart' | 'control.kill' | + 'control.console' | 'control.start' | 'control.stop' | 'control.restart' | 'user.create' | 'user.read' | 'user.update' | 'user.delete' | 'file.create' | 'file.read' | 'file.update' | 'file.delete' | 'file.archive' | 'file.sftp' | 'allocation.read' | 'allocation.update' | @@ -28,7 +27,7 @@ export interface ServerSubuserStore { data: Subuser[]; setSubusers: Action; appendSubuser: Action; - getSubusers: Thunk>; + removeSubuser: Action; } const subusers: ServerSubuserStore = { @@ -39,13 +38,24 @@ const subusers: ServerSubuserStore = { }), appendSubuser: action((state, payload) => { - state.data = [ ...state.data, payload ]; + let matched = false; + state.data = [ + ...state.data + .map(user => { + if (user.uuid === payload.uuid) { + matched = true; + + return payload; + } + + return user; + }) + .concat(matched ? [] : [ payload ]), + ]; }), - getSubusers: thunk(async (actions, payload) => { - const subusers = await getServerSubusers(payload); - - actions.setSubusers(subusers); + removeSubuser: action((state, payload) => { + state.data = [ ...state.data.filter(user => user.uuid !== payload) ]; }), }; diff --git a/resources/styles/components/modal.css b/resources/styles/components/modal.css index 7c3a6ae94..550191c6b 100644 --- a/resources/styles/components/modal.css +++ b/resources/styles/components/modal.css @@ -4,7 +4,7 @@ transition: opacity 250ms ease; & > .modal-container { - @apply .relative .w-full .max-w-md .m-auto .flex-col .flex; + @apply .relative .w-full .max-w-1/2 .m-auto .flex-col .flex; &.top { margin-top: 10%; @@ -22,7 +22,8 @@ } & > .modal-content { - @apply .bg-neutral-800 .rounded .shadow-md; + @apply .bg-neutral-800 .rounded .shadow-md .overflow-y-scroll; + max-height: calc(100vh - 16rem); transition: all 250ms ease; } @@ -39,7 +40,7 @@ } & > .modal-container.full-screen { - @apply .w-3/4 .mt-32; + @apply .w-3/4; height: calc(100vh - 16rem); max-width: none; } diff --git a/resources/styles/components/navigation.css b/resources/styles/components/navigation.css index 16f64e41d..31951ebfb 100644 --- a/resources/styles/components/navigation.css +++ b/resources/styles/components/navigation.css @@ -21,8 +21,8 @@ & .right-navigation { @apply .flex .h-full .items-center .justify-center; - & > a { - @apply .flex .items-center .h-full .no-underline .text-neutral-300 .px-6; + & > a, & > .navigation-link { + @apply .flex .items-center .h-full .no-underline .text-neutral-300 .px-6 .cursor-pointer; transition: background-color 150ms linear, color 150ms linear, box-shadow 150ms ease-in; /*! purgecss start ignore */ diff --git a/resources/views/admin/eggs/variables.blade.php b/resources/views/admin/eggs/variables.blade.php index f8463f4bd..2441c725f 100644 --- a/resources/views/admin/eggs/variables.blade.php +++ b/resources/views/admin/eggs/variables.blade.php @@ -48,7 +48,7 @@

{{ $variable->name }}

-
+
diff --git a/resources/views/admin/eggs/view.blade.php b/resources/views/admin/eggs/view.blade.php index 64c3183e9..d483f049f 100644 --- a/resources/views/admin/eggs/view.blade.php +++ b/resources/views/admin/eggs/view.blade.php @@ -160,7 +160,7 @@
- -
- - I/O + +
+
+

Advanced: Enter the specific CPU cores that this process can run on, or leave blank to allow all cores. This can be a single number, or a comma seperated list. Example: 0, 0-1,3, or 0,1,3,4.

+
+
+ +
+ +
+

Advanced: The IO performance of this server relative to other running containers on the system. Value should be between 10 and 1000.