This commit is contained in:
2024-09-01 18:54:23 +05:00
parent 76d18365a5
commit 061f09eca1
1597 changed files with 109451 additions and 1 deletions

View File

@@ -0,0 +1,110 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Actions\Action;
use Laravel\Nova\Actions\ActionCollection;
use Laravel\Nova\Http\Requests\ActionRequest;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Nova;
use Laravel\Nova\Resource;
class ActionController extends Controller
{
/**
* List the actions for the given resource.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function index(NovaRequest $request)
{
$resourceId = with($request->input('resources'), function ($resourceIds) {
return is_array($resourceIds) && count($resourceIds) === 1 ? $resourceIds[0] : null;
});
$resource = $request->newResourceWith(
$request->findModel($resourceId) ?? $request->model()
);
return response()->json(with([
'actions' => $this->availableActions($request, $resource),
'pivotActions' => [
'name' => Nova::humanize($request->pivotName()),
'actions' => $resource->availablePivotActions($request),
],
], function ($payload) use ($resource, $request) {
$actionCounts = ($request->display !== 'detail' ? $payload['actions'] : $resource->resolveActions($request))->countsByTypeOnIndex();
$pivotActionCounts = ActionCollection::make($payload['pivotActions']['actions'])->countsByTypeOnIndex();
$payload['counts'] = [
'standalone' => $actionCounts['standalone'] + $pivotActionCounts['standalone'],
'resource' => $actionCounts['resource'] + $pivotActionCounts['resource'],
];
return $payload;
}));
}
/**
* Perform an action on the specified resources.
*
* @param \Laravel\Nova\Http\Requests\ActionRequest $request
* @return \Illuminate\Http\Response
*/
public function store(ActionRequest $request)
{
$request->validateFields();
return $request->action()->handleRequest($request);
}
/**
* Sync an action field on the specified resources.
*
* @param \Laravel\Nova\Http\Requests\ActionRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function sync(ActionRequest $request)
{
$action = $this->availableActions($request, $request->newResource())
->first(function ($action) use ($request) {
return $action->uriKey() === $request->query('action');
});
abort_unless($action instanceof Action, 404);
return response()->json(
collect($action->fields($request))
->filter(function ($field) use ($request) {
return $request->query('field') === $field->attribute &&
$request->query('component') === $field->dependentComponentKey();
})->each->syncDependsOn($request)
->first()
);
}
/**
* Get the available actions for the request.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param \Laravel\Nova\Resource $resource
* @return \Laravel\Nova\Actions\ActionCollection<int, \Laravel\Nova\Actions\Action>
*/
protected function availableActions(NovaRequest $request, Resource $resource)
{
switch ($request->display) {
case 'index':
$method = 'availableActionsOnIndex';
break;
case 'detail':
$method = 'availableActionsOnDetail';
break;
default:
$method = 'availableActions';
}
return $resource->{$method}($request);
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Contracts\RelatableField;
use Laravel\Nova\Http\Requests\NovaRequest;
class AssociatableController extends Controller
{
/**
* List the available related resources for a given resource.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return array
*/
public function __invoke(NovaRequest $request)
{
$field = $request->newResource()
->availableFields($request)
->whereInstanceOf(RelatableField::class)
->findFieldByAttribute($request->field, function () {
abort(404);
})->applyDependsOn($request);
$withTrashed = $this->shouldIncludeTrashed(
$request, $associatedResource = $field->resourceClass
);
$limit = $associatedResource::usesScout()
? $associatedResource::$scoutSearchResults
: $associatedResource::$relatableSearchResults;
$shouldReorderAssociatableValues = $field->shouldReorderAssociatableValues($request) && ! $associatedResource::usesScout();
return [
'resources' => $field->buildAssociatableQuery($request, $withTrashed)
->take($limit)
->get()
->mapInto($field->resourceClass)
->filter->authorizedToAdd($request, $request->model())
->map(function ($resource) use ($request, $field) {
return $field->formatAssociatableResource($request, $resource);
})->when($shouldReorderAssociatableValues, function ($collection) {
return $collection->sortBy('display');
})->values(),
'softDeletes' => $associatedResource::softDeletes(),
'withTrashed' => $withTrashed,
];
}
/**
* Determine if the query should include trashed models.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param string $associatedResource
* @return bool
*/
protected function shouldIncludeTrashed(NovaRequest $request, $associatedResource)
{
if ($request->withTrashed === 'true') {
return true;
}
$associatedModel = $associatedResource::newModel();
if ($request->current && empty($request->search) && $associatedResource::softDeletes()) {
$associatedModel = $associatedModel->newQueryWithoutScopes()->find($request->current);
return $associatedModel ? $associatedModel->trashed() : false;
}
return false;
}
}

View File

@@ -0,0 +1,104 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Contracts\PivotableField;
use Laravel\Nova\Http\Requests\NovaRequest;
class AttachableController extends Controller
{
/**
* List the available related resources for a given resource.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return array
*/
public function __invoke(NovaRequest $request)
{
$field = $request->newResource()
->availableFields($request)
->filterForManyToManyRelations()
->filter(function ($field) use ($request) {
return $field->resourceName === $request->field &&
$field->attribute === $request->viaRelationship;
})->first();
$withTrashed = $this->shouldIncludeTrashed(
$request, $associatedResource = $field->resourceClass
);
$parentResource = $request->findResourceOrFail();
$shouldReorderAttachableValues = $field->shouldReorderAttachableValues($request) && ! $associatedResource::usesScout();
return [
'resources' => $field->buildAttachableQuery($request, $withTrashed)
->tap($this->getAttachableQueryResolver($request, $field))
->get()
->mapInto($field->resourceClass)
->filter(function ($resource) use ($request, $parentResource) {
return $parentResource->authorizedToAttach($request, $resource->resource);
})
->map(function ($resource) use ($request, $field) {
return $field->formatAttachableResource($request, $resource);
})->when($shouldReorderAttachableValues, function ($collection) {
return $collection->sortBy('display', SORT_NATURAL | SORT_FLAG_CASE);
})->values(),
'withTrashed' => $withTrashed,
'softDeletes' => $associatedResource::softDeletes(),
];
}
/**
* Determine if the query should include trashed models.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param string $associatedResource
* @return bool
*/
protected function shouldIncludeTrashed(NovaRequest $request, $associatedResource)
{
if ($request->withTrashed === 'true') {
return true;
}
$associatedModel = $associatedResource::newModel();
if ($request->current && $associatedResource::softDeletes()) {
$associatedModel = $associatedModel->newQueryWithoutScopes()->find($request->current);
return $associatedModel ? $associatedModel->trashed() : false;
}
return false;
}
/**
* Get attachable query resolver.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param \Laravel\Nova\Fields\Field&\Laravel\Nova\Contracts\PivotableField $field
* @return callable(\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder):void
*/
protected function getAttachableQueryResolver(NovaRequest $request, PivotableField $field)
{
return function ($query) use ($request, $field) {
if (
$request->first === 'true'
|| $field->allowDuplicateRelations
|| is_null($relatedModel = $request->findModel())
) {
return;
}
$query->whereNotExists(function ($query) use ($field, $relatedModel) {
$relation = $relatedModel->{$field->manyToManyRelationship}();
return $relation->applyDefaultPivotQuery($query)
->select($relation->getRelatedPivotKeyName())
->whereColumn($relation->getQualifiedRelatedKeyName(), $relation->getQualifiedRelatedPivotKeyName());
});
};
}
}

View File

@@ -0,0 +1,155 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Laravel\Nova\Actions\ActionEvent;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Nova;
use Throwable;
class AttachedResourceUpdateController extends Controller
{
use HandlesCustomRelationKeys;
/**
* The action event for the action.
*
* @var \Laravel\Nova\Actions\ActionEvent|null
*/
protected $actionEvent;
/**
* Update an attached resource pivot record.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return \Illuminate\Http\Response
*/
public function __invoke(NovaRequest $request)
{
$resource = $request->resource();
$model = $request->findModelOrFail();
tap(new $resource($model), function ($resource) use ($request) {
abort_unless($resource->hasRelatableField($request, $request->viaRelationship), 404);
});
$this->validate($request, $model, $resource);
try {
return DB::connection($model->getConnectionName())->transaction(function () use ($request, $resource, $model) {
$model->setRelation(
$model->{$request->viaRelationship}()->getPivotAccessor(),
$pivot = $this->findPivot($request, $model)
);
if ($this->modelHasBeenUpdatedSinceRetrieval($request, $pivot)) {
return response('', 409);
}
[$pivot, $callbacks] = $resource::fillPivotForUpdate($request, $model, $pivot);
DB::transaction(function () use ($request, $model, $pivot) {
Nova::usingActionEvent(function (ActionEvent $actionEvent) use ($request, $model, $pivot) {
$this->actionEvent = $actionEvent->forAttachedResourceUpdate($request, $model, $pivot);
$this->actionEvent->save();
});
});
$pivot->save();
collect($callbacks)->each->__invoke();
});
} catch (Throwable $e) {
optional($this->actionEvent)->delete();
throw $e;
}
}
/**
* Validate the attachment request.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param \Illuminate\Database\Eloquent\Model $model
* @param class-string<\Laravel\Nova\Resource> $resourceClass
* @return void
*/
protected function validate(NovaRequest $request, $model, $resourceClass)
{
$attribute = $resourceClass::validationAttachableAttributeFor($request, $request->relatedResource);
tap($this->updateRulesFor($request, $resourceClass), function ($rules) use ($resourceClass, $request, $attribute) {
Validator::make($request->all(), $rules, [], $this->customRulesKeys($request, $attribute))->validate();
$resourceClass::validateForAttachmentUpdate($request);
});
}
/**
* Get update rules for request from the resource.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param class-string<\Laravel\Nova\Resource> $resourceClass
* @return array
*/
protected function updateRulesFor(NovaRequest $request, $resourceClass)
{
$rules = $resourceClass::updateRulesFor($request, $this->getRuleKey($request));
if ($this->usingCustomRelationKey($request)) {
$rules[$request->relatedResource] = $rules[$request->viaRelationship];
unset($rules[$request->viaRelationship]);
}
return $rules;
}
/**
* Find the pivot model for the operation.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param \Illuminate\Database\Eloquent\Model $model
* @return \Illuminate\Database\Eloquent\Relations\Pivot
*/
protected function findPivot(NovaRequest $request, $model)
{
$relation = $model->{$request->viaRelationship}();
if ($request->viaPivotId) {
tap($relation->getPivotClass(), function ($pivotClass) use ($relation, $request) {
$relation->wherePivot((new $pivotClass())->getKeyName(), $request->viaPivotId);
});
}
$accessor = $relation->getPivotAccessor();
return $relation
->withoutGlobalScopes()
->lockForUpdate()
->findOrFail($request->relatedResourceId)->{$accessor};
}
/**
* Determine if the model has been updated since it was retrieved.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param \Illuminate\Database\Eloquent\Model $model
* @return bool
*/
protected function modelHasBeenUpdatedSinceRetrieval(NovaRequest $request, $model)
{
$column = $model->getUpdatedAtColumn();
if (! ($model->usesTimestamps() && $model->{$column})) {
return false;
}
return $request->input('_retrieved_at') && $model->{$column}->gt(
Carbon::createFromTimestamp($request->input('_retrieved_at'))
);
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\CardRequest;
class CardController extends Controller
{
/**
* List the cards for the given resource.
*
* @param \Laravel\Nova\Http\Requests\CardRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(CardRequest $request)
{
return response()->json(
$request->availableCards()
);
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\ResourceCreateOrAttachRequest;
use Laravel\Nova\Http\Resources\CreateViewResource;
use Laravel\Nova\Http\Resources\ReplicateViewResource;
class CreationFieldController extends Controller
{
/**
* List the creation fields for the given resource.
*
* @param \Laravel\Nova\Http\Requests\ResourceCreateOrAttachRequest $request
* @return \Illuminate\Http\JsonResponse
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function __invoke(ResourceCreateOrAttachRequest $request)
{
if ($request->has('fromResourceId')) {
return ReplicateViewResource::make($request->fromResourceId)->toResponse($request);
}
return CreateViewResource::make()->toResponse($request);
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\ResourceCreateOrAttachRequest;
use Laravel\Nova\Http\Resources\CreateViewResource;
use Laravel\Nova\Http\Resources\ReplicateViewResource;
class CreationFieldSyncController extends Controller
{
/**
* Synchronize the field for creation view.
*
* @param \Laravel\Nova\Http\Requests\ResourceCreateOrAttachRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(ResourceCreateOrAttachRequest $request)
{
$resource = $request->has('fromResourceId')
? ReplicateViewResource::make($request->fromResourceId)->newResourceWith($request)
: CreateViewResource::make()->newResourceWith($request);
return response()->json(
$resource->creationFields($request)
->filter(function ($field) use ($request) {
return $request->query('field') === $field->attribute &&
$request->query('component') === $field->dependentComponentKey();
})->each->syncDependsOn($request)
->first()
);
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\ResourceCreateOrAttachRequest;
use Laravel\Nova\Http\Resources\CreationPivotFieldResource;
class CreationPivotFieldController extends Controller
{
/**
* List the pivot fields for the given resource and relation.
*
* @param \Laravel\Nova\Http\Requests\ResourceCreateOrAttachRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(ResourceCreateOrAttachRequest $request)
{
return CreationPivotFieldResource::make()->toResponse($request);
}
/**
* Synchronize the pivot field for creation.
*
* @param \Laravel\Nova\Http\Requests\ResourceCreateOrAttachRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function sync(ResourceCreateOrAttachRequest $request)
{
$resource = CreationPivotFieldResource::make()->newResourceWith($request);
return response()->json(
$resource->creationPivotFields(
$request, $request->relatedResource
)->filter(function ($field) use ($request) {
return $request->query('field') === $field->attribute &&
$request->query('component') === $field->dependentComponentKey();
})->applyDependsOn($request)
->first()
);
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\DashboardRequest;
class DashboardCardController extends Controller
{
/**
* List the cards for the dashboard.
*
* @param \Laravel\Nova\Http\Requests\DashboardRequest $request
* @param string $dashboard
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(DashboardRequest $request, $dashboard = 'main')
{
return response()->json(
$request->availableCards($dashboard)
);
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\DashboardRequest;
use Laravel\Nova\Http\Resources\DashboardViewResource;
class DashboardController extends Controller
{
/**
* Return the details for the Dashboard.
*
* @param \Laravel\Nova\Http\Requests\DashboardRequest $request
* @param string $dashboard
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(DashboardRequest $request, $dashboard = 'main')
{
return DashboardViewResource::make($dashboard)->toResponse($request);
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\DashboardMetricRequest;
class DashboardMetricController extends Controller
{
/**
* Get the specified metric's value.
*
* @param \Laravel\Nova\Http\Requests\DashboardMetricRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(DashboardMetricRequest $request)
{
return response()->json([
'value' => $request->metric()->resolve($request),
]);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Laravel\Nova\DeleteField;
use Laravel\Nova\Http\Requests\NovaRequest;
trait DeletesFields
{
/**
* Delete the deletable fields on the given model / resource.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
protected function forceDeleteFields(NovaRequest $request, $model)
{
$this->deleteFields($request, $model, false);
}
/**
* Delete the deletable fields on the given model / resource.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param \Illuminate\Database\Eloquent\Model $model
* @param bool $skipSoftDeletes
* @return void
*/
protected function deleteFields(NovaRequest $request, $model, $skipSoftDeletes = true)
{
if ($skipSoftDeletes && $request->newResourceWith($model)->softDeletes()) {
return;
}
$request->newResourceWith($model)
->deletableFields($request)
->filter->isPrunable()
->each(function ($field) use ($request, $model) {
DeleteField::forRequest($request, $field, $model);
});
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\MetricRequest;
class DetailMetricController extends Controller
{
/**
* Get the specified metric's value.
*
* @param \Laravel\Nova\Http\Requests\MetricRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(MetricRequest $request)
{
return response()->json([
'value' => $request->detailMetric()->resolve($request),
]);
}
}

View File

@@ -0,0 +1,95 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Illuminate\Support\Str;
use Laravel\Nova\Http\Requests\NovaRequest;
class FieldAttachmentController extends Controller
{
/**
* Store an attachment for a Trix field.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function store(NovaRequest $request)
{
/** @var \Laravel\Nova\Fields\Field&\Laravel\Nova\Contracts\Storable $field */
$field = $request->newResource()
->availableFields($request)
->filter(function ($field) {
return optional($field)->withFiles === true;
})
->findFieldByAttribute($request->field, function () {
abort(404);
});
/** @phpstan-ignore-next-line */
$payload = call_user_func($field->attachCallback, $request);
return response()->json($payload);
}
/**
* Delete a single, persisted attachment for a Trix field by URL.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return \Illuminate\Http\Response
*/
public function destroyAttachment(NovaRequest $request)
{
/** @var \Laravel\Nova\Fields\Field&\Laravel\Nova\Contracts\Storable $field */
$field = $request->newResource()
->availableFields($request)
->filter(function ($field) {
return optional($field)->withFiles === true;
})
->findFieldByAttribute($request->field, function () {
abort(404);
});
/** @phpstan-ignore-next-line */
call_user_func($field->detachCallback, $request);
return response()->noContent(200);
}
/**
* Purge all pending attachments for a Trix field.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return \Illuminate\Http\Response
*/
public function destroyPending(NovaRequest $request)
{
/** @var \Laravel\Nova\Fields\Field&\Laravel\Nova\Contracts\Storable $field */
$field = $request->newResource()
->availableFields($request)
->filter(function ($field) {
return optional($field)->withFiles === true;
})
->findFieldByAttribute($request->field, function () {
abort(404);
});
/** @phpstan-ignore-next-line */
call_user_func($field->discardCallback, $request);
return response()->noContent(200);
}
/**
* Return a new draft ID for the field.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function draftId(NovaRequest $request)
{
return response()->json([
'draftId' => Str::uuid(),
]);
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Contracts\RelatableField;
use Laravel\Nova\Http\Requests\NovaRequest;
class FieldController extends Controller
{
/**
* Retrieve the given field for the given resource.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(NovaRequest $request)
{
$resource = $request->newResource();
$fields = $request->relatable
? $resource->availableFieldsOnIndexOrDetail($request)->whereInstanceOf(RelatableField::class)
: $resource->availableFields($request);
return response()->json(
$fields->findFieldByAttribute($request->field, function () {
abort(404);
})
);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Contracts\Downloadable;
use Laravel\Nova\DeleteField;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Nova;
class FieldDestroyController extends Controller
{
/**
* Delete the file at the given field.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return \Illuminate\Http\Response
*/
public function __invoke(NovaRequest $request)
{
$resource = $request->findResourceOrFail();
$resource->authorizeToUpdate($request);
$field = $resource->updateFields($request)
->whereInstanceOf(Downloadable::class)
->findFieldByAttribute($request->field, function () {
abort(404);
});
DeleteField::forRequest(
$request, $field, $resource->resource
)->save();
Nova::usingActionEvent(function ($actionEvent) use ($request, $resource) {
$actionEvent->forResourceUpdate(
Nova::user($request), $resource->resource
)->save();
});
return response()->noContent(200);
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\NovaRequest;
class FieldDownloadController extends Controller
{
/**
* Download the given field's contents.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return \Illuminate\Http\Response
*/
public function __invoke(NovaRequest $request)
{
$resource = $request->findResourceOrFail();
$resource->authorizeToView($request);
return $resource->downloadableFields($request)
->findFieldByAttribute($request->field, function () {
abort(404);
})
->toDownloadResponse($request, $resource);
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Contracts\Previewable;
use Laravel\Nova\Http\Requests\NovaRequest;
class FieldPreviewController extends Controller
{
/**
* Delete the file at the given field.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(NovaRequest $request)
{
$request->validate(['value' => ['nullable', 'string']]);
$resource = $request->newResource();
/** @var \Laravel\Nova\Fields\Field&\Laravel\Nova\Contracts\Previewable $field */
$field = $resource->availableFields($request)
->whereInstanceOf(Previewable::class)
->findFieldByAttribute($request->field, function () {
abort(404);
});
return response()->json([
'preview' => $field->previewFor($request->value),
]);
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\NovaRequest;
class FilterController extends Controller
{
/**
* List the filters for the given resource.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(NovaRequest $request)
{
return response()->json($request->newResource()->availableFilters($request));
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Auth\Notifications\ResetPassword;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Password;
use Inertia\Inertia;
use Laravel\Nova\Nova;
class ForgotPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset emails and
| includes a trait which assists in sending these notifications from
| your application to your users. Feel free to explore this trait.
|
*/
use SendsPasswordResetEmails;
use ValidatesRequests;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('nova.guest:'.config('nova.guard'));
ResetPassword::toMailUsing(function ($notifiable, $token) {
return (new MailMessage)
->subject(Nova::__('Reset Password Notification'))
->line(Nova::__('You are receiving this email because we received a password reset request for your account.'))
->action(Nova::__('Reset Password'), route('nova.pages.password.reset', ['token' => $token]))
->line(Nova::__('If you did not request a password reset, no further action is required.'));
});
}
/**
* Display the form to request a password reset link.
*
* @return \Inertia\Response
*/
public function showLinkRequestForm()
{
return Inertia::render('Nova.ForgotPassword');
}
/**
* Get the broker to be used during password reset.
*
* @return \Illuminate\Contracts\Auth\PasswordBroker
*/
public function broker()
{
return Password::broker(config('nova.passwords'));
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Laravel\Nova\Http\Requests\NovaRequest;
trait HandlesCustomRelationKeys
{
/**
* Determine if the user has set a custom relation key for the field.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return bool
*/
protected function usingCustomRelationKey(NovaRequest $request)
{
return $request->relatedResource !== $request->viaRelationship;
}
/**
* Get the rule key used for fetching the field's validation rules.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return mixed
*/
protected function getRuleKey(NovaRequest $request)
{
return $this->usingCustomRelationKey($request)
? $request->viaRelationship
: $request->relatedResource;
}
/**
* Get the custom field attributes names for validation.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param string $attribute
* @return array
*/
protected function customRulesKeys(NovaRequest $request, $attribute)
{
return [$this->getRuleKey($request) => $attribute];
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Auth;
use Laravel\Nova\Contracts\ImpersonatesUsers;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Nova;
use Laravel\Nova\Util;
class ImpersonateController extends Controller
{
/**
* Start impersonating a user.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param \Laravel\Nova\Contracts\ImpersonatesUsers $impersonator
* @return \Illuminate\Http\JsonResponse
*/
public function startImpersonating(NovaRequest $request, ImpersonatesUsers $impersonator)
{
if ($impersonator->impersonating($request)) {
return $this->stopImpersonating($request, $impersonator);
}
/** @var class-string<\Illuminate\Contracts\Auth\Authenticatable&\Illuminate\Database\Eloquent\Model> $userModel */
$userModel = with(Nova::modelInstanceForKey($request->input('resource')), function ($model) {
return ! is_null($model) ? get_class($model) : Util::userModel();
});
$authGuard = Util::sessionAuthGuardForModel($userModel);
$currentUser = Nova::user($request);
/** @var \Illuminate\Contracts\Auth\Authenticatable&\Illuminate\Database\Eloquent\Model $user */
$user = $userModel::findOrFail($request->input('resourceId'));
// Now that we're guaranteed to be a 'real' user, we'll make sure we're
// actually trying to impersonate someone besides ourselves, as that
// would be unnecessary.
if (! $currentUser->is($user)) {
abort_unless(optional($currentUser)->canImpersonate() ?? false, 403);
abort_unless(optional($user)->canBeImpersonated() ?? false, 403);
$impersonator->impersonate(
$request,
Auth::guard($authGuard),
$user
);
}
return $impersonator->redirectAfterStartingImpersonation($request);
}
/**
* Stop impersonating a user.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param \Laravel\Nova\Contracts\ImpersonatesUsers $impersonator
* @return \Illuminate\Http\JsonResponse
*/
public function stopImpersonating(NovaRequest $request, ImpersonatesUsers $impersonator)
{
$impersonator->stopImpersonating(
$request,
Auth::guard(config('nova.guard', config('auth.defaults.guard'))),
Util::userModel()
);
return $impersonator->redirectAfterStoppingImpersonation($request);
}
}

View File

@@ -0,0 +1,81 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Actions\Action;
use Laravel\Nova\Actions\ActionCollection;
use Laravel\Nova\Http\Requests\LensActionRequest;
use Laravel\Nova\Http\Requests\LensRequest;
use Laravel\Nova\Nova;
class LensActionController extends Controller
{
/**
* List the actions for the given resource.
*
* @param \Laravel\Nova\Http\Requests\LensRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function index(LensRequest $request)
{
$lens = $request->lens();
return response()->json(with([
'actions' => $lens->availableActionsOnIndex($request),
'pivotActions' => [
'name' => Nova::humanize($request->pivotName()),
'actions' => $lens->availablePivotActions($request),
],
'counts' => $lens->resolveActions($request)->countsByTypeOnIndex(),
], function ($payload) use ($lens, $request) {
$actionCounts = $lens->resolveActions($request)->countsByTypeOnIndex();
$pivotActionCounts = ActionCollection::make($payload['pivotActions']['actions'])->countsByTypeOnIndex();
$payload['counts'] = [
'standalone' => $actionCounts['standalone'] + $pivotActionCounts['standalone'],
'resource' => $actionCounts['resource'] + $pivotActionCounts['resource'],
];
return $payload;
}));
}
/**
* Perform an action on the specified resources.
*
* @param \Laravel\Nova\Http\Requests\LensActionRequest $request
* @return \Illuminate\Http\Response
*/
public function store(LensActionRequest $request)
{
$request->validateFields();
return $request->action()->handleRequest($request);
}
/**
* Sync an action field on the specified resources.
*
* @param \Laravel\Nova\Http\Requests\LensActionRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function sync(LensActionRequest $request)
{
$action = $request->lens()->availableActions($request)
->first(function ($action) use ($request) {
return $action->uriKey() === $request->query('action');
});
abort_unless($action instanceof Action, 404);
return response()->json(
collect($action->fields($request))
->filter(function ($field) use ($request) {
return $request->query('field') === $field->attribute &&
$request->query('component') === $field->dependentComponentKey();
})->each->syncDependsOn($request)
->first()
);
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\LensCardRequest;
class LensCardController extends Controller
{
/**
* List the cards for the given lens.
*
* @param \Laravel\Nova\Http\Requests\LensCardRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(LensCardRequest $request)
{
return response()->json(
$request->availableCards()
);
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\LensRequest;
use Laravel\Nova\Http\Resources\LensViewResource;
class LensController extends Controller
{
/**
* List the lenses for the given resource.
*
* @param \Laravel\Nova\Http\Requests\LensRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function index(LensRequest $request)
{
return response()->json(
$request->availableLenses()
);
}
/**
* Get the specified lens and its resources.
*
* @param \Laravel\Nova\Http\Requests\LensRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function show(LensRequest $request)
{
return LensViewResource::make()->toResponse($request);
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\NovaRequest;
class LensFilterController extends Controller
{
/**
* List the lenses for the given resource.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function index(NovaRequest $request)
{
$lenses = $request->newResource()->availableLenses($request);
$lens = $lenses->first(function ($lens) use ($request) {
return $lens->uriKey() === $request->lens;
});
return response()->json($lens->availableFilters($request));
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\LensMetricRequest;
class LensMetricController extends Controller
{
/**
* List the metrics for the given resource.
*
* @param \Laravel\Nova\Http\Requests\LensMetricRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function index(LensMetricRequest $request)
{
return response()->json(
$request->availableMetrics()
);
}
/**
* Get the specified metric's value.
*
* @param \Laravel\Nova\Http\Requests\LensMetricRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function show(LensMetricRequest $request)
{
return response()->json([
'value' => $request->metric()->resolve($request),
]);
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\LensCountRequest;
class LensResourceCountController extends Controller
{
/**
* Get the resource count for a given query.
*
* @param \Laravel\Nova\Http\Requests\LensCountRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(LensCountRequest $request)
{
return response()->json(['count' => $request->toCount()]);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Actions\Actionable;
use Laravel\Nova\Http\Requests\DeleteLensResourceRequest;
use Laravel\Nova\Nova;
class LensResourceDestroyController extends Controller
{
use DeletesFields;
/**
* Destroy the given resource(s).
*
* @param \Laravel\Nova\Http\Requests\DeleteLensResourceRequest $request
* @return \Illuminate\Http\Response
*/
public function __invoke(DeleteLensResourceRequest $request)
{
$request->chunks(150, function ($models) use ($request) {
$models->each(function ($model) use ($request) {
$this->deleteFields($request, $model);
if (in_array(Actionable::class, class_uses_recursive($model))) {
$model->actions()->delete();
}
$model->delete();
Nova::usingActionEvent(function ($actionEvent) use ($model, $request) {
$actionEvent->insert(
$actionEvent->forResourceDelete(Nova::user($request), collect([$model]))
->map->getAttributes()->all()
);
});
});
});
return response()->noContent(200);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Actions\Actionable;
use Laravel\Nova\Http\Requests\ForceDeleteLensResourceRequest;
use Laravel\Nova\Nova;
class LensResourceForceDeleteController extends Controller
{
use DeletesFields;
/**
* Force delete the given resource(s).
*
* @param \Laravel\Nova\Http\Requests\ForceDeleteLensResourceRequest $request
* @return \Illuminate\Http\Response
*/
public function __invoke(ForceDeleteLensResourceRequest $request)
{
$request->chunks(150, function ($models) use ($request) {
$models->each(function ($model) use ($request) {
$this->forceDeleteFields($request, $model);
if (in_array(Actionable::class, class_uses_recursive($model))) {
$model->actions()->delete();
}
$model->forceDelete();
Nova::usingActionEvent(function ($actionEvent) use ($model, $request) {
$actionEvent->insert(
$actionEvent->forResourceDelete(Nova::user($request), collect([$model]))
->map->getAttributes()->all()
);
});
});
});
return response()->noContent(200);
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\RestoreLensResourceRequest;
use Laravel\Nova\Nova;
class LensResourceRestoreController extends Controller
{
/**
* Force delete the given resource(s).
*
* @param \Laravel\Nova\Http\Requests\RestoreLensResourceRequest $request
* @return \Illuminate\Http\Response
*/
public function __invoke(RestoreLensResourceRequest $request)
{
$request->chunks(150, function ($models) use ($request) {
$models->each(function ($model) use ($request) {
$model->restore();
Nova::usingActionEvent(function ($actionEvent) use ($model, $request) {
$actionEvent->insert(
$actionEvent->forResourceRestore(Nova::user($request), collect([$model]))
->map->getAttributes()->all()
);
});
});
});
return response()->noContent(200);
}
}

View File

@@ -0,0 +1,107 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Auth;
use Inertia\Inertia;
use Laravel\Nova\Nova;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
use ValidatesRequests;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('nova.guest:'.config('nova.guard'))->except('logout');
}
/**
* Show the application's login form.
*
* @return \Inertia\Response|\Symfony\Component\HttpFoundation\Response
*/
public function showLoginForm()
{
if ($loginPath = config('nova.routes.login', false)) {
return Inertia::location($loginPath);
}
return Inertia::render('Nova.Login', []);
}
/**
* The user has been authenticated.
*
* @param \Illuminate\Http\Request $request
* @param mixed $user
* @return mixed
*/
protected function authenticated(Request $request, $user)
{
$redirect = redirect()->intended($this->redirectPath($request));
return $request->wantsJson()
? new JsonResponse([
'redirect' => $redirect->getTargetUrl(),
], 200)
: $redirect;
}
/**
* Log the user out of the application.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function logout(Request $request)
{
$this->guard()->logout();
$request->session()->invalidate();
return redirect()->intended($this->redirectPath($request));
}
/**
* Get the post register / login redirect path.
*
* @param \Illuminate\Http\Request $request
* @return string
*/
public function redirectPath(Request $request)
{
return Nova::url(Nova::resolveInitialPath($request));
}
/**
* Get the guard to be used during authentication.
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard()
{
return Auth::guard(config('nova.guard'));
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\MetricRequest;
class MetricController extends Controller
{
/**
* List the metrics for the given resource.
*
* @param \Laravel\Nova\Http\Requests\MetricRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function index(MetricRequest $request)
{
return response()->json(
$request->availableMetrics()
);
}
/**
* Get the specified metric's value.
*
* @param \Laravel\Nova\Http\Requests\MetricRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function show(MetricRequest $request)
{
return response()->json([
'value' => $request->metric()->resolve($request),
]);
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Contracts\RelatableField;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Nova;
class MorphableController extends Controller
{
/**
* List the available morphable resources for a given resource.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return array
*/
public function __invoke(NovaRequest $request)
{
$relatedResource = Nova::resourceForKey($request->type);
abort_if(is_null($relatedResource), 403);
$field = $request->newResource()
->availableFieldsOnIndexOrDetail($request)
->whereInstanceOf(RelatableField::class)
->findFieldByAttribute($request->field, function () {
abort(404);
})->applyDependsOn($request);
$withTrashed = $this->shouldIncludeTrashed(
$request, $relatedResource
);
$limit = $relatedResource::usesScout()
? $relatedResource::$scoutSearchResults
: $relatedResource::$relatableSearchResults;
$shouldReorderAssociatableValues = $field->shouldReorderAssociatableValues($request) && ! $relatedResource::usesScout();
return [
'resources' => $field->buildMorphableQuery($request, $relatedResource, $withTrashed)
->take($limit)
->get()
->mapInto($relatedResource)
->filter->authorizedToAdd($request, $request->model())
->map(function ($resource) use ($request, $field, $relatedResource) {
return $field->formatMorphableResource($request, $resource, $relatedResource);
})->when($shouldReorderAssociatableValues, function ($collection) {
return $collection->sortBy('display');
})->values(),
'withTrashed' => $withTrashed,
'softDeletes' => $relatedResource::softDeletes(),
];
}
/**
* Determine if the query should include trashed models.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param string $associatedResource
* @return bool
*/
protected function shouldIncludeTrashed(NovaRequest $request, $associatedResource)
{
if ($request->withTrashed === 'true') {
return true;
}
$associatedModel = $associatedResource::newModel();
if ($request->current && empty($request->search) && $associatedResource::softDeletes()) {
$associatedModel = $associatedModel->newQueryWithoutScopes()->find($request->current);
return $associatedModel ? $associatedModel->trashed() : false;
}
return false;
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use DateTime;
use Laravel\Nova\Http\Requests\NovaRequest;
class MorphedResourceAttachController extends ResourceAttachController
{
/**
* Initialize a fresh pivot model for the relationship.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param \Illuminate\Database\Eloquent\Relations\MorphToMany $relationship
* @return \Illuminate\Database\Eloquent\Relations\Pivot
*/
protected function initializePivot(NovaRequest $request, $relationship)
{
$model = tap($request->findResourceOrFail(), function ($resource) use ($request) {
abort_unless($resource->hasRelatableField($request, $request->viaRelationship), 404);
})->model();
$parentKey = $request->resourceId;
$relatedKey = $request->input($request->relatedResource);
$parentKeyName = $relationship->getParentKeyName();
$relatedKeyName = $relationship->getRelatedKeyName();
if ($parentKeyName !== $request->model()->getKeyName()) {
$parentKey = $request->findModelOrFail()->{$parentKeyName};
}
if ($relatedKeyName !== $request->newRelatedResource()::newModel()->getKeyName()) {
$relatedKey = $request->findRelatedModelOrFail()->{$relatedKeyName};
}
($pivot = $relationship->newPivot($relationship->getDefaultPivotAttributes(), false))->forceFill([
$relationship->getForeignPivotKeyName() => $parentKey,
$relationship->getRelatedPivotKeyName() => $relatedKey,
$relationship->getMorphType() => $model->{$request->viaRelationship}()->getMorphClass(),
]);
if ($relationship->withTimestamps) {
$pivot->forceFill([
$relationship->createdAt() => new DateTime,
$relationship->updatedAt() => new DateTime,
]);
}
return $pivot;
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\NotificationRequest;
use Laravel\Nova\Notifications\Notification;
use Laravel\Nova\Nova;
class NotificationDeleteAllController extends Controller
{
/**
* Delete all notifications.
*
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(NotificationRequest $request)
{
Notification::whereNotifiableId(Nova::user($request)->getKey())->delete();
return response()->json();
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\NotificationRequest;
use Laravel\Nova\Notifications\Notification;
class NotificationDeleteController extends Controller
{
/**
* Mark the given notification as read.
*
* @param \Laravel\Nova\Http\Requests\NotificationRequest $request
* @param int|string $notification
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(NotificationRequest $request, $notification)
{
$notification = Notification::findOrFail($notification);
$notification->update(['read_at' => now()]);
$notification->delete();
return response()->json();
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\NotificationRequest;
class NotificationIndexController extends Controller
{
/**
* Return the details for the Dashboard.
*
* @param \Laravel\Nova\Http\Requests\NotificationRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(NotificationRequest $request)
{
return response()->json([
'notifications' => $request->notifications(),
'unread' => $request->unreadCount(),
]);
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\NotificationRequest;
use Laravel\Nova\Notifications\Notification;
use Laravel\Nova\Nova;
class NotificationReadAllController extends Controller
{
/**
* Mark the given notification as read.
*
* @param \Laravel\Nova\Http\Requests\NotificationRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(NotificationRequest $request)
{
Notification::unread()->whereNotifiableId(Nova::user($request)->getKey())->update(['read_at' => now()]);
return response()->json();
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\NotificationRequest;
use Laravel\Nova\Notifications\Notification;
class NotificationReadController extends Controller
{
/**
* Mark the given notification as read.
*
* @param \Laravel\Nova\Http\Requests\NotificationRequest $request
* @param int|string $notification
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(NotificationRequest $request, $notification)
{
$notification = Notification::findOrFail($notification);
$notification->update(['read_at' => now()]);
return response()->json();
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\NotificationRequest;
use Laravel\Nova\Notifications\Notification;
class NotificationUnreadController extends Controller
{
/**
* Mark the given notification as unread.
*
* @param \Laravel\Nova\Http\Requests\NotificationRequest $request
* @param int|string $notification
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(NotificationRequest $request, $notification)
{
$notification = Notification::findOrFail($notification);
$notification->update(['read_at' => null]);
return response()->json();
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace Laravel\Nova\Http\Controllers\Pages;
use Illuminate\Routing\Controller;
use Inertia\Inertia;
use Laravel\Nova\Http\Requests\ResourceCreateOrAttachRequest;
use Laravel\Nova\Menu\Breadcrumb;
use Laravel\Nova\Menu\Breadcrumbs;
use Laravel\Nova\Nova;
class AttachableController extends Controller
{
/**
* Show Resource Attach page using Inertia.
*
* @param \Laravel\Nova\Http\Requests\ResourceCreateOrAttachRequest $request
* @return \Inertia\Response
*/
public function __invoke(ResourceCreateOrAttachRequest $request)
{
$resourceClass = $request->resource();
$isPolymorphic = function ($query) {
return is_null($query) || in_array($query, [true, 1, '1']);
};
$parentResource = $request->findResourceOrFail();
return Inertia::render('Nova.Attach', [
'breadcrumbs' => $this->breadcrumbs($request),
'resourceName' => $resourceClass::uriKey(),
'resourceId' => $request->resourceId,
'relatedResourceName' => $request->relatedResource,
'viaRelationship' => $request->query('viaRelationship'),
'polymorphic' => $isPolymorphic($request->query('polymorphic')),
'viaResource' => $parentResource::uriKey(),
'viaResourceId' => $parentResource->resource->getKey(),
'parentResource' => [
'name' => $parentResource->singularLabel(),
'display' => $parentResource->title(),
],
]);
}
/**
* Get breadcrumb menu for the page.
*
* @param \Laravel\Nova\Http\Requests\ResourceCreateOrAttachRequest $request
* @return \Laravel\Nova\Menu\Breadcrumbs
*/
protected function breadcrumbs(ResourceCreateOrAttachRequest $request)
{
$resourceClass = $request->resource();
$relatedResourceClass = $request->relatedResource();
return Breadcrumbs::make([
Breadcrumb::make(Nova::__('Resources')),
Breadcrumb::resource($resourceClass),
Breadcrumb::resource($request->findResourceOrFail()),
Breadcrumb::make(Nova::__('Attach :resource', ['resource' => $relatedResourceClass::singularLabel()])),
]);
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace Laravel\Nova\Http\Controllers\Pages;
use Illuminate\Routing\Controller;
use Inertia\Inertia;
use Laravel\Nova\Http\Requests\ResourceUpdateOrUpdateAttachedRequest;
use Laravel\Nova\Menu\Breadcrumb;
use Laravel\Nova\Menu\Breadcrumbs;
use Laravel\Nova\Nova;
class AttachedResourceUpdateController extends Controller
{
/**
* Show Resource Update Attached page using Inertia.
*
* @param \Laravel\Nova\Http\Requests\ResourceUpdateOrUpdateAttachedRequest $request
* @return \Inertia\Response
*/
public function __invoke(ResourceUpdateOrUpdateAttachedRequest $request)
{
$resourceClass = $request->resource();
$isPolymorphic = function ($query) {
return is_null($query) || in_array($query, [true, 1, '1']);
};
$parentResource = $request->findResourceOrFail();
return Inertia::render('Nova.UpdateAttached', [
'breadcrumbs' => $this->breadcrumbs($request),
'resourceName' => $resourceClass::uriKey(),
'resourceId' => $request->resourceId,
'relatedResourceName' => $request->relatedResource,
'relatedResourceId' => $request->relatedResourceId,
'viaRelationship' => $request->query('viaRelationship'),
'viaPivotId' => $request->query('viaPivotId'),
'polymorphic' => $isPolymorphic($request->query('polymorphic')),
'viaResource' => $parentResource::uriKey(),
'viaResourceId' => $parentResource->resource->getKey(),
'parentResource' => [
'name' => $parentResource->singularLabel(),
'display' => $parentResource->title(),
],
]);
}
/**
* Get breadcrumb menu for the page.
*
* @param \Laravel\Nova\Http\Requests\ResourceUpdateOrUpdateAttachedRequest $request
* @return \Laravel\Nova\Menu\Breadcrumbs
*/
protected function breadcrumbs(ResourceUpdateOrUpdateAttachedRequest $request)
{
$resourceClass = $request->resource();
$resource = $request->findResourceOrFail();
$relatedResource = $request->findRelatedResourceOrFail($request->relatedResourceId);
return Breadcrumbs::make([
Breadcrumb::make(Nova::__('Resources')),
Breadcrumb::resource($resourceClass),
Breadcrumb::resource($resource),
Breadcrumb::make(Nova::__('Update attached :resource: :title', [
'resource' => $relatedResource::singularLabel(),
'title' => $relatedResource->title(),
])),
]);
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace Laravel\Nova\Http\Controllers\Pages;
use Illuminate\Routing\Controller;
use Inertia\Inertia;
use Laravel\Nova\Http\Requests\DashboardRequest;
use Laravel\Nova\Http\Resources\DashboardViewResource;
use Laravel\Nova\Menu\Breadcrumb;
use Laravel\Nova\Menu\Breadcrumbs;
use Laravel\Nova\Nova;
class DashboardController extends Controller
{
/**
* Show Resource Create page using Inertia.
*
* @param \Laravel\Nova\Http\Requests\DashboardRequest $request
* @param string $name
* @return \Illuminate\Http\RedirectResponse|\Inertia\Response
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
public function __invoke(DashboardRequest $request, $name = 'main')
{
DashboardViewResource::make($name)->authorizedDashboardForRequest($request);
return Inertia::render('Nova.Dashboard', [
'breadcrumbs' => $this->breadcrumbs($request, $name),
'name' => $name,
]);
}
/**
* Get breadcrumb menu for the page.
*
* @param \Laravel\Nova\Http\Requests\DashboardRequest $request
* @param string $name
* @return \Laravel\Nova\Menu\Breadcrumbs
*/
protected function breadcrumbs(DashboardRequest $request, string $name)
{
return Breadcrumbs::make([
Breadcrumb::make(Nova::__('Dashboards')),
Breadcrumb::make(Nova::dashboardForKey($name, $request)->label()),
]);
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Laravel\Nova\Http\Controllers\Pages;
use Illuminate\Routing\Controller;
class Error403Controller extends Controller
{
/**
* Show Nova 403 page using Inertia.
*
* @return void
*
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
*/
public function __invoke()
{
abort(403);
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Laravel\Nova\Http\Controllers\Pages;
use Illuminate\Routing\Controller;
class Error404Controller extends Controller
{
/**
* Show Nova 404 page using Inertia.
*
* @return void
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
public function __invoke()
{
abort(404);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Laravel\Nova\Http\Controllers\Pages;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Laravel\Nova\Nova;
class HomeController extends Controller
{
/**
* Show Nova homepage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function __invoke(Request $request)
{
return redirect(Nova::url(Nova::resolveInitialPath($request)));
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Laravel\Nova\Http\Controllers\Pages;
use Illuminate\Routing\Controller;
use Inertia\Inertia;
use Laravel\Nova\Http\Requests\LensRequest;
use Laravel\Nova\Http\Resources\LensViewResource;
use Laravel\Nova\Menu\Breadcrumb;
use Laravel\Nova\Menu\Breadcrumbs;
use Laravel\Nova\Nova;
class LensController extends Controller
{
/**
* Show Resource Lens page using Inertia.
*
* @param \Laravel\Nova\Http\Requests\LensRequest $request
* @return \Inertia\Response
*/
public function __invoke(LensRequest $request)
{
$lens = LensViewResource::make()->authorizedLensForRequest($request);
return Inertia::render('Nova.Lens', [
'breadcrumbs' => $this->breadcrumbs($request),
'resourceName' => $request->route('resource'),
'lens' => $lens->uriKey(),
'searchable' => $lens::searchable(),
]);
}
/**
* Get breadcrumb menu for the page.
*
* @param \Laravel\Nova\Http\Requests\LensRequest $request
* @return \Laravel\Nova\Menu\Breadcrumbs
*/
protected function breadcrumbs(LensRequest $request)
{
return Breadcrumbs::make([
Breadcrumb::make(Nova::__('Resources')),
Breadcrumb::resource($request->resource()),
Breadcrumb::make($request->lens()->name()),
]);
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace Laravel\Nova\Http\Controllers\Pages;
use Illuminate\Routing\Controller;
use Inertia\Inertia;
use Laravel\Nova\Http\Requests\ResourceCreateOrAttachRequest;
use Laravel\Nova\Menu\Breadcrumb;
use Laravel\Nova\Menu\Breadcrumbs;
use Laravel\Nova\Nova;
class ResourceCreateController extends Controller
{
/**
* Show Resource Create page using Inertia.
*
* @param \Laravel\Nova\Http\Requests\ResourceCreateOrAttachRequest $request
* @return \Inertia\Response
*/
public function __invoke(ResourceCreateOrAttachRequest $request)
{
$resourceClass = $request->resource();
$resourceClass::authorizeToCreate($request);
return Inertia::render('Nova.Create', [
'breadcrumbs' => $this->breadcrumbs($request),
'resourceName' => $resourceClass::uriKey(),
'viaResource' => $request->query('viaResource') ?? '',
'viaResourceId' => $request->query('viaResourceId') ?? '',
'viaRelationship' => $request->query('viaRelationship') ?? '',
]);
}
/**
* Get breadcrumb menu for the page.
*
* @param \Laravel\Nova\Http\Requests\ResourceCreateOrAttachRequest $request
* @return \Laravel\Nova\Menu\Breadcrumbs
*/
protected function breadcrumbs(ResourceCreateOrAttachRequest $request)
{
$resourceClass = $request->resource();
return Breadcrumbs::make(
collect([Breadcrumb::make(Nova::__('Resources'))])->when($request->viaRelationship(), function ($breadcrumbs) use ($request) {
return $breadcrumbs->push(
Breadcrumb::resource($request->viaResource()),
Breadcrumb::resource($request->findParentResourceOrFail())
);
}, function ($breadcrumbs) use ($resourceClass) {
return $breadcrumbs->push(Breadcrumb::resource($resourceClass));
})->push(
Breadcrumb::make(Nova::__('Create :resource', ['resource' => $resourceClass::singularLabel()]))
)->all()
);
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace Laravel\Nova\Http\Controllers\Pages;
use Illuminate\Routing\Controller;
use Inertia\Inertia;
use Laravel\Nova\Http\Requests\ResourceDetailRequest;
use Laravel\Nova\Http\Resources\DetailViewResource;
use Laravel\Nova\Menu\Breadcrumb;
use Laravel\Nova\Menu\Breadcrumbs;
use Laravel\Nova\Nova;
class ResourceDetailController extends Controller
{
/**
* Show Resource Detail page using Inertia.
*
* @param \Laravel\Nova\Http\Requests\ResourceDetailRequest $request
* @return \Inertia\Response
*/
public function __invoke(ResourceDetailRequest $request)
{
$resourceClass = $request->resource();
return Inertia::render('Nova.Detail', [
'breadcrumbs' => $this->breadcrumbs($request),
'resourceName' => $resourceClass::uriKey(),
'resourceId' => $request->resourceId,
]);
}
/**
* Get breadcrumb menu for the page.
*
* @param \Laravel\Nova\Http\Requests\ResourceDetailRequest $request
* @return \Laravel\Nova\Menu\Breadcrumbs
*
* @throws \Illuminate\Auth\Access\AuthorizationException
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
protected function breadcrumbs(ResourceDetailRequest $request)
{
$resource = DetailViewResource::make()->authorizedResourceForRequest($request);
return Breadcrumbs::make([
Breadcrumb::make(Nova::__('Resources')),
Breadcrumb::resource($request->resource()),
Breadcrumb::make(Nova::__(':resource Details: :title', [
'resource' => $resource::singularLabel(),
'title' => $resource->title(),
])),
]);
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Laravel\Nova\Http\Controllers\Pages;
use Illuminate\Routing\Controller;
use Inertia\Inertia;
use Laravel\Nova\Http\Requests\ResourceIndexRequest;
use Laravel\Nova\Http\Resources\IndexViewResource;
use Laravel\Nova\Menu\Breadcrumb;
use Laravel\Nova\Menu\Breadcrumbs;
use Laravel\Nova\Nova;
class ResourceIndexController extends Controller
{
/**
* Show Resource Index page using Inertia.
*
* @param \Laravel\Nova\Http\Requests\ResourceIndexRequest $request
* @return \Inertia\Response
*/
public function __invoke(ResourceIndexRequest $request)
{
$resourceClass = IndexViewResource::make()->authorizedResourceForRequest($request);
return Inertia::render('Nova.Index', [
'breadcrumbs' => $this->breadcrumbs($request),
'resourceName' => $resourceClass::uriKey(),
]);
}
/**
* Get breadcrumb menu for the page.
*
* @param \Laravel\Nova\Http\Requests\ResourceIndexRequest $request
* @return \Laravel\Nova\Menu\Breadcrumbs
*/
protected function breadcrumbs(ResourceIndexRequest $request)
{
return Breadcrumbs::make([
Breadcrumb::make(Nova::__('Resources')),
Breadcrumb::resource($request->resource()),
]);
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Laravel\Nova\Http\Controllers\Pages;
use Illuminate\Routing\Controller;
use Inertia\Inertia;
use Laravel\Nova\Http\Requests\ResourceCreateOrAttachRequest;
use Laravel\Nova\Menu\Breadcrumb;
use Laravel\Nova\Menu\Breadcrumbs;
use Laravel\Nova\Nova;
class ResourceReplicateController extends Controller
{
/**
* Show Resource Replicate page using Inertia.
*
* @param \Laravel\Nova\Http\Requests\ResourceCreateOrAttachRequest $request
* @return \Inertia\Response
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
public function __invoke(ResourceCreateOrAttachRequest $request)
{
abort_unless($request->findModelQuery()->exists(), 404);
$resourceClass = $request->resource();
return Inertia::render('Nova.Replicate', [
'breadcrumbs' => $this->breadcrumbs($request),
'resourceName' => $resourceClass::uriKey(),
'resourceId' => $request->resourceId,
'viaResource' => $request->query('viaResource') ?? '',
'viaResourceId' => $request->query('viaResourceId') ?? '',
'viaRelationship' => $request->query('viaRelationship') ?? '',
]);
}
/**
* Get breadcrumb menu for the page.
*
* @param \Laravel\Nova\Http\Requests\ResourceCreateOrAttachRequest $request
* @return \Laravel\Nova\Menu\Breadcrumbs
*/
protected function breadcrumbs(ResourceCreateOrAttachRequest $request)
{
$resourceClass = $request->resource();
return Breadcrumbs::make([
Breadcrumb::make(Nova::__('Resources')),
Breadcrumb::resource($resourceClass),
Breadcrumb::resource($request->findResourceOrFail()),
Breadcrumb::make(Nova::__('Replicate :resource', ['resource' => $resourceClass::singularLabel()])),
]);
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace Laravel\Nova\Http\Controllers\Pages;
use Illuminate\Routing\Controller;
use Inertia\Inertia;
use Laravel\Nova\Http\Requests\ResourceUpdateOrUpdateAttachedRequest;
use Laravel\Nova\Http\Resources\UpdateViewResource;
use Laravel\Nova\Menu\Breadcrumb;
use Laravel\Nova\Menu\Breadcrumbs;
use Laravel\Nova\Nova;
class ResourceUpdateController extends Controller
{
/**
* Show Resource Update page using Inertia.
*
* @param \Laravel\Nova\Http\Requests\ResourceUpdateOrUpdateAttachedRequest $request
* @return \Inertia\Response
*/
public function __invoke(ResourceUpdateOrUpdateAttachedRequest $request)
{
abort_unless($request->findModelQuery()->exists(), 404);
$resourceClass = $request->resource();
return Inertia::render('Nova.Update', [
'breadcrumbs' => $this->breadcrumb($request),
'resourceName' => $resourceClass::uriKey(),
'resourceId' => $request->resourceId,
'viaResource' => $request->query('viaResource') ?? '',
'viaResourceId' => $request->query('viaResourceId') ?? '',
'viaRelationship' => $request->query('viaRelationship') ?? '',
]);
}
/**
* Get breadcrumb menu for the page.
*
* @param \Laravel\Nova\Http\Requests\ResourceUpdateOrUpdateAttachedRequest $request
* @return \Laravel\Nova\Menu\Breadcrumbs
*
* @throws \Illuminate\Auth\Access\AuthorizationException
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
protected function breadcrumb(ResourceUpdateOrUpdateAttachedRequest $request)
{
$resourceClass = $request->resource();
$resource = UpdateViewResource::make()->newResourceWith($request);
return Breadcrumbs::make(
collect([Breadcrumb::make(Nova::__('Resources'))])->when($request->viaRelationship(), function ($breadcrumbs) use ($request) {
return $breadcrumbs->push(
Breadcrumb::resource($request->viaResource()),
Breadcrumb::resource($request->findParentResource())
);
}, function ($breadcrumbs) use ($resourceClass, $resource) {
return $breadcrumbs->push(
Breadcrumb::resource($resourceClass),
Breadcrumb::resource($resource),
);
})->push(
Breadcrumb::make(Nova::__('Update :resource', ['resource' => $resourceClass::singularLabel()]))
)->all()
);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\DeleteField;
use Laravel\Nova\Http\Requests\PivotFieldDestroyRequest;
use Laravel\Nova\Nova;
class PivotFieldDestroyController extends Controller
{
/**
* Delete the file at the given field.
*
* @param \Laravel\Nova\Http\Requests\PivotFieldDestroyRequest $request
* @return \Illuminate\Http\Response
*/
public function __invoke(PivotFieldDestroyRequest $request)
{
$request->authorizeForAttachment();
DeleteField::forRequest(
$request, $request->findFieldOrFail(),
$pivot = $request->findPivotModel()
)->save();
Nova::usingActionEvent(function ($actionEvent) use ($request, $pivot) {
$actionEvent->forAttachedResourceUpdate(
$request, $request->findModelOrFail(), $pivot
)->save();
});
return response()->noContent(200);
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\NovaRequest;
class RelatableAuthorizationController extends Controller
{
/**
* Get the relatable authorization status for the resource.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return array
*/
public function __invoke(NovaRequest $request)
{
$parentResource = $request->findParentResourceOrFail();
$resource = $request->resource();
if ($request->viaManyToMany()) {
return ['authorized' => $parentResource->authorizedToAttachAny(
$request, $request->model()
)];
}
return ['authorized' => $parentResource->authorizedToAdd(
$request, $request->model()
) && $resource::authorizedToCreate($request)];
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Password;
use Inertia\Inertia;
use Laravel\Nova\Nova;
class ResetPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset requests
| and uses a simple trait to include this behavior. You're free to
| explore this trait and override any methods you wish to tweak.
|
*/
use ResetsPasswords;
use ValidatesRequests;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('nova.guest:'.config('nova.guard'));
}
/**
* Display the password reset view for the given token.
*
* If no token is present, display the link request form.
*
* @param \Illuminate\Http\Request $request
* @return \Inertia\Response
*/
public function showResetForm(Request $request)
{
$token = $request->route()->parameter('token');
return Inertia::render('Nova.ResetPassword', [
'token' => $token,
'email' => $request->email,
]);
}
/**
* Get the URI the user should be redirected to after resetting their password.
*
* @return string
*/
public function redirectPath()
{
return Nova::path();
}
/**
* Get the broker to be used during password reset.
*
* @return \Illuminate\Contracts\Auth\PasswordBroker
*/
public function broker()
{
return Password::broker(config('nova.passwords'));
}
/**
* Get the guard to be used during password reset.
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard()
{
return Auth::guard(config('nova.guard'));
}
}

View File

@@ -0,0 +1,150 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use DateTime;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Nova;
use Throwable;
class ResourceAttachController extends Controller
{
use HandlesCustomRelationKeys;
/**
* The action event for the action.
*
* @var \Laravel\Nova\Actions\ActionEvent|null
*/
protected $actionEvent;
/**
* Attach a related resource to the given resource.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return \Illuminate\Http\Response
*/
public function __invoke(NovaRequest $request)
{
$resource = $request->resource();
$model = $request->findModelOrFail();
tap(new $resource($model), function ($resource) use ($request) {
abort_unless($resource->hasRelatableField($request, $request->viaRelationship), 404);
});
$this->validate($request, $model, $resource);
try {
DB::connection($model->getConnectionName())->transaction(function () use ($request, $resource, $model) {
[$pivot, $callbacks] = $resource::fillPivot(
$request,
$model,
$this->initializePivot(
$request,
$model->{$request->viaRelationship}()
)
);
DB::transaction(function () use ($request, $model, $pivot) {
Nova::usingActionEvent(function ($actionEvent) use ($request, $model, $pivot) {
$this->actionEvent = $actionEvent->forAttachedResource($request, $model, $pivot);
$this->actionEvent->save();
});
});
$pivot->save();
collect($callbacks)->each->__invoke();
});
return response()->noContent(200);
} catch (Throwable $e) {
optional($this->actionEvent)->delete();
throw $e;
}
}
/**
* Validate the attachment request.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param \Illuminate\Database\Eloquent\Model $model
* @param class-string<\Laravel\Nova\Resource> $resourceClass
* @return void
*/
protected function validate(NovaRequest $request, $model, $resourceClass)
{
$attribute = $resourceClass::validationAttachableAttributeFor($request, $request->relatedResource);
tap($this->creationRules($request, $resourceClass), function ($rules) use ($resourceClass, $request, $attribute) {
Validator::make($request->all(), $rules, [], $this->customRulesKeys($request, $attribute))->validate();
$resourceClass::validateForAttachment($request);
});
}
/**
* Return the validation rules used for the request. Correctly aasign the rules used
* to the main attribute if the user has defined a custom relation key.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param class-string<\Laravel\Nova\Resource> $resourceClass
* @return mixed
*/
protected function creationRules(NovaRequest $request, $resourceClass)
{
$rules = $resourceClass::creationRulesFor($request, $this->getRuleKey($request));
if ($this->usingCustomRelationKey($request)) {
$rules[$request->relatedResource] = $rules[$request->viaRelationship];
unset($rules[$request->viaRelationship]);
}
return $rules;
}
/**
* Initialize a fresh pivot model for the relationship.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param \Illuminate\Database\Eloquent\Relations\BelongsToMany $relationship
* @return \Illuminate\Database\Eloquent\Relations\Pivot
*
* @throws \Exception
*/
protected function initializePivot(NovaRequest $request, $relationship)
{
$parentKey = $request->resourceId;
$relatedKey = $request->input($request->relatedResource);
$parentKeyName = $relationship->getParentKeyName();
$relatedKeyName = $relationship->getRelatedKeyName();
if ($parentKeyName !== $request->model()->getKeyName()) {
$parentKey = $request->findModelOrFail()->{$parentKeyName};
}
if ($relatedKeyName !== $request->newRelatedResource()::newModel()->getKeyName()) {
$relatedKey = $request->findRelatedModelOrFail()->{$relatedKeyName};
}
($pivot = $relationship->newPivot($relationship->getDefaultPivotAttributes(), false))->forceFill([
$relationship->getForeignPivotKeyName() => $parentKey,
$relationship->getRelatedPivotKeyName() => $relatedKey,
]);
if ($relationship->withTimestamps) {
$pivot->forceFill([
$relationship->createdAt() => new DateTime,
$relationship->updatedAt() => new DateTime,
]);
}
return $pivot;
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\ResourceIndexRequest;
class ResourceCountController extends Controller
{
/**
* Get the resource count for a given query.
*
* @param \Laravel\Nova\Http\Requests\ResourceIndexRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(ResourceIndexRequest $request)
{
return response()->json(['count' => $request->toCount()]);
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Routing\Controller;
use Laravel\Nova\Actions\Actionable;
use Laravel\Nova\Http\Requests\DeleteResourceRequest;
use Laravel\Nova\Nova;
use Laravel\Nova\URL;
class ResourceDestroyController extends Controller
{
use DeletesFields;
/**
* Destroy the given resource(s).
*
* @param \Laravel\Nova\Http\Requests\DeleteResourceRequest $request
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response
*/
public function __invoke(DeleteResourceRequest $request)
{
$request->chunks(150, function ($models) use ($request) {
$models->each(function ($model) use ($request) {
$this->deleteFields($request, $model);
$uses = class_uses_recursive($model);
if (in_array(Actionable::class, $uses) && ! in_array(SoftDeletes::class, $uses)) {
$model->actions()->delete();
}
$model->delete();
$request->resource()::afterDelete($request, $model);
Nova::usingActionEvent(function ($actionEvent) use ($model, $request) {
$actionEvent->insert(
$actionEvent->forResourceDelete(Nova::user($request), collect([$model]))
->map->getAttributes()->all()
);
});
});
});
if ($request->isForSingleResource() && ! is_null($redirect = $request->resource()::redirectAfterDelete($request))) {
return response()->json([
'redirect' => URL::make($redirect),
]);
}
return response()->noContent(200);
}
}

View File

@@ -0,0 +1,94 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Contracts\Deletable;
use Laravel\Nova\DeleteField;
use Laravel\Nova\Http\Requests\DetachResourceRequest;
use Laravel\Nova\Nova;
class ResourceDetachController extends Controller
{
/**
* Detach the given resource(s).
*
* @param \Laravel\Nova\Http\Requests\DetachResourceRequest $request
* @return \Illuminate\Http\Response
*/
public function __invoke(DetachResourceRequest $request)
{
$parent = tap($request->findParentResourceOrFail(), function ($resource) use ($request) {
abort_unless($resource->hasRelatableField($request, $request->viaRelationship), 409);
})->model();
$relation = $parent->{$request->viaRelationship}();
$accessor = $relation->getPivotAccessor();
$accessorKeyName = transform($relation->getPivotClass(), function ($pivotClass) {
return (new $pivotClass())->getKeyName();
});
$inPivots = $request->resources !== 'all' ? $request->pivots : null;
$request->chunks(150, function ($models) use ($accessor, $accessorKeyName, $inPivots, $parent, $request) {
foreach ($models as $model) {
$pivot = $model->{$accessor};
if (empty($inPivots) || in_array($pivot->getAttribute($accessorKeyName), $inPivots)) {
$this->deletePivot(
$request, $pivot, $model, $parent
);
}
}
});
return response()->noContent(200);
}
/**
* Delete pivot relations from model.
*
* @param \Laravel\Nova\Http\Requests\DetachResourceRequest $request
* @param \Illuminate\Database\Eloquent\Model $pivot
* @param \Illuminate\Database\Eloquent\Model $model
* @param \Illuminate\Database\Eloquent\Model $parent
* @return void
*/
protected function deletePivot(DetachResourceRequest $request, $pivot, $model, $parent)
{
$this->deletePivotFields(
$request, $resource = $request->newResourceWith($model), $pivot
);
$pivot->delete();
Nova::usingActionEvent(function ($actionEvent) use ($pivot, $model, $parent, $request) {
$actionEvent->insert(
$actionEvent->forResourceDetach(
Nova::user($request), $parent, collect([$model]), $pivot->getMorphClass()
)->map->getAttributes()->all()
);
});
}
/**
* Delete the pivot fields on the given pivot model.
*
* @param \Laravel\Nova\Http\Requests\DetachResourceRequest $request
* @param \Laravel\Nova\Resource $resource
* @param \Illuminate\Database\Eloquent\Model $pivot
* @return void
*/
protected function deletePivotFields(DetachResourceRequest $request, $resource, $pivot)
{
$resource->resolvePivotFields($request, $request->viaResource)
->whereInstanceOf(Deletable::class)
->filter->isPrunable()
->each(function ($field) use ($request, $pivot) {
/** @var \Laravel\Nova\Fields\Field&\Laravel\Nova\Contracts\Deletable $field */
DeleteField::forRequest($request, $field, $pivot)->save();
});
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Actions\Actionable;
use Laravel\Nova\Http\Requests\ForceDeleteResourceRequest;
use Laravel\Nova\Nova;
class ResourceForceDeleteController extends Controller
{
use DeletesFields;
/**
* Force delete the given resource(s).
*
* @param \Laravel\Nova\Http\Requests\ForceDeleteResourceRequest $request
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response
*/
public function __invoke(ForceDeleteResourceRequest $request)
{
$request->chunks(150, function ($models) use ($request) {
$models->each(function ($model) use ($request) {
$this->forceDeleteFields($request, $model);
if (in_array(Actionable::class, class_uses_recursive($model))) {
$model->actions()->delete();
}
$model->forceDelete();
$request->resource()::afterForceDelete($request, $model);
Nova::usingActionEvent(function ($actionEvent) use ($model, $request) {
$actionEvent->insert(
$actionEvent->forResourceDelete(Nova::user($request), collect([$model]))
->map->getAttributes()->all()
);
});
});
});
if ($request->isForSingleResource() && ! is_null($redirect = $request->resource()::redirectAfterDelete($request))) {
return response()->json([
'redirect' => $redirect,
]);
}
return response()->noContent(200);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\ResourceIndexRequest;
use Laravel\Nova\Http\Resources\IndexViewResource;
class ResourceIndexController extends Controller
{
/**
* List the resources for administration.
*
* @param \Laravel\Nova\Http\Requests\ResourceIndexRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(ResourceIndexRequest $request)
{
return IndexViewResource::make()->toResponse($request);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\ResourcePeekRequest;
class ResourcePeekController extends Controller
{
/**
* Preview the resource for administration.
*
* @param \Laravel\Nova\Http\Requests\ResourcePeekRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(ResourcePeekRequest $request)
{
$resource = $request->newResourceWith(tap($request->findModelQuery(), function ($query) use ($request) {
$resource = $request->resource();
$resource::detailQuery($request, $query);
})->firstOrFail());
$resource->authorizeToView($request);
return response()->json([
'title' => (string) $resource->title(),
'resource' => $resource->serializeForPeeking($request),
]);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\ResourcePreviewRequest;
class ResourcePreviewController extends Controller
{
/**
* Preview the resource for administration.
*
* @param \Laravel\Nova\Http\Requests\ResourcePreviewRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(ResourcePreviewRequest $request)
{
$resource = $request->newResourceWith(tap($request->findModelQuery(), function ($query) use ($request) {
$resource = $request->resource();
$resource::detailQuery($request, $query);
})->firstOrFail());
$resource->authorizeToView($request);
return response()->json([
'title' => (string) $resource->title(),
'resource' => $resource->serializeForPreview($request),
]);
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\RestoreResourceRequest;
use Laravel\Nova\Nova;
class ResourceRestoreController extends Controller
{
/**
* Restore the given resource(s).
*
* @param \Laravel\Nova\Http\Requests\RestoreResourceRequest $request
* @return \Illuminate\Http\Response
*/
public function __invoke(RestoreResourceRequest $request)
{
$request->chunks(150, function ($models) use ($request) {
$models->each(function ($model) use ($request) {
$model->restore();
$request->resource()::afterRestore($request, $model);
Nova::usingActionEvent(function ($actionEvent) use ($model, $request) {
$actionEvent->insert(
$actionEvent->forResourceRestore(Nova::user($request), collect([$model]))
->map->getAttributes()->all()
);
});
});
});
return response()->noContent(200);
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Http\Requests\ResourceSearchRequest;
use Laravel\Nova\Resource;
use Laravel\Nova\Util;
class ResourceSearchController extends Controller
{
/**
* List the resources for administration.
*
* @param \Laravel\Nova\Http\Requests\ResourceSearchRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(ResourceSearchRequest $request)
{
$resource = $request->resource();
$withTrashed = $this->shouldIncludeTrashed(
$request, $resource
);
return response()->json([
'resources' => $request->searchIndex()
->mapInto($resource)
->map(function ($resource) use ($request) {
return $this->transformResult($request, $resource);
})->values(),
'softDeletes' => $resource::softDeletes(),
'withTrashed' => $withTrashed,
]);
}
/**
* Determine if the query should include trashed models.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param string $resource
* @return bool
*/
protected function shouldIncludeTrashed(NovaRequest $request, $resource)
{
if ($request->withTrashed === 'true') {
return true;
}
$model = $resource::newModel();
if ($request->current && empty($request->search) && $resource::softDeletes()) {
$model = $model->newQueryWithoutScopes()->find($request->current);
return $model ? $model->trashed() : false;
}
return false;
}
/**
* Transform the result from resource.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param \Laravel\Nova\Resource $resource
* @return array
*/
protected function transformResult(NovaRequest $request, Resource $resource)
{
return array_filter([
'avatar' => $resource->resolveAvatarUrl($request),
'display' => (string) $resource->title(),
'subtitle' => $resource->subtitle(),
'value' => Util::safeInt($resource->getKey()),
]);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\ResourceDetailRequest;
use Laravel\Nova\Http\Resources\DetailViewResource;
class ResourceShowController extends Controller
{
/**
* Display the resource for administration.
*
* @param \Laravel\Nova\Http\Requests\ResourceDetailRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(ResourceDetailRequest $request)
{
return DetailViewResource::make()->toResponse($request);
}
}

View File

@@ -0,0 +1,98 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\DB;
use Laravel\Nova\Actions\ActionEvent;
use Laravel\Nova\Exceptions\ResourceSaveCancelledException;
use Laravel\Nova\Http\Requests\CreateResourceRequest;
use Laravel\Nova\Nova;
use Laravel\Nova\URL;
use Throwable;
class ResourceStoreController extends Controller
{
/**
* The action event for the action.
*
* @var \Laravel\Nova\Actions\ActionEvent|null
*/
protected $actionEvent;
/**
* Create a new resource.
*
* @param \Laravel\Nova\Http\Requests\CreateResourceRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(CreateResourceRequest $request)
{
/** @var \Laravel\Nova\Resource $resource */
$resource = $request->resource();
$resource::authorizeToCreate($request);
$resource::validateForCreation($request);
try {
$model = DB::connection($resource::newModel()->getConnectionName())->transaction(function () use ($request, $resource) {
[$model, $callbacks] = $resource::fill(
$request, $resource::newModel()
);
if ($this->storeResource($request, $model) === false) {
throw new ResourceSaveCancelledException();
}
DB::transaction(function () use ($request, $model) {
Nova::usingActionEvent(function (ActionEvent $actionEvent) use ($request, $model) {
$this->actionEvent = $actionEvent->forResourceCreate(Nova::user($request), $model);
$this->actionEvent->save();
});
});
collect($callbacks)->each->__invoke();
$resource::afterCreate($request, $model);
return $model;
});
return response()->json([
'id' => $model->getKey(),
'resource' => $model->attributesToArray(),
'redirect' => URL::make($resource::redirectAfterCreate($request, $request->newResourceWith($model))),
], 201);
} catch (Throwable $e) {
optional($this->actionEvent)->delete();
throw $e;
}
}
/**
* Save the resource.
*
* @param \Laravel\Nova\Http\Requests\CreateResourceRequest $request
* @param \Illuminate\Database\Eloquent\Model $model
* @return bool
*/
protected function storeResource(CreateResourceRequest $request, Model $model)
{
if (! $request->viaRelationship()) {
return $model->save();
}
$relation = tap($request->findParentResourceOrFail(), function ($resource) use ($request) {
abort_unless($resource->hasRelatableField($request, $request->viaRelationship), 404);
})->model()->{$request->viaRelationship}();
if ($relation instanceof HasManyThrough) {
return $model->save();
}
return $relation->save($model);
}
}

View File

@@ -0,0 +1,109 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Laravel\Nova\Exceptions\ResourceSaveCancelledException;
use Laravel\Nova\Http\Requests\UpdateResourceRequest;
use Laravel\Nova\Nova;
use Laravel\Nova\URL;
use Laravel\Nova\Util;
use Throwable;
class ResourceUpdateController extends Controller
{
/**
* The action event for the action.
*
* @var \Laravel\Nova\Actions\ActionEvent|null
*/
protected $actionEvent;
/**
* Create a new resource.
*
* @param \Laravel\Nova\Http\Requests\UpdateResourceRequest $request
* @return \Illuminate\Http\JsonResponse
*
* @throws \Illuminate\Http\Exceptions\HttpResponseException
*/
public function __invoke(UpdateResourceRequest $request)
{
$model = $request->findModelQuery()->lockForUpdate()->firstOrFail();
try {
[$model, $resource] = DB::connection($model->getConnectionName())->transaction(function () use ($request, $model) {
$resource = $request->newResourceWith($model);
$resource->authorizeToUpdate($request);
$resource::validateForUpdate($request, $resource);
if ($this->modelHasBeenUpdatedSinceRetrieval($request, $model)) {
response('', 409)->throwResponse();
}
[$model, $callbacks] = $resource::fillForUpdate($request, $model);
DB::transaction(function () use ($request, $model) {
Nova::usingActionEvent(function ($actionEvent) use ($request, $model) {
$this->actionEvent = $actionEvent->forResourceUpdate(Nova::user($request), $model);
$this->actionEvent->save();
});
});
if ($model->save() === false) {
throw new ResourceSaveCancelledException;
}
collect($callbacks)->each->__invoke();
$resource::afterUpdate($request, $model);
return [$model, $resource];
});
tap(Nova::user($request), function ($user) use ($model) {
if (get_class($model) === Util::userModel() && $model->is($user)) {
$user->refresh();
}
});
return response()->json([
'id' => $model->getKey(),
'resource' => $model->attributesToArray(),
'redirect' => URL::make($resource::redirectAfterUpdate($request, $resource)),
]);
} catch (Throwable $e) {
optional($this->actionEvent)->delete();
throw $e;
}
}
/**
* Determine if the model has been updated since it was retrieved.
*
* @param \Laravel\Nova\Http\Requests\UpdateResourceRequest $request
* @param \Illuminate\Database\Eloquent\Model $model
* @return bool
*/
protected function modelHasBeenUpdatedSinceRetrieval(UpdateResourceRequest $request, $model)
{
$resource = $request->newResource();
// Check to see whether Traffic Cop is enabled for this resource...
if ($resource::trafficCop($request) === false) {
return false;
}
$column = $model->getUpdatedAtColumn();
if (! ($model->usesTimestamps() && $model->{$column})) {
return false;
}
return $request->input('_retrieved_at') && $model->{$column}->gt(
Carbon::createFromTimestamp($request->input('_retrieved_at'))
);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Nova;
class ScriptController extends Controller
{
/**
* Serve the requested script.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return \Laravel\Nova\Script
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
public function __invoke(NovaRequest $request)
{
$asset = collect(Nova::allScripts())
->filter(function ($asset) use ($request) {
return $asset->name() === $request->script;
})->first();
abort_if(is_null($asset), 404);
return $asset;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\GlobalSearch;
use Laravel\Nova\Http\Requests\GlobalSearchRequest;
use Laravel\Nova\Nova;
class SearchController extends Controller
{
/**
* Get the global search results for the given query.
*
* @param \Laravel\Nova\Http\Requests\GlobalSearchRequest $request
* @return array
*/
public function __invoke(GlobalSearchRequest $request)
{
return (new GlobalSearch(
$request, Nova::globallySearchableResources($request)
))->get();
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\NovaRequest;
class SoftDeleteStatusController extends Controller
{
/**
* Determine if the resource is soft deleting.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(NovaRequest $request)
{
$resource = $request->resource();
return response()->json(['softDeletes' => $resource::softDeletes()]);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Nova;
class StyleController extends Controller
{
/**
* Serve the requested stylesheet.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return \Laravel\Nova\Style
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
public function __invoke(NovaRequest $request)
{
$asset = collect(Nova::allStyles())
->filter(function ($asset) use ($request) {
return $asset->name() === $request->style;
})->first();
abort_if(is_null($asset), 404);
return $asset;
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\ResourceUpdateOrUpdateAttachedRequest;
use Laravel\Nova\Http\Resources\UpdateViewResource;
class UpdateFieldController extends Controller
{
/**
* List the update fields for the given resource.
*
* @param \Laravel\Nova\Http\Requests\ResourceUpdateOrUpdateAttachedRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(ResourceUpdateOrUpdateAttachedRequest $request)
{
return UpdateViewResource::make()->toResponse($request);
}
/**
* Synchronize the field for updating.
*
* @param \Laravel\Nova\Http\Requests\ResourceUpdateOrUpdateAttachedRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function sync(ResourceUpdateOrUpdateAttachedRequest $request)
{
$resource = UpdateViewResource::make()->newResourceWith($request);
return response()->json(
$resource->updateFields($request)
->filter(function ($field) use ($request) {
return $request->query('field') === $field->attribute &&
$request->query('component') === $field->dependentComponentKey();
})->each->syncDependsOn($request)
->first()
);
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Laravel\Nova\Http\Controllers;
use Illuminate\Routing\Controller;
use Laravel\Nova\Http\Requests\ResourceUpdateOrUpdateAttachedRequest;
use Laravel\Nova\Http\Resources\UpdatePivotFieldResource;
class UpdatePivotFieldController extends Controller
{
/**
* List the pivot fields for the given resource and relation.
*
* @param \Laravel\Nova\Http\Requests\ResourceUpdateOrUpdateAttachedRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(ResourceUpdateOrUpdateAttachedRequest $request)
{
return UpdatePivotFieldResource::make()->toResponse($request);
}
/**
* Synchronize the pivot field for updating.
*
* @param \Laravel\Nova\Http\Requests\ResourceUpdateOrUpdateAttachedRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function sync(ResourceUpdateOrUpdateAttachedRequest $request)
{
$resource = UpdatePivotFieldResource::make()->newResourceWith($request);
return response()->json(
$resource->updatePivotFields(
$request, $request->relatedResource
)->filter(function ($field) use ($request) {
return $request->query('field') === $field->attribute &&
$request->query('component') === $field->dependentComponentKey();
})->applyDependsOn($request)
->first()
);
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Laravel\Nova\Http\Middleware;
use Closure;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Auth\Middleware\Authenticate as BaseAuthenticationMiddleware;
use Laravel\Nova\Exceptions\AuthenticationException as NovaAuthenticationException;
class Authenticate extends BaseAuthenticationMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request):mixed $next
* @param string[] ...$guards
* @return mixed
*
* @throws \Illuminate\Auth\AuthenticationException
*/
public function handle($request, Closure $next, ...$guards)
{
try {
$guard = config('nova.guard');
if (! empty($guard)) {
$guards[] = $guard;
}
return parent::handle($request, $next, ...$guards);
} catch (AuthenticationException $e) {
throw new NovaAuthenticationException('Unauthenticated.', $e->guards());
}
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Laravel\Nova\Http\Middleware;
use Laravel\Nova\Nova;
class Authorize
{
/**
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request):mixed $next
* @return \Illuminate\Http\Response
*/
public function handle($request, $next)
{
return Nova::check($request) ? $next($request) : abort(403);
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Laravel\Nova\Http\Middleware;
use Laravel\Nova\Nova;
class BootTools
{
/**
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request):mixed $next
* @return \Illuminate\Http\Response
*/
public function handle($request, $next)
{
Nova::bootTools($request);
return $next($request);
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Laravel\Nova\Http\Middleware;
use Illuminate\Container\Container;
use Illuminate\Database\Eloquent\Model;
use Laravel\Nova\Events\ServingNova;
use Laravel\Nova\Http\Requests\NovaRequest;
class DispatchServingNovaEvent
{
/**
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request):mixed $next
* @return \Illuminate\Http\Response
*/
public function handle($request, $next)
{
$preventsAccessingMissingAttributes = method_exists(Model::class, 'preventsAccessingMissingAttributes')
? Model::preventsAccessingMissingAttributes()
: null;
if ($preventsAccessingMissingAttributes === true) {
Model::preventAccessingMissingAttributes(false);
}
ServingNova::dispatch($request);
Container::getInstance()->forgetInstance(NovaRequest::class);
$response = $next($request);
if ($preventsAccessingMissingAttributes === true) {
Model::preventAccessingMissingAttributes(true);
}
return $response;
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace Laravel\Nova\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Config;
use Inertia\Middleware;
use Laravel\Nova\Http\Resources\UserResource;
use Laravel\Nova\Nova;
class HandleInertiaRequests extends Middleware
{
/**
* The root template that's loaded on the first page visit.
*
* @see https://inertiajs.com/server-side-setup#root-template
*
* @var string
*/
protected $rootView = 'nova::layout';
/**
* Determines the current asset version.
*
* @see https://inertiajs.com/asset-versioning
*
* @param \Illuminate\Http\Request $request
* @return string|null
*/
public function version(Request $request)
{
return sprintf('%s:%s', $this->rootView, parent::version($request));
}
/**
* Defines the props that are shared by default.
*
* @see https://inertiajs.com/shared-data
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function share(Request $request)
{
return array_merge(parent::share($request), [
'novaConfig' => function () use ($request) {
return Nova::jsonVariables($request);
},
'currentUser' => function () use ($request) {
return with(Nova::user($request), function ($user) use ($request) {
return ! is_null($user) ? UserResource::make($user)->toArray($request) : null;
});
},
'validLicense' => function () use ($request) {
return with(Nova::user($request), function ($user) {
return ! is_null($user) ? Nova::checkLicenseValidity() : Cache::get('nova_valid_license_key');
});
},
]);
}
/**
* Handle the incoming request.
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function handle(Request $request, Closure $next)
{
Config::set('inertia.ssr.enabled', false);
return parent::handle($request, $next);
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Laravel\Nova\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
use Laravel\Nova\Nova;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request):mixed $next
* @param string|null $guard
* @return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->check()) {
return redirect(Nova::path());
}
return $next($request);
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Laravel\Nova\Http\Middleware;
use Laravel\Nova\Events\NovaServiceProviderRegistered;
use Laravel\Nova\Util;
class ServeNova
{
/**
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request):mixed $next
* @return \Illuminate\Http\Response
*/
public function handle($request, $next)
{
if (Util::isNovaRequest($request)) {
NovaServiceProviderRegistered::dispatch();
}
return $next($request);
}
}

View File

@@ -0,0 +1,306 @@
<?php
namespace Laravel\Nova\Http\Requests;
use Closure;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Arr;
use Laravel\Nova\Actions\ActionModelCollection;
use Laravel\Nova\Fields\ActionFields;
use Laravel\Nova\Fields\FieldCollection;
use Laravel\Nova\Support\Fluent;
/**
* @property-read string|null $resources
* @property-read string|null $pivotAction
*/
class ActionRequest extends NovaRequest
{
use QueriesResources;
/**
* Get the action instance specified by the request.
*
* @return \Laravel\Nova\Actions\Action|\Laravel\Nova\Actions\DestructiveAction
*/
public function action()
{
return once(function () {
$hasResources = ! empty($this->resources);
return $this->availableActions()
->filter(function ($action) use ($hasResources) {
return $hasResources ? true : $action->isStandalone();
})->first(function ($action) {
return $action->uriKey() == $this->query('action');
}) ?: abort($this->actionExists() ? 403 : 404);
});
}
/**
* Get the all actions for the request.
*
* @return \Illuminate\Support\Collection
*/
protected function resolveActions()
{
return $this->isPivotAction()
? $this->newResource()->resolvePivotActions($this)
: $this->newResource()->resolveActions($this);
}
/**
* Get the possible actions for the request.
*
* @return \Illuminate\Support\Collection
*/
protected function availableActions()
{
return $this->resolveActions()->filter->authorizedToSee($this)->values();
}
/**
* Determine if the specified action exists at all.
*
* @return bool
*/
protected function actionExists()
{
return $this->resolveActions()->contains(function ($action) {
return $action->uriKey() == $this->query('action');
});
}
/**
* Determine if the action being executed is a pivot action.
*
* @return bool
*/
public function isPivotAction()
{
return $this->pivotAction === 'true';
}
/**
* Get the selected models for the action in chunks.
*
* @param int $count
* @param \Closure(\Laravel\Nova\Actions\ActionModelCollection):mixed $callback
* @return mixed
*/
public function chunks($count, Closure $callback)
{
$output = [];
$this->toSelectedResourceQuery()
->cursor()
->chunk($count)
->each(function ($chunk) use ($callback, &$output) {
$output[] = $callback($this->mapChunk($chunk));
});
return $output;
}
/**
* Get the query for the models that were selected by the user.
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function toSelectedResourceQuery()
{
if ($this->allResourcesSelected()) {
return $this->toQuery();
}
$query = $this->viaRelationship()
? $this->modelsViaRelationship()
: $this->toQueryWithoutScopes()->whereKey(Arr::wrap($this->resources));
return $query->tap(function ($query) {
$query->latest($this->model()->getQualifiedKeyName());
});
}
/**
* Transform the request into a query without scope.
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function toQueryWithoutScopes()
{
return tap($this->newQueryWithoutScopes(), function ($query) {
$resource = $this->resource();
$query->with($resource::$with);
if (! $this->allResourcesSelected() && $this->selectedResourceIds()->count() === 1) {
$resource::detailQuery($this, $query);
} else {
$resource::indexQuery($this, $query);
}
});
}
/**
* Get the query for the related models that were selected by the user.
*
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function modelsViaRelationship()
{
$relation = tap($this->findParentResource(), function ($resource) {
abort_unless($resource->hasRelatableField($this, $this->viaRelationship), 404);
})->model()->{$this->viaRelationship}()->withoutGlobalScopes();
if (isset($this->pivots) && ! empty($this->pivots)) {
/** @var class-string<\Illuminate\Database\Eloquent\Relations\Pivot> $pivotClass */
$pivotClass = $relation->getPivotClass();
$relation->wherePivotIn((new $pivotClass())->getKeyName(), Arr::wrap($this->pivots));
}
return $relation->whereIn($this->model()->getQualifiedKeyName(), Arr::wrap($this->resources));
}
/**
* Map the chunk of models into an appropriate state.
*
* @param \Illuminate\Support\LazyCollection|\Illuminate\Database\Eloquent\Collection $chunk
* @return \Laravel\Nova\Actions\ActionModelCollection
*/
protected function mapChunk($chunk)
{
return ActionModelCollection::make(
$this->isPivotAction()
? $chunk->map->{$this->pivotRelation()->getPivotAccessor()}
: $chunk
);
}
/**
* Validate the given fields.
*
* @return void
*
* @throws \Illuminate\Validation\ValidationException
*/
public function validateFields()
{
$this->action()->validateFields($this);
}
/**
* Resolve the fields for database storage using the request.
*
* @return array
*/
public function resolveFieldsForStorage()
{
return collect($this->resolveFields()->getAttributes())->map(function ($attribute) {
return $attribute instanceof UploadedFile ? $attribute->hashName() : $attribute;
})->all();
}
/**
* Resolve the fields using the request.
*
* @return \Laravel\Nova\Fields\ActionFields
*/
public function resolveFields()
{
return once(function () {
$fields = new Fluent;
$results = FieldCollection::make($this->action()->fields($this))
->authorized($this)
->applyDependsOn($this)
->withoutReadonly($this)
->withoutUnfillable()
->mapWithKeys(function ($field) use ($fields) {
return [$field->attribute => $field->fillForAction($this, $fields)];
});
return new ActionFields(collect($fields->getAttributes()), $results->filter(function ($field) {
return is_callable($field);
}));
});
}
/**
* Get the key of model that lists the action on its dashboard.
*
* When running pivot actions, this is the key of the owning model.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return int
*/
public function actionableKey($model)
{
return $this->isPivotAction()
? $model->{$this->pivotRelation()->getForeignPivotKeyName()}
: $model->getKey();
}
/**
* Get the model instance that lists the action on its dashboard.
*
* When running pivot actions, this is the owning model.
*
* @return \Illuminate\Database\Eloquent\Model
*/
public function actionableModel()
{
return $this->isPivotAction()
? $this->newViaResource()->model()
: $this->model();
}
/**
* Get the key of model that is the target of the action.
*
* When running pivot actions, this is the key of the target model.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return int
*/
public function targetKey($model)
{
return $this->isPivotAction()
? $model->{$this->pivotRelation()->getRelatedPivotKeyName()}
: $model->getKey();
}
/**
* Get an instance of the target model of the action.
*
* @return \Illuminate\Database\Eloquent\Model
*/
public function targetModel()
{
return $this->isPivotAction() ? $this->pivotRelation()->newPivot() : $this->model();
}
/**
* Get the many-to-many relationship for a pivot action.
*
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany|\Illuminate\Database\Eloquent\Relations\BelongsToMany|null
*/
public function pivotRelation()
{
if ($this->isPivotAction()) {
return tap($this->newViaResource(), function ($resource) {
abort_unless($resource->hasRelatableField($this, $this->viaRelationship), 404);
})->model()->{$this->viaRelationship}();
}
}
/**
* Determine if this request is an action request.
*
* @return bool
*/
public function isActionRequest()
{
return true;
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Laravel\Nova\Http\Requests;
class CardRequest extends NovaRequest
{
/**
* Get all of the possible metrics for the request.
*
* @return \Illuminate\Support\Collection
*/
public function availableCards()
{
$resource = $this->newResource();
if ($this->resourceId) {
return $this->newResource()->availableCardsForDetail($this);
}
return $this->newResource()->availableCards($this);
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Laravel\Nova\Http\Requests;
trait CountsResources
{
/**
* Build a new count query for the given query.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Query\Builder
*/
public function buildCountQuery($query)
{
$baseQuery = $query->toBase();
if (empty($baseQuery->groups)) {
return $baseQuery;
}
$subQuery = $baseQuery->cloneWithout(
$baseQuery->unions ? ['orders', 'limit', 'offset'] : ['columns', 'orders', 'limit', 'offset']
)->cloneWithoutBindings(
$baseQuery->unions ? ['order'] : ['select', 'order']
)->selectRaw('1 as exists_temp');
return $query->getConnection()
->query()
->fromSub($subQuery, 'count_temp');
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Laravel\Nova\Http\Requests;
class CreateResourceRequest extends NovaRequest
{
/**
* Determine if this request is a create or attach request.
*
* @return bool
*/
public function isCreateOrAttachRequest()
{
return true;
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Laravel\Nova\Http\Requests;
use Laravel\Nova\Metrics\Metric;
use Laravel\Nova\Nova;
/**
* @property-read string $metric
*/
class DashboardMetricRequest extends NovaRequest
{
/**
* Get the metric instance for the given request.
*
* @return \Laravel\Nova\Metrics\Metric
*/
public function metric()
{
return $this->availableMetrics()->first(function ($metric) {
return $this->metric === $metric->uriKey();
}) ?: abort(404);
}
/**
* Get all of the possible metrics for the request.
*
* @return \Illuminate\Support\Collection
*/
public function availableMetrics()
{
return Nova::allAvailableDashboardCards($this)->whereInstanceOf(Metric::class);
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Laravel\Nova\Http\Requests;
use Laravel\Nova\Nova;
class DashboardRequest extends NovaRequest
{
/**
* Get all of the possible cards for the request.
*
* @param string $dashboard
* @return \Illuminate\Support\Collection
*/
public function availableCards($dashboard)
{
return Nova::availableDashboardCardsForDashboard($dashboard, $this);
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Laravel\Nova\Http\Requests;
use Laravel\Nova\Filters\FilterDecoder;
/**
* @property-read string $filters
*/
trait DecodesFilters
{
/**
* Get the filters for the request.
*
* @return \Illuminate\Support\Collection
*/
public function filters()
{
return (new FilterDecoder($this->filters, $this->availableFilters()))->filters();
}
/**
* Get all of the possibly available filters for the request.
*
* @return \Illuminate\Support\Collection
*/
protected function availableFilters()
{
return $this->newResource()->availableFilters($this);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Laravel\Nova\Http\Requests;
use Closure;
use Illuminate\Support\Collection;
class DeleteLensResourceRequest extends LensResourceDeletionRequest
{
/**
* Get the selected models for the action in chunks.
*
* @param int $count
* @param \Closure(\Illuminate\Support\Collection):void $callback
* @return mixed
*/
public function chunks($count, Closure $callback)
{
return $this->chunkWithAuthorization($count, $callback, function ($models) {
return $this->deletableModels($models);
});
}
/**
* Get the models that may be deleted.
*
* @param \Illuminate\Support\Collection $models
* @return \Illuminate\Support\Collection
*/
protected function deletableModels(Collection $models)
{
return $models->mapInto($this->resource())
->filter
->authorizedToDelete($this)
->map->model();
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace Laravel\Nova\Http\Requests;
use Closure;
use Illuminate\Support\Collection;
/**
* @property-read string|array<int, mixed> $resources
*/
class DeleteResourceRequest extends DeletionRequest
{
/**
* Get the selected models for the action in chunks.
*
* @param int $count
* @param \Closure(\Illuminate\Support\Collection):void $callback
* @return mixed
*/
public function chunks($count, Closure $callback)
{
return $this->chunkWithAuthorization($count, $callback, function ($models) {
return $this->deletableModels($models);
});
}
/**
* Get the models that may be deleted.
*
* @param \Illuminate\Support\Collection $models
* @return \Illuminate\Support\Collection
*/
protected function deletableModels(Collection $models)
{
return $models->mapInto($this->resource())
->filter
->authorizedToDelete($this)
->map->model();
}
/**
* Determine if the request is for a single resource only.
*
* @return bool
*/
public function isForSingleResource()
{
return $this->resources !== 'all' && count($this->resources) == 1;
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Laravel\Nova\Http\Requests;
use Closure;
/**
* @property-read string|array<int, mixed> $resources
*/
class DeletionRequest extends NovaRequest
{
use QueriesResources;
/**
* Get the selected models for the action in chunks.
*
* @param int $count
* @param \Closure(\Illuminate\Support\Collection):void $callback
* @param \Closure(\Illuminate\Support\Collection):\Illuminate\Support\Collection $authCallback
* @return mixed
*/
protected function chunkWithAuthorization($count, Closure $callback, Closure $authCallback)
{
$model = $this->model();
$this->toSelectedResourceQuery()->when(! $this->allResourcesSelected(), function ($query) {
$query->whereKey($this->resources);
})->tap(function ($query) {
$query->getQuery()->orders = [];
})->chunkById($count, function ($models) use ($callback, $authCallback) {
$models = $authCallback($models);
if ($models->isNotEmpty()) {
$callback($models);
}
}, $model->getQualifiedKeyName(), $model->getKeyName());
}
/**
* Get the query for the models that were selected by the user.
*
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function toSelectedResourceQuery()
{
if ($this->allResourcesSelected()) {
return $this->toQuery();
}
return $this->newQueryWithoutScopes();
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Laravel\Nova\Http\Requests;
use Closure;
use Illuminate\Support\Collection;
class DetachResourceRequest extends DeletionRequest
{
/**
* Get the selected models for the action in chunks.
*
* @param int $count
* @param \Closure(\Illuminate\Support\Collection):void $callback
* @return mixed
*/
public function chunks($count, Closure $callback)
{
$parentResource = $this->findParentResourceOrFail();
$model = $this->model();
$this->toSelectedResourceQuery()->when(! $this->allResourcesSelected(), function ($query) {
$query->whereKey($this->resources);
})->chunkById($count, function ($models) use ($callback, $parentResource) {
$models = $this->detachableModels($models, $parentResource);
if ($models->isNotEmpty()) {
$callback($models);
}
}, $model->getQualifiedKeyName(), $model->getKeyName());
}
/**
* Get the models that may be detached.
*
* @param \Illuminate\Support\Collection $models
* @param \Laravel\Nova\Resource $parentResource
* @return \Illuminate\Support\Collection
*/
protected function detachableModels(Collection $models, $parentResource)
{
return $models->filter(function ($model) use ($parentResource) {
return $parentResource->authorizedToDetach($this, $model, $this->viaRelationship);
});
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Laravel\Nova\Http\Requests;
use Closure;
use Illuminate\Support\Collection;
class ForceDeleteLensResourceRequest extends LensResourceDeletionRequest
{
/**
* Get the selected models for the action in chunks.
*
* @param int $count
* @param \Closure(\Illuminate\Support\Collection):void $callback
* @return mixed
*/
public function chunks($count, Closure $callback)
{
return $this->chunkWithAuthorization($count, $callback, function ($models) {
return $this->deletableModels($models);
});
}
/**
* Get the models that may be deleted.
*
* @param \Illuminate\Support\Collection $models
* @return \Illuminate\Support\Collection
*/
protected function deletableModels(Collection $models)
{
return $models->mapInto($this->resource())
->filter
->authorizedToForceDelete($this)
->map->model();
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Laravel\Nova\Http\Requests;
use Closure;
use Illuminate\Support\Collection;
class ForceDeleteResourceRequest extends DeletionRequest
{
/**
* Get the selected models for the action in chunks.
*
* @param int $count
* @param \Closure(\Illuminate\Support\Collection):void $callback
* @return mixed
*/
public function chunks($count, Closure $callback)
{
return $this->chunkWithAuthorization($count, $callback, function ($models) {
return $this->deletableModels($models);
});
}
/**
* Get the models that may be deleted.
*
* @param \Illuminate\Support\Collection $models
* @return \Illuminate\Support\Collection
*/
protected function deletableModels(Collection $models)
{
return $models->mapInto($this->resource())
->filter
->authorizedToForceDelete($this)
->map->model();
}
/**
* Determine if the request is for a single resource only.
*
* @return bool
*/
public function isForSingleResource()
{
return $this->resources !== 'all' && count($this->resources) == 1;
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace Laravel\Nova\Http\Requests;
class GlobalSearchRequest extends NovaRequest
{
//
}

View File

@@ -0,0 +1,63 @@
<?php
namespace Laravel\Nova\Http\Requests;
use Laravel\Nova\Query\Search;
/**
* @property-read string|null $lens
*/
trait InteractsWithLenses
{
/**
* Get the lens instance for the given request.
*
* @return \Laravel\Nova\Lenses\Lens
*/
public function lens()
{
return $this->availableLenses()->first(function ($lens) {
return $this->lens === $lens->uriKey();
}) ?: abort($this->lensExists() ? 403 : 404);
}
/**
* Get all of the possible lenses for the request.
*
* @return \Illuminate\Support\Collection
*/
public function availableLenses()
{
return transform($this->newResource(), function ($resource) {
abort_unless($resource::authorizedToViewAny($this), 403);
return $resource->availableLenses($this);
});
}
/**
* Transform the request into a search query.
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function newSearchQuery()
{
$lens = $this->lens();
return $lens::searchable() && ! empty($this->search)
? (new Search($this->newQuery(), $this->search))->handle($this->resource(), $lens->searchableColumns())
: $this->newQuery();
}
/**
* Determine if the specified action exists at all.
*
* @return bool
*/
protected function lensExists()
{
return $this->newResource()->resolveLenses($this)->contains(function ($lens) {
return $this->lens === $lens->uriKey();
});
}
}

View File

@@ -0,0 +1,230 @@
<?php
namespace Laravel\Nova\Http\Requests;
use Laravel\Nova\Nova;
use Laravel\Nova\Resource;
trait InteractsWithRelatedResources
{
/**
* Find the parent resource model instance for the request.
*
* @param string|int|null $resourceId
* @return \Laravel\Nova\Resource
*/
public function findParentResource($resourceId = null)
{
$resource = $this->viaResource();
return new $resource($this->findParentModel($resourceId));
}
/**
* Find the parent resource model instance for the request.
*
* @param string|int|null $resourceId
* @return \Laravel\Nova\Resource<\Illuminate\Database\Eloquent\Model>
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public function findParentResourceOrFail($resourceId = null)
{
$resource = $this->viaResource();
return new $resource($this->findParentModelOrFail($resourceId));
}
/**
* Find the parent resource model instance for the request.
*
* @param string|int|null $resourceId
* @return \Illuminate\Database\Eloquent\Model|null
*/
public function findParentModel($resourceId = null)
{
if (! $this->viaRelationship()) {
return null;
}
return rescue(function () use ($resourceId) {
return $this->findParentModelOrFail($resourceId);
}, Nova::modelInstanceForKey($this->viaResource), false);
}
/**
* Find the parent resource model instance for the request or abort.
*
* @param string|int|null $resourceId
* @return \Illuminate\Database\Eloquent\Model
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public function findParentModelOrFail($resourceId = null)
{
$query = Nova::modelInstanceForKey($this->viaResource)->newQueryWithoutScopes();
if (! is_null($resourceId)) {
return $query->whereKey($resourceId)->firstOrFail();
}
return once(function () use ($query) {
return $query->findOrFail($this->viaResourceId);
});
}
/**
* Find the related resource instance for the request.
*
* @param string|int|null $resourceId
* @return \Laravel\Nova\Resource
*/
public function findRelatedResource($resourceId = null)
{
$resource = $this->relatedResource();
return new $resource($this->findRelatedModel($resourceId));
}
/**
* Find the related resource instance for the request or abort.
*
* @param string|int|null $resourceId
* @return \Laravel\Nova\Resource<\Illuminate\Database\Eloquent\Model>
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public function findRelatedResourceOrFail($resourceId = null)
{
$resource = $this->relatedResource();
return new $resource($this->findRelatedModelOrFail($resourceId));
}
/**
* Find the related resource model instance for the request.
*
* @param string|int|null $resourceId
* @return \Illuminate\Database\Eloquent\Model
*/
public function findRelatedModel($resourceId = null)
{
return rescue(function () use ($resourceId) {
return $this->findRelatedModelOrFail($resourceId);
}, Nova::modelInstanceForKey($this->relatedResource), false);
}
/**
* Find the parent resource model instance for the request or abort.
*
* @param string|int|null $resourceId
* @return \Illuminate\Database\Eloquent\Model
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public function findRelatedModelOrFail($resourceId = null)
{
$query = Nova::modelInstanceForKey($this->relatedResource)->newQueryWithoutScopes();
if (! is_null($resourceId)) {
return $query->whereKey($resourceId)->firstOrFail();
}
return once(function () use ($query) {
return $query->findOrFail($this->input($this->relatedResource));
});
}
/**
* Get the displayable pivot model name for a "via relationship" request.
*
* @return string
*/
public function pivotName()
{
if (! $this->viaRelationship()) {
return Resource::DEFAULT_PIVOT_NAME;
}
$resource = Nova::resourceInstanceForKey($this->viaResource);
if ($name = $resource->pivotNameForField($this, $this->viaRelationship)) {
return $name;
}
$parentResource = $this->findParentResource();
$parent = $parentResource->model();
return ($parent && $parentResource->hasRelatableField($this, $this->viaRelationship))
? class_basename($parent->{$this->viaRelationship}()->getPivotClass())
: Resource::DEFAULT_PIVOT_NAME;
}
/**
* Get the class name of the "related" resource being requested.
*
* @return class-string<\Laravel\Nova\Resource>
*/
public function relatedResource()
{
return Nova::resourceForKey($this->relatedResource);
}
/**
* Get a new instance of the "related" resource being requested.
*
* @return \Laravel\Nova\Resource<\Illuminate\Database\Eloquent\Model>
*/
public function newRelatedResource()
{
$resource = $this->relatedResource();
return new $resource($resource::newModel());
}
/**
* Get the class name of the "via" resource being requested.
*
* @return class-string<\Laravel\Nova\Resource>
*/
public function viaResource()
{
return Nova::resourceForKey($this->viaResource);
}
/**
* Get a new instance of the "via" resource being requested.
*
* @return \Laravel\Nova\Resource<\Illuminate\Database\Eloquent\Model>
*/
public function newViaResource()
{
$resource = $this->viaResource();
return new $resource($resource::newModel());
}
/**
* Determine if the request is via a relationship.
*
* @return bool
*/
public function viaRelationship()
{
return filled($this->viaResource) && filled($this->viaResourceId) && $this->viaRelationship;
}
/**
* Determine if this request is via a many-to-many relationship.
*
* @return bool
*/
public function viaManyToMany()
{
return in_array(
$this->relationshipType,
['belongsToMany', 'morphToMany']
);
}
}

View File

@@ -0,0 +1,187 @@
<?php
namespace Laravel\Nova\Http\Requests;
use Laravel\Nova\Contracts\QueryBuilder;
use Laravel\Nova\Nova;
trait InteractsWithResources
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
//
];
}
/**
* Determine if the requested resource is soft deleting.
*
* @return bool
*/
public function resourceSoftDeletes()
{
$resource = $this->resource();
return $resource::softDeletes();
}
/**
* Get the class name of the resource being requested.
*
* @return class-string<\Laravel\Nova\Resource>
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
public function resource()
{
return tap(once(function () {
return Nova::resourceForKey($this->route('resource'));
}), function ($resource) {
abort_if(is_null($resource), 404);
});
}
/**
* Get a new instance of the resource being requested.
*
* @return \Laravel\Nova\Resource<\Illuminate\Database\Eloquent\Model>
*/
public function newResource()
{
$resource = $this->resource();
return new $resource($this->model());
}
/**
* Find the resource instance for the request or abort.
*
* @param string|int|null $resourceId
* @return \Laravel\Nova\Resource<\Illuminate\Database\Eloquent\Model>
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public function findResourceOrFail($resourceId = null)
{
return $this->newResourceWith($this->findModelOrFail($resourceId));
}
/**
* Find the resource instance for the request.
*
* @param string|int|null $resourceId
* @return \Laravel\Nova\Resource
*/
public function findResource($resourceId = null)
{
return $this->newResourceWith($this->findModel($resourceId));
}
/**
* Find the model instance for the request or throw an exception.
*
* @param string|int|null $resourceId
* @return \Illuminate\Database\Eloquent\Model
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public function findModelOrFail($resourceId = null)
{
if (! is_null($resourceId)) {
return $this->findModelQuery($resourceId)->firstOrFail();
}
return once(function () {
return $this->findModelQuery()->firstOrFail();
});
}
/**
* Find the model instance for the request.
*
* @param string|int|null $resourceId
* @return \Illuminate\Database\Eloquent\Model|null
*/
public function findModel($resourceId = null)
{
return rescue(function () use ($resourceId) {
return $this->findModelOrFail($resourceId);
}, $this->model(), false);
}
/**
* Get the query to find the model instance for the request.
*
* @param mixed|null $resourceId
* @return \Illuminate\Database\Eloquent\Builder
*/
public function findModelQuery($resourceId = null)
{
return app()->make(QueryBuilder::class, [$this->resource()])
->whereKey(
$this->newQueryWithoutScopes(),
$resourceId ?? $this->resourceId
)->toBase();
}
/**
* Get a new instance of the resource being requested.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return \Laravel\Nova\Resource<\Illuminate\Database\Eloquent\Model>
*/
public function newResourceWith($model)
{
$resource = $this->resource();
return new $resource($model);
}
/**
* Get a new query builder for the underlying model.
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function newQuery()
{
return $this->model()->newQuery();
}
/**
* Get a new, scopeless query builder for the underlying model.
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function newQueryWithoutScopes()
{
return $this->model()->newQueryWithoutScopes();
}
/**
* Get a new instance of the underlying model.
*
* @return \Illuminate\Database\Eloquent\Model
*/
public function model()
{
$resource = $this->resource();
return $resource::newModel();
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace Laravel\Nova\Http\Requests;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
/**
* @property-read int|null $resourceId
* @property-read array|string|null $resources
*/
trait InteractsWithResourcesSelection
{
/**
* Determine if currently all resources is selected.
*
* @return bool
*/
public function allResourcesSelected()
{
return $this->resources === 'all';
}
/**
* Get selected resource IDs.
*
* @return \Illuminate\Support\Collection<int, string|int>|null
*/
public function selectedResourceIds()
{
if ($this->allResourcesSelected()) {
return null;
}
$resourceIds = array_filter(! empty($this->resources) ? Arr::wrap($this->resources) : [$this->resourceId]);
if (count($resourceIds) < 1) {
return collect(
$this->resource instanceof Model ? [$this->resource->getKey()] : []
);
}
return collect($resourceIds);
}
/**
* Get selected resources.
*
* @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Support\Collection|null
*/
public function selectedResources()
{
if ($this->allResourcesSelected()) {
return null;
}
$resourceIds = array_filter(! empty($this->resources) ? Arr::wrap($this->resources) : [$this->resourceId]);
if (count($resourceIds) < 1) {
return $this->resource instanceof Model ? $this->resource->newCollection() : collect();
}
return $this->newQueryWithoutScopes()
->whereKey($resourceIds)
->get();
}
}

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