diff --git a/composer.json b/composer.json index 5de49ef5a..169f85e66 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,8 @@ "require-dev": { "fakerphp/faker": "^1.9.2", "phpunit/phpunit": "^9.5.8", - "mockery/mockery": "^1.4.4" + "mockery/mockery": "^1.4.4", + "phpstan/phpstan": "^1.4.7" }, "autoload": { "psr-4": { diff --git a/src/Actions/BaseAction.php b/src/Actions/BaseAction.php new file mode 100644 index 000000000..6a66c3cc2 --- /dev/null +++ b/src/Actions/BaseAction.php @@ -0,0 +1,47 @@ +setLabel($label); + } + + public function label(): string + { + return $this->label; + } + + public function setLabel(string $label): static + { + $this->label = $label; + + return $this; + } + + public function resource(): ResourceContract|null + { + return $this->resource; + } + + public function setResource(ResourceContract $resource): static + { + $this->resource = $resource; + + return $this; + } +} \ No newline at end of file diff --git a/src/Traits/Resources/ExportTrait.php b/src/Actions/ExportAction.php similarity index 63% rename from src/Traits/Resources/ExportTrait.php rename to src/Actions/ExportAction.php index 9dbc69612..f15c6a44b 100644 --- a/src/Traits/Resources/ExportTrait.php +++ b/src/Actions/ExportAction.php @@ -1,50 +1,33 @@ true]; - - if(request()->has('filters')) { - foreach (request()->query('filters') as $filterField => $filterQuery) { - if(is_array($filterQuery)) { - foreach ($filterQuery as $filterInnerField => $filterValue) { - if(is_numeric($filterInnerField) && !is_array($filterValue)) { - $query['filters'][$filterField][] = $filterValue; - } else { - $query['filters'][$filterInnerField] = $filterValue; - } - } - } else { - $query['filters'][$filterField] = $filterQuery; - } - } + if(is_null($this->resource())) { + throw new ActionException('Resource is required for action'); } - if(request()->has('search')) { - $query['search'] = request('search'); - } - - return $this->route('index', null, $query); - } - - protected function exportCsv(): Response|Application|ResponseFactory - { $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); $letter = 'A'; - foreach ($this->resource->exportFields() as $index => $field) { + foreach ($this->resource()->exportFields() as $field) { $sheet->setCellValue("{$letter}1", $field->label()); $letter++; @@ -54,9 +37,9 @@ protected function exportCsv(): Response|Application|ResponseFactory $line = 2; - foreach ($this->resource->all() as $item) { + foreach ($this->resource()->all() as $item) { $letter = 'A'; - foreach ($this->resource->exportFields() as $index => $field) { + foreach ($this->resource()->exportFields() as $index => $field) { $sheet->setCellValue($letter . $line, $field->exportViewValue($item)); $letter++; } @@ -66,9 +49,48 @@ protected function exportCsv(): Response|Application|ResponseFactory $writer = new Xlsx($spreadsheet); header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); - header('Content-Disposition: attachment;filename="'.$this->resource->title().'.xlsx"'); + header('Content-Disposition: attachment;filename="'.$this->resource()->title().'.xlsx"'); header('Cache-Control: max-age=0'); return response($writer->save('php://output')); } + + public function isTriggered(): bool + { + return request()->has('exportCsv'); + } + + /** + * @throws ActionException + */ + public function url(): string + { + if(is_null($this->resource())) { + throw new ActionException('Resource is required for action'); + } + + $query = ['exportCsv' => true]; + + if(request()->has('filters')) { + foreach (request()->query('filters') as $filterField => $filterQuery) { + if(is_array($filterQuery)) { + foreach ($filterQuery as $filterInnerField => $filterValue) { + if(is_numeric($filterInnerField) && !is_array($filterValue)) { + $query['filters'][$filterField][] = $filterValue; + } else { + $query['filters'][$filterInnerField] = $filterValue; + } + } + } else { + $query['filters'][$filterField] = $filterQuery; + } + } + } + + if(request()->has('search')) { + $query['search'] = request('search'); + } + + return $this->resource()->route('index', null, $query); + } } \ No newline at end of file diff --git a/src/Commands/BaseMoonShineCommand.php b/src/Commands/BaseMoonShineCommand.php index fda409049..ca40fb0a4 100644 --- a/src/Commands/BaseMoonShineCommand.php +++ b/src/Commands/BaseMoonShineCommand.php @@ -6,32 +6,22 @@ class BaseMoonShineCommand extends Command { - /** - * Install directory. - * - * @var string - */ protected string $directory = 'app/MoonShine'; - /** - * Get stub contents. - * - * @param $name - * - * @return string - */ - protected function getStub($name): string + protected function getStub(string $name): string { - return $this->laravel['files']->get(__DIR__."/stubs/$name.stub"); + return $this->laravel['files']->get(__DIR__."/../stubs/$name.stub"); } - /** - * Make new directory. - * - * @param string $path - */ - protected function makeDir(string $path = '') + protected function getDirectory(): string { - $this->laravel['files']->makeDirectory("$this->directory/$path", 0755, true, true); + return config('moonshine.dir', $this->directory); + } + + protected function makeDir(string $path = ''): void + { + if(isset($this->laravel['files'])) { + $this->laravel['files']->makeDirectory("$this->directory/$path", 0755, true, true); + } } } diff --git a/src/Commands/InstallCommand.php b/src/Commands/InstallCommand.php index bbebe70fd..2b65139b3 100644 --- a/src/Commands/InstallCommand.php +++ b/src/Commands/InstallCommand.php @@ -2,51 +2,27 @@ namespace Leeto\MoonShine\Commands; -use Illuminate\Console\Command; - class InstallCommand extends BaseMoonShineCommand { - /** - * The console command name. - * - * @var string - */ protected $signature = 'moonshine:install'; - /** - * The console command description. - * - * @var string - */ protected $description = 'Install the moonshine package'; - /** - * Execute the console command. - * - * @return void - */ - public function handle() + public function handle(): void { - $this->initDirectory(); + $this->initDirectories(); } - /** - * Initialize the admAin directory. - * - * @return void - */ - protected function initDirectory() + protected function initDirectories(): void { $this->directory = config('moonshine.dir', $this->directory); - if (is_dir($this->directory)) { - $this->error("$this->directory directory already exists!"); - - return; + if (is_dir($this->getDirectory())) { + $this->error("{$this->getDirectory()} directory already exists!"); } $this->makeDir('/'); - $this->info('Directory was created:' . str_replace(base_path(), '', $this->directory)); + $this->info('Directory was created:' . str_replace(base_path(), '', $this->getDirectory())); $this->makeDir('Controllers'); $this->makeDir('Resources'); diff --git a/src/Commands/ResourceCommand.php b/src/Commands/ResourceCommand.php index 42a866348..aa600754d 100644 --- a/src/Commands/ResourceCommand.php +++ b/src/Commands/ResourceCommand.php @@ -7,34 +7,17 @@ class ResourceCommand extends BaseMoonShineCommand { - /** - * The console command name. - * - * @var string - */ protected $signature = 'moonshine:resource {name?} {--m|model=} {--t|title=}'; - /** - * The console command description. - * - * @var string - */ protected $description = 'Create resource'; - /** - * Execute the console command. - * - * @return void - */ - public function handle() + public function handle(): void { $this->createResource(); } - public function createResource() + public function createResource(): void { - $this->directory = config('moonshine.dir', $this->directory); - $name = str($this->argument('name')); if(!$name) { @@ -47,7 +30,7 @@ public function createResource() $model = $this->option('model') ?? $name; $title = $this->option('title') ?? $name; - $resource = $this->directory."/Resources/{$name}Resource.php"; + $resource = $this->getDirectory()."/Resources/{$name}Resource.php"; $contents = $this->getStub('Resource'); $contents = str_replace('DummyModel', $model, $contents); $contents = str_replace('DummyTitle', $title, $contents); @@ -59,7 +42,7 @@ public function createResource() $this->info("{$name}Resource file was created: " . str_replace(base_path(), '', $resource)); - $controller = $this->directory."/Controllers/{$name}Controller.php"; + $controller = $this->getDirectory()."/Controllers/{$name}Controller.php"; $contents = $this->getStub('ResourceController'); $this->laravel['files']->put( diff --git a/src/Commands/UserCommand.php b/src/Commands/UserCommand.php index 2472b77d7..f255d672f 100644 --- a/src/Commands/UserCommand.php +++ b/src/Commands/UserCommand.php @@ -7,22 +7,11 @@ class UserCommand extends BaseMoonShineCommand { - /** - * The console command name. - * - * @var string - */ protected $signature = 'moonshine:user'; - /** - * The console command description. - * - * @var string - */ protected $description = 'Create user'; - - public function handle() + public function handle(): void { $email = $this->ask('Email'); $name = $this->ask('Name'); diff --git a/src/Components/MenuComponent.php b/src/Components/MenuComponent.php index 8c0aa5d6e..efd4e6a5f 100644 --- a/src/Components/MenuComponent.php +++ b/src/Components/MenuComponent.php @@ -14,7 +14,7 @@ class MenuComponent extends Component { public function render(): View|Factory|Htmlable|Closure|string|Application { - $data = app(Menu::class)->get(); + $data = app(Menu::class)->all(); return view('moonshine::components.menu', [ "data" => $data, diff --git a/src/Contracts/Actions/ActionContract.php b/src/Contracts/Actions/ActionContract.php new file mode 100644 index 000000000..a8fe15fff --- /dev/null +++ b/src/Contracts/Actions/ActionContract.php @@ -0,0 +1,12 @@ +authorize('viewAny', $this->resource->getModel()); } - if(request()->has('exportCsv')) { - return $this->exportCsv(); + if($this->resource->getActions()) { + foreach ($this->resource->getActions() as $action) { + if($action->isTriggered()) { + return $action->handle(); + } + } } return view($this->resource->baseIndexView(), [ @@ -54,7 +56,7 @@ public function create(): View|Factory|Redirector|RedirectResponse|Application $this->authorize('create', $this->resource->getModel()); } - if(!in_array('create', $this->resource->getActions())) { + if(!in_array('create', $this->resource->getActiveActions())) { return redirect($this->resource->route('index')); } @@ -66,11 +68,12 @@ public function create(): View|Factory|Redirector|RedirectResponse|Application */ public function edit($id): View|Factory|Redirector|RedirectResponse|Application { - if(!in_array('edit', $this->resource->getActions())) { + if(!in_array('edit', $this->resource->getActiveActions())) { return redirect($this->resource->route('index')); } $item = $this->resource->getModel() + ->query() ->where(['id' => $id]) ->firstOrFail(); @@ -89,6 +92,7 @@ public function edit($id): View|Factory|Redirector|RedirectResponse|Application public function show($id): Redirector|Application|RedirectResponse { $item = $this->resource->getModel() + ->query() ->where(['id' => $id]) ->firstOrFail(); @@ -104,11 +108,12 @@ public function show($id): Redirector|Application|RedirectResponse */ public function update($id, Request $request): Factory|View|Redirector|Application|RedirectResponse { - if(!in_array('edit', $this->resource->getActions())) { + if(!in_array('edit', $this->resource->getActiveActions())) { return redirect($this->resource->route('index')); } $item = $this->resource->getModel() + ->query() ->where(['id' => $id]) ->firstOrFail(); @@ -124,7 +129,7 @@ public function update($id, Request $request): Factory|View|Redirector|Applicati */ public function store(Request $request): Factory|View|Redirector|Application|RedirectResponse { - if(!in_array('edit', $this->resource->getActions()) && !in_array("create", $this->resource->getActions())) { + if(!in_array('edit', $this->resource->getActiveActions()) && !in_array("create", $this->resource->getActions())) { return redirect($this->resource->route('index')); } @@ -142,24 +147,21 @@ public function store(Request $request): Factory|View|Redirector|Application|Red */ public function destroy($id): Redirector|Application|RedirectResponse { - if(!in_array('delete', $this->resource->getActions())) { + if(!in_array('delete', $this->resource->getActiveActions())) { return redirect($this->resource->route('index')); } + if($this->resource->isWithPolicy()) { + $this->authorize('delete', $this->resource->getModel()); + } + if(request()->has('ids')) { $this->resource->getModel() + ->query() ->whereIn('id', explode(';', request('ids'))) ->delete(); - - if($this->resource->isWithPolicy()) { - $this->authorize('delete', $this->resource->getModel()); - } } else { $this->resource->getModel()->destroy($id); - - if($this->resource->isWithPolicy()) { - $this->authorize('delete', $this->resource->getModel()); - } } return redirect($this->resource->route('index')) @@ -184,7 +186,7 @@ protected function save(Request $request, Model $item): Factory|View|Redirector| } catch (ResourceException $e) { throw_if(!app()->isProduction(), $e); - return redirect($this->resource->route('edit', $item->id)) + return redirect($this->resource->route('edit', $item->getKey())) ->with('alert', trans('moonshine::ui.saved_error')); } diff --git a/src/Controllers/IndexController.php b/src/Controllers/MoonShineAuthController.php similarity index 84% rename from src/Controllers/IndexController.php rename to src/Controllers/MoonShineAuthController.php index 88734d459..10fdd8862 100644 --- a/src/Controllers/IndexController.php +++ b/src/Controllers/MoonShineAuthController.php @@ -2,24 +2,17 @@ namespace Leeto\MoonShine\Controllers; -use App\Http\Controllers\Controller; +use Illuminate\Routing\Controller as BaseController; use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\View; use Illuminate\Http\RedirectResponse; use Illuminate\Routing\Redirector; -use Illuminate\Support\Facades\Auth; use Illuminate\Http\Request; use Illuminate\Support\Facades\Storage; -use JetBrains\PhpStorm\ArrayShape; -class IndexController extends Controller +class MoonShineAuthController extends BaseController { - public function index(): Factory|View|Application - { - return view('moonshine::index.index'); - } - public function login(Request $request): Factory|View|Redirector|Application|RedirectResponse { if (auth(config('moonshine.auth.guard'))->check()) { @@ -51,7 +44,6 @@ public function logout(): Redirector|Application|RedirectResponse return redirect(route(config("moonshine.route.prefix") . '.login')); } - #[ArrayShape(['attachment' => "string"])] public function attachments(Request $request): array { if ($request->hasFile('file')) { diff --git a/src/Controllers/MoonShineDashboardController.php b/src/Controllers/MoonShineDashboardController.php new file mode 100644 index 000000000..dbd0013b8 --- /dev/null +++ b/src/Controllers/MoonShineDashboardController.php @@ -0,0 +1,33 @@ +hasFile('file')) { + $file = $request->file('file'); + + return [ + 'attachment' => Storage::url($file->store('attachments', 'public')) + ]; + } + + return []; + } +} diff --git a/src/Controllers/MoonShineUserController.php b/src/Controllers/MoonShineUserController.php index 191533058..0b88d0d01 100644 --- a/src/Controllers/MoonShineUserController.php +++ b/src/Controllers/MoonShineUserController.php @@ -2,12 +2,10 @@ namespace Leeto\MoonShine\Controllers; -use JetBrains\PhpStorm\Pure; use Leeto\MoonShine\Resources\MoonShineUserResource; class MoonShineUserController extends BaseMoonShineController { - #[Pure] public function __construct() { $this->resource = new MoonShineUserResource(); diff --git a/src/Controllers/MoonShineUserRoleController.php b/src/Controllers/MoonShineUserRoleController.php index af66ff19a..dec7d2e92 100644 --- a/src/Controllers/MoonShineUserRoleController.php +++ b/src/Controllers/MoonShineUserRoleController.php @@ -2,12 +2,10 @@ namespace Leeto\MoonShine\Controllers; -use JetBrains\PhpStorm\Pure; use Leeto\MoonShine\Resources\MoonShineUserRoleResource; class MoonShineUserRoleController extends BaseMoonShineController { - #[Pure] public function __construct() { $this->resource = new MoonShineUserRoleResource(); diff --git a/src/Decorations/BaseDecoration.php b/src/Decorations/BaseDecoration.php index 9fbca1421..54018b88d 100644 --- a/src/Decorations/BaseDecoration.php +++ b/src/Decorations/BaseDecoration.php @@ -3,7 +3,6 @@ namespace Leeto\MoonShine\Decorations; -use JetBrains\PhpStorm\Pure; use Leeto\MoonShine\Contracts\Components\ViewComponentContract; class BaseDecoration implements ViewComponentContract @@ -19,7 +18,7 @@ public static function make(...$arguments): static return new static(...$arguments); } - public function __construct($label, array $fields = []) + final public function __construct(string $label, array $fields = []) { $this->setLabel($label); $this->setFields($fields); @@ -37,20 +36,19 @@ public function setFields(array $fields): static return $this; } - #[Pure] public function hasFields(): bool { return !empty($this->fields()); } - public function id($index = null): string + public function id(string $index = null): string { return str($this->label())->slug(); } - public function name($index = null): string + public function name(string $index = null): string { - return $this->id($index = null); + return $this->id($index); } public function label(): string diff --git a/src/Exceptions/ActionException.php b/src/Exceptions/ActionException.php new file mode 100644 index 000000000..3ac877877 --- /dev/null +++ b/src/Exceptions/ActionException.php @@ -0,0 +1,10 @@ +{$this->relation()}) { - return '-'; + return ''; } return $container ? view('moonshine::shared.badge', [ @@ -177,20 +177,20 @@ public function indexViewValue(Model $item, bool $container = true): string ]) : $item->{$this->relation()}->{$this->resourceTitleField()}; } - return $item->{$this->field()}; + return $item->{$this->field()} ?? ''; } public function exportViewValue(Model $item): string { if($this instanceof FieldHasRelationContract) { if(!$item->{$this->relation()}) { - return '-'; + return ''; } - return $item->{$this->relation()}->{$this->resourceTitleField()}; + return $item->{$this->relation()}->{$this->resourceTitleField()} ?? ''; } - return $item->{$this->field()}; + return $item->{$this->field()} ?? ''; } public function save(Model $item): Model diff --git a/src/Fields/BelongsToMany.php b/src/Fields/BelongsToMany.php index 87b6e9570..af4e08479 100644 --- a/src/Fields/BelongsToMany.php +++ b/src/Fields/BelongsToMany.php @@ -33,7 +33,7 @@ public function ids(): array public function treeHtml(): string { - return (string) $this->treeHtml; + return $this->treeHtml; } public function tree(string $treeParentColumn): static diff --git a/src/Fields/Json.php b/src/Fields/Json.php index 7bd142b8f..7ba17aeaa 100644 --- a/src/Fields/Json.php +++ b/src/Fields/Json.php @@ -5,6 +5,7 @@ use Illuminate\Database\Eloquent\Model; use Leeto\MoonShine\Contracts\Fields\FieldHasFieldsContract; use Leeto\MoonShine\Traits\Fields\FieldWithFieldsTrait; +use Throwable; class Json extends BaseField implements FieldHasFieldsContract { @@ -12,25 +13,6 @@ class Json extends BaseField implements FieldHasFieldsContract protected static string $view = 'json'; - protected bool $keyValue = false; - - public function keyValue(string $key = 'Key', string $value = 'Value'): static - { - $this->keyValue = true; - - $this->fields([ - Text::make($key, 'key'), - Text::make($value, 'value'), - ]); - - return $this; - } - - public function isKeyValue(): bool - { - return $this->keyValue; - } - public function indexViewValue(Model $item, bool $container = false): string { $columns = []; diff --git a/src/Filters/BaseFilter.php b/src/Filters/BaseFilter.php index bf2f5c7bb..2fc075911 100644 --- a/src/Filters/BaseFilter.php +++ b/src/Filters/BaseFilter.php @@ -14,7 +14,7 @@ abstract class BaseFilter implements ViewComponentContract { use FormElementBasicTrait, ShowWhenTrait, XModelTrait; - public function name($index = null): string + public function name(string $index = null): string { return $this->prepareName($index, 'filters'); } @@ -26,7 +26,7 @@ public function getQuery(Builder $query): Builder return $this->requestValue() ? $query->whereHas($this->relation(), function (Builder $q) use($table) { - return $q->whereIn("{$table}.id", $this->requestValue()); + return $q->whereIn("$table.id", $this->requestValue()); }) : $query; } diff --git a/src/Menu/BaseMenuSection.php b/src/Menu/BaseMenuSection.php index f1d359892..ac2392a7d 100644 --- a/src/Menu/BaseMenuSection.php +++ b/src/Menu/BaseMenuSection.php @@ -2,22 +2,29 @@ namespace Leeto\MoonShine\Menu; -class BaseMenuSection +use Illuminate\Support\Collection; +use Leeto\MoonShine\Resources\BaseResource; + +abstract class BaseMenuSection { protected string $title; protected string|null $icon = null; - public static function make(...$arguments): static - { - return new static(...$arguments); - } + protected Collection $items; + + protected BaseResource $resource; public function title(): String { return $this->title; } + public function items(): Collection + { + return $this->items; + } + public function icon(string $icon): static { $this->icon = $icon; @@ -25,6 +32,11 @@ public function icon(string $icon): static return $this; } + public function resource(): BaseResource + { + return $this->resource; + } + public function getIcon(string $size = '8', string $color = '', string $class = ''): string { $icon = $this->icon ?? 'app'; diff --git a/src/Menu/Menu.php b/src/Menu/Menu.php index f4f1b82d4..5913dc220 100644 --- a/src/Menu/Menu.php +++ b/src/Menu/Menu.php @@ -2,7 +2,6 @@ namespace Leeto\MoonShine\Menu; - use Illuminate\Support\Collection; class Menu @@ -14,7 +13,7 @@ public function register(Collection $data): void $this->menu = $data; } - public function get(): Collection|null + public function all(): Collection|null { return $this->menu; } diff --git a/src/Menu/MenuGroup.php b/src/Menu/MenuGroup.php index 5f41cbe4c..13ed7e244 100644 --- a/src/Menu/MenuGroup.php +++ b/src/Menu/MenuGroup.php @@ -8,9 +8,12 @@ class MenuGroup extends BaseMenuSection { - protected Collection $items; + public static function make(...$arguments): static + { + return new static(...$arguments); + } - public function __construct(string $title, array $items, string $icon = null) + final public function __construct(string $title, array $items, string $icon = null) { $this->title = $title; $this->items = collect($items)->map(function($item) { @@ -32,9 +35,4 @@ public function __construct(string $title, array $items, string $icon = null) $this->icon($icon); } } - - public function items(): Collection - { - return $this->items; - } } \ No newline at end of file diff --git a/src/Menu/MenuItem.php b/src/Menu/MenuItem.php index a708c1060..eda820c20 100644 --- a/src/Menu/MenuItem.php +++ b/src/Menu/MenuItem.php @@ -6,9 +6,12 @@ class MenuItem extends BaseMenuSection { - protected BaseResource $resource; + public static function make(...$arguments): static + { + return new static(...$arguments); + } - public function __construct(string $title, BaseResource|string $resource, string $icon = null) + final public function __construct(string $title, BaseResource|string $resource, string $icon = null) { $this->title = $title; $this->resource = is_string($resource) ? new $resource() : $resource; @@ -17,9 +20,4 @@ public function __construct(string $title, BaseResource|string $resource, string $this->icon($icon); } } - - public function resource(): BaseResource - { - return $this->resource; - } } \ No newline at end of file diff --git a/src/MoonShine.php b/src/MoonShine.php index 9e6b19900..b0750eeb1 100644 --- a/src/MoonShine.php +++ b/src/MoonShine.php @@ -3,13 +3,17 @@ use Illuminate\Support\Collection; use Illuminate\Support\Facades\Route; +use Leeto\MoonShine\Controllers\MoonShineAuthController; use Leeto\MoonShine\Controllers\MoonShineUserRoleController; use Leeto\MoonShine\Controllers\MoonShineUserController; -use Leeto\MoonShine\Controllers\IndexController; +use Leeto\MoonShine\Controllers\MoonShineDashboardController; use Leeto\MoonShine\Menu\Menu; use Leeto\MoonShine\Menu\MenuGroup; use Leeto\MoonShine\Menu\MenuItem; +use Leeto\MoonShine\Models\MoonshineUserRole; use Leeto\MoonShine\Resources\BaseResource; +use Leeto\MoonShine\Resources\MoonShineUserResource; +use Leeto\MoonShine\Resources\MoonShineUserRoleResource; class MoonShine { @@ -61,15 +65,14 @@ protected function addRoutes(): void ->middleware(config('moonshine.route.middleware')) ->name(config('moonshine.route.prefix') . '.')->group(function () { - Route::get('/', [IndexController::class, 'index'])->name('index'); + Route::get('/', [MoonShineDashboardController::class, 'index'])->name('index'); + Route::post('/attachments', [MoonShineDashboardController::class, 'attachments'])->name('attachments'); - Route::any('/login', [IndexController::class, 'login'])->name('login'); - Route::get('/logout', [IndexController::class, 'logout'])->name('logout'); + Route::any('/login', [MoonShineAuthController::class, 'login'])->name('login'); + Route::get('/logout', [MoonShineAuthController::class, 'logout'])->name('logout'); - Route::post('/attachments', [IndexController::class, 'attachments'])->name('attachments'); - - Route::resource('moonshineusers', MoonShineUserController::class); - Route::resource('moonshineuserroles', MoonShineUserRoleController::class); + Route::resource((new MoonShineUserResource())->routeAlias(), MoonShineUserController::class); + Route::resource((new MoonshineUserRoleResource())->routeAlias(), MoonShineUserRoleController::class); $this->resources->each(function ($resource) { /* @var BaseResource $resource */ diff --git a/src/Resources/BaseResource.php b/src/Resources/BaseResource.php index d1aa3908d..85df9e522 100644 --- a/src/Resources/BaseResource.php +++ b/src/Resources/BaseResource.php @@ -3,13 +3,17 @@ namespace Leeto\MoonShine\Resources; use Illuminate\Contracts\Foundation\Application; +use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\View; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Collection; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Validator; use Illuminate\Database\QueryException; +use Leeto\MoonShine\Actions\BaseAction; +use Leeto\MoonShine\Contracts\Actions\ActionContract; use Leeto\MoonShine\Contracts\Components\ViewComponentContract; use Leeto\MoonShine\Contracts\Fields\FieldHasRelationContract; use Leeto\MoonShine\Contracts\Resources\ResourceContract; @@ -20,14 +24,9 @@ use Leeto\MoonShine\Fields\BaseField; use Leeto\MoonShine\Filters\BaseFilter; -use Leeto\MoonShine\Traits\Resources\ExportTrait; -use Leeto\MoonShine\Traits\Resources\RouteTrait; -use Leeto\MoonShine\Traits\Resources\QueryTrait; abstract class BaseResource implements ResourceContract { - use QueryTrait, RouteTrait, ExportTrait; - public static string $model; public Model $item; @@ -38,12 +37,18 @@ abstract class BaseResource implements ResourceContract public static string $subtitle = ''; - public static array $actions = ['create', 'show', 'edit', 'delete']; + public static array $activeActions = ['create', 'show', 'edit', 'delete']; public static array $with = []; public static bool $withPolicy = false; + public static string $orderField = 'id'; + + public static string $orderType = 'DESC'; + + public static int $itemsPerPage = 25; + public static string $baseIndexView = 'moonshine::base.index'; public static string $baseEditView = 'moonshine::base.form'; @@ -52,12 +57,23 @@ abstract class BaseResource implements ResourceContract abstract function rules(Model $item): array; + /** + * @return BaseField[] + */ abstract function fields(): array; - abstract function search(): array; - + /** + * @return BaseFilter[] + */ abstract function filters(): array; + /** + * @return BaseAction[] + */ + abstract function actions(): array; + + abstract function search(): array; + public function baseIndexView(): string { return static::$baseIndexView; @@ -98,9 +114,9 @@ public function getModel(): Model return new static::$model(); } - public function getActions(): array + public function getActiveActions(): array { - return static::$actions; + return static::$activeActions; } public function isWithPolicy(): bool @@ -125,6 +141,23 @@ public function routeName(string|null $action = null): string ->when($action, fn($str) => $str->append('.')->append($action)); } + public function route(string $action, int $id = null, array $query = []): string + { + $route = str(request()->route()->getName())->beforeLast('.'); + + if($id) { + $parameter = $route->afterLast(config('moonshine.route.prefix') . '.') + ->singular(); + + return route( + "$route.$action", + array_merge([(string) $parameter => $id], $query) + ); + } else { + return route("$route.$action", $query); + } + } + public function controllerName(): string { return (string) str(static::class) @@ -138,7 +171,24 @@ public function controllerName(): string ); } - /* @return BaseField[] */ + /** + * @return ActionContract[] + */ + public function getActions(): Collection + { + $actions = collect(); + + foreach ($this->actions() as $action) { + $actions->add($action->setResource($this)); + } + + return $actions; + } + + + /** + * @return BaseField[] + */ public function getFields(): Collection { $fields = []; @@ -158,6 +208,14 @@ public function getFields(): Collection return collect($fields); } + /** + * @return BaseFilter\[] + */ + public function getFilters(): Collection + { + return collect($this->filters()); + } + /* @return Tab[] */ public function tabs(): Collection { @@ -169,7 +227,7 @@ public function tabs(): Collection public function whenFields(): Collection { return collect($this->getFields()) - ->filter(fn (ViewComponentContract $field) => $field->showWhenState); + ->filter(fn (ViewComponentContract $field) => $field instanceof BaseField && $field->showWhenState); } public function whenFieldNames(): Collection @@ -252,7 +310,7 @@ public function getAssets(string $type): array public function getFilter(string $filterName): BaseFilter|null { - return collect($this->filters())->filter(function (BaseFilter $filter) use($filterName) { + return collect($this->getFilters())->filter(function (BaseFilter $filter) use($filterName) { return $filter->field() == $filterName; })->first(); } @@ -279,6 +337,52 @@ public function extensions($name, Model $item): string return (string) $views; } + public function all(): Collection + { + return $this->query()->get(); + } + + public function paginate(): LengthAwarePaginator + { + return $this->query()->paginate(static::$itemsPerPage); + } + + public function query(): Builder + { + $query = $this->getModel()->query(); + + if(static::$with) { + $query = $query->with(static::$with); + } + + if(request()->has('search') && count($this->search())) { + foreach($this->search() as $field) { + $query = $query->orWhere( + $field, + 'LIKE', + '%' .request('search') . '%' + ); + } + } + + if(request()->has('filters') && count($this->filters())) { + foreach ($this->filters() as $filter) { + $query = $filter->getQuery($query); + } + } + + if(request()->has('order')) { + $query = $query->orderBy( + request('order.field'), + request('order.type') + ); + } else { + $query = $query->orderBy(static::$orderField, static::$orderType); + } + + return $query; + } + public function validate(Model $item): array { return Validator::validate( diff --git a/src/Resources/MoonShineUserResource.php b/src/Resources/MoonShineUserResource.php index cd7385fcb..e6aed4e0f 100644 --- a/src/Resources/MoonShineUserResource.php +++ b/src/Resources/MoonShineUserResource.php @@ -3,6 +3,7 @@ namespace Leeto\MoonShine\Resources; +use Leeto\MoonShine\Actions\ExportAction; use Leeto\MoonShine\Fields\BelongsTo; use Leeto\MoonShine\Fields\Email; use Leeto\MoonShine\Fields\ID; @@ -83,4 +84,11 @@ public function filters(): array TextFilter::make('Имя', 'name'), ]; } + + public function actions(): array + { + return [ + ExportAction::make('Экспорт'), + ]; + } } diff --git a/src/Resources/MoonShineUserRoleResource.php b/src/Resources/MoonShineUserRoleResource.php index fb25b1c81..e2fd09933 100644 --- a/src/Resources/MoonShineUserRoleResource.php +++ b/src/Resources/MoonShineUserRoleResource.php @@ -44,4 +44,9 @@ public function filters(): array TextFilter::make('Название', 'name'), ]; } + + public function actions(): array + { + return []; + } } diff --git a/src/Traits/Fields/FieldWithFieldsTrait.php b/src/Traits/Fields/FieldWithFieldsTrait.php index cca088f61..2cfbe00a2 100644 --- a/src/Traits/Fields/FieldWithFieldsTrait.php +++ b/src/Traits/Fields/FieldWithFieldsTrait.php @@ -4,15 +4,17 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; -use JetBrains\PhpStorm\Pure; use Leeto\MoonShine\Contracts\Fields\FieldHasRelationContract; use Leeto\MoonShine\Contracts\Fields\FieldWithPivotContract; use Leeto\MoonShine\Exceptions\FieldException; use Leeto\MoonShine\Fields\HasMany; +use Leeto\MoonShine\Fields\Text; use Throwable; trait FieldWithFieldsTrait { + protected bool $keyValue = false; + protected array $fields = []; public function getFields(): array @@ -20,7 +22,6 @@ public function getFields(): array return $this->fields; } - #[Pure] public function hasFields(): bool { return isset($this->fields) && count($this->getFields()); @@ -70,6 +71,26 @@ public function fields(array $fields): static } + /** + * @throws Throwable + */ + public function keyValue(string $key = 'Key', string $value = 'Value'): static + { + $this->keyValue = true; + + $this->fields([ + Text::make($key, 'key'), + Text::make($value, 'value'), + ]); + + return $this; + } + + public function isKeyValue(): bool + { + return $this->keyValue; + } + public function jsonValues(Model $item = null): array { if(is_null($item)) { diff --git a/src/Traits/Fields/FormElementBasicTrait.php b/src/Traits/Fields/FormElementBasicTrait.php index d373c9bf4..bf6dc10e1 100644 --- a/src/Traits/Fields/FormElementBasicTrait.php +++ b/src/Traits/Fields/FormElementBasicTrait.php @@ -53,7 +53,7 @@ public static function make(...$arguments): static return new static(...$arguments); } - public function __construct($label = null, $field = null, ResourceContract|string|null $resource = null) + final public function __construct(string $label = null, string $field = null, ResourceContract|string|null $resource = null) { $this->setLabel($label ?? str($this->label)->ucfirst()); $this->setField($field ?? str($this->label)->lower()->snake()); @@ -91,7 +91,7 @@ public function __construct($label = null, $field = null, ResourceContract|strin } } - public function id($index = null): string + public function id(string $index = null): string { if($this->id) { return $this->id; @@ -103,7 +103,7 @@ public function id($index = null): string ->when(!is_null($index), fn(Stringable $str) => $str->append("_$index")); } - public function name($index = null): string + public function name(string $index = null): string { return $this->prepareName($index); } diff --git a/src/Traits/Resources/QueryTrait.php b/src/Traits/Resources/QueryTrait.php deleted file mode 100644 index 738fb2756..000000000 --- a/src/Traits/Resources/QueryTrait.php +++ /dev/null @@ -1,62 +0,0 @@ -query()->get(); - } - - public function paginate(): LengthAwarePaginator - { - return $this->query()->paginate(static::$itemsPerPage); - } - - public function query(): Builder - { - $query = $this->getModel()->query(); - - if(static::$with) { - $query = $query->with(static::$with); - } - - if(request()->has('search') && count($this->search())) { - foreach($this->search() as $field) { - $query = $query->orWhere( - $field, - 'LIKE', - '%' .request('search') . '%' - ); - } - } - - if(request()->has('filters') && count($this->filters())) { - foreach ($this->filters() as $filter) { - $query = $filter->getQuery($query); - } - } - - if(request()->has('order')) { - $query = $query->orderBy( - request('order.field'), - request('order.type') - ); - } else { - $query = $query->orderBy(static::$orderField, static::$orderType); - } - - return $query; - } -} \ No newline at end of file diff --git a/src/Traits/Resources/RouteTrait.php b/src/Traits/Resources/RouteTrait.php deleted file mode 100644 index edfffab8d..000000000 --- a/src/Traits/Resources/RouteTrait.php +++ /dev/null @@ -1,22 +0,0 @@ -route()->getName())->beforeLast('.'); - - if($id) { - $parameter = $route->afterLast(config('moonshine.route.prefix') . '.')->singular(); - - return route( - "$route.$action", - array_merge([(string) $parameter => $id], $query) - ); - } else { - return route("$route.$action", $query); - } - } -} \ No newline at end of file diff --git a/src/config/moonshine.php b/src/config/moonshine.php index 7bada3f24..f82edad63 100644 --- a/src/config/moonshine.php +++ b/src/config/moonshine.php @@ -1,12 +1,15 @@ 'app/MoonShine', 'title' => env('MOONSHINE_TITLE', 'MoonShine'), 'logo' => env('MOONSHINE_LOGO', ''), 'auth' => [ - 'controller' => Leeto\MoonShine\Controllers\IndexController::class, + 'controller' => MoonShineDashboardController::class, 'guard' => 'moonshine', 'guards' => [ 'moonshine' => [ @@ -19,7 +22,7 @@ 'providers' => [ 'moonshine' => [ 'driver' => 'eloquent', - 'model' => \Leeto\MoonShine\Models\MoonshineUser::class, + 'model' => MoonshineUser::class, ], ], ], diff --git a/src/Commands/stubs/Resource.stub b/src/stubs/Resource.stub similarity index 89% rename from src/Commands/stubs/Resource.stub rename to src/stubs/Resource.stub index 1cebd6bca..faebec47c 100644 --- a/src/Commands/stubs/Resource.stub +++ b/src/stubs/Resource.stub @@ -34,4 +34,9 @@ class DummyResource extends BaseResource { return []; } + + public function actions(): array + { + return []; + } } diff --git a/src/Commands/stubs/ResourceController.stub b/src/stubs/ResourceController.stub similarity index 100% rename from src/Commands/stubs/ResourceController.stub rename to src/stubs/ResourceController.stub diff --git a/src/views/base/index.blade.php b/src/views/base/index.blade.php index 3dd8860c2..53dfa715d 100644 --- a/src/views/base/index.blade.php +++ b/src/views/base/index.blade.php @@ -3,7 +3,7 @@ @section('sidebar-inner') @parent - @if(in_array("create", $resource->getActions())) + @if(in_array("create", $resource->getActiveActions()))
@include('moonshine::shared.btn', [ 'title' => trans('moonshine::ui.create'), @@ -39,13 +39,10 @@ @endif - @if($resource->exportFields()->count()) + @if($resource->actions())
- @include('moonshine::shared.btn', [ - 'title' => trans('moonshine::ui.export'), - 'href' => $resource->exportRoute(), - 'icon' => 'export', - 'filled' => true, + @include("moonshine::base.index.shared.actions", [ + 'actions' => $resource->getActions() ])
@endif @@ -59,7 +56,7 @@
- @if(in_array("create", $resource->getActions())) + @if(in_array("create", $resource->getActiveActions())) @include('moonshine::shared.btn', [ 'title' => trans('moonshine::ui.create'), 'href' => $resource->route("create"), diff --git a/src/views/base/index/foot.blade.php b/src/views/base/index/foot.blade.php index 16fed2a41..edd418172 100644 --- a/src/views/base/index/foot.blade.php +++ b/src/views/base/index/foot.blade.php @@ -8,7 +8,7 @@ - @if(in_array("delete", $resource->getActions())) + @if(in_array("delete", $resource->getActiveActions())) diff --git a/src/views/base/index/items.blade.php b/src/views/base/index/items.blade.php index 210c40fb5..a4d282a44 100644 --- a/src/views/base/index/items.blade.php +++ b/src/views/base/index/items.blade.php @@ -11,7 +11,7 @@ @endforeach - @include("moonshine::base.index.shared.actions", ["item" => $item, "resource" => $resource]) + @include("moonshine::base.index.shared.item_actions", ["item" => $item, "resource" => $resource]) @endforeach diff --git a/src/views/base/index/shared/actions.blade.php b/src/views/base/index/shared/actions.blade.php index 646d2dd3b..b54c8fd57 100644 --- a/src/views/base/index/shared/actions.blade.php +++ b/src/views/base/index/shared/actions.blade.php @@ -1,44 +1,10 @@ -@if(in_array("edit", $resource->getActions())) - id) }}" class="text-purple inline-block"> - @include("moonshine::shared.icons.edit", ["size" => 6, "class" => "mr-2", "color" => "purple"]) - -@endif - -@if(in_array("delete", $resource->getActions())) - - {{ trans('moonshine::ui.deleteareyousure') }} - - - - - - - - {{ trans('moonshine::ui.deleting') }} - - -
-
id) }}"> - {{ csrf_field() }} - - @method("delete") - - -
-
-
- -
-
- - - - -
-@endif \ No newline at end of file +
+ @foreach($actions as $action) + @include('moonshine::shared.btn', [ + 'title' => $action->label(), + 'href' => $action->url(), + 'filled' => true, + 'icon' => 'app' + ]) + @endforeach +
\ No newline at end of file diff --git a/src/views/base/index/shared/item_actions.blade.php b/src/views/base/index/shared/item_actions.blade.php new file mode 100644 index 000000000..da27bf111 --- /dev/null +++ b/src/views/base/index/shared/item_actions.blade.php @@ -0,0 +1,44 @@ +@if(in_array("edit", $resource->getActiveActions())) + id) }}" class="text-purple inline-block"> + @include("moonshine::shared.icons.edit", ["size" => 6, "class" => "mr-2", "color" => "purple"]) + +@endif + +@if(in_array("delete", $resource->getActiveActions())) + + {{ trans('moonshine::ui.deleteareyousure') }} + + + + + + + + {{ trans('moonshine::ui.deleting') }} + + +
+
id) }}"> + {{ csrf_field() }} + + @method("delete") + + +
+
+
+ +
+
+ + + + +
+@endif \ No newline at end of file diff --git a/tests/Actions/BaseActionTest.php b/tests/Actions/BaseActionTest.php new file mode 100644 index 000000000..1670a67dc --- /dev/null +++ b/tests/Actions/BaseActionTest.php @@ -0,0 +1,16 @@ +assertEquals('Export', $action->label()); + } +} \ No newline at end of file diff --git a/tests/Resources/BaseResourceTest.php b/tests/Resources/BaseResourceTest.php index e7916f2f2..4238d39b3 100644 --- a/tests/Resources/BaseResourceTest.php +++ b/tests/Resources/BaseResourceTest.php @@ -1,6 +1,6 @@ assertEquals('moonshineusers', $resource->routeAlias()); + + $this->assertNotEmpty($resource->getFields()); + $this->assertNotEmpty($resource->getFilters()); + $this->assertNotEmpty($resource->getActions()); } } \ No newline at end of file