*/ public function indexFields(NovaRequest $request) { return $this->availableFields($request) ->when($request->viaManyToMany(), $this->relatedFieldResolverCallback($request)) ->filterForIndex($request, $this->resource) ->withoutListableFields() ->authorized($request) ->resolveForDisplay($this->resource); } /** * Resolve the detail fields. * * @return \Laravel\Nova\Fields\FieldCollection */ public function detailFields(NovaRequest $request) { return $this->availableFields($request) ->when($request->viaManyToMany(), $this->fieldResolverCallback($request)) ->when($this->shouldAddActionsField($request), function ($fields) { return $fields->push($this->actionfield()); }) ->filterForDetail($request, $this->resource) ->authorized($request) ->resolveForDisplay($this->resource); } /** * Resolve the authorized preview fields. * * @return \Laravel\Nova\Fields\FieldCollection */ protected function previewFieldsCollection(NovaRequest $request) { // If the user has specified the `fieldsForPreview` method, we're going to ignore any fields // using `showOnPreview` inside the resource's `fields`, `fieldsForIndex`, and `fieldsForDetail` methods. if (method_exists($this, 'fieldsForPreview')) { return FieldCollection::make(array_values($this->filter($this->fieldsForPreview($request)))); } return $this->buildAvailableFields($request, ['fieldsForIndex', 'fieldsForDetail']) ->when($request->viaManyToMany(), $this->fieldResolverCallback($request)) ->flattenStackedFields() ->withoutResourceTools() ->withoutListableFields() ->filter ->isShownOnPreview($request, $this->resource); } /** * Resolve the preview fields. * * @return \Laravel\Nova\Fields\FieldCollection */ public function previewFields(NovaRequest $request) { return $this->previewFieldsCollection($request) ->authorized($request) ->resolveForDisplay($this->resource); } /** * Return the count of preview fields available. * * @return int */ public function previewFieldsCount(NovaRequest $request) { return $this->previewFieldsCollection($request) ->authorized($request) ->count(); } /** * Resolve the authorized preview fields. * * @return \Laravel\Nova\Fields\FieldCollection */ protected function peekableFieldsCollection(NovaRequest $request) { // If the user has specified the `fieldsForPeeking` method, we're going to ignore any fields // using `showWhenPeeking` inside the resource's `fields`, `fieldsForIndex`, and `fieldsForDetail` methods. if (method_exists($this, 'fieldsForPeeking')) { return FieldCollection::make(array_values($this->filter($this->fieldsForPeeking($request)))); } return $this->buildAvailableFields($request, ['fieldsForIndex', 'fieldsForDetail']) ->when($request->viaManyToMany(), $this->fieldResolverCallback($request)) ->flattenStackedFields() ->withoutResourceTools() ->withoutListableFields() ->filterForPeeking($request); } /** * Resolve the peekable fields. * * @return \Laravel\Nova\Fields\FieldCollection */ public function peekableFields(NovaRequest $request) { return $this->peekableFieldsCollection($request) ->authorized($request) ->resolveForDisplay($this->resource) ->each(function (Field $field) { if (property_exists($field, 'copyable')) { $field->copyable = false; } }); } /** * Return the count of peekable fields available. * * @return int */ public function peekableFieldsCount(NovaRequest $request) { return $this->peekableFieldsCollection($request) ->authorized($request) ->count(); } /** * Resolve the deletable fields. * * @return \Laravel\Nova\Fields\FieldCollection */ public function deletableFields(NovaRequest $request) { return $this->availableFieldsOnIndexOrDetail($request) ->when($request->viaManyToMany(), $this->fieldResolverCallback($request)) ->reject(function ($field) { return $field instanceof Unfillable; }) ->whereInstanceOf(Deletable::class) ->unique(function ($field) { /** @var \Laravel\Nova\Fields\Field&\Laravel\Nova\Contracts\Deletable $field */ return $field->attribute; }) ->authorized($request) ->resolveForDisplay($this->resource); } /** * Resolve the downloadable fields. * * @return \Laravel\Nova\Fields\FieldCollection */ public function downloadableFields(NovaRequest $request) { return $this->availableFieldsOnIndexOrDetail($request) ->when($request->viaManyToMany(), $this->fieldResolverCallback($request)) ->whereInstanceOf(Downloadable::class) ->unique(function ($field) { /** @var \Laravel\Nova\Fields\Field&\Laravel\Nova\Contracts\Downloadable $field */ return $field->attribute; }) ->authorized($request) ->resolveForDisplay($this->resource); } /** * Resolve the filterable fields. * * @return \Laravel\Nova\Fields\FieldCollection */ public function filterableFields(NovaRequest $request) { return $this->availableFieldsOnIndexOrDetail($request) ->when($request->viaManyToMany(), function ($fields) use ($request) { $relatedField = $request->findParentResource()->relatableField($request, $request->viaRelationship); if (! is_null($relatedField)) { $fields->prepend($relatedField); } return call_user_func($this->relatedFieldResolverCallback($request), $fields); }) ->flattenStackedFields() ->withOnlyFilterableFields() ->unique(function ($field) { return $field->attribute; }) ->authorized($request); } /** * Get related field from resource by attribute. * * @param string $attribute * @return \Laravel\Nova\Fields\Field|null */ public function relatableField(NovaRequest $request, $attribute) { return $this->availableFieldsOnIndexOrDetail($request) ->when($request->viaManyToMany(), $this->fieldResolverCallback($request)) ->whereInstanceOf(RelatableField::class) ->when($this->shouldAddActionsField($request), function ($fields) { return $fields->push($this->actionfield()); }) ->first(function ($field) use ($attribute) { return $field->attribute === $attribute; }); } /** * Determine resource has relatable field by attribute. * * @param string $attribute * @return bool */ public function hasRelatableField(NovaRequest $request, $attribute) { return $this->relatableField($request, $attribute) !== null; } /** * Determine if the resource should have an Action field. * * @param \Laravel\Nova\Http\Requests\NovaRequest $request * @return bool */ protected function shouldAddActionsField($request) { return with($this->actionfield(), function ($actionField) use ($request) { return in_array(Actionable::class, class_uses_recursive(static::newModel())) && $actionField->authorizedToSee($request); }); } /** * Return a new Action field instance. * * @return \Laravel\Nova\Fields\MorphMany */ protected function actionfield() { return MorphMany::make(Nova::__('Actions'), 'actions', Nova::actionResource()) ->canSee(function ($request) { return Nova::actionResource()::authorizedToViewAny($request); }); } /** * Resolve the detail fields and assign them to their associated panel. * * @param \Laravel\Nova\Resource $resource * @return \Laravel\Nova\Fields\FieldCollection */ public function detailFieldsWithinPanels(NovaRequest $request, Resource $resource) { return $this->detailFields($request) ->assignDefaultPanel( $request->viaRelationship() && $request->isResourceDetailRequest() ? Panel::defaultNameForViaRelationship($resource, $request) : Panel::defaultNameForDetail($resource) ); } /** * Resolve the creation fields. * * @return \Laravel\Nova\Fields\FieldCollection */ public function creationFields(NovaRequest $request) { $fields = $this->availableFields($request) ->authorized($request) ->onlyCreateFields($request, $this->resource) ->resolve($this->resource); return $request->viaRelationship() ? $this->withPivotFields($request, $fields->all()) : $fields; } /** * Return the creation fields excluding any readonly ones. * * @return \Laravel\Nova\Fields\FieldCollection */ public function creationFieldsWithoutReadonly(NovaRequest $request) { return $this->creationFields($request) ->withoutReadonly($request); } /** * Resolve the creation fields and assign them to their associated panel. * * @return \Laravel\Nova\Fields\FieldCollection */ public function creationFieldsWithinPanels(NovaRequest $request) { return $this->creationFields($request) ->assignDefaultPanel(Panel::defaultNameForCreate($request->newResource())); } /** * Resolve the creation pivot fields for a related resource. * * @param string $relatedResource * @return \Laravel\Nova\Fields\FieldCollection */ public function creationPivotFields(NovaRequest $request, $relatedResource) { return $this->resolvePivotFields($request, $relatedResource) ->onlyCreateFields($request, $this->resource); } /** * Resolve the update fields. * * @return \Laravel\Nova\Fields\FieldCollection */ public function updateFields(NovaRequest $request) { return $this->resolveFields($request) ->onlyUpdateFields($request, $this->resource); } /** * Return the update fields excluding any readonly ones. * * @return \Laravel\Nova\Fields\FieldCollection */ public function updateFieldsWithoutReadonly(NovaRequest $request) { return $this->updateFields($request) ->withoutReadonly($request); } /** * Resolve the update fields and assign them to their associated panel. * * @param \Laravel\Nova\Resource|null $resource * @return \Laravel\Nova\Fields\FieldCollection */ public function updateFieldsWithinPanels(NovaRequest $request, Resource $resource = null) { return $this->updateFields($request) ->assignDefaultPanel(Panel::defaultNameForUpdate($resource ?? $request->newResource())); } /** * Resolve the update pivot fields for a related resource. * * @param string $relatedResource * @return \Laravel\Nova\Fields\FieldCollection */ public function updatePivotFields(NovaRequest $request, $relatedResource) { return $this->resolvePivotFields($request, $relatedResource) ->onlyUpdateFields($request, $this->resource); } /** * Remove non-preview fields from the given collection. * * @return \Laravel\Nova\Fields\FieldCollection */ protected function removeNonPreviewFields(NovaRequest $request, FieldCollection $fields) { return $fields->reject(function ($field) { return $field instanceof ListableField || ($field instanceof ResourceTool || $field instanceof ResourceToolElement) || $field->attribute === 'ComputedField' || ($field instanceof ID && $field->attribute === $this->resource->getKeyName()); }); } /** * Resolve the given fields to their values. * * @param (\Closure(\Laravel\Nova\Fields\FieldCollection):(\Laravel\Nova\Fields\FieldCollection))|null $filter * @return \Laravel\Nova\Fields\FieldCollection */ protected function resolveFields(NovaRequest $request, Closure $filter = null) { $fields = $this->availableFields($request)->authorized($request); if (! is_null($filter)) { $fields = $filter($fields); } $fields->resolve($this->resource); return $request->viaRelationship() ? $this->withPivotFields($request, $fields->all()) : $fields; } /** * Resolve the non pivot fields for the resource. * * @return \Laravel\Nova\Fields\FieldCollection * * @deprecated 4.x */ protected function resolveNonPivotFields(NovaRequest $request) { return $this->availableFields($request) ->resolve($this->resource) ->authorized($request); } /** * Resolve the field for the given attribute. * * @param string $attribute * @return \Laravel\Nova\Fields\Field */ public function resolveFieldForAttribute(NovaRequest $request, $attribute) { return $this->resolveFields($request)->findFieldByAttribute($attribute); } /** * Resolve the inverse field for the given relationship attribute. * * This is primarily used for Relatable rule to check if has-one / morph-one relationships are "full". * * @param string $attribute * @param string|null $morphType * @return \Laravel\Nova\Fields\FieldCollection */ public function resolveInverseFieldsForAttribute(NovaRequest $request, $attribute, $morphType = null) { $field = $this->availableFields($request) ->findFieldByAttribute($attribute); if (! (! is_null($field) && $field->authorize($request) && isset($field->resourceClass))) { return new FieldCollection; } /** @var class-string<\Laravel\Nova\Resource> $relatedResource */ $relatedResource = $field instanceof MorphTo ? Nova::resourceForKey($morphType ?? $request->{$attribute.'_type'}) : ($field->resourceClass ?? null); $relatedResource = new $relatedResource($relatedResource::newModel()); return $relatedResource->availableFields($request)->reject(function ($f) use ($field) { return isset($f->attribute) && isset($field->inverse) && $f->attribute !== $field->inverse; })->filter(function ($field) use ($request) { return isset($field->resourceClass) && $field->resourceClass == $request->resource(); }); } /** * Resolve the resource's avatar field. * * @return \Laravel\Nova\Contracts\Cover|null */ public function resolveAvatarField(NovaRequest $request) { return tap( $this->availableFields($request) ->whereInstanceOf(Cover::class) ->authorized($request) ->first(), function ($field) { if ($field instanceof Resolvable) { $field->resolve($this->resource); } } ); } /** * Resolve the resource's avatar URL, if applicable. * * @return string|null */ public function resolveAvatarUrl(NovaRequest $request) { $field = $this->resolveAvatarField($request); if ($field) { return $field->resolveThumbnailUrl(); } } /** * Determine whether the resource's avatar should be rounded, if applicable. * * @return bool */ public function resolveIfAvatarShouldBeRounded(NovaRequest $request) { $field = $this->resolveAvatarField($request); if ($field) { return $field->isRounded(); } return false; } /** * Get the panels that are available for the given create request. * * @param \Laravel\Nova\Http\Requests\NovaRequest $request * @param \Laravel\Nova\Fields\FieldCollection|null $fields * @return array */ public function availablePanelsForCreate($request, FieldCollection $fields = null) { $method = $this->fieldsMethod($request); $fields = $fields ?? FieldCollection::make(value(function () use ($request, $method) { return array_values($this->{$method}($request)); }))->onlyCreateFields($request, $this->resource); return $this->resolvePanelsFromFields( $request, $fields, Panel::defaultNameForCreate($request->newResource()) )->all(); } /** * Get the panels that are available for the given update request. * * @param \Laravel\Nova\Resource $resource * @param \Laravel\Nova\Fields\FieldCollection|null $fields * @return array */ public function availablePanelsForUpdate(NovaRequest $request, Resource $resource = null, FieldCollection $fields = null) { $method = $this->fieldsMethod($request); $fields = $fields ?? FieldCollection::make(value(function () use ($request, $method) { return array_values($this->{$method}($request)); }))->onlyUpdateFields($request, $this->resource); return $this->resolvePanelsFromFields( $request, $fields, Panel::defaultNameForUpdate($resource ?? $request->newResource()) )->all(); } /** * Get the panels that are available for the given detail request. * * @param \Laravel\Nova\Resource $resource * @param \Laravel\Nova\Fields\FieldCollection $fields * @return array */ public function availablePanelsForDetail(NovaRequest $request, Resource $resource, FieldCollection $fields) { return $this->resolvePanelsFromFields( $request, $fields, $request->viaRelationship() && $request->isResourceDetailRequest() ? Panel::defaultNameForViaRelationship($resource, $request) : Panel::defaultNameForDetail($resource) )->all(); } /** * Get the fields that are available for the given request. * * @return \Laravel\Nova\Fields\FieldCollection */ public function availableFields(NovaRequest $request) { $method = $this->fieldsMethod($request); return FieldCollection::make(array_values($this->filter($this->{$method}($request)))); } /** * Get the fields that are available on "index" or "detail" for the given request. * * @return \Laravel\Nova\Fields\FieldCollection */ public function availableFieldsOnIndexOrDetail(NovaRequest $request) { return $this->buildAvailableFields($request, ['fieldsForIndex', 'fieldsForDetail']); } /** * Get the fields that are available for the given request. * * @return \Laravel\Nova\Fields\FieldCollection */ public function buildAvailableFields(NovaRequest $request, array $methods) { $fields = collect([ method_exists($this, 'fields') ? $this->fields($request) : [], ]); collect($methods) ->filter(function ($method) { return $method != 'fields' && method_exists($this, $method); })->each(function ($method) use ($request, $fields) { $fields->push([$this->{$method}($request)]); }); return FieldCollection::make(array_values($this->filter($fields->flatten()->all()))); } /** * Compute the method to use to get the available fields. * * @return string */ protected function fieldsMethod(NovaRequest $request) { if ($request->isInlineCreateRequest() && method_exists($this, 'fieldsForInlineCreate')) { return 'fieldsForInlineCreate'; } if ($request->isResourceIndexRequest() && method_exists($this, 'fieldsForIndex')) { return 'fieldsForIndex'; } if ($request->isResourceDetailRequest() && method_exists($this, 'fieldsForDetail')) { return 'fieldsForDetail'; } if ($request->isCreateOrAttachRequest() && method_exists($this, 'fieldsForCreate')) { return 'fieldsForCreate'; } if ($request->isUpdateOrUpdateAttachedRequest() && method_exists($this, 'fieldsForUpdate')) { return 'fieldsForUpdate'; } return 'fields'; } /** * Merge the available pivot fields with the given fields. * * @param array $fields * @return \Laravel\Nova\Fields\FieldCollection */ protected function withPivotFields(NovaRequest $request, array $fields) { $pivotFields = $this->resolvePivotFields($request, $request->viaResource)->all(); if ($index = $this->indexToInsertPivotFields($request, $fields)) { array_splice($fields, $index + 1, 0, $pivotFields); } else { $fields = array_merge($fields, $pivotFields); } return FieldCollection::make($fields); } /** * Resolve the pivot fields for the requested resource. * * @param string $relatedResource * @return \Laravel\Nova\Fields\FieldCollection */ public function resolvePivotFields(NovaRequest $request, $relatedResource) { $fields = $this->pivotFieldsFor($request, $relatedResource); return FieldCollection::make($this->filter($fields->each(function ($field) { if ($field instanceof Resolvable) { $field->resolve( $this->{$field->pivotAccessor} ?? $field->pivotRelation->newPivot($field->pivotRelation->getDefaultPivotAttributes(), false) ); } })->authorized($request)->all()))->values(); } /** * Get the pivot fields for the resource and relation. * * @param string $relatedResource * @return \Laravel\Nova\Fields\FieldCollection */ protected function pivotFieldsFor(NovaRequest $request, $relatedResource) { $fields = $this->availableFields($request)->filter(function ($field) use ($relatedResource) { return ($field instanceof BelongsToMany || $field instanceof MorphToMany) && isset($field->resourceName) && $field->resourceName == $relatedResource; }); /** @var \Laravel\Nova\Fields\BelongsToMany|\Laravel\Nova\Fields\MorphToMany|null $field */ $field = $fields->count() === 1 ? $fields->first(function ($field) { return $field; }) : $fields->first(function ($field) use ($request) { return $field->manyToManyRelationship === $request->viaRelationship; }); if ($field && isset($field->fieldsCallback)) { $model = $this->model() ?? static::newModel(); $pivotRelation = $model->{$field->manyToManyRelationship}(); $field->pivotAccessor = $pivotAccessor = $pivotRelation->getPivotAccessor(); return FieldCollection::make(array_values( $this->filter(call_user_func($field->fieldsCallback, $request, $this->resource)) ))->each(function ($field) use ($pivotAccessor, $pivotRelation) { $field->pivot = true; $field->pivotAccessor = $pivotAccessor; $field->pivotRelation = $pivotRelation; }); } return FieldCollection::make(); } /** * Get the pivot fields for the resource and relation from related relationship. * * @param string $relatedResource * @return \Laravel\Nova\Fields\FieldCollection */ protected function relatedPivotFieldsFor(NovaRequest $request, $relatedResource) { $resource = Nova::resourceInstanceForKey($relatedResource); $fields = $resource->availableFields($request)->filter(function ($field) { return ($field instanceof BelongsToMany || $field instanceof MorphToMany) && isset($field->resourceName) && $field->resourceName == $this->uriKey(); }); /** @var \Laravel\Nova\Fields\BelongsToMany|\Laravel\Nova\Fields\MorphToMany|null $field */ $field = $fields->count() === 1 ? $fields->first(function ($field) { return $field; }) : $fields->first(function ($field) use ($request) { return $field->manyToManyRelationship === $request->viaRelationship; }); if ($field && isset($field->fieldsCallback)) { $pivotRelation = $resource->model()->{$field->manyToManyRelationship}(); $field->pivotAccessor = $pivotAccessor = $pivotRelation->getPivotAccessor(); return FieldCollection::make(array_values( $this->filter(call_user_func($field->fieldsCallback, $request, $this->resource)) ))->each(function ($field) use ($pivotAccessor, $pivotRelation) { $field->pivot = true; $field->pivotAccessor = $pivotAccessor; $field->pivotRelation = $pivotRelation; }); } return FieldCollection::make(); } /** * Get the index where the pivot fields should be spliced into the field array. * * @param array $fields * @return int|null */ protected function indexToInsertPivotFields(NovaRequest $request, array $fields) { foreach ($fields as $index => $field) { if ( isset($field->resourceName) && $field->resourceName == $request->viaResource ) { return $index; } } } /** * Get the displayable pivot model name from a field. * * @param string $field * @return string|null */ public function pivotNameForField(NovaRequest $request, $field) { $field = $this->availableFields($request)->findFieldByAttribute($field); if (! ($field instanceof BelongsToMany || $field instanceof MorphToMany)) { return self::DEFAULT_PIVOT_NAME; } if (isset($field->pivotName)) { return $field->pivotName; } } /** * Resolve available panels from fields. * * @param \Laravel\Nova\Fields\FieldCollection $fields * @param string $label * @return \Illuminate\Support\Collection */ protected function resolvePanelsFromFields(NovaRequest $request, FieldCollection $fields, $label) { [$defaultFields, $fieldsWithPanels] = $fields->each(function ($field) { if ($field instanceof BehavesAsPanel) { $field->asPanel(); } })->partition(function ($field) { return ! isset($field->panel) || blank($field->panel); }); $panels = $fieldsWithPanels->groupBy(function ($field) { return (string) $field->panel; })->transform(function ($fields, $name) { return Panel::mutate($name, $fields); })->toBase(); return $this->panelsWithDefaultLabel( $panels, $defaultFields->values(), $label ); } /** * Return the panels for this request with the default label. * * @param \Illuminate\Support\Collection $panels * @param \Laravel\Nova\Fields\FieldCollection $fields * @param string $label * @return \Illuminate\Support\Collection */ protected function panelsWithDefaultLabel(Collection $panels, FieldCollection $fields, $label) { return $panels->values() ->when($panels->where('name', $label)->isEmpty(), function ($panels) use ($label, $fields) { return $fields->isNotEmpty() ? $panels->prepend(Panel::make($label, $fields)->withMeta(['fields' => $fields])) : $panels; })->tap(function ($panels) { tap($panels->first(), function ($panel) { if (! is_null($panel)) { $panel->withToolbar(); } }); }); } /** * Return the callback used for resolving fields. * * @return \Closure(\Laravel\Nova\Fields\FieldCollection):\Laravel\Nova\Fields\FieldCollection */ protected function fieldResolverCallback(NovaRequest $request) { return function ($fields) use ($request) { $fields = $fields->values()->all(); $pivotFields = $this->pivotFieldsFor($request, $request->viaResource)->all(); if (! is_null($index = $this->indexToInsertPivotFields($request, $fields))) { array_splice($fields, $index + 1, 0, $pivotFields); } else { $fields = array_merge($fields, $pivotFields); } return FieldCollection::make($fields); }; } /** * Return the callback used for resolving fields with pivot from related relationship. * * @return \Closure(\Laravel\Nova\Fields\FieldCollection):\Laravel\Nova\Fields\FieldCollection */ protected function relatedFieldResolverCallback(NovaRequest $request) { return function ($fields) use ($request) { $fields = $fields->values()->all(); $pivotFields = $this->relatedPivotFieldsFor($request, $request->viaResource)->all(); if (! is_null($index = $this->indexToInsertPivotFields($request, $fields))) { array_splice($fields, $index + 1, 0, $pivotFields); } else { $fields = array_merge($fields, $pivotFields); } return FieldCollection::make($fields); }; } }