diff --git a/composer.json b/composer.json index fe3b69ed1d..2191263e9b 100644 --- a/composer.json +++ b/composer.json @@ -62,10 +62,7 @@ "laravel": { "providers": [ "Backpack\\CRUD\\CrudServiceProvider" - ], - "aliases": { - "CRUD": "Backpack\\CRUD\\CrudServiceProvider" - } + ] } } } diff --git a/src/CrudRouter.php b/src/CrudRouter.php deleted file mode 100644 index 1573422636..0000000000 --- a/src/CrudRouter.php +++ /dev/null @@ -1,144 +0,0 @@ -name = $name; - $this->controller = $controller; - $this->options = $options; - - // CRUD routes for core features - Route::post($this->name.'/search', [ - 'as' => 'crud.'.$this->name.'.search', - 'uses' => $this->controller.'@search', - ]); - - Route::delete($this->name.'/bulk-delete', [ - 'as' => 'crud.'.$this->name.'.bulkDelete', - 'uses' => $this->controller.'@bulkDelete', - ]); - - Route::get($this->name.'/reorder', [ - 'as' => 'crud.'.$this->name.'.reorder', - 'uses' => $this->controller.'@reorder', - ]); - - Route::post($this->name.'/reorder', [ - 'as' => 'crud.'.$this->name.'.save.reorder', - 'uses' => $this->controller.'@saveReorder', - ]); - - Route::get($this->name.'/{id}/details', [ - 'as' => 'crud.'.$this->name.'.showDetailsRow', - 'uses' => $this->controller.'@showDetailsRow', - ]); - - Route::get($this->name.'/{id}/translate/{lang}', [ - 'as' => 'crud.'.$this->name.'.translateItem', - 'uses' => $this->controller.'@translateItem', - ]); - - Route::get($this->name.'/{id}/revisions', [ - 'as' => 'crud.'.$this->name.'.listRevisions', - 'uses' => $this->controller.'@listRevisions', - ]); - - Route::post($this->name.'/{id}/revisions/{revisionId}/restore', [ - 'as' => 'crud.'.$this->name.'.restoreRevision', - 'uses' => $this->controller.'@restoreRevision', - ]); - - Route::post($this->name.'/{id}/clone', [ - 'as' => 'crud.'.$this->name.'.clone', - 'uses' => $this->controller.'@clone', - ]); - - Route::post($this->name.'/bulk-clone', [ - 'as' => 'crud.'.$this->name.'.bulkClone', - 'uses' => $this->controller.'@bulkClone', - ]); - } - - /** - * The CRUD resource needs to be registered after all the other routes. - */ - public function __destruct() - { - $options_with_default_route_names = array_merge([ - 'names' => [ - 'index' => 'crud.'.$this->name.'.index', - 'create' => 'crud.'.$this->name.'.create', - 'store' => 'crud.'.$this->name.'.store', - 'edit' => 'crud.'.$this->name.'.edit', - 'update' => 'crud.'.$this->name.'.update', - 'show' => 'crud.'.$this->name.'.show', - 'destroy' => 'crud.'.$this->name.'.destroy', - ], - ], $this->options); - - Route::resource($this->name, $this->controller, $options_with_default_route_names); - } - - /** - * Call other methods in this class, that register extra routes. - * - * @param [type] $injectables [description] - * @return [type] [description] - */ - public function with($injectables) - { - if (is_string($injectables)) { - $this->extraRoutes[] = 'with'.ucwords($injectables); - } elseif (is_array($injectables)) { - foreach ($injectables as $injectable) { - $this->extraRoutes[] = 'with'.ucwords($injectable); - } - } else { - $reflection = new \ReflectionFunction($injectables); - - if ($reflection->isClosure()) { - $this->extraRoutes[] = $injectables; - } - } - - return $this->registerExtraRoutes(); - } - - /** - * TODO - * Give developers the ability to unregister a route. - */ - // public function without($injectables) {} - - /** - * Register the routes that were passed using the "with" syntax. - */ - private function registerExtraRoutes() - { - foreach ($this->extraRoutes as $route) { - if (is_string($route)) { - $this->{$route}(); - } else { - $route(); - } - } - } - - public function __call($method, $parameters = null) - { - if (method_exists($this, $method)) { - $this->{$method}($parameters); - } - } -} diff --git a/src/CrudServiceProvider.php b/src/CrudServiceProvider.php index 56db44fb0e..c0535e5fea 100644 --- a/src/CrudServiceProvider.php +++ b/src/CrudServiceProvider.php @@ -2,6 +2,7 @@ namespace Backpack\CRUD; +use Illuminate\Support\Facades\Route; use Illuminate\Support\ServiceProvider; class CrudServiceProvider extends ServiceProvider @@ -88,6 +89,12 @@ public function register() return new CRUD($app); }); + // load a macro for Route, + // for developers to be able to load all routes for a CRUD resource in one line + if (! Route::hasMacro('crud')) { + $this->addRouteMacro(); + } + // register the helper functions $this->loadHelpers(); @@ -100,9 +107,40 @@ public function register() } } - public static function resource($name, $controller, array $options = []) + /** + * The route macro allows developers to generate the routes for a CrudController, + * for all operations, using a simple syntax: Route::crud(). + * + * It will go to the given CrudController and get the routes() method on it. + */ + private function addRouteMacro() { - return new CrudRouter($name, $controller, $options); + Route::macro('crud', function ($name, $controller) { + // put together the route name prefix, + // as passed to the Route::group() statements + $routeName = ''; + if ($this->hasGroupStack()) { + foreach ($this->getGroupStack() as $key => $groupStack) { + if (isset($groupStack['name'])) { + if (is_array($groupStack['name'])) { + $routeName = implode('', $groupStack['name']); + } else { + $routeName = $groupStack['name']; + } + } + } + } + // add the name of the current entity to the route name prefix + // the result will be the current route name (not ending in dot) + $routeName .= $name; + + // get an instance of the controller + $groupStack = $this->hasGroupStack() ? $this->getGroupStack()[0]['namespace'].'\\' : 'App\\'; + $namespacedController = $groupStack.$controller; + $controllerInstance = new $namespacedController; + + return $controllerInstance->routes($name, $routeName, $controller); + }); } /** diff --git a/src/app/Http/Controllers/CrudController.php b/src/app/Http/Controllers/CrudController.php index 3200deb491..e1fd24af0b 100644 --- a/src/app/Http/Controllers/CrudController.php +++ b/src/app/Http/Controllers/CrudController.php @@ -7,20 +7,10 @@ use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Routing\Controller as BaseController; use Illuminate\Foundation\Validation\ValidatesRequests; -use Backpack\CRUD\app\Http\Controllers\Operations\SaveActions; -use Backpack\CRUD\app\Http\Controllers\Operations\ListOperation; -use Backpack\CRUD\app\Http\Controllers\Operations\ShowOperation; -use Backpack\CRUD\app\Http\Controllers\Operations\CloneOperation; -use Backpack\CRUD\app\Http\Controllers\Operations\CreateOperation; -use Backpack\CRUD\app\Http\Controllers\Operations\DeleteOperation; -use Backpack\CRUD\app\Http\Controllers\Operations\UpdateOperation; -use Backpack\CRUD\app\Http\Controllers\Operations\ReorderOperation; -use Backpack\CRUD\app\Http\Controllers\Operations\RevisionsOperation; class CrudController extends BaseController { use DispatchesJobs, ValidatesRequests; - use CreateOperation, CloneOperation, DeleteOperation, ListOperation, ReorderOperation, RevisionsOperation, SaveActions, ShowOperation, UpdateOperation; public $data = []; public $request; @@ -53,4 +43,23 @@ public function __construct() public function setup() { } + + /** + * Load routes for all operations. + * Allow developers to load extra routes by creating a method that starts with setupRoutesFor. + * + * @param string $segment Name of the current entity (singular). + * @param string $routeName Route name prefix (ends with .). + * @param string $controller Name of the current controller. + */ + public function routes($segment, $routeName, $controller) + { + preg_match_all('/(?<=^|;)setup([^;]+?)Routes(;|$)/', implode(';', get_class_methods($this)), $matches); + + if (count($matches[1])) { + foreach ($matches[1] as $methodName) { + $this->{'setup'.$methodName.'Routes'}($segment, $routeName, $controller); + } + } + } } diff --git a/src/app/Http/Controllers/Operations/BulkCloneOperation.php b/src/app/Http/Controllers/Operations/BulkCloneOperation.php new file mode 100644 index 0000000000..23110337c1 --- /dev/null +++ b/src/app/Http/Controllers/Operations/BulkCloneOperation.php @@ -0,0 +1,47 @@ + $routeName.'.bulkClone', + 'uses' => $controller.'@bulkClone', + ]); + } + + /** + * Create duplicates of multiple entries in the datatabase. + * + * @param int $id + * + * @return Response + */ + public function bulkClone() + { + $this->crud->hasAccessOrFail('clone'); + $this->crud->setOperation('clone'); + + $entries = $this->request->input('entries'); + $clonedEntries = []; + + foreach ($entries as $key => $id) { + if ($entry = $this->crud->model->find($id)) { + $clonedEntries[] = $entry->replicate()->push(); + } + } + + return $clonedEntries; + } +} diff --git a/src/app/Http/Controllers/Operations/BulkDeleteOperation.php b/src/app/Http/Controllers/Operations/BulkDeleteOperation.php new file mode 100644 index 0000000000..f3b5afe244 --- /dev/null +++ b/src/app/Http/Controllers/Operations/BulkDeleteOperation.php @@ -0,0 +1,45 @@ + $routeName.'.bulkDelete', + 'uses' => $controller.'@bulkDelete', + ]); + } + + /** + * Delete multiple entries in one go. + * + * @return string + */ + public function bulkDelete() + { + $this->crud->hasAccessOrFail('delete'); + $this->crud->setOperation('delete'); + + $entries = $this->request->input('entries'); + $deletedEntries = []; + + foreach ($entries as $key => $id) { + if ($entry = $this->crud->model->find($id)) { + $deletedEntries[] = $entry->delete(); + } + } + + return $deletedEntries; + } +} diff --git a/src/app/Http/Controllers/Operations/CloneOperation.php b/src/app/Http/Controllers/Operations/CloneOperation.php index 6d40585ea9..632b05865f 100644 --- a/src/app/Http/Controllers/Operations/CloneOperation.php +++ b/src/app/Http/Controllers/Operations/CloneOperation.php @@ -2,46 +2,39 @@ namespace Backpack\CRUD\app\Http\Controllers\Operations; +use Illuminate\Support\Facades\Route; + trait CloneOperation { /** - * Create a duplicate of the current entry in the datatabase. + * Define which routes are needed for this operation. * - * @param int $id - * - * @return Response + * @param string $segment Name of the current entity (singular). Used as first URL segment. + * @param string $routeName Prefix of the route name. + * @param string $controller Name of the current CrudController. */ - public function clone($id) + protected function setupCloneRoutes($segment, $routeName, $controller) { - $this->crud->hasAccessOrFail('clone'); - $this->crud->setOperation('clone'); - - $clonedEntry = $this->crud->model->findOrFail($id)->replicate(); - - return (string) $clonedEntry->push(); + Route::post($segment.'/{id}/clone', [ + 'as' => $routeName.'.clone', + 'uses' => $controller.'@clone', + ]); } /** - * Create duplicates of multiple entries in the datatabase. + * Create a duplicate of the current entry in the datatabase. * * @param int $id * * @return Response */ - public function bulkClone() + public function clone($id) { $this->crud->hasAccessOrFail('clone'); $this->crud->setOperation('clone'); - $entries = $this->request->input('entries'); - $clonedEntries = []; - - foreach ($entries as $key => $id) { - if ($entry = $this->crud->model->find($id)) { - $clonedEntries[] = $entry->replicate()->push(); - } - } + $clonedEntry = $this->crud->model->findOrFail($id)->replicate(); - return $clonedEntries; + return (string) $clonedEntry->push(); } } diff --git a/src/app/Http/Controllers/Operations/CreateOperation.php b/src/app/Http/Controllers/Operations/CreateOperation.php index 06a7556e50..20e75c5d2e 100644 --- a/src/app/Http/Controllers/Operations/CreateOperation.php +++ b/src/app/Http/Controllers/Operations/CreateOperation.php @@ -2,10 +2,36 @@ namespace Backpack\CRUD\app\Http\Controllers\Operations; +use Illuminate\Support\Facades\Route; use Illuminate\Http\Request as StoreRequest; trait CreateOperation { + /** + * Define which routes are needed for this operation. + * + * @param string $segment Name of the current entity (singular). Used as first URL segment. + * @param string $routeName Prefix of the route name. + * @param string $controller Name of the current CrudController. + */ + protected function setupCreateRoutes($segment, $routeName, $controller) + { + Route::get($segment.'/create', [ + 'as' => $routeName.'.create', + 'uses' => $controller.'@create', + ]); + + Route::put($segment.'/create', [ + 'as' => $routeName.'store', + 'uses' => $controller.'@store', + ]); + + Route::post($segment, [ + 'as' => $routeName.'store', + 'uses' => $controller.'@store', + ]); + } + /** * Show the form for creating inserting a new row. * @@ -33,7 +59,7 @@ public function create() * * @return \Illuminate\Http\RedirectResponse */ - public function storeCrud(StoreRequest $request = null) + public function storeEntry(StoreRequest $request = null) { $this->crud->hasAccessOrFail('create'); $this->crud->setOperation('create'); diff --git a/src/app/Http/Controllers/Operations/DeleteOperation.php b/src/app/Http/Controllers/Operations/DeleteOperation.php index 8c1b14d7d8..308eef9983 100644 --- a/src/app/Http/Controllers/Operations/DeleteOperation.php +++ b/src/app/Http/Controllers/Operations/DeleteOperation.php @@ -2,8 +2,25 @@ namespace Backpack\CRUD\app\Http\Controllers\Operations; +use Illuminate\Support\Facades\Route; + trait DeleteOperation { + /** + * Define which routes are needed for this operation. + * + * @param string $segment Name of the current entity (singular). Used as first URL segment. + * @param string $routeName Prefix of the route name. + * @param string $controller Name of the current CrudController. + */ + protected function setupDeleteRoutes($segment, $routeName, $controller) + { + Route::delete($segment.'/{id}', [ + 'as' => $routeName.'.destroy', + 'uses' => $controller.'@destroy', + ]); + } + /** * Remove the specified resource from storage. * @@ -21,26 +38,4 @@ public function destroy($id) return $this->crud->delete($id); } - - /** - * Delete multiple entries in one go. - * - * @return string - */ - public function bulkDelete() - { - $this->crud->hasAccessOrFail('delete'); - $this->crud->setOperation('delete'); - - $entries = $this->request->input('entries'); - $deletedEntries = []; - - foreach ($entries as $key => $id) { - if ($entry = $this->crud->model->find($id)) { - $deletedEntries[] = $entry->delete(); - } - } - - return $deletedEntries; - } } diff --git a/src/app/Http/Controllers/Operations/ListOperation.php b/src/app/Http/Controllers/Operations/ListOperation.php index 5c6d7db46c..2d1da70575 100644 --- a/src/app/Http/Controllers/Operations/ListOperation.php +++ b/src/app/Http/Controllers/Operations/ListOperation.php @@ -2,8 +2,35 @@ namespace Backpack\CRUD\app\Http\Controllers\Operations; +use Illuminate\Support\Facades\Route; + trait ListOperation { + /** + * Define which routes are needed for this operation. + * + * @param string $segment Name of the current entity (singular). Used as first URL segment. + * @param string $routeName Prefix of the route name. + * @param string $controller Name of the current CrudController. + */ + protected function setupListRoutes($segment, $routeName, $controller) + { + Route::get($segment.'/', [ + 'as' => $routeName.'.index', + 'uses' => $controller.'@index', + ]); + + Route::post($segment.'/search', [ + 'as' => $routeName.'.search', + 'uses' => $controller.'@search', + ]); + + Route::get($segment.'/{id}/details', [ + 'as' => $routeName.'.showDetailsRow', + 'uses' => $controller.'@showDetailsRow', + ]); + } + /** * Display all rows in the database for this entity. * diff --git a/src/app/Http/Controllers/Operations/ReorderOperation.php b/src/app/Http/Controllers/Operations/ReorderOperation.php index c774135c64..22ac8eb513 100644 --- a/src/app/Http/Controllers/Operations/ReorderOperation.php +++ b/src/app/Http/Controllers/Operations/ReorderOperation.php @@ -2,8 +2,30 @@ namespace Backpack\CRUD\app\Http\Controllers\Operations; +use Illuminate\Support\Facades\Route; + trait ReorderOperation { + /** + * Define which routes are needed for this operation. + * + * @param string $name Name of the current entity (singular). Used as first URL segment. + * @param string $routeName Prefix of the route name. + * @param string $controller Name of the current CrudController. + */ + protected function setupReorderRoutes($segment, $routeName, $controller) + { + Route::get($segment.'/reorder', [ + 'as' => $routeName.'.reorder', + 'uses' => $controller.'@reorder', + ]); + + Route::post($segment.'/reorder', [ + 'as' => $routeName.'.save.reorder', + 'uses' => $controller.'@saveReorder', + ]); + } + /** * Reorder the items in the database using the Nested Set pattern. * diff --git a/src/app/Http/Controllers/Operations/RevisionsOperation.php b/src/app/Http/Controllers/Operations/RevisionsOperation.php index 7571e88c0d..5db39e5c86 100644 --- a/src/app/Http/Controllers/Operations/RevisionsOperation.php +++ b/src/app/Http/Controllers/Operations/RevisionsOperation.php @@ -2,8 +2,30 @@ namespace Backpack\CRUD\app\Http\Controllers\Operations; +use Illuminate\Support\Facades\Route; + trait RevisionsOperation { + /** + * Define which routes are needed for this operation. + * + * @param string $segment Name of the current entity (singular). Used as first URL segment. + * @param string $routeName Prefix of the route name. + * @param string $controller Name of the current CrudController. + */ + protected function setupRevisionsRoutes($segment, $routeName, $controller) + { + Route::get($segment.'/{id}/revisions', [ + 'as' => $routeName.'.listRevisions', + 'uses' => $controller.'@listRevisions', + ]); + + Route::post($segment.'/{id}/revisions/{revisionId}/restore', [ + 'as' => $routeName.'.restoreRevision', + 'uses' => $controller.'@restoreRevision', + ]); + } + /** * Display the revisions for specified resource. * diff --git a/src/app/Http/Controllers/Operations/ShowOperation.php b/src/app/Http/Controllers/Operations/ShowOperation.php index 00afafcad2..af0b3f0e2a 100644 --- a/src/app/Http/Controllers/Operations/ShowOperation.php +++ b/src/app/Http/Controllers/Operations/ShowOperation.php @@ -2,8 +2,25 @@ namespace Backpack\CRUD\app\Http\Controllers\Operations; +use Illuminate\Support\Facades\Route; + trait ShowOperation { + /** + * Define which routes are needed for this operation. + * + * @param string $segment Name of the current entity (singular). Used as first URL segment. + * @param string $routeName Prefix of the route name. + * @param string $controller Name of the current CrudController. + */ + protected function setupShowRoutes($segment, $routeName, $controller) + { + Route::get($segment.'/{id}', [ + 'as' => $routeName.'.show', + 'uses' => $controller.'@show', + ]); + } + /** * Display the specified resource. * diff --git a/src/app/Http/Controllers/Operations/UpdateOperation.php b/src/app/Http/Controllers/Operations/UpdateOperation.php index fc42487b5b..7c024d2096 100644 --- a/src/app/Http/Controllers/Operations/UpdateOperation.php +++ b/src/app/Http/Controllers/Operations/UpdateOperation.php @@ -2,10 +2,36 @@ namespace Backpack\CRUD\app\Http\Controllers\Operations; +use Illuminate\Support\Facades\Route; use Illuminate\Http\Request as UpdateRequest; trait UpdateOperation { + /** + * Define which routes are needed for this operation. + * + * @param string $name Name of the current entity (singular). Used as first URL segment. + * @param string $routeName Prefix of the route name. + * @param string $controller Name of the current CrudController. + */ + protected function setupUpdateRoutes($segment, $routeName, $controller) + { + Route::get($segment.'/{id}/edit', [ + 'as' => $routeName.'.edit', + 'uses' => $controller.'@edit', + ]); + + Route::put($segment.'/{id}', [ + 'as' => $routeName.'.update', + 'uses' => $controller.'@update', + ]); + + Route::get($segment.'/{id}/translate/{lang}', [ + 'as' => $routeName.'.translateItem', + 'uses' => $controller.'@translateItem', + ]); + } + /** * Show the form for editing the specified resource. * @@ -41,7 +67,7 @@ public function edit($id) * * @return \Illuminate\Http\RedirectResponse */ - public function updateCrud(UpdateRequest $request = null) + public function updateEntry(UpdateRequest $request = null) { $this->crud->hasAccessOrFail('update'); $this->crud->setOperation('update'); diff --git a/src/resources/views/buttons/bulk_delete.blade.php b/src/resources/views/buttons/bulk_delete.blade.php index 95a418b0c0..a04a861d3c 100644 --- a/src/resources/views/buttons/bulk_delete.blade.php +++ b/src/resources/views/buttons/bulk_delete.blade.php @@ -48,7 +48,7 @@ className: "bg-danger", // submit an AJAX delete call $.ajax({ url: delete_route, - type: 'DELETE', + type: 'POST', data: { entries: crud.checkedItems }, success: function(result) { // Show an alert with the result @@ -73,4 +73,4 @@ className: "bg-danger", } } -@endpush \ No newline at end of file +@endpush