Skip to content

Commit d2c1d59

Browse files
authored
Add support for Behaviour default views (#1312)
Co-authored-by: Marc Jauvin <marc.jauvin@gmail.com> Co-authored-by: Luke Towers <luke@luketowers.ca> Adds default views for the following backend controller behaviors: - FormController - ImportExportController - ListController - ReorderController `Backend\Classes\ControllerBehavior` will now automatically append their `views` folder to the controller's view paths allowing them to provide fallbacks for any views required by the behavior. The `create:controller` command will now no longer generate the views by default unless `--stubs` is also passed and the `--sidebar` flag is replaced with a `--layout=(standard|sidebar|fancy)` option to choose the form layout to use. Also: - Added `appendViewPath()` and `prependViewPath()` to the `System\Traits\ViewMaker`. `addViewPath` is renamed to `prependViewPath()` and is for paths that have higher priority than the existing paths while `appendViewPath()` is for paths that should have lower priority than the existing paths (i.e. fallbacks). - Backend controllers will now automatically set their navigation context in the form of `Author.Plugin` as the author, `$pluginName` as the main menu code, and `$controllerName` as the side menu code. This means that you can remove calls to `BackendMenu::setContext()` and constructor overrides in your controllers if they follow that convention. - Support for passing `new: true` as a parameter in the request body to `onSave()` calls that will return a redirect to the `create` action - `formMakePartial(string $partial, array $params = [])` to the `FormController` behavior that will render a partial through the controller's `makePartial` using the following priority list of contextual names: `form_$context_$partial`, `form_$partial`, `$partial`). - Support for `abort(403)` to return the access denied view in the backend - Improved handling of `abort(404)` in the backend - Improved styling of disabled fields in the fancy form layout - Improved styling of file generated / updated status message in scaffolding commands
1 parent 513d062 commit d2c1d59

File tree

70 files changed

+1017
-497
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+1017
-497
lines changed

modules/backend/assets/css/winter.css

Lines changed: 28 additions & 27 deletions
Large diffs are not rendered by default.

modules/backend/assets/less/layout/fancylayout.less

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,10 @@ body.fancy-layout .master-tabs.control-tabs,
493493
margin-bottom: 0;
494494
}
495495

496+
.form-control[disabled] {
497+
background-color: rgba(29, 29, 29, 0.11) !important;
498+
}
499+
496500
input[type=text] {
497501
background: transparent;
498502
border: none;

modules/backend/behaviors/FormController.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,9 @@ public function initForm($model, $context = null)
191191
protected function prepareVars($model)
192192
{
193193
$this->controller->vars['formModel'] = $model;
194+
$this->controller->vars['formConfig'] = $this->getConfig();
194195
$this->controller->vars['formContext'] = $this->formGetContext();
196+
$this->controller->vars['formController'] = $this;
195197
$this->controller->vars['formRecordName'] = Lang::get($this->getConfig('name', 'backend::lang.model.name'));
196198
}
197199

@@ -473,6 +475,10 @@ public function makeRedirect($context = null, $model = null)
473475
return Redirect::refresh();
474476
}
475477

478+
if (post('new', false)) {
479+
return Redirect::to($this->controller->actionUrl('create'));
480+
}
481+
476482
if (post('redirect', true)) {
477483
$redirectUrl = $this->controller->formGetRedirectUrl($context, $model);
478484
}
@@ -882,4 +888,20 @@ public static function extendFormFields($callback)
882888
call_user_func_array($callback, [$widget, $widget->model, $widget->getContext()]);
883889
});
884890
}
891+
892+
/**
893+
* Controller accessor for making partials within this behavior.
894+
*/
895+
public function formMakePartial(string $partial, array $params = []): string
896+
{
897+
$contents = $this->controller->makePartial('form_' . $this->context . '_' . $partial, $params + $this->vars, false);
898+
if (!$contents) {
899+
$contents = $this->controller->makePartial('form_' . $partial, $params + $this->vars, false);
900+
}
901+
if (!$contents) {
902+
$contents = $this->makePartial($partial, $params);
903+
}
904+
905+
return $contents;
906+
}
885907
}

modules/backend/behaviors/ImportExportController.php

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
1-
<?php namespace Backend\Behaviors;
2-
3-
use Str;
4-
use Lang;
5-
use View;
6-
use Response;
7-
use Backend;
8-
use BackendAuth;
9-
use Backend\Classes\ControllerBehavior;
1+
<?php
2+
3+
namespace Backend\Behaviors;
4+
105
use Backend\Behaviors\ImportExportController\TranscodeFilter;
6+
use Backend\Classes\ControllerBehavior;
7+
use Backend\Facades\Backend;
8+
use Backend\Facades\BackendAuth;
9+
use Exception;
1110
use Illuminate\Database\Eloquent\MassAssignmentException;
12-
use League\Csv\Reader as CsvReader;
13-
use League\Csv\Writer as CsvWriter;
11+
use Illuminate\Support\Facades\Lang;
12+
use Illuminate\Support\Facades\Response;
1413
use League\Csv\EscapeFormula as CsvEscapeFormula;
14+
use League\Csv\Reader as CsvReader;
1515
use League\Csv\Statement as CsvStatement;
16-
use ApplicationException;
16+
use League\Csv\Writer as CsvWriter;
1717
use SplTempFileObject;
18-
use Exception;
18+
use Winter\Storm\Exception\ApplicationException;
19+
use Winter\Storm\Support\Str;
1920

2021
/**
2122
* Adds features for importing and exporting data.
@@ -146,8 +147,8 @@ public function __construct($controller)
146147

147148
public function import()
148149
{
149-
if ($response = $this->checkPermissionsForType('import')) {
150-
return $response;
150+
if (!$this->userHasAccess('import')) {
151+
abort(403);
151152
}
152153

153154
$this->addJs('js/winter.import.js', 'core');
@@ -161,8 +162,8 @@ public function import()
161162

162163
public function export()
163164
{
164-
if ($response = $this->checkPermissionsForType('export')) {
165-
return $response;
165+
if (!$this->userHasAccess('export')) {
166+
abort(403);
166167
}
167168

168169
if ($response = $this->checkUseListExportMode()) {
@@ -697,18 +698,18 @@ public function importExportMakePartial($partial, $params = [])
697698
}
698699

699700
/**
700-
* Checks to see if the import/export is controlled by permissions
701-
* and if the logged in user has permissions.
702-
* @return \View
701+
* Check if the current user has access to the provided import/export action
703702
*/
704-
protected function checkPermissionsForType($type)
703+
public function userHasAccess(string $type): bool
705704
{
706705
if (
707706
($permissions = $this->getConfig($type.'[permissions]')) &&
708707
(!BackendAuth::getUser()->hasAnyAccess((array) $permissions))
709708
) {
710-
return Response::make(View::make('backend::access_denied'), 403);
709+
return false;
711710
}
711+
712+
return true;
712713
}
713714

714715
protected function makeOptionsFormWidgetForType($type)
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
$modelName = $formConfig->name ?? '';
3+
?>
4+
<?php if ($formContext === 'create'): ?>
5+
<div class="loading-indicator-container">
6+
<button
7+
type="submit"
8+
data-request="onSave"
9+
data-request-data="new:1"
10+
data-browser-validate
11+
data-hotkey="ctrl+shift+s, cmd+shift+s"
12+
data-load-indicator="<?= e(trans('backend::lang.form.creating_name', ['name' => trans($modelName)])); ?>"
13+
data-request-before-update="$el.trigger('unchange.oc.changeMonitor')"
14+
class="btn btn-primary wn-icon-plus">
15+
<?= e(trans('backend::lang.form.create_and_new')); ?>
16+
</button>
17+
<button
18+
type="button"
19+
data-request="onSave"
20+
data-browser-validate
21+
data-hotkey="ctrl+s, cmd+s"
22+
data-load-indicator="<?= e(trans('backend::lang.form.creating_name', ['name' => trans($modelName)])); ?>"
23+
data-request-before-update="$el.trigger('unchange.oc.changeMonitor')"
24+
class="btn btn-primary wn-icon-save">
25+
<?= e(trans('backend::lang.form.create')); ?>
26+
</button>
27+
<button
28+
type="button"
29+
data-request="onSave"
30+
data-browser-validate
31+
data-request-data="close:1"
32+
data-hotkey="ctrl+enter, cmd+enter"
33+
data-load-indicator="<?= e(trans('backend::lang.form.creating_name', ['name' => trans($modelName)])); ?>"
34+
data-request-before-update="$el.trigger('unchange.oc.changeMonitor')"
35+
class="btn btn-default wn-icon-check">
36+
<?= e(trans('backend::lang.form.create_and_close')); ?>
37+
</button>
38+
<span class="btn-text">
39+
<?= e(trans('backend::lang.form.or')) ?> <a href="<?= Backend::url($formConfig->defaultRedirect) ?>"><?= e(trans('backend::lang.form.cancel')); ?></a>
40+
</span>
41+
</div>
42+
<?php elseif ($formContext === 'update'): ?>
43+
<div class="loading-indicator-container">
44+
<button
45+
type="button"
46+
data-request="onSave"
47+
data-browser-validate
48+
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
49+
data-request-before-update="$el.trigger('unchange.oc.changeMonitor')"
50+
data-request-data="redirect:0"
51+
data-hotkey="ctrl+s, cmd+s"
52+
class="btn btn-primary wn-icon-save"
53+
>
54+
<?= e(trans('backend::lang.form.save')); ?>
55+
</button>
56+
<button
57+
type="button"
58+
data-request="onSave"
59+
data-browser-validate
60+
data-request-data="close:1"
61+
data-hotkey="ctrl+enter, cmd+enter"
62+
data-load-indicator="<?= e(trans('backend::lang.form.saving')); ?>"
63+
data-request-before-update="$el.trigger('unchange.oc.changeMonitor')"
64+
class="btn btn-default wn-icon-check"
65+
>
66+
<?= e(trans('backend::lang.form.save_and_close')); ?>
67+
</button>
68+
<button
69+
type="button"
70+
data-request="onDelete"
71+
data-load-indicator="<?= e(trans('backend::lang.form.deleting_name', ['name' => trans($modelName)])); ?>"
72+
data-request-before-update="$el.trigger('unchange.oc.changeMonitor')"
73+
data-request-confirm="<?= e(trans('backend::lang.form.confirm_delete')); ?>"
74+
class="wn-icon-trash-o btn-icon danger pull-right"
75+
>
76+
</button>
77+
<span class="btn-text">
78+
<?= e(trans('backend::lang.form.or')) ?> <a href="<?= Backend::url($formConfig->defaultRedirect) ?>"><?= e(trans('backend::lang.form.cancel')); ?></a>
79+
</span>
80+
</div>
81+
<?php endif ?>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
// Decide which layout we should be rendering
4+
$layout = $this->formLayout ?? $formConfig->formLayout ?? null;
5+
if (!in_array($layout, ['standard', 'sidebar', 'fancy'])) {
6+
$layout = 'standard';
7+
}
8+
9+
// If required, set the appropriate body classes
10+
$this->bodyClass = match ($layout) {
11+
'fancy' => 'fancy-layout compact-container breadcrumb-flush breadcrumb-fancy',
12+
'sidebar' => 'compact-container',
13+
default => '',
14+
};
15+
16+
// Define layout mode view path for inclusion
17+
$this->appendViewPath(sprintf('%s/create/%s', __DIR__, $layout));
18+
19+
// Render the form layout
20+
echo $this->makePartial(sprintf('create/%s.php', $layout));
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php Block::put('breadcrumb') ?>
2+
<?= $this->makeLayoutPartial('breadcrumb') ?>
3+
<?php Block::endPut() ?>
4+
5+
<?php if (!$this->fatalError): ?>
6+
<div class="layout fancy-layout">
7+
<?= Form::open([
8+
'class' => 'layout',
9+
'data-change-monitor' => 'true',
10+
'data-window-close-confirm' => 'true',
11+
]) ?>
12+
<div class="layout-row">
13+
<?= $this->formRender() ?>
14+
</div>
15+
<?= Form::close() ?>
16+
</div>
17+
<?php else: ?>
18+
<p class="flash-message static error"><?= e($this->fatalError) ?></p>
19+
<p><a href="<?= Backend::url($formConfig->defaultRedirect) ?>" class="btn btn-default"><?= e(trans('backend::lang.form.return_to_list')); ?></a></p>
20+
<?php endif ?>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php Block::put('breadcrumb') ?>
2+
<?= $this->makeLayoutPartial('breadcrumb') ?>
3+
<?php Block::endPut() ?>
4+
5+
<?php if (!$this->fatalError): ?>
6+
<?php Block::put('form-contents') ?>
7+
<div class="layout">
8+
<div class="layout-row">
9+
<?= $this->formRenderOutsideFields() ?>
10+
<?= $this->formRenderPrimaryTabs() ?>
11+
</div>
12+
13+
<div class="form-buttons p-t">
14+
<?= $this->formMakePartial('toolbar') ?>
15+
</div>
16+
</div>
17+
<?php Block::endPut() ?>
18+
19+
<?php Block::put('form-sidebar') ?>
20+
<div class="hide-tabs"><?= $this->formRenderSecondaryTabs() ?></div>
21+
<?php Block::endPut() ?>
22+
23+
<?php Block::put('body') ?>
24+
<?= Form::open([
25+
'class' => 'layout stretch',
26+
'data-change-monitor' => 'true',
27+
'data-window-close-confirm' => 'true',
28+
]) ?>
29+
<?= $this->makeLayout('form-with-sidebar') ?>
30+
<?= Form::close() ?>
31+
<?php Block::endPut() ?>
32+
<?php else: ?>
33+
<div class="control-breadcrumb">
34+
<?= Block::placeholder('breadcrumb') ?>
35+
</div>
36+
<div class="padded-container">
37+
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
38+
<p><a href="<?= Backend::url($formConfig->defaultRedirect) ?>" class="btn btn-default"><?= e(trans('backend::lang.form.return_to_list')); ?></a></p>
39+
</div>
40+
<?php endif ?>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php Block::put('breadcrumb') ?>
2+
<?= $this->makeLayoutPartial('breadcrumb') ?>
3+
<?php Block::endPut() ?>
4+
5+
<?php if (!$this->fatalError): ?>
6+
<?= Form::open([
7+
'class' => 'layout',
8+
'data-change-monitor' => 'true',
9+
'data-window-close-confirm' => 'true',
10+
]) ?>
11+
<div class="layout-row">
12+
<?= $this->formRender() ?>
13+
</div>
14+
15+
<div class="form-buttons p-t">
16+
<?= $this->formMakePartial('toolbar') ?>
17+
</div>
18+
<?= Form::close() ?>
19+
<?php else: ?>
20+
<p class="flash-message static error"><?= e($this->fatalError) ?></p>
21+
<p><a href="<?= Backend::url($formConfig->defaultRedirect) ?>" class="btn btn-default"><?= e(trans('backend::lang.form.return_to_list')); ?></a></p>
22+
<?php endif ?>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<div class="form-buttons loading-indicator-container">
2+
<button
3+
type="submit"
4+
data-request="onSave"
5+
data-request-data="new:1"
6+
data-browser-validate
7+
data-hotkey="ctrl+shift+s, cmd+shift+s"
8+
data-load-indicator="<?= e(trans('backend::lang.form.creating')); ?>"
9+
data-request-before-update="$el.trigger('unchange.oc.changeMonitor')"
10+
class="btn btn-primary wn-icon-plus">
11+
<?= e(trans('backend::lang.form.create_and_new')); ?>
12+
</button>
13+
<button
14+
type="button"
15+
data-request="onSave"
16+
data-browser-validate
17+
data-hotkey="ctrl+s, cmd+s"
18+
data-load-indicator="<?= e(trans('backend::lang.form.creating')); ?>"
19+
data-request-before-update="$el.trigger('unchange.oc.changeMonitor')"
20+
class="btn btn-primary wn-icon-save">
21+
<?= e(trans('backend::lang.form.create')); ?>
22+
</button>
23+
<button
24+
type="button"
25+
data-request="onSave"
26+
data-browser-validate
27+
data-request-data="close:1"
28+
data-hotkey="ctrl+enter, cmd+enter"
29+
data-load-indicator="<?= e(trans('backend::lang.form.creating')); ?>"
30+
data-request-before-update="$el.trigger('unchange.oc.changeMonitor')"
31+
class="btn btn-default wn-icon-check">
32+
<?= e(trans('backend::lang.form.create_and_close')); ?>
33+
</button>
34+
35+
<a class="btn btn-default wn-icon-ban" href="<?= $this->actionUrl('') ?>"><?= e(trans('backend::lang.form.cancel')); ?></a>
36+
</div>

0 commit comments

Comments
 (0)