Skip to content

Commit

Permalink
feat: include subfield dependson middleware
Browse files Browse the repository at this point in the history
- Includes middleware to allow for dependsOn functionality for any field containing subfields

- Will allow different types of nested fields to be resolved universally, so long as they all use this package
  • Loading branch information
ianrobertsFF committed Dec 29, 2022
1 parent 91ca199 commit 0bfe536
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 3 deletions.
13 changes: 10 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "formfeed-uk/nova-subfields",
"description": "Subfields interface for Nova Fields",
"name": "formfeed-uk/nova-subfield-dependson",
"description": "Functionality to allow fields which are contained within other fields to use Nova DependsOn",
"license": "MIT",
"require": {
"php": "^8.0",
Expand All @@ -9,14 +9,21 @@
},
"autoload": {
"psr-4": {
"Formfeed\\Subfields\\": "src/"
"Formfeed\\SubfieldDependsOn\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"extra": {
"laravel": {
"providers": [
"Formfeed\\SubfieldDependsOn\\SubfieldServiceProvider"
]
}
},
"repositories": [
{
"type": "composer",
Expand Down
2 changes: 2 additions & 0 deletions src/Subfield.php → src/Contracts/Subfield.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ public function hasSubfields(): bool;

public function getSubfields(): FieldCollection;

public function afterDependsOnSync() : self;

}
144 changes: 144 additions & 0 deletions src/Http/Middleware/InterceptSubfieldDependsOn.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php

namespace Formfeed\SubfieldDependsOn\Http\Middleware;

use ArrayObject;
use Closure;
use Formfeed\DependablePanel\DependablePanel;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

use Illuminate\Support\Str;
use Laravel\Nova\Fields\FieldCollection;
use Laravel\Nova\Http\Controllers\UpdateFieldController;
use Laravel\Nova\Http\Controllers\CreationFieldController;
use Laravel\Nova\Http\Controllers\CreationFieldSyncController;
use Laravel\Nova\Http\Controllers\UpdatePivotFieldController;
use Laravel\Nova\Http\Controllers\CreationPivotFieldController;
use Laravel\Nova\Http\Requests\NovaRequest;

use Formfeed\NovaFlexibleContent\Flexible as FormfeedFlexible;
use Illuminate\Support\Collection;
use Laravel\Nova\Fields\Field;
use Whitecube\NovaFlexibleContent\Flexible as WhitecubeFlexible;

class InterceptSubfieldDependsOn {

/**
* Handle the given request and get the response.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return \Symfony\Component\HttpFoundation\Response
*/
public function handle(Request $request, Closure $next): Response {

// Pass through if closure, getController is unsafe to use with closures, if its a closure it's not a Nova DependsOn request
if (array_key_exists("uses", $request->route()->action) && $request->route()->action['uses'] instanceof Closure) {
return $next($request);
}

$novaRequest = NovaRequest::createFrom($request);

if (!$this->isDependentFieldRequest($request)) {
return $next($request);
}

if (!$this->resourceHasSubfields($novaRequest)) {
return $next($request);
}

$response = $next($request);

if ($response instanceof JsonResponse) {
$content = $response->getOriginalContent();

$field = [];

// If the Field is successfully resolved via another middleware or resource method, just use that
if ($content instanceof Field) {
$field = $content;
}

// If the Field is not resolved, try to find it and sync depends on
if ($content instanceof ArrayObject && $content->count() === 0) {
$field = $this->findField($novaRequest, $request->input("component"), $request->input("_viaField"));
if ($field instanceof Field) {
$field->syncDependsOn($novaRequest);
}
}

// If there are additional actions needed after dependsOn sync, run them
if (method_exists($field, "afterDependsOnSync")) {
$field = $field->afterDependsOnSync() ?? $field;
}

$response = response()->json($field);
}

return $response;
}

protected function isDependentFieldRequest(Request $request) {
if (!$request->isMethod("PATCH")) {
return false;
}
return (is_null($this->getFieldMethod($request))) ? false : true;
}

protected function findField(NovaRequest $request, string $componentKey, string $viaField) {
$fields = $this->getResourceFields($request);
return $this->findNestedField($request, $fields, $componentKey, $viaField);
}

protected function findNestedField(NovaRequest $request, FieldCollection $fields, string $componentKey) {

if ($fields->count() === 0) {
return [];
}

return $fields->first(fn ($field) => ($field->dependentComponentKey() === $componentKey)) ?? $this->findNestedField($request, $this->getSubfields($request, $fields), $componentKey);
}

protected function getSubfields(NovaRequest $request, FieldCollection $fields) {
return rescue(function () use ($fields, $request) {
return $fields->filter(function ($field) use ($request) {
return $this->fieldHasSubfields($request, $field);
})->map(function ($field) use ($request) {
return $field->getSubfields($request);
})->flatten();
}, FieldCollection::make([]), false);
}

protected function fieldHasSubfields(NovaRequest $request, Field $field): bool {
return method_exists($field, "getSubfields") && method_exists($field, "hasSubfields") && $field->hasSubfields($request);
}

protected function getResourceFields(NovaRequest $request): FieldCollection {
$fieldMethod = $this->getFieldMethod($request);
return $request->newResource()
->$fieldMethod($request);
}

protected function resourceHasSubfields(NovaRequest $request): bool {
return $this->getResourceFields($request)->contains(function ($field) {
return method_exists($field, "getSubfields") && method_exists($field, "hasSubfields");
});
}

protected function getFieldMethod(Request $request) {
$routeController = $request->route()->getController();
switch (get_class($routeController)) {
case UpdateFieldController::class:
return "updateFieldsWithinPanels";
case UpdatePivotFieldController::class:
return "updatePivotFields";
case CreationFieldController::class:
case CreationFieldSyncController::class:
return "creationFieldsWithinPanels";
case CreationPivotFieldController::class:
return "creationPivotFields";
}
}
}
28 changes: 28 additions & 0 deletions src/SubfieldServiceProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace Formfeed\SubfieldDependsOn;

use Illuminate\Support\ServiceProvider;
use Formfeed\SubfieldDependsOn\Http\Middleware\InterceptSubfieldDependsOn;

class SubfieldServiceProvider extends ServiceProvider
{

public function boot()
{
$router = $this->app['router'];

if ($router->hasMiddlewareGroup('nova')) {
$router->pushMiddlewareToGroup('nova', InterceptSubfieldDependsOn::class);
return;
}

if (!$this->app->configurationIsCached()) {
config()->set('nova.middleware', array_merge(
config('nova.middleware', []),
[InterceptSubfieldDependsOn::class]
));
}
}

}

0 comments on commit 0bfe536

Please sign in to comment.