diff --git a/app/Http/Controllers/Bo/NewsController.php b/app/Http/Controllers/Bo/NewsController.php
new file mode 100644
index 0000000..bc58f6f
--- /dev/null
+++ b/app/Http/Controllers/Bo/NewsController.php
@@ -0,0 +1,171 @@
+authorizeResource(News::class);
+ }
+
+ /**
+ * Display a listing of the resource.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @return \Illuminate\Contracts\View\View
+ */
+ public function index(Request $request): \Illuminate\Contracts\View\View
+ {
+ /** @var \Illuminate\Database\Eloquent\Builder $newsModels */
+ $newsModels = News::query();
+
+ /** @var string $search Search field */
+ $search = $request->search;
+ if ($search) {
+ $this->searchQuery(
+ $newsModels,
+ $search,
+ null,
+ 'title',
+ 'subtitle',
+ 'content',
+ 'category',
+ );
+ }
+ $searchFields = \implode(', ', [
+ Str::ucfirst(trans('validation.attributes.title')),
+ trans('validation.custom.subtitle'),
+ trans('validation.attributes.content'),
+ trans('validation.custom.category'),
+ ]);
+
+ /** Sort columns with a query */
+ $this->sortQuery($newsModels);
+
+ /** Custom pagination */
+ $newsModels = $this->paginate($newsModels);
+
+ return view('bo.pages.news.index', compact('newsModels', 'search', 'searchFields'));
+ }
+
+
+ /**
+ * Show the form for creating a new resource.
+ *
+ * @param \App\Models\News $news
+ * @return \Illuminate\Contracts\View\View
+ */
+ public function create(News $news): \Illuminate\Contracts\View\View
+ {
+ return view('bo.pages.news.create', ['newsModel' => $news]);
+ }
+
+ /**
+ * Store a newly created resource in storage.
+ *
+ * @param \App\Http\Requests\Bo\News\StoreNewsRequest $request
+ * @return \Illuminate\Http\RedirectResponse
+ */
+ public function store(StoreNewsRequest $request): \Illuminate\Http\RedirectResponse
+ {
+ return DB::transaction(function () use ($request) {
+ $news = new News();
+ $news->fill($request->validated());
+
+ if ($news->saveOrFail()) {
+ return redirect()->route('bo.news.edit', $news)
+ ->with('success', __('crud.messages.has_been_created', [
+ 'model' => Str::ucfirst(__('models.classes.news'))
+ ]));
+ }
+ return redirect()->back()
+ ->with('error', __('crud.messages.cannot_be_created', [
+ 'model' => Str::ucfirst(__('models.classes.news'))
+ ]));
+ });
+ }
+
+ /**
+ * Show the form for editing the specified resource.
+ *
+ * @param App\Models\News $news
+ * @return \Illuminate\Contracts\View\View
+ */
+ public function edit(News $news): \Illuminate\Contracts\View\View
+ {
+ return view('bo.pages.news.edit', ['newsModel' => $news]);
+ }
+
+ /**
+ * Update the specified resource in storage.
+ *
+ * @param \App\Http\Requests\Bo\News\UpdateNewsRequest $request
+ * @param \App\Models\News $news
+ * @return \Illuminate\Http\RedirectResponse
+ */
+ public function update(UpdateNewsRequest $request, News $news): \Illuminate\Http\RedirectResponse
+ {
+ return DB::transaction(function () use ($request, $news) {
+ $news->fill($request->validated());
+
+ if ($news->saveOrFail()) {
+ return redirect()->route('bo.news.edit', $news)
+ ->with('success', __('crud.messages.has_been_updated', [
+ 'model' => Str::ucfirst(__('models.classes.news'))
+ ]));
+ }
+ return redirect()->route('bo.news.edit', $news)
+ ->with('error', __('crud.messages.cannot_be_updated', [
+ 'model' => Str::ucfirst(__('models.classes.news'))
+ ]));
+ });
+ }
+
+ /**
+ * Remove the specified resource from storage.
+ *
+ * @param \App\Models\News $news
+ * @return \Illuminate\Http\RedirectResponse
+ */
+ public function destroy(News $news): \Illuminate\Http\RedirectResponse
+ {
+ if ($news->deleteOrFail()) {
+ return redirect()->route('bo.news.index')
+ ->with('success', __('crud.messages.has_been_deleted', [
+ 'model' => Str::ucfirst(__('models.classes.news'))
+ ]));
+ }
+ return redirect()->back()
+ ->with('error', __('crud.messages.cannot_be_deleted', [
+ 'model' => Str::ucfirst(__('models.classes.news'))
+ ]));
+ }
+
+ /**
+ * Duplicate the specified resource.
+ *
+ * @param \App\Models\News $news
+ * @return \Illuminate\Contracts\View\View
+ */
+ public function duplicate(News $news): \Illuminate\Contracts\View\View
+ {
+ return $this->create($news->replicate());
+ }
+}
diff --git a/app/Http/Requests/Bo/News/StoreNewsRequest.php b/app/Http/Requests/Bo/News/StoreNewsRequest.php
new file mode 100644
index 0000000..9f05f11
--- /dev/null
+++ b/app/Http/Requests/Bo/News/StoreNewsRequest.php
@@ -0,0 +1,91 @@
+check();
+ }
+
+ /**
+ * Prepare the data for validation.
+ *
+ * @return void
+ */
+ protected function prepareForValidation()
+ {
+ $this->merge([
+ 'published' => $this->published ? true : false
+ ]);
+ $this->mergePicture('picture');
+ $this->mergePicture('picture_thumbnail');
+ }
+
+ /**
+ * Get the validation rules that apply to the request.
+ *
+ * @return array
+ */
+ public function rules(): array
+ {
+ $rules = [
+ 'seo_title' => 'required|between:45,70',
+ 'seo_description' => 'required|between:50,160',
+ 'title' => 'required|string|between:3,100',
+ 'category' => ['required', new Enum(CategoryEnum::class)],
+ 'picture_alt' => 'required|string|between:3,255',
+ 'picture_title' => 'required|string|between:3,255',
+ 'picture_thumbnail_alt' => 'required|string|between:3,255',
+ 'picture_thumbnail_title' => 'required|string|between:3,255',
+ 'subtitle' => 'required|string|between:3,255',
+ 'content' => 'required|string|min:15',
+ 'published' => 'required|boolean',
+ 'publish_at' => 'nullable|date',
+ 'unpublish_at' => 'nullable|date|after:publish_at',
+
+ ];
+ return \array_merge(
+ $rules,
+ $this->pictureRules('picture', 1002, 565, 1002, 565, false),
+ $this->pictureRules('picture_thumbnail', 265, 354, 265, 354, false),
+ );
+ }
+
+ /**
+ * Get custom attributes for validator errors.
+ *
+ * @return array
+ */
+ public function attributes(): array
+ {
+ return [
+ 'seo_title' => trans('validation.custom.seo_title'),
+ 'seo_description' => trans('validation.custom.seo_description'),
+ 'title' => trans('validation.attributes.title'),
+ 'category' => trans('validation.custom.category'),
+ 'subtitle' => trans('validation.custom.subtitle'),
+ 'content' => trans('validation.attributes.content'),
+ 'picture_alt' => trans('validation.custom.picture_alt'),
+ 'picture_title' => trans('validation.custom.picture_title'),
+ 'picture_thumbnail_alt' => trans('validation.custom.picture_thumbnail_alt'),
+ 'picture_thumbnail_title' => trans('validation.custom.picture_thumbnail_title'),
+ 'published' => trans('validation.custom.activated'),
+ 'publish_at' => trans('validation.custom.publish_at'),
+ 'unpublish_at' => trans('validation.custom.unpublish_at'),
+ ];
+ }
+}
diff --git a/app/Http/Requests/Bo/News/UpdateNewsRequest.php b/app/Http/Requests/Bo/News/UpdateNewsRequest.php
new file mode 100644
index 0000000..3e4e0b2
--- /dev/null
+++ b/app/Http/Requests/Bo/News/UpdateNewsRequest.php
@@ -0,0 +1,7 @@
+json('csp-report'))
- ->flatMap(function ($v, $k) {
- return [$k => is_array($v) ? collect($v)->flatten()->implode(', ') : $v];
- })->all();
+ ->flatMap(function ($v, $k) {
+ return [$k => is_array($v) ? collect($v)->flatten()->implode(', ') : $v];
+ })->all();
// * Filter array keys
$cspReport = collect(array_intersect_key($cspReport, [
'blocked-uri' => '', 'document-uri' => '', 'original-policy' => '',
diff --git a/database/seeders/NewsSeeder.php b/database/seeders/NewsSeeder.php
index 45f9239..c186ca7 100644
--- a/database/seeders/NewsSeeder.php
+++ b/database/seeders/NewsSeeder.php
@@ -16,13 +16,14 @@ class NewsSeeder extends Seeder
public function run()
{
FileStorageHelper::removeModelFiles(new News());
- News::factory(10)->create([
- 'picture_thumbnail' => FileStorageHelper::storeFile(new News(), new \SplFileInfo(\resource_path(
+ News::factory(10)->make()->each(function (News $news) {
+ $news->picture_thumbnail = FileStorageHelper::storeFile($news, new \SplFileInfo(\resource_path(
'../database/factories/assets/news/default-picture-265x354.png'
- ))),
- 'picture' => FileStorageHelper::storeFile(new News(), new \SplFileInfo(\resource_path(
+ )));
+ $news->picture = FileStorageHelper::storeFile($news, new \SplFileInfo(\resource_path(
'../database/factories/assets/news/default-picture-1002x565.png'
- ))),
- ]);
+ )));
+ $news->save();
+ });
}
}
diff --git a/resources/lang/en/crud.php b/resources/lang/en/crud.php
index 85e4066..af6b5f2 100644
--- a/resources/lang/en/crud.php
+++ b/resources/lang/en/crud.php
@@ -28,6 +28,9 @@ return [
'seo_title' => 'Must give a precise description of the page, which is recommended for optimum
positioning in search engines (
tag).',
'seo_description' => 'Must give a precise description of the page content ( description tag).',
+ 'visibility_with_dates' => 'This field will only have an effect if no date is entered.',
+ 'publish_at' => 'The :model will be published from that date.',
+ 'unpublish_at' => 'The :model will be unpublished from that date.',
],
'libs' => [
diff --git a/resources/lang/en/models.php b/resources/lang/en/models.php
index c5969b8..28513fc 100644
--- a/resources/lang/en/models.php
+++ b/resources/lang/en/models.php
@@ -6,5 +6,6 @@ return [
'activity_log' => 'activity log|activity logs',
'static_page' => 'static page|static pages',
'range' => 'range',
+ 'news' => 'news',
]
];
diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php
index 2a77847..c53d798 100644
--- a/resources/lang/en/validation.php
+++ b/resources/lang/en/validation.php
@@ -237,6 +237,8 @@ return [
'attribute_title' => 'title attribute',
'picture_productsheet' => 'productsheet picture',
'picture_thumbnail' => 'thumbnail',
-
+ 'category' => 'category',
+ 'publish_at' => 'publication date',
+ 'unpublish_at' => 'end of publication date',
],
];
diff --git a/resources/lang/fr/crud.php b/resources/lang/fr/crud.php
index 7a31cbb..bda4271 100644
--- a/resources/lang/fr/crud.php
+++ b/resources/lang/fr/crud.php
@@ -28,7 +28,9 @@ return [
'seo_title' => 'Doit décrire précisément la page, recommandation pour un positionnement
optimal dans les moteurs de recherche (balise ).',
'seo_description' => 'Doit décrire précisément le contenu de la page (balise description).',
-
+ 'visibility_with_dates' => 'Ce champ n\'aura d\'effet que si aucune date n\'est renseignée.',
+ 'publish_at' => 'Le :model sera publié à partir de cette date.',
+ 'unpublish_at' => 'Le :model sera dépublié à partir de cette date.',
],
'libs' => [
@@ -89,6 +91,5 @@ return [
'sort_delete' => 'Supprimer le tri',
'sort_arrow' => 'Ici, les flèches sont utilisées pour trier et non pour filtrer',
'right' => 'Vous n\'avez pas les droits',
-
]
];
diff --git a/resources/lang/fr/models.php b/resources/lang/fr/models.php
index fa7a7a4..23c470f 100644
--- a/resources/lang/fr/models.php
+++ b/resources/lang/fr/models.php
@@ -6,5 +6,6 @@ return [
'activity_log' => 'journal d\'activité|journaux d\'activité',
'static_page' => 'page statique|pages statiques',
'range' => 'gamme',
+ 'news' => 'actualité',
]
];
diff --git a/resources/lang/fr/validation.php b/resources/lang/fr/validation.php
index 9d94bff..324a0ae 100644
--- a/resources/lang/fr/validation.php
+++ b/resources/lang/fr/validation.php
@@ -237,6 +237,8 @@ return [
'attribute_title' => 'attribut title',
'picture_productsheet' => 'image de la fiche produit',
'picture_thumbnail' => 'vignette',
-
+ 'category' => 'catégorie',
+ 'publish_at' => 'date de publication',
+ 'unpublish_at' => 'date de fin de publication',
],
];
diff --git a/resources/views/bo/layouts/nav-list.blade.php b/resources/views/bo/layouts/nav-list.blade.php
index 77b8032..6c9742e 100644
--- a/resources/views/bo/layouts/nav-list.blade.php
+++ b/resources/views/bo/layouts/nav-list.blade.php
@@ -24,6 +24,15 @@
@endcan
+ @can('viewAny', \App\Models\News::class)
+
+
+ {{ Str::of(__('models.classes.news'))->plural()->title() }}
+
+
+ @endcan
@canAny(['isConceptor', 'viewAny'], \App\Models\User::class)
diff --git a/resources/views/bo/pages/news/create.blade.php b/resources/views/bo/pages/news/create.blade.php
new file mode 100644
index 0000000..aedd76b
--- /dev/null
+++ b/resources/views/bo/pages/news/create.blade.php
@@ -0,0 +1,36 @@
+@extends('bo.layout', ['brParam' => $newsModel])
+
+@section('title', __('Création d\'un :model', ['model' => __('models.classes.news')]))
+@section('description', __('Création d\'un tout nouveau :model', ['model' => __('models.classes.news')]))
+@section('breadcrumb',
+ request()->route()->getName())
+
+@section('content')
+
+
+
+
+
+ @include('modules.breadcrumbs.breadcrumb-body', ['brParam' => $newsModel])
+
+
+
+
+
+
+@endsection
diff --git a/resources/views/bo/pages/news/edit.blade.php b/resources/views/bo/pages/news/edit.blade.php
new file mode 100644
index 0000000..35f1a4d
--- /dev/null
+++ b/resources/views/bo/pages/news/edit.blade.php
@@ -0,0 +1,52 @@
+@extends('bo.layout', ['brParam' => $newsModel])
+
+@section('title', __('Édition d\'un :model', ['model' => __('models.classes.news')]))
+@section('description', __('Édition d\'un :model déjà existant', ['model' => __('models.classes.news')]))
+@section('breadcrumb',
+ request()->route()->getName())
+
+@section('content')
+
+
+
+
+
+ @include('modules.breadcrumbs.breadcrumb-body', ['brParam' => $newsModel])
+
+
+
+
+@endsection
diff --git a/resources/views/bo/pages/news/form.blade.php b/resources/views/bo/pages/news/form.blade.php
new file mode 100644
index 0000000..d54c3f6
--- /dev/null
+++ b/resources/views/bo/pages/news/form.blade.php
@@ -0,0 +1,399 @@
+
+ {{-- SEO --}}
+
+
+
+ {{-- TITLE --}}
+
+
+
+ {{-- SUBTITLE --}}
+
+
+
+ {{-- CATEGORY --}}
+
+
+
+ {{-- CONTENT --}}
+
+
+
+ {{-- PICTURE --}}
+
+
+
+ {{-- PICTURE_THUMBNAIL --}}
+
+
+
+ {{-- PUBLISHED --}}
+
+
+
+ {{-- PUBLICATION --}}
+
+
+
+
+
+
+
+
+
{{ __('* Champs obligatoires') }}
+
+
+
+@push('scripts')
+
+@endpush
diff --git a/resources/views/bo/pages/news/index-list.blade.php b/resources/views/bo/pages/news/index-list.blade.php
new file mode 100644
index 0000000..42f19e0
--- /dev/null
+++ b/resources/views/bo/pages/news/index-list.blade.php
@@ -0,0 +1,83 @@
+
+ @if (count($newsModels) > 0)
+
+ @include('bo.modules.table-col-sorter', [
+ 'cols' => [
+ 'title' => Str::ucfirst(__('validation.attributes.title')),
+ 'category' => Str::ucfirst(__('validation.custom.category')),
+ 'picture' => Str::ucfirst(__('validation.attributes.image')),
+ 'order' => Str::ucfirst(__('validation.custom.order')),
+ 'published' => Str::ucfirst(__('validation.custom.activated')),
+ 'updated_at' => Str::ucfirst(__('validation.attributes.updated_at')),
+ ],
+ ])
+
+
+ @foreach ($newsModels as $newsModel)
+
+ | {{ $newsModel->title }} |
+ {{ $newsModel->category->label() }} |
+
+
+ |
+ @php
+ $routeName = request()
+ ->route()
+ ->getName();
+ @endphp
+ @if (empty(request()->search) && Session::get("$routeName.sort_col") === 'order')
+ @include('bo.modules.change-model-order', [
+ 'routeName' => 'news',
+ 'model' => $newsModel,
+ 'models' => $newsModels,
+ ])
+ @endif
+ @include('bo.modules.change-published-status', [
+ 'routeName' => 'news',
+ 'model' => $newsModel,
+ ])
+
+ {{ $newsModel->updated_at->isoFormat('LLLL') }}
+ |
+
+ @canAny(['delete', 'duplicate', 'update'], $newsModel)
+
+ @else
+
+
+
+ @endcan
+ |
+
+ @endforeach
+
+ @else
+
+ | {{ __('crud.other.no_model_found', ['model' => __('models.classes.news')]) }} |
+
+ @endif
+
diff --git a/resources/views/bo/pages/news/index.blade.php b/resources/views/bo/pages/news/index.blade.php
new file mode 100644
index 0000000..a5251a0
--- /dev/null
+++ b/resources/views/bo/pages/news/index.blade.php
@@ -0,0 +1,21 @@
+@extends('bo.layout')
+
+@section('title', Str::ucfirst(__('models.classes.news')))
+@section('description', __('Liste de tous les :model', ['model' => Str::plural(__('models.classes.news'))]))
+
+@section('content')
+
+ @include('modules.breadcrumbs.breadcrumb-body')
+ @can('create', \App\Models\News::class)
+
+
+
+ @endcan
+
+ @include('bo.modules.search-bar')
+
+ @include('bo.pages.news.index-list')
+
+ {{ $newsModels->links() }}
+@endsection
diff --git a/resources/views/bo/pages/ranges/form.blade.php b/resources/views/bo/pages/ranges/form.blade.php
index c0cf11c..deae11e 100644
--- a/resources/views/bo/pages/ranges/form.blade.php
+++ b/resources/views/bo/pages/ranges/form.blade.php
@@ -95,7 +95,7 @@
'model' => __('models.classes.range'),
]),
'min' => 3,
- 'max' => 255,
+ 'max' => 100,
]),
'placeholder' => __('Un titre clair et concis'),
])
diff --git a/routes/breadcrumbs-bo.php b/routes/breadcrumbs-bo.php
index 1a993c1..cdd44fc 100644
--- a/routes/breadcrumbs-bo.php
+++ b/routes/breadcrumbs-bo.php
@@ -2,6 +2,7 @@
use App\Models\ActivityLog;
use App\Models\Range;
+use App\Models\News;
use App\Models\StaticPage;
use App\Models\User;
use Diglactic\Breadcrumbs\Breadcrumbs;
@@ -71,3 +72,23 @@ Breadcrumbs::for('bo.ranges.duplicate', function (Generator $trail) {
$trail->parent('bo.ranges.index');
$trail->push(Str::ucfirst(trans('crud.actions.duplicate')));
});
+
+// * NEWS
+Breadcrumbs::for('bo.news.index', function (Generator $trail) {
+ $trail->push(
+ Str::of(trans('models.classes.news'))->plural()->ucfirst(),
+ route('bo.news.index', ['sort_col' => 'updated_at', 'sort_way' => 'desc'])
+ );
+});
+Breadcrumbs::for('bo.news.create', function (Generator $trail) {
+ $trail->parent('bo.news.index');
+ $trail->push(Str::ucfirst(trans('crud.actions.create')), route('bo.news.create'));
+});
+Breadcrumbs::for('bo.news.edit', function (Generator $trail, News $news) {
+ $trail->parent('bo.news.index');
+ $trail->push(Str::ucfirst(trans('crud.actions.edit')), route('bo.news.edit', $news));
+});
+Breadcrumbs::for('bo.news.duplicate', function (Generator $trail) {
+ $trail->parent('bo.news.index');
+ $trail->push(Str::ucfirst(trans('crud.actions.duplicate')));
+});
diff --git a/routes/web-bo.php b/routes/web-bo.php
index 70aed87..ac6082e 100644
--- a/routes/web-bo.php
+++ b/routes/web-bo.php
@@ -2,6 +2,7 @@
use App\Http\Controllers\Bo\ActivityLogsController;
use App\Http\Controllers\Bo\DashboardController;
+use App\Http\Controllers\Bo\NewsController;
use App\Http\Controllers\Bo\StaticPageController;
use App\Http\Controllers\Bo\RangeController;
use App\Http\Controllers\Bo\UsersController;
@@ -51,7 +52,9 @@ Route::prefix('bo')
->middleware('htmlsanitize:content');
// * RANGES.
- Route::resource('ranges', RangeController::class)->except('show');
+ Route::resource('ranges', RangeController::class)->except('show')
+ ->withoutMiddleware('htmlsanitize')
+ ->middleware('htmlsanitize:description');
Route::patch('/ranges/{range}/change-published', [
RangeController::class, 'changePublished'
])->name('ranges.change-published');
@@ -62,6 +65,20 @@ Route::prefix('bo')
RangeController::class, 'duplicate'
])->name('ranges.duplicate');
+ // * NEWS.
+ Route::resource('news', NewsController::class)->except('show')
+ ->withoutMiddleware('htmlsanitize')
+ ->middleware('htmlsanitize:content');
+ Route::patch('/news/{news}/change-published', [
+ NewsController::class, 'changePublished'
+ ])->name('news.change-published');
+ Route::patch('/news/{news}/change-order/{direction}', [
+ RangeController::class, 'changeOrder'
+ ])->where('direction', 'up|down')->name('news.change-order');
+ Route::get('/news/{news}/duplicate', [
+ NewsController::class, 'duplicate'
+ ])->name('news.duplicate');
+
Route::middleware('can:isConceptor')->group(function () {
// * ACTIVITY LOGS.
Route::resource('activity_logs', ActivityLogsController::class)->only(['index', 'show']);