Bump with laravel base controller phpstan and phpcs

Still TODO picture sizes for seeders
This commit is contained in:
Jérémy Munsch 2024-02-12 15:51:31 +01:00
parent 74cebd7eb3
commit f3d79cf7a9
No known key found for this signature in database
GPG key ID: 18361AF3BFEC0FEC
224 changed files with 6381 additions and 5964 deletions

View file

@ -45,9 +45,6 @@
"vue/script-indent": ["error", 2, { "baseIndent": 0, "switchCase": 1 }],
"vue/one-component-per-file": "off"
},
"globals": {
"require": true
},
"settings": {
"import/resolver": {
"node": {

14
.gitignore vendored
View file

@ -8,6 +8,7 @@
/.phpunit.cache
/.vscode
/node_modules
/bootstrap
/public/build
/public/cache
/public/hot
@ -22,3 +23,16 @@ Homestead.yaml
npm-debug.log
yarn-error.log
.npmrc
# Ignore public favicon
/public/android-chrome-192x192.png
/public/android-chrome-512x512.png
/public/apple-touch-icon.png
/public/browserconfig.xml
/public/browserconfig.xml
/public/favicon-16x16.png
/public/favicon-32x32.png
/public/favicon.ico
/public/mstile-150x150.png
/public/safari-pinned-tab.svg
/public/site.webmanifest

View file

@ -8,7 +8,6 @@
"neilbrayfield.php-docblocker",
"bmewburn.vscode-intelephense-client",
"valeryanm.vscode-phpsab",
"shevaua.phpcs",
"syler.sass-indented",
"sibiraj-s.vscode-scss-formatter",
"mrmlnc.vscode-scss",

5
Dockerfile Normal file
View file

@ -0,0 +1,5 @@
FROM registry.s-web.fr/ldc-web/projets/ldc-web-images-docker/ldcweb-production-8.2:latest
COPY --chown=www-data:www-data . /app/srcs
USER root
RUN chmod -R 755 /app/srcs/storage
USER www-data

View file

@ -8,6 +8,7 @@ use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Str;
class UserCreate extends Command
{
@ -28,35 +29,35 @@ class UserCreate extends Command
/**
* The first name of the new user.
*
* @var string
* @var string|null
*/
protected $first_name;
/**
* The last name of the new user.
*
* @var string
* @var string|null
*/
protected $last_name;
/**
* The email of the new user.
*
* @var string
* @var string|null
*/
protected $email;
/**
* The password of the new user.
*
* @var string
* @var string|null
*/
protected $password;
/**
* The role of the new user.
*
* @var string
* @var string|null
*/
protected $role;
@ -200,18 +201,22 @@ class UserCreate extends Command
try {
$tmp = $this->choice(
'Select his role',
[RoleEnum::conceptor->name, RoleEnum::admin->name, RoleEnum::visitor->name],
RoleEnum::visitor->name,
[
Str::ucFirst(RoleEnum::conceptor->label()),
Str::ucFirst(RoleEnum::admin->label()),
Str::ucFirst(RoleEnum::visitor->label())
],
Str::ucFirst(RoleEnum::visitor->label()),
$maxAttempts = null,
$allowMultipleSelections = false
);
if ($tmp === RoleEnum::conceptor->name) {
if ($tmp === Str::ucFirst(RoleEnum::conceptor->label())) {
$tmp = RoleEnum::conceptor;
}
if ($tmp === RoleEnum::admin->name) {
if ($tmp === Str::ucFirst(RoleEnum::admin->label())) {
$tmp = RoleEnum::admin;
}
if ($tmp === RoleEnum::visitor->name) {
if ($tmp === Str::ucFirst(RoleEnum::visitor->label())) {
$tmp = RoleEnum::visitor;
}
$validator = Validator::make(['role' => $tmp], [
@ -221,7 +226,7 @@ class UserCreate extends Command
$this->role = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please choose from the selection',
'Please choose from the selection %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;

View file

@ -15,6 +15,8 @@ enum ActivityLogsEventEnum: int
/**
* Optionnal labels definition
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::created->name => 'Création',

View file

@ -14,6 +14,8 @@ enum CkEditorConfigName: int
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::minimal->name => 'minimal',

View file

@ -260,6 +260,8 @@ enum Country: int
/**
* Optionnal labels definition
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::afghanistan->name => 'Afghanistan',

View file

@ -40,6 +40,8 @@ enum PageSectionEnum: int
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::home_missions->name => 'Nos Missions',

View file

@ -18,6 +18,8 @@ enum SpeciesEnum: int
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::chicken->name => 'Poulet',

View file

@ -14,6 +14,8 @@ enum SliderTypeEnum: int
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::home->name => 'Accueil',

View file

@ -21,6 +21,8 @@ enum StaticPageType: int
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::contact->name => 'Contact',

View file

@ -17,6 +17,8 @@ enum PictoEnum: int
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::danger->name => 'Danger',

View file

@ -17,6 +17,8 @@ enum TypeEnum: int
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::breeding->name => 'Elever Durablement',

View file

@ -13,9 +13,11 @@ enum BootstrapThemeEnum: int
/**
* Optionnal labels definition
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::light->name => 'Light',
self::dark->name => 'Dark',
self::light->name => 'enums.bootstrap-theme.light',
self::dark->name => 'enums.bootstrap-theme.dark',
];
}

View file

@ -14,10 +14,12 @@ enum RoleEnum: int
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::conceptor->name => 'Concepteur',
self::admin->name => 'Administrateur',
self::visitor->name => 'Visiteur',
self::conceptor->name => 'enums.user-role.conceptor',
self::admin->name => 'enums.user-role.admin',
self::visitor->name => 'enums.user-role.visitor',
];
}

View file

@ -51,7 +51,7 @@ class ActivityLogController extends Controller
/**
* Show the form for editing the specified resource.
*
* @param App\Models\ActivityLog $activity_log
* @param \App\Models\ActivityLog $activity_log
* @return \Illuminate\Contracts\View\View
*/
public function show(ActivityLog $activity_log): \Illuminate\Contracts\View\View

View file

@ -32,7 +32,7 @@ class HistoricalMilestoneController extends Controller
$historicalMilestoneModels = HistoricalMilestone::query();
/** @var string $search Search field */
$search = $request->search;
$search = $request->input('search');
if ($search) {
$this->searchQuery(
$historicalMilestoneModels,
@ -74,7 +74,7 @@ class HistoricalMilestoneController extends Controller
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\HistoricalMilestone\StoreHistoricalMilestoneRequest $request
* @param \App\Http\Requests\Bo\HistoricalMilestones\StoreHistoricalMilestoneRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreHistoricalMilestoneRequest $request): \Illuminate\Http\RedirectResponse
@ -86,12 +86,12 @@ class HistoricalMilestoneController extends Controller
if ($historicalMilestone->saveOrFail()) {
return redirect()->route('bo.historicalmilestones.edit', $historicalMilestone)
->with('success', __('crud.messages.has_been_created', [
'model' => Str::ucfirst(trans_choice('models.classes.historicalMilestone', '1'))
'model' => Str::ucfirst(trans_choice('models.classes.historicalMilestone', 1))
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_created', [
'model' => Str::ucfirst(trans_choice('models.classes.historicalMilestone', '1'))
'model' => Str::ucfirst(trans_choice('models.classes.historicalMilestone', 1))
]));
});
}
@ -99,7 +99,7 @@ class HistoricalMilestoneController extends Controller
/**
* Show the form for editing the specified resource.
*
* @param App\Models\HistoricalMilestone $historicalMilestone
* @param \App\Models\HistoricalMilestone $historicalMilestone
* @return \Illuminate\Contracts\View\View
*/
public function edit(HistoricalMilestone $historicalMilestone): \Illuminate\Contracts\View\View
@ -110,8 +110,8 @@ class HistoricalMilestoneController extends Controller
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\HistoricalMilestone\UpdateHistoricalMilestoneRequest $request
* @param \App\Models\HistoricalMilestone $historicalMilestone
* @param \App\Http\Requests\Bo\HistoricalMilestones\UpdateHistoricalMilestoneRequest $request
* @param \App\Models\HistoricalMilestone $historicalMilestone
* @return \Illuminate\Http\RedirectResponse
*/
public function update(
@ -124,12 +124,12 @@ class HistoricalMilestoneController extends Controller
if ($historicalMilestone->saveOrFail()) {
return redirect()->route('bo.historicalmilestones.edit', $historicalMilestone)
->with('success', __('crud.messages.has_been_updated', [
'model' => Str::ucfirst(trans_choice('models.classes.historicalMilestone', '1'))
'model' => Str::ucfirst(trans_choice('models.classes.historicalMilestone', 1))
]));
}
return redirect()->route('bo.historicalmilestones.edit', $historicalMilestone)
->with('error', __('crud.messages.cannot_be_updated', [
'model' => Str::ucfirst(trans_choice('models.classes.historicalMilestone', '1'))
'model' => Str::ucfirst(trans_choice('models.classes.historicalMilestone', 1))
]));
});
}
@ -145,12 +145,12 @@ class HistoricalMilestoneController extends Controller
if ($historicalMilestone->deleteOrFail()) {
return redirect()->route('bo.historicalmilestones.index')
->with('success', __('crud.messages.has_been_deleted', [
'model' => Str::ucfirst(trans_choice('models.classes.historicalMilestone', '1'))
'model' => Str::ucfirst(trans_choice('models.classes.historicalMilestone', 1))
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_deleted', [
'model' => Str::ucfirst(trans_choice('models.classes.historicalMilestone', '1'))
'model' => Str::ucfirst(trans_choice('models.classes.historicalMilestone', 1))
]));
}

View file

@ -32,7 +32,7 @@ class JobVideoController extends Controller
$jobVideoModels = JobVideo::query();
/** @var string $search Search field */
$search = $request->search;
$search = $request->input('search');
if ($search) {
$this->searchQuery(
$jobVideoModels,
@ -86,12 +86,12 @@ class JobVideoController extends Controller
if ($jobVideo->saveOrFail()) {
return redirect()->route('bo.jobvideos.edit', $jobVideo)
->with('success', __('crud.messages.has_been_created', [
'model' => Str::ucfirst(trans_choice('models.classes.job_video', '1'))
'model' => Str::ucfirst(trans_choice('models.classes.job_video', 1))
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_created', [
'model' => Str::ucfirst(trans_choice('models.classes.job_video', '1'))
'model' => Str::ucfirst(trans_choice('models.classes.job_video', 1))
]));
});
}
@ -99,7 +99,7 @@ class JobVideoController extends Controller
/**
* Show the form for editing the specified resource.
*
* @param App\Models\JobVideo $jobVideo
* @param \App\Models\JobVideo $jobVideo
* @return \Illuminate\Contracts\View\View
*/
public function edit(JobVideo $jobVideo): \Illuminate\Contracts\View\View
@ -124,12 +124,12 @@ class JobVideoController extends Controller
if ($jobVideo->saveOrFail()) {
return redirect()->route('bo.jobvideos.edit', $jobVideo)
->with('success', __('crud.messages.has_been_updated', [
'model' => Str::ucfirst(trans_choice('models.classes.job_video', '1'))
'model' => Str::ucfirst(trans_choice('models.classes.job_video', 1))
]));
}
return redirect()->route('bo.jobvideos.edit', $jobVideo)
->with('error', __('crud.messages.cannot_be_updated', [
'model' => Str::ucfirst(trans_choice('models.classes.job_video', '1'))
'model' => Str::ucfirst(trans_choice('models.classes.job_video', 1))
]));
});
}
@ -145,12 +145,12 @@ class JobVideoController extends Controller
if ($jobVideo->deleteOrFail()) {
return redirect()->route('bo.jobvideos.index')
->with('success', __('crud.messages.has_been_deleted', [
'model' => Str::ucfirst(trans_choice('models.classes.job_video', '1'))
'model' => Str::ucfirst(trans_choice('models.classes.job_video', 1))
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_deleted', [
'model' => Str::ucfirst(trans_choice('models.classes.job_video', '1'))
'model' => Str::ucfirst(trans_choice('models.classes.job_video', 1))
]));
}

View file

@ -97,7 +97,7 @@ class LogoController extends Controller
/**
* Show the form for editing the specified resource.
*
* @param App\Models\Logo $logo
* @param \App\Models\Logo $logo
* @return \Illuminate\Contracts\View\View
*/
public function edit(Logo $logo): \Illuminate\Contracts\View\View

View file

@ -102,7 +102,7 @@ class NewsController extends Controller
/**
* Show the form for editing the specified resource.
*
* @param App\Models\News $news
* @param \App\Models\News $news
* @return \Illuminate\Contracts\View\View
*/
public function edit(News $news): \Illuminate\Contracts\View\View

View file

@ -73,7 +73,7 @@ class SectionController extends Controller
/**
* Show the form for editing the specified resource.
*
* @param App\Models\Section $section
* @param \App\Models\Section $section
* @return \Illuminate\Contracts\View\View
*/
public function edit(Section $section): \Illuminate\Contracts\View\View

View file

@ -108,7 +108,7 @@ class SiteController extends Controller
/**
* Show the form for editing the specified resource.
*
* @param App\Models\Site $site
* @param \App\Models\Site $site
* @return \Illuminate\Contracts\View\View
*/
public function edit(Site $site): \Illuminate\Contracts\View\View

View file

@ -95,7 +95,7 @@ class SlideController extends Controller
/**
* Show the form for editing the specified resource.
*
* @param App\Models\Slide $slide
* @param \App\Models\Slide $slide
* @return \Illuminate\Contracts\View\View
*/
public function edit(Slide $slide): \Illuminate\Contracts\View\View

View file

@ -56,7 +56,7 @@ class StaticPageController extends Controller
/**
* Show the form for editing the specified resource.
*
* @param App\Models\StaticPage $static_page
* @param \App\Models\StaticPage $static_page
* @return \Illuminate\Contracts\View\View
*/
public function edit(StaticPage $static_page): \Illuminate\Contracts\View\View

View file

@ -105,7 +105,7 @@ class StripController extends Controller
/**
* Show the form for editing the specified resource.
*
* @param App\Models\Strip $strip
* @param \App\Models\Strip $strip
* @return \Illuminate\Contracts\View\View
*/
public function edit(Strip $strip): \Illuminate\Contracts\View\View

View file

@ -99,7 +99,7 @@ class TestimonialController extends Controller
/**
* Show the form for editing the specified resource.
*
* @param App\Models\Testimonial $testimonial
* @param \App\Models\Testimonial $testimonial
* @return \Illuminate\Contracts\View\View
*/
public function edit(Testimonial $testimonial): \Illuminate\Contracts\View\View

View file

@ -63,7 +63,6 @@ class UserController extends Controller
return view('bo.pages.users.index', compact('userModels', 'search', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
@ -103,7 +102,7 @@ class UserController extends Controller
/**
* Show the form for editing the specified resource.
*
* @param App\Models\User $user
* @param \App\Models\User $user
* @return \Illuminate\Contracts\View\View
*/
public function edit(User $user): \Illuminate\Contracts\View\View
@ -144,8 +143,11 @@ class UserController extends Controller
*/
public function destroy(User $user): \Illuminate\Http\RedirectResponse
{
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($user->deleteOrFail()) {
return redirect()->route('bo.users.index')
return redirect()->route('bo.users.index', $previousQueries)
->with('success', __('crud.messages.has_been_deleted', [
'model' => Str::ucfirst(__('models.classes.user'))
]));

View file

@ -94,7 +94,7 @@ class Controller extends BaseController
return $query;
}
// Get models with relation's name/label.
if (Str::endsWith($field, '_id') && $query->getModel()->has(Str::remove('_id', $field))) {
if (Str::endsWith($field, '_id')) {
$query->orWhereHas(Str::remove('_id', $field), function ($queryRelation) use ($field, $search) {
if (Schema::hasColumn(Str::remove('_id', $field) . 's', 'name')) {
$queryRelation->where('name', 'like', '%' . $search . '%');
@ -122,12 +122,15 @@ class Controller extends BaseController
*/
protected function getSearchValue(Builder $query, string $search, string $field): string|array
{
$cast = $query->getModel()->getCasts()[$field] ?? null;
if (
count($query->getModel()->getCasts()) and // Check if the model has casts.
isset($query->getModel()->getCasts()[$field]) and // Check if there is cast about specific field.
enum_exists($query->getModel()->getCasts()[$field]) // Check if the cast is an enum.
$cast and // Check if there is cast about specific field.
enum_exists($cast) // Check if the cast is an enum.
) {
$cases = collect($query->getModel()->getCasts()[$field]::toArray());
/** @var \App\Traits\Enums $cast */
// @phpstan-ignore-next-line
$cases = collect($cast::toArray());
$enum = $cases->filter(function ($item) use ($search) {
return preg_match('/' . Str::lower($search) . '/', Str::lower($item->label));
});
@ -148,12 +151,12 @@ class Controller extends BaseController
{
$rName = request()->route()->getName();
$table = $query->getModel()->getTable();
if (request()->sort_col) {
Session::put("$rName.sort_col", request()->sort_col);
if (request()->input('sort_col')) {
Session::put("$rName.sort_col", request()->input('sort_col'));
Session::put("$rName.sorted", true);
}
if (request()->sort_way) {
Session::put("$rName.sort_way", request()->sort_way);
if (request()->input('sort_way')) {
Session::put("$rName.sort_way", request()->input('sort_way'));
Session::put("$rName.sorted", true);
}
if (request()->rst) {
@ -161,12 +164,16 @@ class Controller extends BaseController
Session::remove("$rName.sort_col");
Session::remove("$rName.sort_way");
}
if (request()->sort_col and request()->sort_way and Schema::hasColumn($table, request()->sort_col)) {
if (
!isset($ignore) or
(isset($ignore) and !in_array(request()->sort_col, $ignore))
) {
$query = $query->orderBy(\sprintf("$table.%s", request()->sort_col), request()->sort_way);
if (
request()->input('sort_col') and
request()->input('sort_way') and
Schema::hasColumn($table, request()->input('sort_col'))
) {
if (!in_array(request()->input('sort_col'), $ignore)) {
$query = $query->orderBy(
\sprintf("$table.%s", request()->input('sort_col')),
request()->input('sort_way')
);
}
} elseif (Schema::hasColumn($table, 'order')) {
$query = $query->orderBy('order', 'ASC');

View file

@ -54,16 +54,16 @@ class Kernel extends HttpKernel
* @var array<string, class-string|string>
*/
protected $middlewareAliases = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'htmlsanitize' => \App\Http\Middleware\HtmlSanitizer::class,
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'htmlsanitize' => \App\Http\Middleware\HtmlSanitizer::class,
];
}

View file

@ -53,18 +53,21 @@ class SecureHeaders
"default-src 'none';",
"manifest-src 'self';",
// * CKEDITOR Specific https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/csp.html
"connect-src 'self' " . implode(' ', [
"connect-src 'self' blob: " . implode(' ', [
// * VITE DEV
config('app.debug') ? "wss://{$host}:4443" : '',
config('app.debug') ? "https://{$host}:4443" : '',
'https://yl.s-web.fr'
]) . ';',
"img-src * data: blob:;",
'frame-src ' . implode(' ', [
'\'self\'',
"img-src 'self' data: blob: " . implode(' ', [
// * VITE DEV
config('app.debug') ? "https://{$host}:4443" : '',
]) . ';',
"object-src 'self' " . implode(' ', []) . ';',
"frame-src 'self' " . implode(' ', [
'https://www.google.com/recaptcha/',
'https://recaptcha.google.com/recaptcha/',
'https://www.google.com/maps/',
'https://www.youtube.com',
'https://www.google.com/maps/'
]) . ';',
"font-src 'self' data: " . \implode(' ', [
// * VITE DEV

View file

@ -10,9 +10,11 @@ class TrustProxies extends Middleware
/**
* The trusted proxies for this application.
*
* ! Added * as tmp Fix for #13
*
* @var array<int, string>|string|null
*/
protected $proxies;
protected $proxies = '*';
/**
* The headers that should be used to detect proxies.
@ -20,7 +22,7 @@ class TrustProxies extends Middleware
* @var integer
*/
protected $headers =
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |

View file

@ -27,8 +27,8 @@ class StoreHistoricalMilestoneRequest extends FormRequest
protected function prepareForValidation()
{
$this->merge([
'button_status' => $this->button_status ? true : false,
'button_new_tab' => $this->button_new_tab ? true : false
'button_status' => $this->boolean('button_status'),
'button_new_tab' => $this->boolean('button_new_tab')
]);
$this->mergePicture('picture');
}
@ -54,8 +54,7 @@ class StoreHistoricalMilestoneRequest extends FormRequest
return \array_merge(
$rules,
// FIXME: Mettre à jour les tailles.
$this->pictureRules('picture', 100, 100, 100, 100, null, false),
$this->pictureRules('picture', 606, 800, 606, 800, false),
);
}

View file

@ -44,8 +44,7 @@ class StoreLogoRequest extends FormRequest
];
return \array_merge(
$rules,
// FIXME: Mettre à jour les tailles.
$this->pictureRules('picture', 100, 100, 100, 100, null, false),
$this->pictureRules('picture', 331, 240, 331, 100, false),
);
}

View file

@ -27,7 +27,7 @@ class StoreNewsRequest extends FormRequest
protected function prepareForValidation()
{
$this->merge([
'published' => $this->published ? true : false
'published' => $this->boolean('published')
]);
$this->mergePicture('picture');
$this->mergePicture('thumbnail');

View file

@ -33,16 +33,20 @@ class UpdateSectionRequest extends UpdateMiniCrudRequest
$rules = [
'h2_title' => 'required|between:3,255',
];
if ($this->route('section')->section !== \app\Enums\Sections\PageSectionEnum::hire_grow) {
$rules = \array_merge($rules, [
'text' => 'present',
]);
}
if ($this->route('section')->section->containsData()) {
$rules = \array_merge($rules, [
'data' => 'present|array',
'data.*' => 'required|array',
]);
/** @var \App\Models\Section|null $section */
$section = $this->route('section');
if ($section) {
if ($section->section !== \App\Enums\Sections\PageSectionEnum::hire_grow) {
$rules = \array_merge($rules, [
'text' => 'present',
]);
}
if ($section->section->containsData()) {
$rules = \array_merge($rules, [
'data' => 'present|array',
'data.*' => 'required|array',
]);
}
}
return \array_merge(

View file

@ -28,8 +28,8 @@ class StoreSiteRequest extends FormRequest
protected function prepareForValidation()
{
$this->merge([
'published' => $this->published ? true : false,
'pictos' => \collect($this->pictos)->all(),
'published' => $this->boolean('published'),
'pictos' => \collect($this->input('pictos'))->all(),
]);
$this->mergePicture('logo');
$this->mergePicture('picture_1');
@ -84,11 +84,11 @@ class StoreSiteRequest extends FormRequest
return \array_merge(
$rules,
// FIXME: Mettre à jour les tailles.
$this->pictureRules('logo', 100, 100, 100, 100, null, false),
$this->pictureRules('picture_1', 100, 100, 100, 100, null, false),
$this->pictureRules('picture_2', 100, 100, 100, 100, null, true),
$this->pictureRules('picture_3', 100, 100, 100, 100, null, true),
$this->pictureRules('map_picture', 100, 100, 100, 100, null, false),
$this->pictureRules('logo', 100, 100, 100, 100, false),
$this->pictureRules('picture_1', 100, 100, 100, 100, false),
$this->pictureRules('picture_2', 100, 100, 100, 100, true),
$this->pictureRules('picture_3', 100, 100, 100, 100, true),
$this->pictureRules('map_picture', 100, 100, 100, 100, false),
);
}

View file

@ -28,8 +28,8 @@ class StoreSlideRequest extends FormRequest
protected function prepareForValidation()
{
$this->merge([
'button_status' => $this->button_status ? true : false,
'button_new_tab' => $this->button_new_tab ? true : false
'button_status' => $this->boolean('button_status'),
'button_new_tab' => $this->boolean('button_new_tab')
]);
if (intval($this->slider) !== \App\Enums\Slides\SliderTypeEnum::home->value()) {
$this->merge([
@ -69,7 +69,7 @@ class StoreSlideRequest extends FormRequest
return \array_merge(
$rules,
// FIXME: Mettre à jour les tailles.
$this->pictureRules('picture', 100, 100, 100, 100, null, false),
$this->pictureRules('picture', 100, 100, 100, 100, false),
);
}

View file

@ -3,6 +3,7 @@
namespace App\Http\Requests\Bo\StaticPages;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Str;
class UpdateStaticPageRequest extends FormRequest
@ -14,7 +15,7 @@ class UpdateStaticPageRequest extends FormRequest
*/
public function authorize(): bool
{
return auth('backend')->check();
return Gate::check('update', $this->route('static_page'));
}
/**
@ -25,7 +26,7 @@ class UpdateStaticPageRequest extends FormRequest
public function prepareForValidation(): void
{
$this->merge([
'content' => (string)Str::of($this->post('content'))
'content' => Str::of($this->input('content'))->value()
]);
}
@ -37,10 +38,25 @@ class UpdateStaticPageRequest extends FormRequest
public function rules(): array
{
return [
'seo_title' => 'required|min:45|max:70',
'seo_title' => 'required|min:45|max:70',
'seo_description' => 'required|min:50|max:160',
'title' => 'required|min:3|max:255',
'content' => 'present',
'title' => 'required|min:3|max:255',
'content' => 'present',
];
}
/**
* 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'),
'content' => trans('validation.attributes.content'),
];
}
}

View file

@ -29,7 +29,7 @@ class StoreStripRequest extends FormRequest
protected function prepareForValidation()
{
$this->merge([
'published' => $this->published ? true : false,
'published' => $this->boolean('published'),
]);
}
@ -49,9 +49,8 @@ class StoreStripRequest extends FormRequest
'required',
'string',
'min:3',
// phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function (string $attribute, mixed $value, \Closure $fail) {
// phpcs:enable
if (Str::wordCount(\strip_tags($value)) > 255) {
$fail(trans('validation.max_words', [
'attribute' => trans('validation.attributes.text'),

View file

@ -51,7 +51,7 @@ class StoreTestimonialRequest extends FormRequest
return \array_merge(
$rules,
// FIXME: Mettre à jour les tailles.
$this->pictureRules('picture', 100, 100, 100, 100, null, false),
$this->pictureRules('picture', 100, 100, 100, 100, false),
);
}

View file

@ -24,7 +24,7 @@ class UpdateChangeOrderRequest extends FormRequest
protected function prepareForValidation(): void
{
$this->merge([
'direction' => $this->direction === 'up' ? true : false
'direction' => $this->route('direction') === 'up' ? true : false
]);
}

View file

@ -21,40 +21,19 @@ abstract class UpdateMiniCrudRequest extends FormRequest
*
* @return boolean
*/
public function authorize()
public function authorize(): bool
{
return auth('backend')->check();
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation(): void
{
foreach ($this->{$this->modelName}->data as $iptName => $input) {
$input = \json_decode(\json_encode($input), true);
switch ($input['inputType']) {
case InputType::text->value:
if (is_null($this->data[$iptName]['value']) && !$input['required']) {
$this->merge([
$this->data[$iptName]['value'] => ''
]);
}
break;
}
}
}
/**
* Get the validation rules that apply to the request.
*
* @return array
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.MaxExceeded
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
* @phpcs:disable Generic.Metrics.NestingLevel.TooHigh
*/
public function rules()
public function rules(): array
{
// phpcs:enable
$rules = [];
@ -89,8 +68,7 @@ abstract class UpdateMiniCrudRequest extends FormRequest
]);
break;
case InputType::text->value:
$required = $input['required'] ? 'required' : 'nullable';
$mergeRules = $this->compileValidate($required . '|string|min:15', $input);
$mergeRules = $this->compileValidate('required|string|min:15', $input);
$mergeRules = collect($mergeRules)
->map(function ($rule) {
if (\strpos($rule, 'max:') !== false) {
@ -109,10 +87,9 @@ abstract class UpdateMiniCrudRequest extends FormRequest
break;
case InputType::image->value:
$required = $this->isUpdate() ? 'sometimes' : 'required';
$ipt = $this->data[$iptName];
$max = isset($ipt['dimensionsMax']);
$dim = \is_string($input['dimensions']) ? collect(explode(
$ipt = $this->data[$iptName];
$max = isset($ipt['dimensionsMax']);
$dim = \is_string($input['dimensions']) ? collect(explode(
',',
(isset($ipt['dimensionsMax']) and is_string($ipt['dimensionsMax'])) ?
$ipt['dimensionsMax'] : $input['dimensions']
@ -125,17 +102,13 @@ abstract class UpdateMiniCrudRequest extends FormRequest
$sizes = (object)[
'minHeight' => $dim['height'],
'maxHeight' => $dim['height'],
'minWith' => $dim['width'],
'maxWith' => $dim['width'],
'minWidth' => $dim['width'],
'maxWidth' => $dim['width'],
];
$rules = array_merge($rules, [
"data.$iptName.value" => [
function (string $attribute, $value, callable $fail) use ($sizes, $input) {
// Picture can be null.
if ($input['required'] || $value !== null) {
return ToolboxHelper::validatePicture($attribute, $value, $fail, $sizes);
}
function (string $attribute, $value, callable $fail) use ($sizes) {
ToolboxHelper::validatePicture($attribute, $value, $fail, $sizes);
}
],
$this->compileValidate(
@ -153,13 +126,7 @@ abstract class UpdateMiniCrudRequest extends FormRequest
]),
$input
),
]);
// Attributes are required if a picture is set.
$required = $this->data['bloc_image']['value'] ? 'required' : 'nullable';
$rules = array_merge($rules, [
"data.$iptName.alt" => $this->compileValidate("$required|string|min:5", $input),
"data.$iptName.alt" => $this->compileValidate("$required|string|min:5", $input),
"data.$iptName.title" => $this->compileValidate("$required|string|min:5", $input)
]);
break;

View file

@ -3,7 +3,9 @@
namespace App\Http\Requests\Bo\Users;
use App\Enums\Users\RoleEnum;
use App\Models\User;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rules\Enum;
use Illuminate\Validation\Rules\Password;
@ -16,7 +18,7 @@ class StoreUserRequest extends FormRequest
*/
public function authorize(): bool
{
return auth('backend')->check();
return Gate::check('create', User::class);
}
/**
@ -24,10 +26,10 @@ class StoreUserRequest extends FormRequest
*
* @return void
*/
protected function prepareForValidation()
protected function prepareForValidation(): void
{
$this->merge([
'published' => $this->published ? true : false
'published' => $this->boolean('published')
]);
}
@ -40,9 +42,9 @@ class StoreUserRequest extends FormRequest
{
return [
'first_name' => 'required|string|min:3|max:255',
'last_name' => 'required|string|min:3|max:255',
'email' => 'required|email:rfc,strict,dns,spoof,filter|unique:users,email',
'password' => [
'last_name' => 'required|string|min:3|max:255',
'email' => 'required|email:rfc,strict,dns,spoof,filter|unique:users,email',
'password' => [
'required', 'required_with:password_confirmation', 'confirmed', 'max:255', Password::min(8)
->letters()
->mixedCase()
@ -50,8 +52,8 @@ class StoreUserRequest extends FormRequest
->symbols()
->uncompromised()
],
'role' => ['required', new Enum(RoleEnum::class)],
'published' => 'required|boolean',
'role' => ['required', new Enum(RoleEnum::class)],
'published' => 'required|boolean',
];
}
@ -64,11 +66,11 @@ class StoreUserRequest extends FormRequest
{
return [
'first_name' => trans('validation.attributes.first_name'),
'last_name' => trans('validation.attributes.last_name'),
'email' => trans('validation.attributes.email'),
'password' => trans('validation.attributes.password'),
'role' => trans('validation.attributes.role'),
'published' => trans('validation.custom.publication')
'last_name' => trans('validation.attributes.last_name'),
'email' => trans('validation.attributes.email'),
'password' => trans('validation.attributes.password'),
'role' => trans('validation.attributes.role'),
'published' => trans('validation.custom.publication')
];
}
}

View file

@ -2,23 +2,35 @@
namespace App\Http\Requests\Bo\Users;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Password;
class UpdateUserRequest extends StoreUserRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('user'));
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation()
protected function prepareForValidation(): void
{
$this->merge([
'published' => $this->published ? true : false,
'password' => $this->password ? $this->password : $this->route('user')->password,
'password_confirmation' => $this->password_confirmation ?
$this->password_confirmation :
$this->route('user')->password
'published' => $this->boolean('published'),
'password' => $this->input('password') ??
\optional($this->route('user'))->password,
'password_confirmation' => $this->input('password_confirmation') ??
\optional($this->route('user'))->password
]);
}
@ -30,10 +42,10 @@ class UpdateUserRequest extends StoreUserRequest
public function rules(): array
{
return array_merge(parent::rules(), [
'email' => [
'email' => [
'required',
'email:rfc,strict,dns,spoof,filter',
'unique:users,email,' . $this->route('user')->id . ',id',
Rule::unique('users', 'email')->ignore(\optional($this->route('user'))->getRouteKey())
],
'password' => [
'required', 'required_with:password_confirmation', 'confirmed', 'max:255', Password::min(8)

View file

@ -145,7 +145,7 @@ class ExceptionsHelper
$traceStr .= self::handleArgs($stack);
}
$traceStr .= ")\n";
} elseif (isset($stack['function'])) {
} else {
$traceStr .= "{$stack['function']}(";
if (isset($stack['args'])) {
$traceStr .= self::handleArgs($stack);

View file

@ -30,6 +30,20 @@ use Illuminate\Support\Str;
*/
class FileStorageHelper
{
/**
* Remove all files from string attribute html that are stored in public
*
* @param string $publicStorageFilePath The file path from public directory.
* @return string The local storage path.
*/
public static function getFileFullPath(string $publicStorageFilePath): string
{
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$disk = Storage::disk('uploads');
$path = ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($publicStorageFilePath), '/'));
return $disk->path($path);
}
/**
* Destroy the file associate with the attribute.
*
@ -308,8 +322,7 @@ class FileStorageHelper
$folderPath = self::getStoragePath(
$private ? 'app/private/modelfiles' : 'app/public/modelfiles',
$tableName,
$filename,
$private
$filename
);
$filename = self::getFileUniqueName($folderPath, $filename);
return "{$folderPath}/{$filename}";
@ -404,7 +417,7 @@ class FileStorageHelper
$filename = Str::of($filename)->ltrim('.-');
// * Optional beautification
if ($beautify) {
$filename = static::beautifyFilename($filename);
$filename = self::beautifyFilename($filename);
}
// * Maximize filename length to 255 bytes http://serverfault.com/a/9548/44086
/** @var string */

View file

@ -21,6 +21,36 @@ use Symfony\Component\Finder\SplFileInfo;
class ToolboxHelper
{
/**
* Run a terminal command and gives the output
*
* @param array|string $command The command to execute on terminal.
* @param boolean $throw Wether to throw en esception on failure or not.
* @return string|false Return false on failure only if $throw is false.
* @phpcs:disable Squiz.Commenting.FunctionComment.EmptyThrows
* @phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
* @throws \Illuminate\Process\Exceptions\ProcessTimedOutException
* @throws \Symfony\Component\Process\Exception\LogicException
* @throws \Symfony\Component\Process\Exception\InvalidArgumentException
* @throws \Symfony\Component\Process\Exception\RuntimeException
* @throws \LogicException
* @throws \RuntimeException If the command fails.
*/
public static function runConsoleCommand(array|string $command, bool $throw = true): string | false
{
// phpcs:enable
$process = \Illuminate\Support\Facades\Process::command($command)->run();
if ($process->failed()) {
$code = $process->exitCode();
$ouput = $process->errorOutput();
if ($throw) {
throw new \RuntimeException("Failed to run command exit code {$code} with output \n{$ouput}");
}
return false;
}
return $process->output();
}
/**
* @param array<int|string, mixed> $array1
* @param array<int|string, mixed> $array2
@ -175,11 +205,10 @@ class ToolboxHelper
$url = $match;
try {
$request = Request::create($url);
// If a route is match then it do not throws exception.
if (
\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST) &&
$routes->match($request)
) {
// Assert route matches existing routes.
$routes->match($request);
// ? Is url internal ?
if (\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST)) {
// Removes the matched url.
throw ValidationException::withMessages([
$field => trans(
@ -205,14 +234,14 @@ class ToolboxHelper
* @param array|string $replace
* @param array|string $subject
* @param integer $count
* @return array|string
* @return array|string|false
*/
public static function mbReplace(
array|string $search,
array|string $replace,
array|string $subject,
int &$count = 0
): array|string {
): array|string|false {
if (!is_array($search) && is_array($replace)) {
return false;
}
@ -288,10 +317,17 @@ class ToolboxHelper
*/
public static function getOriginal(Model $model, string $attribute): mixed
{
if (\in_array('Spatie\Translatable\HasTranslations', \class_uses_recursive($model))) {
if (
\in_array('Spatie\Translatable\HasTranslations', \class_uses_recursive($model)) and
// @phpstan-ignore-next-line
$model->isTranslatableAttribute($attribute)
) {
// @phpstan-ignore-next-line
return $model->getTranslation(
$attribute,
// @phpstan-ignore-next-line
$model->getLocale(),
// @phpstan-ignore-next-line
$model->useFallbackLocale()
);
}
@ -361,47 +397,45 @@ class ToolboxHelper
/**
* Convert deeply an object to array
*
* @param array|object $input
* @param mixed $input
* @return array
* @throws \RuntimeException If $input var is a scalar value.
*/
public static function objectToArray($input): array
public static function objectToArray(mixed $input): array
{
// Only process if it's an object or array being passed to the function.
if (is_object($input) or is_array($input)) {
$ret = (array)$input;
foreach ($ret as &$item) {
// Recursively process EACH element only if not scalar.
$item = (is_object($item) or is_array($item)) ?
self::objectToArray($item) : $item;
}
return $ret;
} else {
if (!(is_object($input) or is_array($input))) {
throw new \RuntimeException('Cannot handle $input of type ' . \gettype($input));
}
$ret = (array)$input;
foreach ($ret as &$item) {
// Recursively process EACH element only if not scalar.
$item = (is_object($item) or is_array($item)) ?
self::objectToArray($item) : $item;
}
return $ret;
}
/**
* Convert deeply an array to object
*
* @param array|object $input
* @param mixed $input
* @return object
* @throws \RuntimeException If $input var is a scalar value.
*/
public static function arrayToObject($input): object
public static function arrayToObject(mixed $input): object
{
// Only process if it's an object or array being passed to the function.
if (is_object($input) or is_array($input)) {
$ret = (object)$input;
foreach ($ret as &$item) {
// Recursively process EACH element only if not scalar.
$item = (is_object($item) or is_array($item)) ?
self::arrayToObject($item) : $item;
}
return $ret;
} else {
if (!(is_object($input) or is_array($input))) {
throw new \RuntimeException('Cannot handle $input of type ' . \gettype($input));
}
$ret = (object)$input;
foreach ($ret as &$item) {
// Recursively process EACH element only if not scalar.
$item = (is_object($item) or is_array($item)) ?
self::arrayToObject($item) : $item;
}
return $ret;
}
/**
@ -409,11 +443,11 @@ class ToolboxHelper
*
* @param string $attribute
* @param string|null $value
* @param callback $fail
* @param callable $fail
* @param object $sizes
* @return void
*/
public static function validatePicture(string $attribute, string|null $value, callable $fail, object $sizes)
public static function validatePicture(string $attribute, string|null $value, callable $fail, object $sizes): void
{
if (!($useStorage = Storage::exists($value)) and !File::exists($value)) {
$fail(trans(':attribute n\'existe pas dans le système de fichier', [
@ -421,7 +455,6 @@ class ToolboxHelper
]));
return;
}
/** @var string */
$fileFullPath = self::getUploadedFileFullPath($value, $useStorage);
if (
!\in_array(self::getUploadedFileMimeType($value, $useStorage), [
@ -432,7 +465,7 @@ class ToolboxHelper
'attribute' => $attribute
]));
}
if (!($dimensions = \getimagesize($fileFullPath)) or !isset($dimensions[0]) or !isset($dimensions[1])) {
if (!($dimensions = \getimagesize($fileFullPath))) {
$fail(trans(':attribute impossible de valider les dimensions', [
'attribute' => $attribute
]));
@ -441,19 +474,19 @@ class ToolboxHelper
$height = intval($dimensions[1]);
$xMessage = trans(
":attribute la largeur doit être comprise entre {$sizes->minWith}px et {$sizes->maxWith}px"
":attribute la largeur doit être comprise entre {$sizes->minWidth}px et {$sizes->maxWidth}px"
);
if ($sizes->minWith === $sizes->maxWith) {
$xMessage = trans(":attribute la largeur doit être de {$sizes->minWith}px");
if ($sizes->minWidth === $sizes->maxWidth) {
$xMessage = trans(":attribute la largeur doit être de {$sizes->minWidth}px");
}
$yMessage = trans(
":attribute la hauteur doit être comprise entre {$sizes->minWith}px et {$sizes->maxWith}px"
":attribute la hauteur doit être comprise entre {$sizes->minHeight}px et {$sizes->maxHeight}px"
);
if ($sizes->minHeight === $sizes->maxHeight) {
$yMessage = trans(":attribute la hauteur doit être de {$sizes->minWith}px");
$yMessage = trans(":attribute la hauteur doit être de {$sizes->minHeight}px");
}
if (($width < $sizes->minWith) or ($width > $sizes->maxWith)) {
if (($width < $sizes->minWidth) or ($width > $sizes->maxWidth)) {
$fail($xMessage);
}
if (($height < $sizes->minHeight) or ($height > $sizes->maxHeight)) {

View file

@ -3,18 +3,17 @@
namespace App\Models;
use App\Enums\ActivityLogs\ActivityLogsEventEnum;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
/**
* @property string $model Target model.
* @property string $model_id Id of the target model.
* @property string $event Event of this activity (ActivityLogsEventEnum).
* @property string $data Changes.
* @property-read \Carbon\Carbon $created_at Created date.
* @property string $model Target model.
* @property string $model_id Id of the target model.
* @property \App\Enums\ActivityLogs\ActivityLogsEventEnum $event Event of this activity (ActivityLogsEventEnum).
* @property array $data Changes.
* @property-read \Illuminate\Support\Carbon $created_at Created date.
*
* @property-read \App\Models\User $user User BelongsTo relation.
*/
@ -82,37 +81,88 @@ class ActivityLog extends Model
$activity->model_id = $model->getKey();
$activity->event = $eventEnum;
$activity->data = static::getChangedColumns($activity, $model, $eventEnum);
$activity->created_at = Carbon::now();
// @phpstan-ignore-next-line
$activity->created_at = now();
$activity->saveOrFail();
}
/**
* Get old, new columns that has changed.
*
* @param \self $activity
* @param self $activity
* @param \Illuminate\Database\Eloquent\Model $model
* @param \App\Enums\ActivityLogs\ActivityLogsEventEnum $eventEnum
* @return array|null
* @return array
*/
public static function getChangedColumns(self $activity, Model $model, ActivityLogsEventEnum $eventEnum): array|null
public static function getChangedColumns(self $activity, Model $model, ActivityLogsEventEnum $eventEnum): array
{
$modelClassName = $activity->model_class;
/** Get type of each fields of the target model */
/** @var \Illuminate\Database\Eloquent\Model|null $targetModel */
$targetModel = $activity->model_class::where(
(new $modelClassName())->getRouteKeyName(),
$activity->model_id
)->first();
if ($targetModel != null) {
// phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
$targetModelTypes = collect($targetModel->toArray())
->map(function ($field, $fieldIndex) use ($targetModel) {
return Schema::getColumnType($targetModel->getTable(), $fieldIndex);
})->toArray();
// phpcs:enable
if (\is_null($targetModel)) {
return [];
}
$targetModelTypes = collect($targetModel->toArray())
->map(fn ($value) => self::getValueType($value))
->toArray();
/** Return old, new and type of values changed */
return ($eventEnum === ActivityLogsEventEnum::updated) ?
array_intersect_key($targetModelTypes, $model->getChanges()) : null;
return match ($eventEnum) {
ActivityLogsEventEnum::created => array_intersect_key($targetModelTypes, $model->toArray()),
ActivityLogsEventEnum::duplicated => array_intersect_key($targetModelTypes, $model->toArray()),
ActivityLogsEventEnum::updated => array_intersect_key($targetModelTypes, $model->getChanges()),
default => []
};
}
/**
* Get a value type as a string
* ! Use this for display purpose only
*
* @param mixed $value
* @return string
* @throws \RuntimeException If type is unhlanded.
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.MaxExceeded
*/
private static function getValueType(mixed $value): string
{
// phpcs:enable
$type = \gettype($value);
switch ($type) {
// phpcs:ignore PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
case 'string':
return match (true) {
Str::length(\strip_tags($value)) !== Str::length($value) => 'html',
Str::startsWith($value, 'storage/modelfiles') => 'file',
Str::isUuid($value) => 'uuid',
Str::isUlid($value) => 'ulid',
Str::isUrl($value) => 'url',
Str::isMatch('/^[0-9A-Za-z_-]{10}[048AEIMQUYcgkosw]$/', $value) => 'youtube_video_id',
\is_numeric($value) => 'numeric',
Str::isJson($value) and
(Str::startsWith($value, '[') or Str::startsWith($value, '{')) => 'json',
default => 'string'
};
case 'NULL':
case 'object':
case 'array':
case 'integer':
case 'boolean':
return $type;
case 'double':
return is_float($value) ? 'float' : 'double';
case 'resource (closed)':
case 'resource':
throw new \RuntimeException('Laravel model properties should not contain resources !');
case 'unknown type':
default:
throw new \RuntimeException('Unhandled Type ' . \gettype($value));
} //end switch
}
// * RELATIONS
@ -122,7 +172,7 @@ class ActivityLog extends Model
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(User::class);
}

View file

@ -12,12 +12,12 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
/**
* @property App\Enums\Sections\PageSectionEnum $section Slider that contains the slide.
* @property string $h2_title Title.
* @property string $text WYSIWYG - Content.
* @property string $data Minicrud content.
* @property-read \Carbon\Carbon $created_at Created date.
* @property-read \Carbon\Carbon $updated_at Updated date.
* @property \App\Enums\Sections\PageSectionEnum $section Slider that contains the slide.
* @property string $h2_title Title.
* @property string $text WYSIWYG - Content.
* @property string $data Minicrud content.
* @property-read \Carbon\Carbon $created_at Created date.
* @property-read \Carbon\Carbon $updated_at Updated date.
*
* @property-read \App\Models\StaticPage $page StaticPage Belongs to relationship.
@ -83,17 +83,6 @@ class Section extends Model
// * METHODS
/**
* Manage model fields.
*
* @param self $model
* @return void
*/
private static function manageFields(self $model): void
{
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
}
/**
* Get MiniCrud bloc for home_missions section.
*

View file

@ -101,7 +101,7 @@ class Site extends Model
/**
* The attributes that should be cast.
*
* @var array
* @var array<string, string>
*/
protected $casts = [
'pictos' => 'array',
@ -200,54 +200,6 @@ class Site extends Model
ToolboxHelper::assertFieldIsUnique($table, $nameField, $valueField, $id);
}
/**
* Adjusts model status using current status and publish dates
*
* @param self $model
* @return void
*/
private static function makeStatusSense(self $model): void
{
/** @var \Carbon\Carbon|null */
$publish_at = $model->publish_at;
/** @var \Carbon\Carbon|null */
$unpublish_at = $model->unpublish_at;
$wantedStatus = $model->published;
if ($publish_at or $unpublish_at) {
$model->published = $model->planificated_status;
}
if ($wantedStatus !== $model->published) {
$previousMessage = session()->get('bo.info', '');
session()->flash('bo.info', \trans(
'La publication est modifiée en concordance avec les dates enregistrées.
Pour modifier l\'état de publication, veuillez modifier ou enlever les dates correspondantes.'
) . " \n{$previousMessage}");
}
}
/**
* Getter the current status following planification dates planificated_status
*
* @return boolean
*/
public function getPlanificatedStatusAttribute(): bool
{
/** @var \Carbon\Carbon|null */
$publish_at = $this->publish_at;
/** @var \Carbon\Carbon|null */
$unpublish_at = $this->unpublish_at;
if ($publish_at and $unpublish_at) {
return $unpublish_at->greaterThanOrEqualTo(now()) and $publish_at->lessThan(now());
}
if ($publish_at) {
return $publish_at->lessThan(now());
}
if ($unpublish_at) {
return $unpublish_at->greaterThanOrEqualTo(now());
}
return false;
}
// * RELATIONS
/**

View file

@ -7,20 +7,20 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* @property App\Enums\Slides\SliderTypeEnum $slider Slider that contains the slide.
* @property string $title Title.
* @property string $content WYSIWYG - Content.
* @property string $video_id Id of the Youtube video.
* @property boolean $button_status Is the button visible ?.
* @property string $button_label Button label, max 35 chars.
* Required if button_status == true.
* @property string $button_url Button URL. equired if the button is visible.
* @property boolean $button_new_tab Does the link need to be opened in a new tab ?
* @property string $picture Path of the image.
* @property string $picture_alt Alt attribute of the image.
* @property string $picture_title Title attribute of the image.
* @property-read \Carbon\Carbon $created_at Created date.
* @property-read \Carbon\Carbon $updated_at Updated date.
* @property \App\Enums\Slides\SliderTypeEnum $slider Slider that contains the slide.
* @property string $title Title.
* @property string $content WYSIWYG - Content.
* @property string $video_id Id of the Youtube video.
* @property boolean $button_status Is the button visible ?.
* @property string $button_label Button label, max 35 chars.
* Required if button_status == true.
* @property string $button_url Button URL. equired if the button is visible.
* @property boolean $button_new_tab Does the link need to be opened in a new tab ?
* @property string $picture Path of the image.
* @property string $picture_alt Alt attribute of the image.
* @property string $picture_title Title attribute of the image.
* @property-read \Carbon\Carbon $created_at Created date.
* @property-read \Carbon\Carbon $updated_at Updated date.
*/
class Slide extends Model
{

View file

@ -16,10 +16,8 @@ use Illuminate\Support\Str;
* @property string $title Title for the page.
* @property string $slug Slug of the title for the page.
* @property string $content WYSIWYG text content using ckeditor.
* @property-read \Carbon\Carbon $created_at Created date.
* @property-read \Carbon\Carbon $updated_at Updated date.
*
* @property-read \App\Models\Section $sections Sections Has Many relationship.
* @property-read \Illuminate\Support\Carbon $created_at Created date.
* @property-read \Illuminate\Support\Carbon $updated_at Updated date.
*/
class StaticPage extends Model
{
@ -54,21 +52,48 @@ class StaticPage extends Model
*/
protected static function booted(): void
{
static::creating(function () {
throw new \RuntimeException("Creating a new page is not authorized");
});
static::updating(function (self $model) {
static::creating(function (self $model) {
// * ASSERTIONS
static::assertTitleIsUnique($model->title, $model->id);
self::assertTitleIsUnique($model->title);
// * CHANGES
$model->slug = Str::slug($model->title);
});
static::deleting(function () {
throw new \RuntimeException('Deleting a page is not authorized');
static::updating(function (self $model) {
// * ASSERTIONS
self::assertTitleIsUnique($model->title, $model->id);
// * CHANGES
$model->slug = Str::slug($model->title);
});
}
// * ROUTABLE
/**
* Get the value of the model's route key.
*
* @return mixed
*/
public function getRouteKey()
{
/** @var \App\Enums\StaticPageType */
$enum = $this->getAttribute($this->getRouteKeyName());
return $enum->value();
}
/**
* Get the route key for the model.
*
* ! Type is the routable parameter
*
* @return string
*/
public function getRouteKeyName(): string
{
return 'type';
}
// * METHODS
/**
@ -85,16 +110,4 @@ class StaticPage extends Model
ToolboxHelper::assertFieldIsUnique($table, 'title', $title, $id);
ToolboxHelper::assertFieldIsUnique($table, 'slug', Str::slug($title), $id);
}
// * RELATIONS
/**
* Get the sections of the page.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function sections(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Section::class);
}
}

View file

@ -7,15 +7,15 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* @property string $name Name of the person.
* @property string $job Function of the person.
* @property string $content WYSIWYG - Content.
* @property App\Enums\Testimonial\TypeEnum $type Type of the testimonial.
* @property string $picture Path of the image.
* @property string $picture_alt Alt attribute of the image.
* @property string $picture_title Title attribute of the image.
* @property-read \Carbon\Carbon $created_at Created date.
* @property-read \Carbon\Carbon $updated_at Updated date.
* @property string $name Name of the person.
* @property string $job Function of the person.
* @property string $content WYSIWYG - Content.
* @property \App\Enums\Testimonials\TypeEnum $type Type of the testimonial.
* @property string $picture Path of the image.
* @property string $picture_alt Alt attribute of the image.
* @property string $picture_title Title attribute of the image.
* @property-read \Carbon\Carbon $created_at Created date.
* @property-read \Carbon\Carbon $updated_at Updated date.
*/
class Testimonial extends Model
{

View file

@ -4,21 +4,22 @@ namespace App\Models;
use App\Enums\Users\RoleEnum;
use App\Traits\Models\ActivityLog;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
/**
* @property string $first_name Prénom.
* @property string $last_name Nom.
* @property string $first_name Firstname.
* @property string $last_name Lastname.
* @property string $email Email.
* @property string $password Mot de passe.
* @property \App\Enums\Users\RoleEnum $role Rôle.
* @property \Illuminate\Support\Carbon $published État de publication.
* @property \Illuminate\Support\Carbon $published_at Date de mise à jour de l'état de publication.
* @property-read \Carbon\Carbon $created_at Created date.
* @property-read \Carbon\Carbon $updated_at Updated date.
* @property string $password Password.
* @property \App\Enums\Users\RoleEnum $role Role.
* @property \Illuminate\Support\Carbon $published Published status.
* @property \Illuminate\Support\Carbon $published_at Publication status update date.
* @property-read \Illuminate\Support\Carbon $created_at Created date.
* @property-read \Illuminate\Support\Carbon $updated_at Updated date.
*
* @property-read \App\Models\ActivityLog $activityLogs Activities logs One-to-many relationship.
*/
@ -62,6 +63,7 @@ class User extends Authenticatable
'role' => RoleEnum::class,
'email_verified_at' => 'datetime',
'published' => 'boolean',
'published_at' => 'datetime',
];
/**
@ -72,7 +74,7 @@ class User extends Authenticatable
protected static function booted(): void
{
static::creating(function (self $model) {
static::updatePassword($model);
self::updatePassword($model);
// * Prevent user elevation privileges
throw_if(
auth('backend')->user() and auth('backend')->user()->role->value() > $model->role->value(),
@ -80,12 +82,12 @@ class User extends Authenticatable
);
});
static::updating(function (self $model) {
static::updatePassword($model);
self::updatePassword($model);
if (optional(auth('backend')->user())->getKey() === $model->getKey()) {
\validator(
['published' => $model->published],
['published' => 'required|boolean|accepted'],
['published.accepted' => trans('Vous ne pouvez pas déactiver votre propre compte')],
['published.accepted' => trans('crud.messages.user_unpublished')],
)->validate();
}
// * Prevent user elevation privileges

View file

@ -2,8 +2,6 @@
namespace App\Providers;
use App\Models\News;
use App\Models\Strip;
use App\Models\User;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
@ -44,8 +42,6 @@ class RouteServiceProvider extends ServiceProvider
});
Route::model('user', User::class);
Route::model('news', News::class);
Route::model('strip', Strip::class);
}
/**

View file

@ -2,7 +2,7 @@
namespace App\Providers;
use Illuminate\Support\Collection;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\ServiceProvider;
@ -18,33 +18,45 @@ class TranslationServiceProvider extends ServiceProvider
public function boot(): void
{
foreach ($this->collectLocalesStrings() as $locale) {
// * Suported locales
// * Suported locales.
Cache::rememberForever(sprintf('translations.%s', $locale), function () use ($locale) {
$translations = [
'php' => $this->phpTranslations($locale),
'json' => $this->jsonTranslations($locale),
$phpTransFallback = $this->phpTranslations(config('app.fallback_locale'));
$phpTransLocale = $this->phpTranslations($locale);
$jsonTransFallback = $this->jsonTranslations(config('app.fallback_locale'));
$jsonTransLocale = $this->jsonTranslations($locale);
$translations = [
'php' => ToolboxHelper::arrayMergeRecursiveDistinct(
$phpTransFallback,
$phpTransLocale,
),
'json' => ToolboxHelper::arrayMergeRecursiveDistinct(
$jsonTransFallback,
$jsonTransLocale,
),
];
return $translations;
});
}
} //end foreach
}
/**
* Gather provided langs checking from files
* Gather provided langs checking from files.
*
* @return \Illuminate\Support\Collection<string, string>
*/
private function collectLocalesStrings(): Collection
private function collectLocalesStrings(): \Illuminate\Support\Collection
{
// * En dur dans ->keys array<int,string>.
// @phpstan-ignore-next-line
return collect(File::allFiles(resource_path('lang/')))->flatMap(function (SplFileInfo $file) {
if ($file->getRelativePath() and \strpos($file->getRelativePath(), 'vendor') !== 0) {
return [$file->getRelativePath() => ''];
}
})->keys();
})->keys()->merge(config('app.locales', []));
}
/**
* Gather all php translations
* Gather all php translations.
*
* @param string $locale
* @return array<string, string>
@ -53,6 +65,10 @@ class TranslationServiceProvider extends ServiceProvider
{
$path = resource_path("lang/$locale");
if (!File::exists($path) or !File::isReadable($path)) {
return [];
}
return collect(File::allFiles($path))->flatMap(function ($file) use ($locale) {
$translation = $file->getBasename('.php');
return [$translation => trans($translation, [], $locale)];
@ -60,7 +76,7 @@ class TranslationServiceProvider extends ServiceProvider
}
/**
* Gather json translations
* Gather json translations.
*
* @param string $locale
* @return array<string, string>
@ -69,10 +85,10 @@ class TranslationServiceProvider extends ServiceProvider
{
$path = resource_path("lang/$locale.json");
if (is_string($path) && is_readable($path)) {
return json_decode(file_get_contents($path), true) ?? [];
if (!File::exists($path) or !File::isReadable($path)) {
return [];
}
return [];
return json_decode(File::get($path), true) ?? [];
}
}

View file

@ -0,0 +1,43 @@
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Str;
/**
* Validate a vimeo ID regex
*
* @package App\Rules
*/
class IsValidVimeoId implements ValidationRule
{
/**
* Run the validation rule.
*
* @param string $attribute
* @param mixed $value
* @param \Closure(string):\Illuminate\Translation\PotentiallyTranslatedString $fail
* @return void
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
* @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint
*/
public function validate(string $attribute, mixed $value, \Closure $fail): void
{
// phpcs:enable
if (!self::validates($value)) {
$fail(trans('validation.vimeo_id'));
}
}
/**
* Validate a value
*
* @param mixed $value
* @return boolean
*/
public static function validates(mixed $value): bool
{
return Str::of($value)->isMatch('/^[0-9]{8,10}$/');
}
}

View file

@ -0,0 +1,44 @@
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Str;
/**
* Validate a youtube ID regex
*
* @url https://webapps.stackexchange.com/questions/54443/format-for-id-of-youtube-video
* @package App\Rules
*/
class IsValidYoutubeId implements ValidationRule
{
/**
* Run the validation rule.
*
* @param string $attribute
* @param mixed $value
* @param \Closure(string):\Illuminate\Translation\PotentiallyTranslatedString $fail
* @return void
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
* @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint
*/
public function validate(string $attribute, mixed $value, \Closure $fail): void
{
// phpcs:enable
if (!self::validates($value)) {
$fail(trans('validation.youtube_id'));
}
}
/**
* Validate a value
*
* @param mixed $value
* @return boolean
*/
public static function validates(mixed $value): bool
{
return Str::of($value)->isMatch('/^[0-9A-Za-z_-]{10}[048AEIMQUYcgkosw]$/');
}
}

View file

@ -3,7 +3,7 @@
namespace App\Traits\Controllers;
use App\Http\Requests\Bo\UpdateChangeOrderRequest;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
@ -14,22 +14,31 @@ trait ChangesModelOrder
* Change order.
*
* @param \App\Http\Requests\Bo\UpdateChangeOrderRequest $request
* @param \Illuminate\Database\Eloquent\Model $model
* @param mixed ...$params
* @return \Illuminate\Http\RedirectResponse
* @throws \RuntimeException Si le nom du parametre de la route ne correspond pas au model utilisé.
* @throws \RuntimeException Si le nom du parametre de la route ne correspond pas au params utilisé.
*/
public function changeOrder(UpdateChangeOrderRequest $request, Model $model): \Illuminate\Http\RedirectResponse
public function changeOrder(UpdateChangeOrderRequest $request, mixed ...$params): \Illuminate\Http\RedirectResponse
{
$params = collect(request()->route()->parameterNames);
$className = Str::ucfirst(Str::lower($params->get($params->count() - 2)));
$className = "\\App\\Models\\$className";
$paramNames = collect(request()->route()->parameterNames);
$className = Str::of($paramNames->get($paramNames->count() - 2))->lower()->studly();
$className = "\\App\\Models\\{$className}";
$model = Arr::get($params, $paramNames->count() - 2);
if (!class_exists($className)) {
throw new \RuntimeException("The $className class doesn't exist.");
throw new \RuntimeException("The {$className} class doesn't exist.");
}
if (!\is_object($model)) {
throw new \RuntimeException("The second last param isn't an object.");
}
if (get_class($model) !== (string)Str::of($className)->replaceStart('\\', '')) {
throw new \RuntimeException("The {$className} doesn't match second last binded param.");
}
/** @var \Illuminate\Database\Eloquent\Model|null */
$model = $className::where($model->getRouteKeyName(), $model->getRouteKey())->first();
$model = $className::query()->where($model->getRouteKeyName(), $model->getRouteKey())->first();
if (!$model) {
return back()->with('error', trans('crud.messages.order_not_changed'));
@ -37,18 +46,16 @@ trait ChangesModelOrder
return DB::transaction(function () use ($request, $className, $model) {
/** @var \Illuminate\Database\Eloquent\Model|null */
$tmp = $className::where('order', $request->direction ? '<' : '>', $model->order)
$tmp = $className::where('order', $request->direction ? '<' : '>', $model->getOriginal('order'))
->orderBy('order', $request->direction ? 'DESC' : 'ASC')->first();
if (!$tmp) {
return back()->with('error', trans('crud.messages.order_not_changed'));
}
$newOrder = $tmp->order;
$tmp->order = $model->order;
$model->order = $newOrder;
$tmp->saveOrFail();
$model->saveOrFail();
$newOrder = $tmp->getOriginal('order');
$tmp->update(['order' => $model->getOriginal('order')]);
$model->update(['order' => $newOrder]);
Session::flash('success', trans('crud.messages.order_changed'));
return back();

View file

@ -19,8 +19,7 @@ trait UpdateModelPublished
*/
public function changePublished(Model $model)
{
$model->published = !$model->published;
$model->saveOrFail();
$model->update(['published' => !$model->getOriginal('published')]);
return redirect()->back()->with('success', trans(__('crud.messages.publish_status_saved')));
}
}

View file

@ -89,7 +89,7 @@ trait BaseEnum
/**
* Get enums.
*
* @return array
* @return array<self>
*/
public static function toArray(): array
{
@ -118,9 +118,9 @@ trait BaseEnum
/**
* Fetch enum using value.
*
* @param \App\Traits\Enums\BaseEnum|integer|string $value
* @param string $field
* @return \App\Traits\Enums\BaseEnum|null
* @param self|integer|string $value
* @param string $field
* @return self|null
*/
public static function make(self|int|string $value, string $field = 'value'): self|null
{

View file

@ -10,7 +10,8 @@ use Illuminate\Support\Facades\Cache;
/**
* ! Use this only on limited elements, (not on tags, or products, or posts for ex.)
*
* @property private static array $cacheConditions Eloquent condition to cache elements (to be used in where statement).
* @property array<string,string|integer|float|boolean> $cacheConditions
* Eloquent condition to cache elements (to be used in where statement).
*/
trait IsCachedAsCollection
{

View file

@ -11,8 +11,9 @@ trait MiniCrudModel
/**
* Fill the model with an array of attributes.
*
* @param array $attributes
* @return void
* @param array $attributes
* @return $this
*
* @throws \Illuminate\Database\Eloquent\MassAssignmentException If fill prop is not allowed.
* @phpcs:disable Squiz.Commenting.FunctionComment.EmptyThrows
*/
@ -23,6 +24,7 @@ trait MiniCrudModel
if (isset($attributes['data'])) {
$this->setData($attributes['data']);
}
return $this;
}
/**
@ -62,7 +64,7 @@ trait MiniCrudModel
*/
public function minicrudUpdated()
{
/** @var array */
/** @var array $input */
foreach ($this->data as $index => $input) {
switch ($input['inputType']) {
case InputType::text->value:
@ -166,7 +168,7 @@ trait MiniCrudModel
case InputType::string->value:
case InputType::text->value:
case InputType::url->value:
ToolboxHelper::assertNoInternalBoUrls('content', $input['value'] ?? '');
ToolboxHelper::assertNoInternalBoUrls('content', $input['value']);
break;
default:
throw new \ErrorException(sprintf(

View file

@ -14,14 +14,14 @@ trait HasPicture
*
* @var integer
*/
private $minWith = 400;
private $minWidth = 400;
/**
* Maximum width
*
* @var integer
*/
private $maxWith = 1800;
private $maxWidth = 1800;
/**
* Minimum height
@ -41,38 +41,41 @@ trait HasPicture
* Image rules
*
* @param string $name
* @param integer|null $minWith
* @param integer|null $minWidth
* @param integer|null $minHeight
* @param integer|null $maxWith
* @param integer|null $maxWidth
* @param integer|null $maxHeight
* @param integer|null $size
* @param boolean $nullable
* @param integer|null $size
* @param string|null $requiredWith
* @param string|null $requiredWithout
* @return array
*/
public function pictureRules(
string $name = 'picture',
?int $minWith = null,
?int $minWidth = null,
?int $minHeight = null,
?int $maxWith = null,
?int $maxWidth = null,
?int $maxHeight = null,
?int $size = null,
bool $nullable = false,
?int $size = null,
?string $requiredWith = null,
?string $requiredWithout = null,
): array {
$dimensions = (object)[
'minWith' => $minWith ?? $this->minWith,
'minWidth' => $minWidth ?? $this->minWidth,
'minHeight' => $minHeight ?? $this->minHeight,
'maxWith' => $maxWith ?? $this->maxWith,
'maxWidth' => $maxWidth ?? $this->maxWidth,
'maxHeight' => $maxHeight ?? $this->maxHeight
];
return [
$name => [
!$nullable ? 'required' : 'nullable',
(strlen($requiredWith)) ? 'required_with:' . $requiredWith : '',
(strlen($requiredWithout)) ? 'required_without:' . $requiredWithout : '',
($size) ? 'max:' . $size : '',
function (string $attribute, $value, callable $fail) use ($dimensions) {
return ToolboxHelper::validatePicture($attribute, $value, $fail, $dimensions);
ToolboxHelper::validatePicture($attribute, $value, $fail, $dimensions);
}
]
];
@ -82,32 +85,38 @@ trait HasPicture
* Images rules
*
* @param string $name
* @param integer|null $minWith
* @param integer|null $minWidth
* @param integer|null $minHeight
* @param integer|null $maxWith
* @param integer|null $maxWidth
* @param integer|null $maxHeight
* @param integer $limitMin
* @param integer $limitMax
* @param boolean $nullable
* @return array
*/
public function picturesRules(
string $name = 'pictures',
?int $minWith = null,
?int $minWidth = null,
?int $minHeight = null,
?int $maxWith = null,
?int $maxHeight = null
?int $maxWidth = null,
?int $maxHeight = null,
int $limitMin = 1,
int $limitMax = 5,
bool $nullable = false
): array {
$sizes = (object)[
'minWith' => $minWith ?? $this->minWith,
'minWidth' => $minWidth ?? $this->minWidth,
'minHeight' => $minHeight ?? $this->minHeight,
'maxWith' => $maxWith ?? $this->maxWith,
'maxHeight' => $maxHeight ?? $this->maxHeight
'maxWidth' => $maxWidth ?? $this->maxWidth,
'maxHeight' => $maxHeight ?? $this->maxHeight,
];
return [
$name => 'sometimes|array',
$name => [!$nullable ? 'required' : 'nullable', 'array', "between:{$limitMin},{$limitMax}"],
"{$name}Validate" => "required_with:{$name}|array",
"{$name}Validate.*" => [
'required',
function (string $attribute, $value, callable $fail) use ($sizes) {
return $this->validatePicture($attribute, $value, $fail, $sizes);
ToolboxHelper::validatePicture($attribute, $value, $fail, $sizes);
}
]
];

View file

@ -1,8 +1,16 @@
{
"name": "uwil/volena",
"version": "2.0.0",
"name": "uwil/laravel-base-project",
"version": "1.0.0",
"type": "project",
"description": "Volena website.",
"description": "Laravel base project reference.",
"support": {
"email": "equipe.web@ldc.fr"
},
"keywords": [
"framework",
"laravel"
],
"license": "MIT",
"require": {
"php": "^8.2",
"diglactic/laravel-breadcrumbs": "^8.0",
@ -11,12 +19,14 @@
"laravel/tinker": "^2.8",
"laravel/ui": "^4.2",
"mews/purifier": "^3.4",
"sentry/sentry-laravel": "*"
"sentry/sentry-laravel": "*",
"uwil/k8s-laravel": "^1.2"
},
"require-dev": {
"fakerphp/faker": "^1.9.1",
"kitloong/laravel-migrations-generator": "^6.11",
"kitloong/laravel-migrations-generator": "^6.8",
"krlove/eloquent-model-generator": "dev-l10-compatibility",
"larastan/larastan": "^2.6",
"laravel-lang/common": "^4.0",
"laravel/pint": "^1.0",
"laravel/sail": "^1.18",
@ -38,6 +48,10 @@
{
"type": "vcs",
"url": "https://github.com/laravel-shift/eloquent-model-generator.git"
},
{
"type": "composer",
"url": "https://lab.s-web.fr/api/v4/group/23/-/packages/composer/packages.json"
}
],
"autoload-dev": {
@ -73,7 +87,10 @@
"pestphp/pest-plugin": true,
"php-http/discovery": true,
"dealerdirect/phpcodesniffer-composer-installer": true
}
},
"gitlab-domains": [
"lab.s-web.fr"
]
},
"minimum-stability": "stable",
"prefer-stable": true

3031
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -111,7 +111,10 @@ return [
'theme' => 'default',
'paths' => [
resource_path('views/vendor/mail'),
// * For custom compiled css.
public_path('build/vendor/mail'),
// * For generic customized blade (moteur seulement)
resource_path('views/vendor/mail')
],
],

View file

@ -30,13 +30,13 @@ return [
'URI.SafeIframeRegexp' => "%^(http://|https://|//)(www.youtube.com/embed/|player.vimeo.com/video/|vimeo.com/|www.dailymotion.com/video/)%",
'HTML.Doctype' => 'HTML 4.01 Transitional',
// * Legacy filter en full avec safe legacy.
'HTML.Allowed' => 'h1[style|id],h2[style|id],h3[style|id],h4[style|id],h5[style|id],h6[style|id],figure[class|style],oembed[url],div[style],b,strong,i,em,u,s,pre,code[class],a[href|title|target|rel|id],ul[style],ol[style],dl[style],li,p[style],br,span[style|class],img[width|height|alt|src|style|class|title],iframe[src|width|height|class|frameborder|style],table[style|align|border|cellspacing|cellpadding|width],thead,tbody,th[colspan|align|valign|style],tr[align|valign|style],td[style|colspan|rowspan|scope|align|valign],hr,dl,dt,mark[class]',
// 'HTML.Allowed' => 'h1[style|id],h2[style|id],h3[style|id],h4[style|id],h5[style|id],h6[style|id],figure[class|style],oembed[url],div[style],b,strong,i,em,u,s,pre,code[class],a[href|title|target|rel|id],ul[style],ol[style],dl[style],li,p[style],br,span[style|class],img[width|height|alt|src|style|class|title],iframe[src|width|height|class|frameborder|style],table[style|align|border|cellspacing|cellpadding|width],thead,tbody,th[colspan|align|valign|style],tr[align|valign|style],td[style|colspan|rowspan|scope|align|valign],hr,dl,dt,mark[class]',
// * Restricted basics.
// 'HTML.Allowed' => 'br,p[style],b[style],u[style],i[style],a[href|title|target|rel],strong[style],span[class],ul[style],ol[style],li[style],h2[style],h3[style],h4[style],hr,blockquote[style]',
'HTML.Allowed' => 'br,p[style],b[style],u[style],i[style],a[href|title|target|rel],strong[style],span[class],ul[style],ol[style],li[style],h2[style],h3[style],h4[style],hr,blockquote[style]',
// * Legacy filter en full avec safe legacy.
'CSS.AllowedProperties' => 'font,font-size,font-weight,font-style,font-family,text-decoration,padding-left,color,background-color,text-align,list-style-type,float,width,height,vertical-align,border,border-color,margin-left',
// 'CSS.AllowedProperties' => 'font,font-size,font-weight,font-style,font-family,text-decoration,padding-left,color,background-color,text-align,list-style-type,float,width,height,vertical-align,border,border-color,margin-left',
// * Restricted basics.
// 'CSS.AllowedProperties' => 'text-align,margin-left',
'CSS.AllowedProperties' => 'text-align,margin-left',
'AutoFormat.AutoParagraph' => false,
'AutoFormat.RemoveEmpty' => false,
'AutoFormat.RemoveEmpty.RemoveNbsp' => false,

View file

@ -13,12 +13,12 @@ class UserFactory extends Factory
*
* @return array<string, mixed>
*/
public function definition()
public function definition(): array
{
$published = $this->faker->boolean(75);
return [
'first_name' => fake()->firstname(),
'last_name' => fake()->lastname(),
'first_name' => fake()->firstname(),
'last_name' => fake()->lastname(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
@ -36,7 +36,7 @@ class UserFactory extends Factory
*/
public function unverified(): static
{
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
return $this->state(fn (array $attributes) => [
'email_verified_at' => null,
]);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

View file

@ -23,7 +23,7 @@ return new class extends Migration
$table->rememberToken();
$table->unsignedTinyInteger('role')->comment('Role of the user who defines the rights/access.');
$table->boolean('published')->comment('The user is acivated or not.');
$table->timestamp('published_at')->nullable()->comment('The date on which the user was acivated.');
$table->timestamp('published_at')->nullable()->comment('The date on which the user was activated.');
$table->timestamps();
});
}

View file

@ -15,7 +15,7 @@ return new class extends Migration
{
Schema::create('activity_logs', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->nullable()->constrained('users')
$table->foreignId('user_id')->nullable()->constrained('users')->nullOnDelete()
->comment('Id of the user associated, or nullable for a deleted user.');
$table->boolean('is_anonymous')->comment('If there is no user connected when action was realised.');
$table->boolean('is_console')->comment('If the action was realised in console.');

View file

@ -2,7 +2,6 @@
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
@ -16,22 +15,14 @@ return new class extends Migration
{
Schema::create('static_pages', function (Blueprint $table) {
$table->id();
$table->unsignedTinyInteger('type')->unique()->comment('Page type (StaticPageEnum)');
$table->string('seo_title')->comment('Seo title about 70 chars as recommended');
$table->string('seo_description')->comment('Seo description about 160 chars as recommended');
$table->string('title')->comment('Title for the page');
$table->string('slug')->unique()->comment('Unique title slug');
$table->text('content')->comment('WYSIWYG text content using ckeditor');
$table->timestamps();
});
Artisan::call('db:seed', [
'class' => 'StaticPageSeeder'
]);
}
/**

View file

@ -0,0 +1,30 @@
<?php
use Database\Seeders\MandatorySeeder;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Artisan;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Artisan::call('db:seed', [
'class' => MandatorySeeder::class
]);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
// Do nothing.
}
};

View file

@ -14,16 +14,8 @@ class DatabaseSeeder extends Seeder
public function run()
{
$this->call([
UserSeeder::class,
HistoricalMilestoneSeeder::class,
JobVideoSeeder::class,
LogoSeeder::class,
NewsSeeder::class,
SectionSeeder::class,
SiteSeeder::class,
SlideSeeder::class,
StripSeeder::class,
TestimonialSeeder::class
MandatorySeeder::class,
DevelopmentSeeder::class,
]);
}
}

View file

@ -1,6 +1,6 @@
<?php
namespace Database\Seeders;
namespace Database\Seeders\Development;
use App\Lib\Helpers\FileStorageHelper;
use App\Models\HistoricalMilestone;
@ -16,11 +16,12 @@ class HistoricalMilestoneSeeder extends Seeder
public function run()
{
FileStorageHelper::removeModelFiles(new HistoricalMilestone());
// @phpstan-ignore-next-line
HistoricalMilestone::factory(10)->make()->each(function (HistoricalMilestone $historicalMilestone) {
$historicalMilestone->picture = FileStorageHelper::storeFile(
new HistoricalMilestone(),
new \SplFileInfo(\resource_path(
'../database/factories/assets/historical_milestones/default-picture-100x100.png'
'../database/factories/assets/historical_milestones/default-picture-606x800.jpg'
))
);
$historicalMilestone->save();

View file

@ -1,6 +1,6 @@
<?php
namespace Database\Seeders;
namespace Database\Seeders\Development;
use App\Models\JobVideo;
use Illuminate\Database\Seeder;

View file

@ -1,6 +1,6 @@
<?php
namespace Database\Seeders;
namespace Database\Seeders\Development;
use App\Lib\Helpers\FileStorageHelper;
use App\Models\Logo;
@ -16,11 +16,12 @@ class LogoSeeder extends Seeder
public function run()
{
FileStorageHelper::removeModelFiles(new Logo());
// @phpstan-ignore-next-line
Logo::factory(10)->make()->each(function (Logo $logo) {
$logo->picture = FileStorageHelper::storeFile(
new Logo(),
new \SplFileInfo(\resource_path(
'../database/factories/assets/logos/default-picture-100x100.png'
'../database/factories/assets/logos/default-picture-331x240.jpg'
))
);
$logo->save();

View file

@ -1,11 +1,16 @@
<?php
namespace Database\Seeders;
namespace Database\Seeders\Development;
use App\Lib\Helpers\FileStorageHelper;
use App\Models\News;
use Illuminate\Database\Seeder;
/**
* FIXME: Definir les tailes d'images pour les news
*
* @package Database\Seeders\Development
*/
class NewsSeeder extends Seeder
{
/**
@ -16,6 +21,7 @@ class NewsSeeder extends Seeder
public function run()
{
FileStorageHelper::removeModelFiles(new News());
// @phpstan-ignore-next-line
News::factory(10)->make()->each(function (News $news) {
$news->thumbnail = FileStorageHelper::storeFile(
new News(),

View file

@ -1,6 +1,6 @@
<?php
namespace Database\Seeders;
namespace Database\Seeders\Development;
use App\Enums\Sections\PageSectionEnum;
use App\Enums\StaticPageType;
@ -10,6 +10,11 @@ use App\Models\StaticPage;
use Illuminate\Database\Seeder;
use Illuminate\Support\Str;
/**
* FIXME: Definir les tailes d'images pour les sections
*
* @package Database\Seeders\Development
*/
class SectionSeeder extends Seeder
{
/**
@ -22,8 +27,9 @@ class SectionSeeder extends Seeder
FileStorageHelper::removeModelFiles(new Section());
foreach (PageSectionEnum::cases() as $sectionEnum) {
// @phpstan-ignore-next-line
Section::factory(1)->make()->each(function (Section $section) use ($sectionEnum) {
$section->section = $sectionEnum->value;
$section->section = $sectionEnum;
$section->page_id = $this->getPageId($sectionEnum);
if ($sectionEnum === PageSectionEnum::hire_grow) {
$section->text = '';
@ -113,13 +119,13 @@ class SectionSeeder extends Seeder
FileStorageHelper::storeFile(
new Section(),
new \SplFileInfo(\resource_path(
'../database/factories/assets/sections/default-picture-100x100.png'
'../database/factories/assets/sections/home-missions-430x430.jpg'
))
),
fake()->sentence(),
fake()->sentence(),
100,
100,
430,
430,
fake()->url(),
fake()->randomNumber(),
fake()->randomNumber()
@ -139,13 +145,13 @@ class SectionSeeder extends Seeder
FileStorageHelper::storeFile(
new Section(),
new \SplFileInfo(\resource_path(
'../database/factories/assets/sections/default-picture-100x100.png'
'../database/factories/assets/sections/home-team-465x525.jpg'
))
),
fake()->sentence(),
fake()->sentence(),
100,
100,
465,
525,
fake()->url(),
);
}

View file

@ -1,12 +1,17 @@
<?php
namespace Database\Seeders;
namespace Database\Seeders\Development;
use App\Lib\Helpers\FileStorageHelper;
use App\Models\Logo;
use App\Models\Site;
use Illuminate\Database\Seeder;
/**
* FIXME: Definir les tailes d'images pour les sites
*
* @package Database\Seeders\Development
*/
class SiteSeeder extends Seeder
{
/**
@ -17,6 +22,7 @@ class SiteSeeder extends Seeder
public function run()
{
FileStorageHelper::removeModelFiles(new Site());
// @phpstan-ignore-next-line
Site::factory(10)->make()->each(function (Site $site) {
$site->logo = FileStorageHelper::storeFile(
new Site(),

View file

@ -1,12 +1,17 @@
<?php
namespace Database\Seeders;
namespace Database\Seeders\Development;
use App\Enums\Slides\SliderTypeEnum;
use App\Lib\Helpers\FileStorageHelper;
use App\Models\Slide;
use Illuminate\Database\Seeder;
/**
* FIXME: Definir les tailes d'images pour les slides
*
* @package Database\Seeders\Development
*/
class SlideSeeder extends Seeder
{
/**
@ -18,8 +23,9 @@ class SlideSeeder extends Seeder
{
FileStorageHelper::removeModelFiles(new Slide());
foreach (SliderTypeEnum::cases() as $slider) {
// @phpstan-ignore-next-line
Slide::factory(3)->make()->each(function (Slide $slide) use ($slider) {
$slide->slider = $slider->value;
$slide->slider = $slider;
$slide->video_id = $slider === SliderTypeEnum::home ? '6B4O-_1CQoI' : '';
$slide->picture = FileStorageHelper::storeFile(
new Slide(),

View file

@ -1,6 +1,6 @@
<?php
namespace Database\Seeders;
namespace Database\Seeders\Development;
use App\Lib\Helpers\FileStorageHelper;
use App\Models\Strip;

View file

@ -1,12 +1,17 @@
<?php
namespace Database\Seeders;
namespace Database\Seeders\Development;
use App\Enums\Testimonials\TypeEnum;
use App\Lib\Helpers\FileStorageHelper;
use App\Models\Testimonial;
use Illuminate\Database\Seeder;
/**
* FIXME: Definir les tailes d'images pour les testimonials
*
* @package Database\Seeders\Development
*/
class TestimonialSeeder extends Seeder
{
/**
@ -18,8 +23,9 @@ class TestimonialSeeder extends Seeder
{
FileStorageHelper::removeModelFiles(new Testimonial());
foreach (TypeEnum::cases() as $type) {
// @phpstan-ignore-next-line
Testimonial::factory(3)->make()->each(function (Testimonial $testimonial) use ($type) {
$testimonial->type = $type->value;
$testimonial->type = $type;
$testimonial->picture = FileStorageHelper::storeFile(
new Testimonial(),
new \SplFileInfo(\resource_path(

View file

@ -1,6 +1,6 @@
<?php
namespace Database\Seeders;
namespace Database\Seeders\Development;
use App\Models\User;
use Illuminate\Database\Seeder;

View file

@ -0,0 +1,39 @@
<?php
namespace Database\Seeders;
use Database\Seeders\Development\HistoricalMilestoneSeeder;
use Database\Seeders\Development\JobVideoSeeder;
use Database\Seeders\Development\LogoSeeder;
use Database\Seeders\Development\NewsSeeder;
use Database\Seeders\Development\SectionSeeder;
use Database\Seeders\Development\SiteSeeder;
use Database\Seeders\Development\SlideSeeder;
use Database\Seeders\Development\StripSeeder;
use Database\Seeders\Development\TestimonialSeeder;
use Database\Seeders\Development\UserSeeder;
use Illuminate\Database\Seeder;
class DevelopmentSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run(): void
{
$this->call([
UserSeeder::class,
HistoricalMilestoneSeeder::class,
JobVideoSeeder::class,
LogoSeeder::class,
NewsSeeder::class,
SectionSeeder::class,
SiteSeeder::class,
SlideSeeder::class,
StripSeeder::class,
TestimonialSeeder::class
]);
}
}

View file

@ -1,6 +1,6 @@
<?php
namespace Database\Seeders;
namespace Database\Seeders\Mandatory;
use App\Enums\StaticPageType;
use App\Models\StaticPage;

View file

@ -0,0 +1,21 @@
<?php
namespace Database\Seeders;
use Database\Seeders\Mandatory\StaticPageSeeder;
use Illuminate\Database\Seeder;
class MandatorySeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run(): void
{
$this->call([
StaticPageSeeder::class
]);
}
}

3273
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -16,11 +16,10 @@
"devDependencies": {
"@babel/core": "^7.22.11",
"@babel/types": "^7.21.2",
"@fortawesome/fontawesome-svg-core": "latest",
"@fortawesome/free-brands-svg-icons": "latest",
"@fortawesome/free-regular-svg-icons": "latest",
"@fortawesome/free-solid-svg-icons": "latest",
"@fortawesome/vue-fontawesome": "^3.0.3",
"@fortawesome/fontawesome-svg-core": "^6.5.1",
"@fortawesome/free-regular-svg-icons": "^6.5.1",
"@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/vue-fontawesome": "^3.0.5",
"@sipec/vue3-tags-input": "^3.0.4",
"@types/bootstrap": "^5.2.7",
"@types/node": "^20.8.2",
@ -28,44 +27,44 @@
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.36.2",
"@vitejs/plugin-basic-ssl": "^1.0.1",
"@vitejs/plugin-legacy": "latest",
"@vitejs/plugin-vue": "latest",
"@vitejs/plugin-vue": "^4.6.2",
"@vue/compiler-sfc": "latest",
"@vue/eslint-config-typescript": "latest",
"@vue/tsconfig": "latest",
"@vue/tsconfig": "^0.4.0",
"axios": "^1.5.1",
"bootstrap": "^5.3.2",
"chart.js": "^4.4.0",
"cropperjs": "^1.6.1",
"eslint": "latest",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-vue": "latest",
"eslint": "latest",
"laravel-vite-plugin": "latest",
"laravel-vite-plugin": "^0.8.1",
"postcss-discard-comments": "^6.0.0",
"prettier": "latest",
"print-js": "^1.6.0",
"resolve-url-loader": "^5.0.0",
"rollup-plugin-copy": "^3.5.0",
"sass": "latest",
"slugify": "^1.6.6",
"stylelint": "^15.11.0",
"stylelint-config-idiomatic-order": "github:ream88/stylelint-config-idiomatic-order",
"stylelint-config-sass-guidelines": "latest",
"stylelint-config-standard-scss": "latest",
"stylelint-scss": "latest",
"stylelint": "latest",
"stylelint-config-sass-guidelines": "^10.0.0",
"stylelint-config-standard-scss": "^11.1.0",
"stylelint-scss": "^5.3.2",
"sweetalert2": "^11.7.31",
"terser": "latest",
"toastify-js": "^1.12.0",
"ts-loader": "^9.3.1",
"typescript": ">=3.3.1 <5.2.0",
"typescript-eslint": "latest",
"typescript": "latest",
"vite": "^4.5.1",
"vite-plugin-babel": "latest",
"vite-plugin-checker": "latest",
"vite-plugin-eslint": "latest",
"vite-plugin-stylelint": "latest",
"vite": "^4.0.0",
"vue-eslint-parser": "^9.1.0",
"vue-loader": "^16.8.3",
"vue": "latest",
"vue-tsc": "1.6.0"
"vue-eslint-parser": "^9.3.2",
"vue-loader": "^17.3.1",
"vue-tsc": "1.8.11"
}
}

10
phpstan.neon Normal file
View file

@ -0,0 +1,10 @@
includes:
- vendor/larastan/larastan/extension.neon
parameters:
paths:
- app/
# Level 9 is the highest level
level: 5

Some files were not shown because too many files have changed in this diff Show more