Compare commits

..

No commits in common. "master" and "issue-25" have entirely different histories.

358 changed files with 21633 additions and 23828 deletions

View file

@ -1,13 +0,0 @@
# Browsers that we support
# https://github.com/browserslist/browserslist#readme
>= 0.5%
last 2 major versions
not dead
Chrome >= 60
Firefox >= 60
Firefox ESR
iOS >= 12
Safari >= 12
not Explorer <= 11

View file

@ -1,16 +0,0 @@
.git
*.md
node_modules/
npm-debug.log
.npmrc
tests/
Dockerfile*
.dockerignore
.git
.gitignore
README.md
LICENSE
.vscode
vendor/
public/build/
auth.json

View file

@ -3,9 +3,9 @@ root = true
[*] [*]
charset = utf-8 charset = utf-8
end_of_line = lf end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true trim_trailing_whitespace = true
[*.md] [*.md]
@ -14,8 +14,8 @@ trim_trailing_whitespace = false
[*.{yml,yaml}] [*.{yml,yaml}]
indent_size = 2 indent_size = 2
[docker-compose.yml]
indent_size = 4
[*.vue] [*.vue]
indent_size = 2 indent_size = 2
[docker-compose.yml]
indent_size = 4

View file

@ -5,6 +5,8 @@ APP_DEBUG=true
APP_URL=http://localhost APP_URL=http://localhost
API_TOKEN=toto API_TOKEN=toto
BRAND=
LOG_CHANNEL=stack LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug LOG_LEVEL=debug
@ -31,20 +33,6 @@ MAIL_PASSWORD=null
MAIL_ENCRYPTION=null MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}" MAIL_FROM_NAME="${APP_NAME}"
MAIL_ADMIN_ADDRESS=null
# SENTRY # SENTRY
SENTRY_LARAVEL_DSN= SENTRY_LARAVEL_DSN=
# SAML
SAML2_DEFAULT_TENANT_UUID=
SAML2_SP_CERT_x509=
SAML2_SP_CERT_PRIVATEKEY=
SAML2_CONTACT_TECHNICAL_EMAIL=
SAML2_CONTACT_SUPPORT_EMAIL=
# UWIL ADMIN SECURE
# - LIST SEPARATED WITH ',' (Required)
UWIL_ADMIN_SECURE_CIDR_LIST=""
# - LIST SEPARATED WITH ',' (Optional)
UWIL_ADMIN_SECURE_CIDR_PROJECT_ALLOWED_LIST=""

View file

@ -29,14 +29,7 @@
"sourceType": "module" "sourceType": "module"
}, },
"plugins": ["vue", "@typescript-eslint"], "plugins": ["vue", "@typescript-eslint"],
"ignorePatterns": [ "ignorePatterns": ["webpack.mix.js", "**/vendor/*.js", "**/vendor/**/*.js"],
"webpack.mix.js",
"**/*.php",
"**/*.svg",
"**/vendor/*.js",
"**/vendor/**/*.js",
"**/vendor/**/*.scss"
],
"rules": { "rules": {
"indent": ["error", 4, { "SwitchCase": 1 }], "indent": ["error", 4, { "SwitchCase": 1 }],
"linebreak-style": ["error", "unix"], "linebreak-style": ["error", "unix"],
@ -44,19 +37,5 @@
"semi": ["error", "always"], "semi": ["error", "always"],
"vue/script-indent": ["error", 2, { "baseIndent": 0, "switchCase": 1 }], "vue/script-indent": ["error", 2, { "baseIndent": 0, "switchCase": 1 }],
"vue/one-component-per-file": "off" "vue/one-component-per-file": "off"
},
"settings": {
"import/resolver": {
"node": {
"extensions": [".js", ".vue", ".ts", ".d.ts"]
},
"alias": {
"extensions": [".vue", ".js", ".ts", ".scss", ".d.ts"],
"map": [
["~", "./resources/assets"],
["@", "./node_modules"]
]
}
}
} }
} }

3
.gitattributes vendored
View file

@ -1,4 +1,4 @@
* text=auto eol=lf * text=auto
*.blade.php diff=html *.blade.php diff=html
*.css diff=css *.css diff=css
@ -8,4 +8,3 @@
/.github export-ignore /.github export-ignore
CHANGELOG.md export-ignore CHANGELOG.md export-ignore
.styleci.yml export-ignore

3
.gitignore vendored
View file

@ -25,6 +25,3 @@ npm-debug.log
yarn-error.log yarn-error.log
.vscode/settings.json .vscode/settings.json
.npmrc .npmrc
.zshrc
.composer/
.npm/

View file

@ -1,23 +0,0 @@
include:
- project: "ldc-web/shared-ci"
ref: "main"
file: "lint/lint.yml"
- project: "ldc-web/shared-ci"
ref: "main"
file: "security/security.yml"
- project: "ldc-web/shared-ci"
ref: "main"
file: "build/docker-multistage.yml"
- project: "ldc-web/shared-ci"
ref: "main"
file: "test/test.yml"
- project: "ldc-web/shared-ci"
ref: "main"
file: "deploy/deploy.yml"
stages:
- lint
- security
- build
- test
- deploy

View file

@ -1,11 +0,0 @@
## Comportement constaté
## Comportement attendu
## Solution envisagée
## Étapes pour la reproduction du bug
*

View file

@ -1,7 +0,0 @@
## Présentation du besoin
## Implémentation
## Maquettes, documentations...

14
.styleci.yml Normal file
View file

@ -0,0 +1,14 @@
php:
preset: laravel
version: 8
disabled:
- no_unused_imports
finder:
not-name:
- index.php
- server.php
js:
finder:
not-name:
- webpack.mix.js
css: true

View file

@ -18,7 +18,8 @@
], ],
"customSyntax": "postcss-scss", "customSyntax": "postcss-scss",
"rules": { "rules": {
"declaration-property-value-no-unknown": true, "indentation": 4,
"string-quotes": "double",
"selector-max-id": null, "selector-max-id": null,
"color-no-invalid-hex": true, "color-no-invalid-hex": true,
"order/properties-alphabetical-order": null, "order/properties-alphabetical-order": null,

View file

@ -1,46 +0,0 @@
FROM composer:2.8 AS vendor
ARG GITLAB_USER
ARG GITLAB_PASSWORD
ARG PHP_8_5_TAG=latest
COPY database/ database/
COPY composer.json composer.json
COPY composer.lock composer.lock
RUN composer config gitlab-token.lab.s-web.fr ${GITLAB_USER} ${GITLAB_PASSWORD}
RUN composer install \
--ignore-platform-reqs \
--no-interaction \
--no-plugins \
--no-scripts \
--no-dev \
--prefer-dist
FROM node:20.18 AS frontend
ARG GITLAB_PASSWORD
RUN mkdir -p /app/public/build
COPY package*.json tsconfig.json vite.config.ts .eslintrc.json .stylelintrc.json /app/
COPY resources/ /app/resources/
COPY --from=vendor /app/vendor/ /app/vendor/
WORKDIR /app
RUN npm config set @ldc-web:registry https://lab.s-web.fr/api/v4/packages/npm/
RUN npm config set -- '//lab.s-web.fr/api/v4/packages/npm/:_authToken' "${GITLAB_PASSWORD}"
RUN npm ci && npm run prod
FROM registry.s-web.fr/ldc-web/projets/ldc-web-images-docker/uwil-production-8.5:${PHP_8_5_TAG}
ARG USER=www-data
COPY --chown=${USER}:${USER} . /app/srcs
COPY --chown=${USER}:${USER} ./resources/favicon /app/srcs/public
COPY --from=vendor --chown=${USER}:${USER} /app/vendor/ /app/srcs/vendor/
COPY --from=frontend --chown=${USER}:${USER} /app/public/build/ /app/srcs/public/build/
USER root
RUN chmod -R 755 /app/srcs/storage
USER ${USER}

View file

@ -8,6 +8,3 @@
&nbsp; &nbsp;
- continue migrations : - continue migrations :
`php artisan migrate` `php artisan migrate`
TODO:
- sur les méthodes GET le token est visible dans les logs server..

View file

@ -6,42 +6,44 @@ use App\Enums\Formularies\InputTypeEnum;
use App\Lib\Inputs\Definition\Input; use App\Lib\Inputs\Definition\Input;
use App\Lib\Inputs\Definition\InputData; use App\Lib\Inputs\Definition\InputData;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes; use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
class AsInput implements CastsAttributes class AsInput implements CastsAttributes
{ {
/** /**
* Cast the given value. * Cast the given value.
* *
* @param \Illuminate\Database\Eloquent\Model $model * @param \Illuminate\Database\Eloquent\Model $model
* @param string $key * @param string $key
* @param mixed $value * @param mixed $value
* @param array $attributes * @param array $attributes
* @return \App\Lib\Inputs\Definition\Input * @return \App\Lib\Inputs\Definition\Input
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed * @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed * @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed
*/ */
public function get(Model $model, string $key, mixed $value, array $attributes): Input public function get($model, string $key, $value, array $attributes): Input
{ {
// phpcs:enable // phpcs:enable
/** @var array<string, integer|string|boolean> $inputData */ /** @var string $class */
/** @var array<integer|string|boolean> $data */
$inputData = \json_decode($value, true); $inputData = \json_decode($value, true);
$inputClass = InputTypeEnum::make($inputData['type'])->getInputClass(); $inputClass = InputTypeEnum::make($inputData['type'])->inputClass;
return $inputClass::from(InputData::make(...\array_values($inputData)), $inputData['required'] ?? false); return $inputClass::from(InputData::make(...\array_values($inputData)), $inputData['required'] ?? false);
} }
/** /**
* Prepare the given value for storage. * Prepare the given value for storage.
* *
* @param \Illuminate\Database\Eloquent\Model $model * @param \Illuminate\Database\Eloquent\Model $model
* @param string $key * @param string $key
* @param mixed $value * @param \App\Lib\Inputs\Definition\Input $value
* @param array $attributes * @param array $attributes
* @return string Json encoded \App\Lib\Inputs\Definition\Input * @return string Json encoded \App\Lib\Inputs\Definition\Input
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed * @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed * @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed
*/ */
public function set(Model $model, string $key, mixed $value, array $attributes): string public function set($model, string $key, $value, array $attributes)
{ {
// phpcs:enable // phpcs:enable
$this->assertIsInput($value); $this->assertIsInput($value);
@ -51,12 +53,11 @@ class AsInput implements CastsAttributes
/** /**
* Assert is instance of \App\Lib\Inputs\Definition\Input * Assert is instance of \App\Lib\Inputs\Definition\Input
*
* @param mixed $value * @param mixed $value
* @return void * @return void
* @throws \RuntimeException If the value is not an input. * @throws \RuntimeException If the value is not an input.
*/ */
private function assertIsInput(mixed $value): void private function assertIsInput($value): void
{ {
if (!\is_object($value) or !\in_array(Input::class, \class_parents($value))) { if (!\is_object($value) or !\in_array(Input::class, \class_parents($value))) {
$value = \json_encode($value); $value = \json_encode($value);

View file

@ -28,30 +28,37 @@ class CreateAdmin extends Command
protected $description = 'Create a backend admin user'; protected $description = 'Create a backend admin user';
/** /**
* The first name of the new user. * The firstname of the new user.
* *
* @var string|null * @var string
*/ */
protected $first_name; protected $firstname;
/** /**
* The last name of the new user. * The lastname of the new user.
* *
* @var string|null * @var string
*/ */
protected $last_name; protected $lastname;
/** /**
* The email of the new user. * The email of the new user.
* *
* @var string|null * @var string
*/ */
protected $email; protected $email;
/**
* The password of the new user.
*
* @var string
*/
protected $password;
/** /**
* The role of the new user. * The role of the new user.
* *
* @var string|null * @var string
*/ */
protected $role; protected $role;
@ -60,26 +67,30 @@ class CreateAdmin extends Command
* *
* @return integer * @return integer
*/ */
public function handle(): int public function handle()
{ {
$this->first_name = null; $this->firstname = null;
$this->addFirstname(); $this->addFirstname();
$this->last_name = null; $this->lastname = null;
$this->addLastname(); $this->addLastname();
$this->email = null; $this->email = null;
$this->addEmail(); $this->addEmail();
$this->password = null;
$this->addPassword();
$this->role = null; $this->role = null;
$this->addRole(); $this->addRole();
$user = new User([ $user = new User([
'first_name' => $this->first_name, 'firstname' => $this->firstname,
'last_name' => $this->last_name, 'lastname' => $this->lastname,
'email' => $this->email, 'email' => $this->email,
'email_verified_at' => now(), 'email_verified_at' => now(),
'role' => $this->role, 'password' => $this->password,
'role' => $this->role
]); ]);
$user->saveOrFail(); $user->saveOrFail();
@ -95,14 +106,14 @@ class CreateAdmin extends Command
*/ */
private function addFirstname(): void private function addFirstname(): void
{ {
while (is_null($this->first_name)) { while (is_null($this->firstname)) {
try { try {
$tmp = $this->ask('Type the wanted user firstname', 'Admin'); $tmp = $this->ask('Type the wanted user firstname', 'Admin');
$validator = Validator::make(['firstname' => $tmp], [ $validator = Validator::make(['firstname' => $tmp], [
'firstname' => 'required|min:3|max:255' 'firstname' => 'required|min:3|max:255'
]); ]);
$validator->validate(); $validator->validate();
$this->first_name = $tmp; $this->firstname = $tmp;
} catch (ValidationException $e) { } catch (ValidationException $e) {
$this->error(sprintf( $this->error(sprintf(
'Please provide a valid firstname %s', 'Please provide a valid firstname %s',
@ -120,14 +131,14 @@ class CreateAdmin extends Command
*/ */
private function addLastname(): void private function addLastname(): void
{ {
while (is_null($this->last_name)) { while (is_null($this->lastname)) {
try { try {
$tmp = $this->ask('Type the wanted user lastname', 'Saïtama'); $tmp = $this->ask('Type the wanted user lastname', 'Saïtama');
$validator = Validator::make(['lastname' => $tmp], [ $validator = Validator::make(['lastname' => $tmp], [
'lastname' => 'required|min:3|max:255' 'lastname' => 'required|min:3|max:255'
]); ]);
$validator->validate(); $validator->validate();
$this->last_name = $tmp; $this->lastname = $tmp;
} catch (ValidationException $e) { } catch (ValidationException $e) {
$this->error(sprintf( $this->error(sprintf(
'Please provide a valid lastname %s', 'Please provide a valid lastname %s',
@ -163,6 +174,22 @@ class CreateAdmin extends Command
}; //end while }; //end while
} }
/**
* Add a password to the user.
*
* @return void
*/
private function addPassword(): void
{
while (
($this->password = $this->secret(
(!\is_null($this->password) ? 'Failed to confirm, ' : '') . 'Type the wanted user password'
)) !== $this->secret('Confirm the password')
) {
continue;
}; //end while
}
/** /**
* Add a role to the user. * Add a role to the user.
* *
@ -174,16 +201,16 @@ class CreateAdmin extends Command
try { try {
$tmp = $this->choice( $tmp = $this->choice(
'Select his role', 'Select his role',
[UserRolesEnum::admin->label(), UserRolesEnum::client->label()], [UserRolesEnum::admin()->label, UserRolesEnum::client()->label],
UserRolesEnum::client->label(), UserRolesEnum::client()->label,
$maxAttempts = null, $maxAttempts = null,
$allowMultipleSelections = false $allowMultipleSelections = false
); );
if ($tmp === UserRolesEnum::admin->label()) { if ($tmp === UserRolesEnum::admin()->label) {
$tmp = UserRolesEnum::admin; $tmp = UserRolesEnum::admin();
} }
if ($tmp === UserRolesEnum::client->label()) { if ($tmp === UserRolesEnum::client()->label) {
$tmp = UserRolesEnum::client; $tmp = UserRolesEnum::client();
} }
$validator = Validator::make(['role' => $tmp], [ $validator = Validator::make(['role' => $tmp], [
'role' => 'required' 'role' => 'required'
@ -192,7 +219,7 @@ class CreateAdmin extends Command
$this->role = $tmp; $this->role = $tmp;
} catch (ValidationException $e) { } catch (ValidationException $e) {
$this->error(sprintf( $this->error(sprintf(
'Please choose from the selection %s', 'Please choose from the selection',
\implode(',', collect($e->errors())->flatten()->all()) \implode(',', collect($e->errors())->flatten()->all())
)); ));
continue; continue;

View file

@ -2,17 +2,30 @@
namespace App\Console; namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel; use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel class Kernel extends ConsoleKernel
{ {
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
}
/** /**
* Register the commands for the application. * Register the commands for the application.
* *
* @return void * @return void
*/ */
protected function commands(): void protected function commands()
{ {
$this->load(__DIR__ . '/Commands'); $this->load(__DIR__ . '/Commands');
require base_path('routes/console.php');
} }
} }

View file

@ -2,22 +2,37 @@
namespace App\Enums; namespace App\Enums;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum; use Kwaadpepper\Enum\BaseEnumRoutable;
enum AppUserRolesEnum: int /**
* This defines a data type.
*
* @method static self total() total role
* @method static self user() user role
*/
final class AppUserRolesEnum extends BaseEnumRoutable
{ {
use BaseEnum; /**
* enum values
case total = 1; * @return array
case user = 2; */
protected static function values(): array
{
return [
'total' => 1,
'user' => 2,
];
}
/** /**
* Optionnal labels definition. * enum labels
* * @return array
* @phpstan-ignore-next-line
*/ */
private const LABELS = [ protected static function labels(): array
self::total->name => 'Total', {
self::user->name => 'Utilisateur', return [
]; 'total' => 'Total',
'user' => 'Utilisateur',
];
}
} }

View file

@ -2,26 +2,43 @@
namespace App\Enums\Documents; namespace App\Enums\Documents;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum; use Kwaadpepper\Enum\BaseEnumRoutable;
enum LangEnum: int /**
* This defines a data type.
*
* @method static self fr()
* @method static self en()
* @method static self de()
* @method static self nl()
*/
final class LangEnum extends BaseEnumRoutable
{ {
use BaseEnum; /**
* enum values
case fr = 1; * @return array
case en = 2; */
case de = 3; protected static function values(): array
case nl = 4; {
return [
'fr' => 1,
'en' => 2,
'de' => 3,
'nl' => 4,
];
}
/** /**
* Optionnal labels definition. * enum labels
* * @return array
* @phpstan-ignore-next-line
*/ */
private const LABELS = [ protected static function labels(): array
self::fr->name => 'Français (FR)', {
self::en->name => 'Anglais (EN)', return [
self::de->name => 'Allemand (DE)', 'fr' => 'Français (FR)',
self::nl->name => 'Néerlandais (NL)', 'en' => 'Anglais (EN)',
]; 'de' => 'Allemand (DE)',
'nl' => 'Néerlandais (NL)',
];
}
} }

View file

@ -2,36 +2,37 @@
namespace App\Enums\Documents; namespace App\Enums\Documents;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum; use Kwaadpepper\Enum\BaseEnumRoutable;
enum TypeEnum: int /**
* This defines a data type.
*
* @method static self pdf()
* @method static self mp4()
*/
final class TypeEnum extends BaseEnumRoutable
{ {
use BaseEnum;
case pdf = 1;
case mp4 = 2;
/** /**
* Optionnal labels definition. * enum values
* * @return array
* @phpstan-ignore-next-line
*/ */
private const LABELS = [ protected static function values(): array
self::pdf->name => 'PDF',
self::mp4->name => 'MP4',
];
/**
* Get translation key.
*
* @return string
*/
public function icon(): string
{ {
return match ($this) { return [
self::pdf => 'file', 'pdf' => 1,
self::mp4 => 'video', 'mp4' => 2,
}; ];
}
/**
* enum labels
* @return array
*/
protected static function labels(): array
{
return [
'pdf' => 'PDF',
'mp4' => 'MP4',
];
} }
} }

View file

@ -2,24 +2,40 @@
namespace App\Enums\Formularies; namespace App\Enums\Formularies;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum; use Kwaadpepper\Enum\BaseEnumRoutable;
enum InputStatusEnum: int /**
* This defines a data type.
*
* @method static self system() input system
* @method static self locked() input locked
* @method static self default() input default
*/
final class InputStatusEnum extends BaseEnumRoutable
{ {
use BaseEnum; /**
* enum values
case system = 1; * @return array
case locked = 2; */
case default = 3; protected static function values(): array
{
return [
'system' => 1,
'locked' => 2,
'default' => 3,
];
}
/** /**
* Optionnal labels definition. * enum labels
* * @return array
* @phpstan-ignore-next-line
*/ */
private const LABELS = [ protected static function labels(): array
self::system->name => 'system', {
self::locked->name => 'verrouillé', return [
self::default->name => 'default', 'system' => __('system'),
]; 'locked' => __('verrouillé'),
'default' => __('default'),
];
}
} }

View file

@ -9,50 +9,87 @@ use App\Lib\Inputs\InputRadio;
use App\Lib\Inputs\InputSelect; use App\Lib\Inputs\InputSelect;
use App\Lib\Inputs\InputSelectMultiple; use App\Lib\Inputs\InputSelectMultiple;
use App\Lib\Inputs\InputText; use App\Lib\Inputs\InputText;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum; use Kwaadpepper\Enum\BaseEnumRoutable;
enum InputTypeEnum: int /**
* This defines a data type.
*
* @property-read integer value
* @property-read string label
* @property-read string inputClass InputType class.
*
* @method static self text() input text
* @method static self email() input email
* @method static self phone() input phone
* @method static self radio() input radio
* @method static self select() input select
* @method static self selectMultiple() input selectMultiple
* @method static self checkbox() input checkbox
*/
final class InputTypeEnum extends BaseEnumRoutable
{ {
use BaseEnum;
case text = 1;
case email = 2;
case phone = 3;
case radio = 4;
case select = 5;
case selectMultiple = 6;
case checkbox = 7;
/** /**
* Optionnal labels definition. * enum values
* * @return array
* @phpstan-ignore-next-line
*/ */
private const LABELS = [ protected static function values(): array
self::text->name => 'texte',
self::email->name => 'email',
self::phone->name => 'téléphone',
self::radio->name => 'radio',
self::select->name => 'liste déroulante',
self::selectMultiple->name => 'liste déroulante à choix multiple',
self::checkbox->name => 'case à cocher',
];
/**
* Get InputType class based on the enum value.
*
* @return string
*/
public function getInputClass(): string
{ {
return match ($this) { return [
self::text => InputText::class, 'text' => 1,
self::email => InputEmail::class, 'email' => 2,
self::phone => InputPhone::class, 'phone' => 3,
self::radio => InputRadio::class, 'radio' => 4,
self::select => InputSelect::class, 'select' => 5,
self::selectMultiple => InputSelectMultiple::class, 'selectMultiple' => 6,
self::checkbox => InputCheckbox::class, 'checkbox' => 7,
}; ];
}
/**
* enum labels
* @return array
*/
protected static function labels(): array
{
return [
'text' => __('texte'),
'email' => __('e-mail'),
'phone' => __('téléphone'),
'radio' => __('radio'),
'select' => __('liste déroulante'),
'selectMultiple' => __('liste déroulante à choix multiple'),
'checkbox' => __('case à cocher'),
];
}
/**
* Enum input Types
*
* @return array
*/
protected static function inputTypes(): array
{
return [
'text' => InputText::class,
'email' => InputEmail::class,
'phone' => InputPhone::class,
'radio' => InputRadio::class,
'select' => InputSelect::class,
'selectMultiple' => InputSelectMultiple::class,
'checkbox' => InputCheckbox::class,
];
}
/**
* @param string $attribute
* @return integer|string
* @throws \Kwaadpepper\Enum\Exceptions\UnknownEnumProperty If the prop cannot be found.
*/
public function __get(string $attribute)
{
if ($attribute === 'inputClass') {
return $this->inputTypes()[$this->getDefinition()];
}
return parent::__get($attribute);
} }
} }

View file

@ -1,21 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Enums;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
/**
* Defines the available pagination page size options.
*/
enum ItemsPerPaginationEnum: int
{
use BaseEnum;
case five = 5;
case twelve = 12;
case twentyfive = 25;
case fifty = 50;
case onehundred = 100;
}

View file

@ -0,0 +1,30 @@
<?php
namespace App\Enums\Pagination;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self five()
* @method static self twelve()
* @method static self twentyfive()
* @method static self fifty()
* @method static self onehundred()
*/
final class ItemsPerPaginationEnum extends BaseEnumRoutable
{
/**
* enum values
* @return array
*/
protected static function values(): array
{
return [
'five' => 5,
'twelve' => 12,
'twentyfive' => 25,
'fifty' => 50,
'onehundred' => 100
];
}
}

View file

@ -2,32 +2,52 @@
namespace App\Enums; namespace App\Enums;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum; use Kwaadpepper\Enum\BaseEnumRoutable;
enum SettingTypesEnum: int /**
* This defines a data type.
*
* @method static self string() string type
* @method static self numeric() numeric type
* @method static self picture() picture type
* @method static self json() string type
* @method static self boolean() boolean type
* @method static self color() color type
* @method static self directory() directory type
*/
final class SettingTypesEnum extends BaseEnumRoutable
{ {
use BaseEnum; /**
* enum values
case string = 1; * @return array
case numeric = 2; */
case picture = 3; protected static function values(): array
case json = 4; {
case boolean = 5; return [
case color = 6; 'string' => 1,
case directory = 7; 'numeric' => 2,
'picture' => 3,
'json' => 4,
'boolean' => 5,
'color' => 6,
'directory' => 7
];
}
/** /**
* Optionnal labels definition. * enum labels
* * @return array
* @phpstan-ignore-next-line
*/ */
private const LABELS = [ protected static function labels(): array
self::string->name => 'String (Hexa)', {
self::numeric->name => 'Numeric', return [
self::picture->name => 'Picture', 'string' => 'String (Hexa)',
self::json->name => 'Json', 'numeric' => 'Numeric',
self::boolean->name => 'Boolean', 'picture' => 'Picture',
self::color->name => 'Color', 'json' => 'Json',
self::directory->name => 'Directory', 'boolean' => 'Boolean',
]; 'color' => 'Color',
'directory' => 'Directory',
];
}
} }

View file

@ -2,22 +2,37 @@
namespace App\Enums; namespace App\Enums;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum; use Kwaadpepper\Enum\BaseEnumRoutable;
enum UserRolesEnum: int /**
* This defines a data type.
*
* @method static self admin() admin role
* @method static self client() client role
*/
final class UserRolesEnum extends BaseEnumRoutable
{ {
use BaseEnum; /**
* enum values
case admin = 1; * @return array
case client = 2; */
protected static function values(): array
{
return [
'admin' => 1,
'client' => 2,
];
}
/** /**
* Optionnal labels definition. * enum labels
* * @return array
* @phpstan-ignore-next-line
*/ */
private const LABELS = [ protected static function labels(): array
self::admin->name => 'Administrateur', {
self::client->name => 'Client', return [
]; 'admin' => 'Administrateur',
'client' => 'Client',
];
}
} }

View file

@ -17,7 +17,7 @@ class Handler extends ExceptionHandler
/** /**
* A list of the inputs that are never flashed for validation exceptions. * A list of the inputs that are never flashed for validation exceptions.
* *
* @var list<string> * @var array<int, string>
*/ */
protected $dontFlash = [ protected $dontFlash = [
'current_password', 'current_password',
@ -30,12 +30,9 @@ class Handler extends ExceptionHandler
* *
* @return void * @return void
*/ */
public function register(): void public function register()
{ {
$this->reportable(function (Throwable $e) { $this->reportable(function (Throwable $e) {
if (app()->bound('sentry')) {
app('sentry')->captureException($e);
}
}); });
} }
} }

View file

@ -11,21 +11,12 @@ class AppController extends Controller
{ {
/** /**
* Get App Config. * Get App Config.
* * @return ApiResponseResource
* @return \App\Http\Resources\ApiResponseResource
*/ */
public function get(): \App\Http\Resources\ApiResponseResource public function get()
{ {
/** @var string $tenantKey */
$tenantKey = request()->tenant;
$response = []; $response = [];
$settings = Setting::query() $settings = Setting::where('key', 'Button1')->orWhere('key', 'Button2')->orWhere('key', 'Button3')->get();
->where('tenant_id', $tenantKey)
->orWhere('key', 'Button1')
->orWhere('key', 'Button2')
->orWhere('key', 'Button3')
->get();
foreach ($settings as $setting) { foreach ($settings as $setting) {
$directory = Directory::find($setting->value)->only('id', 'name'); $directory = Directory::find($setting->value)->only('id', 'name');

View file

@ -12,17 +12,17 @@ class SettingsController extends Controller
/** /**
* Get Settings. * Get Settings.
* *
* @return \App\Http\Resources\ApiResponseResource * @return \Illuminate\Http\JsonResponse
*/ */
public function get(): \App\Http\Resources\ApiResponseResource public function get()
{ {
$response = []; $response = [];
$settings = Setting::all(); $settings = Setting::all();
foreach ($settings as $setting) { foreach ($settings as $setting) {
$response[$setting->key] = $response[$setting->key] =
SettingTypesEnum::numeric->equals($setting->type) || SettingTypesEnum::numeric()->equals($setting->type) ||
SettingTypesEnum::boolean->equals($setting->type) || SettingTypesEnum::boolean()->equals($setting->type) ||
SettingTypesEnum::directory->equals($setting->type) SettingTypesEnum::directory()->equals($setting->type)
? \intval($setting->value) ? \intval($setting->value)
: $setting->value; : $setting->value;
} }

View file

@ -19,6 +19,7 @@ use App\Models\Directory;
use App\Models\Document; use App\Models\Document;
use App\Models\Formulary; use App\Models\Formulary;
use App\Models\Setting; use App\Models\Setting;
use Google\Cloud\Vision\V1\ImageAnnotatorClient;
use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Exception\ClientException;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -31,13 +32,13 @@ use Illuminate\Support\Facades\URL;
class SyncController extends Controller class SyncController extends Controller
{ {
/** /**
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection * @param \Illuminate\Http\Request $request
* @return \App\Http\Resources\AppUserCollection
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/ */
public function getUsers(): \Illuminate\Http\Resources\Json\AnonymousResourceCollection public function getUsers(Request $request)
{ {
return AppUserResource::collection( return AppUserResource::collection(AppUser::where('status', 1)->get());
AppUser::query()->where('tenant_id', \tenant()->getKey())->where('status', 1)->get()
);
} }
// *** --- START CONTACTS --- // *** --- START CONTACTS ---
@ -48,11 +49,11 @@ class SyncController extends Controller
* @param \App\Models\AppUser $app_user * @param \App\Models\AppUser $app_user
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/ */
public function getContacts(AppUser $app_user): \Illuminate\Http\Resources\Json\AnonymousResourceCollection public function getContacts(AppUser $app_user)
{ {
return ContactResource::collection( return ContactResource::collection(
Contact::query()->whereHas('appUser', function (Builder $q) use ($app_user) { Contact::query()->whereHas('appUser', function (Builder $q) use ($app_user) {
$q->where('tenant_id', \tenant()->getKey())->where('id', $app_user->id); $q->where('id', $app_user->id);
})->get() })->get()
); );
} }
@ -62,49 +63,43 @@ class SyncController extends Controller
* *
* @param \App\Http\Requests\Api\Contacts\UpsertContactsRequest $request * @param \App\Http\Requests\Api\Contacts\UpsertContactsRequest $request
* @param \App\Models\AppUser $app_user * @param \App\Models\AppUser $app_user
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/ */
public function upsertContact( public function upsertContact(UpsertContactsRequest $request, AppUser $app_user)
UpsertContactsRequest $request, {
AppUser $app_user
): \Illuminate\Http\JsonResponse {
/** @var \Illuminate\Support\Collection<string,integer> */ /** @var \Illuminate\Support\Collection<string,integer> */
$formularies = Formulary::query()->pluck('slug')->flip(); $formularies = Formulary::query()->pluck('slug')->flip();
$contactQuery = Contact::query()->whereHas('appUser', function (Builder $q) use ($app_user) { $contactQuery = Contact::query()->whereHas('appUser', function (Builder $q) use ($app_user) {
$q->where('tenant_id', \tenant()->getKey())->where('id', $app_user->getKey()); $q->where('id', $app_user->id);
}); });
\collect($request->contacts)->each( \collect($request->contacts)->each(function (array $contactRequestData) use ($contactQuery, $formularies) {
function (array $contactRequestData) use ($contactQuery, $formularies, $app_user) { /** @var \App\Models\Contact */
/** @var \App\Models\Contact $model */ $model = (clone $contactQuery)->where('id', Arr::get($contactRequestData, 'id'))->firstOrNew();
$model = (clone $contactQuery)->where('id', Arr::get($contactRequestData, 'id'))->firstOrNew();
$contactRequestData['timestamp'] = Carbon::parse($contactRequestData['timestamp'], 'UTC') $contactRequestData['timestamp'] = Carbon::parse($contactRequestData['timestamp'], 'UTC')
->setTimezone(\now()->getTimezone()); ->setTimezone(\now()->getTimezone());
// @phpstan-ignore logicalAnd.leftAlwaysTrue if ($model->timestamp and $model->timestamp->gte($contactRequestData['timestamp'])) {
if ($model->timestamp and $model->timestamp->gte($contactRequestData['timestamp'])) { return;
return;
}
// * Set parse values on data prop to fill in model.
$contactRequestData['data'] = \collect($contactRequestData['dataParsed'])
// * FILTER CONTACT DATA WITH ACTUAL FORMULARY
->filter(function (Input $input) use ($formularies) {
return $formularies->has($input->data->formSlug);
});
$model->fill($contactRequestData);
$model->app_user_id = $app_user->getKey();
// * Send contact to Feature
if (
!empty($contactRequestData['send_to_app_user_id']) and
($destUser = AppUser::query()->where('id', $contactRequestData['send_to_app_user_id'])->first())
) {
$model->appUser()->associate($destUser);
}
$model->saveOrFail();
} }
); // * Set parse values on data prop to fill in model.
$contactRequestData['data'] = \collect($contactRequestData['dataParsed'])
// * FILTER CONTACT DATA WITH ACTUAL FORMULARY
->filter(function (Input $input) use ($formularies) {
return $formularies->has($input->data->formSlug);
});
$model->fill($contactRequestData);
// * Send contact to Feature
if (
!empty($contactRequestData['send_to_app_user_id']) and
($destUser = AppUser::query()->where('id', $contactRequestData['send_to_app_user_id'])->first())
) {
$model->appUser()->associate($destUser);
}
$model->saveOrFail();
});
// * HTTP 200 is ok. // * HTTP 200 is ok.
return \response()->json(); return \response()->json();
@ -115,13 +110,13 @@ class SyncController extends Controller
* *
* @param \App\Http\Requests\Api\Contacts\DestroyContactsRequest $request * @param \App\Http\Requests\Api\Contacts\DestroyContactsRequest $request
* @param \App\Models\AppUser $app_user * @param \App\Models\AppUser $app_user
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\Resources\Json
*/ */
public function destroyContacts(DestroyContactsRequest $request, AppUser $app_user): \Illuminate\Http\JsonResponse public function destroyContacts(DestroyContactsRequest $request, AppUser $app_user)
{ {
Contact::query()->whereIn('id', $request->validated()['contactUuidList']) Contact::query()->whereIn('id', $request->contactUuidList)
->whereHas('appUser', function (Builder $q) use ($app_user) { ->whereHas('appUser', function (Builder $q) use ($app_user) {
$q->where('tenant_id', \tenant()->getKey())->where('id', $app_user->id); $q->where('id', $app_user->id);
})->delete(); })->delete();
// * HTTP 200 is ok. // * HTTP 200 is ok.
@ -131,42 +126,72 @@ class SyncController extends Controller
// *** --- END CONTACTS --- // *** --- END CONTACTS ---
/** /**
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection * @param \Illuminate\Http\Request $request
* @return \App\Http\Resources\DirectoriesCollection
*/ */
public function getDirectories(): \Illuminate\Http\Resources\Json\AnonymousResourceCollection public function getDirectories(Request $request)
{ {
return DirectoryResource::collection( return DirectoryResource::collection(Directory::query()->orderBy('order')->get());
Directory::query()->where('tenant_id', \tenant()->getKey())->orderBy('order')->get()
);
} }
/** /**
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection * @param \Illuminate\Http\Request $request
* @return \App\Http\Resources\DocumentsCollection
*/ */
public function getDocuments(): \Illuminate\Http\Resources\Json\AnonymousResourceCollection public function getDocuments(Request $request)
{ {
return DocumentResource::collection( return DocumentResource::collection(Document::where('status', 1)->get());
Document::query()->where('tenant_id', \tenant()->getKey())->where('status', 1)->get()
);
} }
/** /**
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection * @param \Illuminate\Http\Request $request
* @return array
*/ */
public function getFormularies(): \Illuminate\Http\Resources\Json\AnonymousResourceCollection public function getVersion(Request $request)
{ {
return FormularyResource::collection( $app_uuid = $request->app_uuid;
Formulary::query()->where('tenant_id', \tenant()->getKey())->orderBy('order')->get() $app_version = $request->app_version;
); $version = Setting::select('value AS version')->where('key', 'version')->first();
$ShowIsActive = Setting::select('value AS ShowIsActive')->where('key', 'ShowIsActive')->first();
try {
$client = new \GuzzleHttp\Client();
$response = $client->request(
'GET',
'https://store.ldc.fr/api/get-version?token=@P$oi6or!bL2-r%3C&app_uuid=' . $app_uuid
);
$body = $response->getBody();
$storeVersion = json_decode($body)->version;
} catch (ClientException $e) {
$storeVersion = $app_version;
}
if ($storeVersion > $app_version) {
$newApp = true;
} else {
$newApp = false;
}
return ['version' => $version->version, 'ShowIsActive' => $ShowIsActive->ShowIsActive, 'newApp' => $newApp];
} }
/** /**
* @param \App\Http\Requests\Api\SendFlyerRequest $request * @return \App\Http\Resources\ContactsCollection
*/
public function getFormularies()
{
return FormularyResource::collection(Formulary::query()->orderBy('order')->get());
}
/**
* NOTE: This should be refactored, on app side also.
* @param \Illuminate\Http\Request $request
* @return string * @return string
*/ */
public function sendFlyer(Request $request): string public function sendFlyer(Request $request)
{ {
$requestData = $request->emails; // FIXME: This should be validated OMG.
$requestData = \json_decode($request->emails, true);
$email = $requestData['email']; $email = $requestData['email'];
$subject = $requestData['subject']; $subject = $requestData['subject'];
$message = $requestData['message']; $message = $requestData['message'];
@ -179,14 +204,12 @@ class SyncController extends Controller
$filesName = []; $filesName = [];
$noExists = []; $noExists = [];
foreach ($docs as $doc) { foreach ($docs as $doc) {
$exists = Storage::disk('public') $exists = Storage::disk('public')->exists('docs/' . $doc['uuid'] . '.' . $doc['type']);
->exists(sprintf('docs/%s/%s.%s', \tenant()->getKey(), $doc['uuid'], $doc['type']));
if ($exists) { if ($exists) {
array_push($attach, config('app.url') . URL::temporarySignedRoute( array_push($attach, URL::temporarySignedRoute(
'docs.download', 'docs.download',
now()->addDays(30), now()->addDays(30),
['tenant' => \tenant()->getKey(), 'uuid' => $doc['uuid']], ['uuid' => $doc['uuid']]
false
)); ));
array_push($filesName, $doc['title']); array_push($filesName, $doc['title']);
} else { } else {
@ -194,26 +217,56 @@ class SyncController extends Controller
} }
} }
if (count($noExists) === 0) { if (count($noExists) === 0) {
$mail = new SendFlyer([ $message = new SendFlyer([
'body' => $message, 'body' => $message,
'fromName' => $user->firstname . ' ' . $user->lastname, 'from' => $user->email,
'subject' => $subject, 'fromName' => $user->firstname . ' ' . $user->lastname,
'attach' => $attach, 'subject' => '[' . config('app.BRAND') . '] - ' . $subject,
'attach' => $attach,
'filesName' => $filesName, 'filesName' => $filesName,
]); ]);
Mail::to($email)->cc($user->email)->send($mail); Mail::to($email)
->cc($user->email)
->queue($message);
} else { } else {
$mail = new SendFlyerFailed([ $message = new SendFlyerFailed([
'email' => $email, 'email' => $email,
'message' => $message, 'message' => $message,
'subject' => $subject, 'subject' => $subject,
'filesName' => $filesName, 'filesName' => $filesName,
'noExists' => $noExists 'noExists' => $noExists
]); ]);
Mail::to($user->email)->send($mail); Mail::to($user->email)
->queue($message);
} //end if } //end if
return 'OK'; return 'OK';
} }
/**
* @param \Illuminate\Http\Request $request
* @return void
*/
public function processOcr(Request $request)
{
// Instantiates a client.
$imageAnnotator = new ImageAnnotatorClient([
'credentials' => public_path() . '/credentials.json'
]);
// The name of the image file to annotate.
$fileName = 'test.jpg';
// Prepare the image to be annotated.
$image = file_get_contents(public_path() . "/" . $fileName);
// Performs label detection on the image file.
$response = $imageAnnotator->textDetection($image);
$labels = $response->getTextAnnotations();
foreach ($labels as $label) {
echo $label->getDescription() . "<br>";
}
}
} }

View file

@ -12,31 +12,25 @@ use App\Models\Directory;
use App\Traits\Controllers\ChangesModelOrder; use App\Traits\Controllers\ChangesModelOrder;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Maatwebsite\Excel\Facades\Excel; use Maatwebsite\Excel\Facades\Excel;
class AppUserController extends Controller class AppUserController extends Controller
{ {
use ChangesModelOrder; use ChangesModelOrder;
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(AppUser::class);
}
/** /**
* Display a listing of the resource. * Display a listing of the resource.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View * @return \Illuminate\Http\Response
*/ */
public function index(Request $request): \Illuminate\View\View public function index(Request $request)
{ {
/** @var \Illuminate\Database\Eloquent\Builder $query */ /** @var \Illuminate\Database\Eloquent\Builder $query */
$query = AppUser::query(); $query = AppUser::query();
/** @var string $search Search field */
if ($request->search) { if ($request->search) {
$this->searchQuery( $this->searchQuery(
$query, $query,
@ -47,45 +41,36 @@ class AppUserController extends Controller
'email' 'email'
); );
} }
$searchFields = \implode(', ', [ $searchFields = trans('Nom, Prénom, Email');
trans('validation.attributes.last_name'),
trans('validation.attributes.first_name'),
trans('validation.attributes.email'),
]);
/** Sort columns with a query */ /** Sort columns with a query */
$this->sortQuery($query); $this->sortQuery($query);
$appUserModels = $this->paginate($query); $app_users = $this->paginate($query);
return view('bo.pages.app_users.index', compact('appUserModels', 'searchFields')); return view('bo.pages.app-users.index', compact('app_users', 'searchFields'));
} }
/** /**
* Show the form for creating a new resource. * Show the form for creating a new resource.
* *
* @param \App\Models\AppUser $app_user * @return \Illuminate\Http\Response
* @return \Illuminate\View\View
*/ */
public function create(AppUser $app_user): \Illuminate\View\View public function create()
{ {
$directories = Directory::renderDirectories( $directories = Directory::renderDirectories(Directory::where('needAuthorization', 1)
Directory::query()->where('needAuthorization', 1)->orderBy('name', 'asc')->get() ->orderBy('name', 'ASC')->get());
);
return view('bo.pages.app_users.create', [ return view('bo.pages.app-users.create', compact('directories'));
'appUserModel' => $app_user,
'directories' => $directories
]);
} }
/** /**
* Store a newly created resource in storage. * Store a newly created resource in storage.
* *
* @param \App\Http\Requests\Bo\AppUser\StoreAppUserRequest $request * @param \App\Http\Requests\Bo\AppUser\StoreAppUserRequest $request
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\Response
*/ */
public function store(StoreAppUserRequest $request): \Illuminate\Http\RedirectResponse public function store(StoreAppUserRequest $request)
{ {
return DB::transaction(function () use ($request) { return DB::transaction(function () use ($request) {
$app_user = new AppUser(); $app_user = new AppUser();
@ -94,10 +79,10 @@ class AppUserController extends Controller
if ($app_user->saveOrFail()) { if ($app_user->saveOrFail()) {
$app_user->directories()->sync(collect($request->directories)->pluck('id')); $app_user->directories()->sync(collect($request->directories)->pluck('id'));
return redirect() return redirect()
->route('bo.app_users.edit', ['tenant' => \tenant(), 'app_user' => $app_user]) ->route('bo.app-users.edit', $app_user)
->with('success', trans( ->with('success', trans(
':model a été créé', ':model a été créé',
['model' => trans('models.classes.app_user')] ['model' => Str::singular(trans('models.classes.app_users'))]
)); ));
} }
@ -105,7 +90,7 @@ class AppUserController extends Controller
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été créé', ':model n\'a pas été créé',
['model' => trans('models.classes.app_user')] ['model' => Str::singular(trans('models.classes.app_users'))]
)); ));
}); });
} }
@ -114,19 +99,17 @@ class AppUserController extends Controller
* Show the form for editing the specified resource. * Show the form for editing the specified resource.
* *
* @param \App\Models\AppUser $app_user * @param \App\Models\AppUser $app_user
* @return \Illuminate\View\View * @return \Illuminate\Http\Response
*/ */
public function edit(AppUser $app_user): \Illuminate\View\View public function edit(AppUser $app_user)
{ {
$directories = Directory::renderDirectories( $directories = Directory::renderDirectories(Directory::query()->where('needAuthorization', 1)
Directory::query()->where('needAuthorization', 1)->orderBy('name', 'asc')->get() ->orderBy('name', 'ASC')->get());
);
// @phpstan-ignore assign.propertyReadOnly
$app_user->directories = Directory::renderDirectories($app_user->directories); $app_user->directories = Directory::renderDirectories($app_user->directories);
return view('bo.pages.app_users.edit', [ return view('bo.pages.app-users.edit', [
'appUserModel' => $app_user, 'app_userModel' => $app_user,
'directories' => $directories 'directories' => $directories
]); ]);
} }
@ -135,9 +118,9 @@ class AppUserController extends Controller
* *
* @param \App\Http\Requests\Bo\AppUser\UpdateAppUserRequest $request * @param \App\Http\Requests\Bo\AppUser\UpdateAppUserRequest $request
* @param \App\Models\AppUser $app_user * @param \App\Models\AppUser $app_user
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\Response
*/ */
public function update(UpdateAppUserRequest $request, AppUser $app_user): \Illuminate\Http\RedirectResponse public function update(UpdateAppUserRequest $request, AppUser $app_user)
{ {
$app_user->directories()->sync(collect($request->directories)->pluck('id')); $app_user->directories()->sync(collect($request->directories)->pluck('id'));
$params = $request->validated(); $params = $request->validated();
@ -149,17 +132,17 @@ class AppUserController extends Controller
if ($app_user->saveOrFail()) { if ($app_user->saveOrFail()) {
return redirect() return redirect()
->route('bo.app_users.edit', ['tenant' => \tenant(), 'app_user' => $app_user]) ->route('bo.app-users.edit', $app_user)
->with('success', trans( ->with('success', trans(
':model a été mis à jour', ':model a été mis à jour',
['model' => trans('models.classes.app_user')] ['model' => Str::singular(trans('models.classes.app_users'))]
)); ));
} }
return redirect() return redirect()
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été mis à jour', ':model n\'a pas été mis à jour',
['model' => trans('models.classes.app_user')] ['model' => Str::singular(trans('models.classes.app_users'))]
)); ));
}); });
} }
@ -167,17 +150,17 @@ class AppUserController extends Controller
/** /**
* Remove the specified resource from storage. * Remove the specified resource from storage.
* *
* @param \App\Models\AppUser $app_user * @param \App\Models\AppUser $app_user
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\Response
*/ */
public function destroy(AppUser $app_user): \Illuminate\Http\RedirectResponse public function destroy(AppUser $app_user)
{ {
if ($app_user->deleteOrFail()) { if ($app_user->deleteOrFail()) {
return redirect() return redirect()
->route('bo.app_users.index', ['tenant' => \tenant()]) ->route('bo.app-users.index')
->with('warning', trans( ->with('warning', trans(
':model a bien été supprimé', ':model a bien été supprimé',
['model' => trans('models.classes.app_user')] ['model' => Str::singular(trans('models.classes.app_users'))]
)); ));
} }
@ -185,21 +168,10 @@ class AppUserController extends Controller
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été supprimé', ':model n\'a pas été supprimé',
['model' => trans('models.classes.app_user')] ['model' => Str::singular(trans('models.classes.app_users'))]
)); ));
} }
/**
* Duplicate the specified resource.
*
* @param \App\Models\AppUser $app_user
* @return \Illuminate\Contracts\View\View
*/
public function duplicate(AppUser $app_user): \Illuminate\Contracts\View\View
{
return $this->create($app_user->replicate());
}
/** /**
* Mass import by CSV. * Mass import by CSV.
* matricule;lastname;firstname;email * matricule;lastname;firstname;email
@ -208,11 +180,10 @@ class AppUserController extends Controller
* @param \App\Http\Requests\Bo\ImportRequest $request * @param \App\Http\Requests\Bo\ImportRequest $request
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
*/ */
public function import(ImportRequest $request): \Illuminate\Http\RedirectResponse public function import(ImportRequest $request)
{ {
Excel::import(new AppUserImport(), $request->import, null, \Maatwebsite\Excel\Excel::CSV); Excel::import(new AppUserImport(), $request->import, null, \Maatwebsite\Excel\Excel::CSV);
return redirect()->route('bo.app_users.index', ['tenant' => \tenant()]) return redirect()->route('bo.app-users.index')->with('success', 'Import terminé.');
->with('success', 'Import terminé.');
} }
} }

View file

@ -25,7 +25,7 @@ class LoginController extends Controller
* *
* @return \Illuminate\Contracts\Auth\StatefulGuard * @return \Illuminate\Contracts\Auth\StatefulGuard
*/ */
protected function guard(): \Illuminate\Contracts\Auth\StatefulGuard protected function guard()
{ {
return Auth::guard('backend'); return Auth::guard('backend');
} }
@ -35,14 +35,13 @@ class LoginController extends Controller
* *
* @return \Illuminate\View\View * @return \Illuminate\View\View
*/ */
public function showLoginForm(): \Illuminate\View\View public function showLoginForm()
{ {
return view('bo.auth.login'); return view('bo.auth.login');
} }
/** /**
* Where to redirect users after login. * Where to redirect users after login.
*
* @return string * @return string
*/ */
protected function redirectTo(): string protected function redirectTo(): string

View file

@ -15,14 +15,13 @@ class ResetPasswordController extends Controller
* *
* @return \Illuminate\Contracts\Auth\StatefulGuard * @return \Illuminate\Contracts\Auth\StatefulGuard
*/ */
protected function guard(): \Illuminate\Contracts\Auth\StatefulGuard protected function guard()
{ {
return Auth::guard('backend'); return Auth::guard('backend');
} }
/** /**
* Where to redirect users after login. * Where to redirect users after login.
*
* @return string * @return string
*/ */
protected function redirectTo(): string protected function redirectTo(): string

View file

@ -11,27 +11,20 @@ use Illuminate\Database\Eloquent\Builder;
class ContactController extends Controller class ContactController extends Controller
{ {
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Contact::class);
}
/** /**
* Display a listing of the resource. * Display a listing of the resource.
* *
* @param \App\Http\Requests\Bo\FilterCustomerRequest $request * @param \App\Http\Requests\Bo\FilterCustomerRequest $request
* @return \Illuminate\View\View * @return \Illuminate\Http\Response
*/ */
public function index(FilterCustomerRequest $request): \Illuminate\View\View public function index(FilterCustomerRequest $request)
{ {
/** @var \Illuminate\Database\Eloquent\Builder $query */ /** @var \Illuminate\Database\Eloquent\Builder $query */
$query = Contact::query()->with('appUser')->whereHas('appUser', function ($query) { $query = Contact::query();
$query->where('tenant_id', tenant('id'));
});
$query->with('appUser');
/** @var string $search Search field */
if ($request->search) { if ($request->search) {
$this->searchQuery( $this->searchQuery(
$query, $query,
@ -42,11 +35,7 @@ class ContactController extends Controller
'email', 'email',
); );
} }
$searchFields = \implode(', ', [ $searchFields = \implode(',', [trans('Nom'), trans('Prénom'), trans('Email')]);
trans('validation.attributes.last_name'),
trans('validation.attributes.email'),
trans('validation.attributes.phone'),
]);
/** Sort columns with a query */ /** Sort columns with a query */
$this->sortQuery($query); $this->sortQuery($query);
@ -57,26 +46,26 @@ class ContactController extends Controller
}); });
} }
$contactModels = $this->paginate($query); $contactModelList = $this->paginate($query);
$appUsers = AppUser::query()->select(['id', 'lastname', 'firstname'])->orderBy('lastname')->get(); $appUsers = AppUser::query()->select(['id', 'lastname', 'firstname'])->get();
return view('bo.pages.contacts.index', compact('contactModels', 'searchFields', 'appUsers')); return view('bo.pages.contacts.index', compact('contactModelList', 'searchFields', 'appUsers'));
} }
/** /**
* Display the specified resource. * Display the specified resource.
* *
* @param \App\Models\Contact $contact * @param \App\Models\Contact $contact
* @return \Illuminate\View\View * @return \Illuminate\Http\Response
*/ */
public function show(Contact $contact): \Illuminate\View\View public function show(Contact $contact)
{ {
$contact->load('appUser.directories'); $contact->load('appUser.directories');
$fomularies = Formulary::all()->keyBy('name'); $fomularies = Formulary::all()->keyBy('name');
return view('bo.pages.contacts.show', [ return \view('bo.pages.contacts.show', [
'contactModel' => $contact, 'contactModel' => $contact,
'fomularies' => $fomularies 'fomularies' => $fomularies
]); ]);
} }
} }

View file

@ -11,7 +11,7 @@ class DashboardController extends Controller
* *
* @return \Illuminate\Contracts\View\View * @return \Illuminate\Contracts\View\View
*/ */
public function index(): \Illuminate\Contracts\View\View public function index()
{ {
return view('bo.pages.dashboard'); return view('bo.pages.dashboard');
} }

View file

@ -6,55 +6,51 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Directories\StoreDirectoryRequest; use App\Http\Requests\Bo\Directories\StoreDirectoryRequest;
use App\Http\Requests\Bo\Directories\UpdateDirectoryRequest; use App\Http\Requests\Bo\Directories\UpdateDirectoryRequest;
use App\Models\Directory; use App\Models\Directory;
use App\Models\Document;
use App\Models\Setting; use App\Models\Setting;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Str;
class DirectoriesController extends Controller class DirectoriesController extends Controller
{ {
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Directory::class);
}
/** /**
* Show the form for creating a new resource. * Show the form for creating a new resource.
* *
* @return \Illuminate\Contracts\View\View * @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/ */
public function index(): \Illuminate\Contracts\View\View public function index()
{ {
$directoryModels = $this->getDirectoriesForComponent(); $directories = $this->getDirectoriesForComponent();
return \view('bo.pages.directories.index', \compact('directoryModels'));
return \view('bo.pages.directories.index', \compact('directories'));
} }
/** /**
* Store a newly created resource in storage. * Store a newly created resource in storage.
* *
* @param \App\Http\Requests\Bo\Directories\StoreDirectoryRequest $request * @param \App\Http\Requests\Bo\Directory\StoreDirectoryRequest $request
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\Response
*/ */
public function store(StoreDirectoryRequest $request): \Illuminate\Http\RedirectResponse public function store(StoreDirectoryRequest $request)
{ {
return DB::transaction(function () use ($request) { return DB::transaction(function () use ($request) {
$directory = new Directory(); $directory = new Directory();
$directory->fill($request->validated()); $directory->fill($request->validated());
if ($directory->saveOrFail()) { if ($directory->saveOrFail()) {
return redirect() return redirect()
->route('bo.directories.index', ['tenant' => \tenant()]) ->route('bo.directories.index')
->with('success', trans( ->with('success', trans(
':model a été créé', ':model a été créé',
['model' => trans('models.classes.directory')] ['model' => Str::singular(trans('models.classes.directories'))]
)); ));
} }
return redirect() return redirect()
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été créé', ':model n\'a pas été créé',
['model' => trans('models.classes.directory')] ['model' => Str::singular(trans('models.classes.directories'))]
)); ));
}); });
} }
@ -63,10 +59,11 @@ class DirectoriesController extends Controller
* Show the form for editing the specified resource. * Show the form for editing the specified resource.
* *
* @param \App\Models\Directory $directory * @param \App\Models\Directory $directory
* @return \Illuminate\View\View * @return \Illuminate\Http\Response
*/ */
public function edit(Directory $directory): \Illuminate\View\View public function edit(Directory $directory)
{ {
$this->authorize('edit', $directory);
return view('bo.pages.directories.edit', ['directoryModel' => $directory]); return view('bo.pages.directories.edit', ['directoryModel' => $directory]);
} }
@ -75,26 +72,27 @@ class DirectoriesController extends Controller
* *
* @param \App\Http\Requests\Bo\Directories\UpdateDirectoryRequest $request * @param \App\Http\Requests\Bo\Directories\UpdateDirectoryRequest $request
* @param \App\Models\Directory $directory * @param \App\Models\Directory $directory
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\Response
*/ */
public function update(UpdateDirectoryRequest $request, Directory $directory): \Illuminate\Http\RedirectResponse public function update(UpdateDirectoryRequest $request, Directory $directory)
{ {
$this->authorize('edit', $directory);
return DB::transaction(function () use ($request, $directory) { return DB::transaction(function () use ($request, $directory) {
$directory->fill($request->validated()); $directory->fill($request->validated());
if ($directory->saveOrFail()) { if ($directory->saveOrFail()) {
return redirect() return redirect()
->route('bo.directories.edit', ['tenant' => \tenant(), 'directory' => $directory]) ->route('bo.directories.edit', $directory)
->with('success', trans( ->with('success', trans(
':model a été mis à jour', ':model a été mis à jour',
['model' => trans('models.classes.directory')] ['model' => Str::singular(trans('models.classes.directories'))]
)); ));
} }
return redirect() return redirect()
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été mis à jour', ':model n\'a pas été mis à jour',
['model' => trans('models.classes.directory')] ['model' => Str::singular(trans('models.classes.directories'))]
)); ));
}); });
} }
@ -103,12 +101,12 @@ class DirectoriesController extends Controller
* Remove the specified resource from storage. * Remove the specified resource from storage.
* *
* @param \App\Models\Directory $directory * @param \App\Models\Directory $directory
* @return \Illuminate\Database\Eloquent\Collection * @return \Illuminate\Http\Response
*/ */
public function destroy(Directory $directory): \Illuminate\Database\Eloquent\Collection public function destroy(Directory $directory)
{ {
$this->authorize('delete', $directory);
return DB::transaction(function () use ($directory) { return DB::transaction(function () use ($directory) {
/** @var \App\Models\Directory $directoryChild */
foreach ($directory->children as $directoryChild) { foreach ($directory->children as $directoryChild) {
$directoryChild->directory_id = null; $directoryChild->directory_id = null;
$directoryChild->saveOrFail(); $directoryChild->saveOrFail();
@ -116,8 +114,7 @@ class DirectoriesController extends Controller
/** @var \Illuminate\Database\Eloquent\Collection<\App\Models\Document> */ /** @var \Illuminate\Database\Eloquent\Collection<\App\Models\Document> */
$documents = $directory->documents()->get()->sortBy('lang', \SORT_REGULAR, true); $documents = $directory->documents()->get()->sortBy('lang', \SORT_REGULAR, true);
if ($documents->count()) { if ($documents->count()) {
$documents->each(function (\Illuminate\Database\Eloquent\Model $document) { $documents->each(function (Document $document) {
/** @var \App\Models\Document $document */
$document->deleteOrFail(); $document->deleteOrFail();
}); });
} }
@ -130,7 +127,7 @@ class DirectoriesController extends Controller
':model :name n\'a pas été supprimé, ':model :name n\'a pas été supprimé,
un bouton est encore associé à ce dernier dans les paramètres :', un bouton est encore associé à ce dernier dans les paramètres :',
[ [
'model' => trans('models.classes.directory'), 'model' => Str::singular(trans('models.classes.directories')),
'name' => $directory->name['fr'] 'name' => $directory->name['fr']
] ]
) )
@ -149,7 +146,7 @@ class DirectoriesController extends Controller
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return void * @return void
*/ */
public function saveOrder(Request $request): void public function saveOrder(Request $request)
{ {
$this->assignOrder($request->directories, null); $this->assignOrder($request->directories, null);
} }
@ -157,11 +154,11 @@ class DirectoriesController extends Controller
/** /**
* Assign the new order and directory id to each directory. * Assign the new order and directory id to each directory.
* *
* @param array $directories * @param array $directories
* @param integer|null $directoryId * @param integer $directoryId
* @return void * @return void
*/ */
public function assignOrder(array $directories, int|null $directoryId = null): void public function assignOrder(array $directories, int $directoryId = null)
{ {
foreach ($directories as $newDirectory) { foreach ($directories as $newDirectory) {
$directory = Directory::where('id', $newDirectory['id'])->first(); $directory = Directory::where('id', $newDirectory['id'])->first();
@ -178,27 +175,27 @@ class DirectoriesController extends Controller
* Save the new order/structure of directories. * Save the new order/structure of directories.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \App\Models\Directory * @return \Illuminate\Http\Response
*/ */
public function needAuthorization(Request $request): \App\Models\Directory public function needAuthorization(Request $request)
{ {
$directory = Directory::query()->where('id', $request->id)->firstOrFail(); $directory = Directory::where('id', $request->id)->firstOrFail();
$directory->needAuthorization = $directory->needAuthorization ? false : true; $directory->needAuthorization = $directory->needAuthorization ? false : true;
$directory->saveOrFail();
return $directory; if ($directory->saveOrFail()) {
return $directory;
}
} }
/** /**
* Get directories for component. * Get directories for component
* *
* @return \Illuminate\Database\Eloquent\Collection * @return \Illuminate\Database\Eloquent\Collection<array-key,\App\Models\Directory>
*/ */
private function getDirectoriesForComponent(): \Illuminate\Database\Eloquent\Collection private function getDirectoriesForComponent()
{ {
return Directory::query() return Directory::query()
->where('tenant_id', \tenant()->getKey())
->withCount('documents') ->withCount('documents')
->orderBy('order', 'asc') ->orderBy('order', 'ASC')->get();
->get();
} }
} }

View file

@ -2,11 +2,9 @@
namespace App\Http\Controllers\Bo; namespace App\Http\Controllers\Bo;
use App\Enums\Documents\LangEnum;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Documents\StoreDocumentRequest; use App\Http\Requests\Bo\Documents\StoreDocumentRequest;
use App\Http\Requests\Bo\Documents\UpdateDocumentRequest; use App\Http\Requests\Bo\Documents\UpdateDocumentRequest;
use App\Http\Requests\Bo\UploadRequest;
use App\Models\Directory; use App\Models\Directory;
use App\Models\Document; use App\Models\Document;
use App\Models\Tag; use App\Models\Tag;
@ -25,26 +23,19 @@ class DocumentController extends Controller
{ {
use ChangesModelOrder; use ChangesModelOrder;
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Document::class);
}
/** /**
* Display a listing of the resource. * Display a listing of the resource.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View * @return \Illuminate\Http\Response
*/ */
public function index(Request $request): \Illuminate\View\View public function index(Request $request)
{ {
/** @var \Illuminate\Database\Eloquent\Builder $query */ /** @var \Illuminate\Database\Eloquent\Builder $query */
$query = Document::query()->with(['directory', 'parentDocument']); $query = Document::query();
$tags = Tag::get(); $tags = Tag::get();
/** @var string $search Search field */
if ($request->search) { if ($request->search) {
$this->searchQuery( $this->searchQuery(
$query, $query,
@ -53,7 +44,7 @@ class DocumentController extends Controller
'title', 'title',
); );
} }
$searchFields = trans('validation.attributes.title'); $searchFields = trans('Titre');
/** Search by tags */ /** Search by tags */
$query->when($request->tag, function ($query) use ($request) { $query->when($request->tag, function ($query) use ($request) {
@ -65,43 +56,37 @@ class DocumentController extends Controller
/** Sort columns with a query */ /** Sort columns with a query */
$this->sortQuery($query); $this->sortQuery($query);
$documentModels = $this->paginate($query); $documents = $this->paginate($query);
return view('bo.pages.documents.index', [ return view('bo.pages.documents.index', [
'documentModels' => $documentModels, 'documents' => $documents,
'searchFields' => $searchFields, 'searchFields' => $searchFields,
'searchTag' => $request->tag, 'searchTag' => $request->tag,
'tags' => $tags 'tags' => $tags
]); ]);
} }
/** /**
* Show the form for creating a new resource. * Show the form for creating a new resource.
* *
* @param \App\Models\Document $document * @return \Illuminate\Http\Response
* @return \Illuminate\View\View
*/ */
public function create(Document $document): \Illuminate\View\View public function create()
{ {
$documentsList = Document::query()->where('lang', LangEnum::fr)->get(); $documents = Document::where('lang', \App\Enums\Documents\LangEnum::fr())->get();
$directories = Directory::getChildrenDirectories(); $directories = Directory::getChildrenDirectories();
$tags = Tag::get(); $tags = Tag::get();
return view('bo.pages.documents.create', [ return view('bo.pages.documents.create', compact('documents', 'directories', 'tags'));
'documentModel' => $document,
'documentsList' => $documentsList,
'directories' => $directories,
'tags' => $tags
]);
} }
/** /**
* Store a newly created resource in storage. * Store a newly created resource in storage.
* *
* @param \App\Http\Requests\Bo\Documents\StoreDocumentRequest $request * @param \App\Http\Requests\Bo\Documents\StoreDocumentRequest $request
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\Response
*/ */
public function store(StoreDocumentRequest $request): \Illuminate\Http\RedirectResponse public function store(StoreDocumentRequest $request)
{ {
return DB::transaction(function () use ($request) { return DB::transaction(function () use ($request) {
$document = new Document(); $document = new Document();
@ -109,17 +94,17 @@ class DocumentController extends Controller
if ($document->saveOrFail()) { if ($document->saveOrFail()) {
$document->tags()->sync(collect($request->tags)->pluck('id')); $document->tags()->sync(collect($request->tags)->pluck('id'));
return redirect() return redirect()
->route('bo.documents.edit', ['tenant' => \tenant(), 'document' => $document]) ->route('bo.documents.edit', $document)
->with('success', trans( ->with('success', trans(
':model a été créé', ':model a été créé',
['model' => trans('models.classes.document')] ['model' => Str::singular(trans('models.classes.documents'))]
)); ));
} }
return redirect() return redirect()
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été créé', ':model n\'a pas été créé',
['model' => trans('models.classes.document')] ['model' => Str::singular(trans('models.classes.documents'))]
)); ));
}); });
} }
@ -128,47 +113,47 @@ class DocumentController extends Controller
* Show the form for editing the specified resource. * Show the form for editing the specified resource.
* *
* @param \App\Models\Document $document * @param \App\Models\Document $document
* @return \Illuminate\View\View * @return \Illuminate\Http\Response
*/ */
public function edit(Document $document): \Illuminate\View\View public function edit(Document $document)
{ {
$documentsList = Document::query()->where('lang', LangEnum::fr)->get(); $documents = Document::where('lang', \App\Enums\Documents\LangEnum::fr())->get();
$directories = Directory::getChildrenDirectories(); $directories = Directory::getChildrenDirectories();
$tags = Tag::get(); $tags = Tag::get();
return view('bo.pages.documents.edit', [ return view('bo.pages.documents.edit', [
'documents' => $documents,
'documentModel' => $document, 'documentModel' => $document,
'documentsList' => $documentsList, 'directories' => $directories,
'directories' => $directories, 'tags' => $tags
'tags' => $tags
]); ]);
} }
/** /**
* Update the specified resource in storage. * Update the specified resource in storage.
* *
* @param \App\Http\Requests\Bo\Documents\UpdateDocumentRequest $request * @param \App\Http\Requests\Bo\Document\UpdateDocumentRequest $request
* @param \App\Models\Document $document * @param \App\Models\Document $document
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\Response
*/ */
public function update(UpdateDocumentRequest $request, Document $document): \Illuminate\Http\RedirectResponse public function update(UpdateDocumentRequest $request, Document $document)
{ {
return DB::transaction(function () use ($request, $document) { return DB::transaction(function () use ($request, $document) {
$document->fill($request->validated()); $document->fill($request->validated());
$document->tags()->sync(collect($request->tags)->pluck('id')); $document->tags()->sync(collect($request->tags)->pluck('id'));
if ($document->saveOrFail()) { if ($document->saveOrFail()) {
return redirect() return redirect()
->route('bo.documents.edit', ['tenant' => \tenant(), 'document' => $document]) ->route('bo.documents.edit', $document)
->with('success', trans( ->with('success', trans(
':model a été mis à jour', ':model a été mis à jour',
['model' => trans('models.classes.document')] ['model' => Str::singular(trans('models.classes.documents'))]
)); ));
} }
return redirect() return redirect()
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été mis à jour', ':model n\'a pas été mis à jour',
['model' => trans('models.classes.document')] ['model' => Str::singular(trans('models.classes.documents'))]
)); ));
}); });
} }
@ -176,59 +161,57 @@ class DocumentController extends Controller
/** /**
* Remove the specified resource from storage. * Remove the specified resource from storage.
* *
* @param \App\Models\Document $document * @param \App\Models\Document $document
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\Response
*/ */
public function destroy(Document $document): \Illuminate\Http\RedirectResponse public function destroy(Document $document)
{ {
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
$document->tags()->sync([]); $document->tags()->sync([]);
/** @var \App\Models\Document $documentChild */
foreach ($document->children as $documentChild) { foreach ($document->children as $documentChild) {
Document::removeDocument($documentChild); Document::removeDocument($documentChild);
$documentChild->deleteOrFail(); $documentChild->deleteOrFail();
} }
if ($document->deleteOrFail()) { if ($document->deleteOrFail()) {
return redirect() return redirect()
->route('bo.documents.index', array_merge(['tenant' => \tenant()], $previousQueries)) ->route('bo.documents.index')
->with('warning', trans( ->with('warning', trans(
':model a bien été supprimé', ':model a bien été supprimé',
['model' => trans('models.classes.document')] ['model' => Str::singular(trans('models.classes.documents'))]
)); ));
} }
return redirect() return redirect()
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été supprimé', ':model n\'a pas été supprimé',
['model' => trans('models.classes.document')] ['model' => Str::singular(trans('models.classes.documents'))]
)); ));
} }
/** /**
* Download the document * Download the document
* *
* @param string $tenant * @param mixed $uuid
* @param string $uuid
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse * @return \Symfony\Component\HttpFoundation\BinaryFileResponse
*/ */
public function download(string $tenant, string $uuid): \Symfony\Component\HttpFoundation\BinaryFileResponse public function download($uuid)
{ {
$doc = Document::where('uuid', $uuid)->firstOrFail(); $doc = Document::where('uuid', $uuid)->firstOrFail();
$pathToFile = public_path() . Storage::url( $pathToFile = public_path() . Storage::url('docs/' . $doc->uuid . '.' . $doc->type);
sprintf('docs/%s/%s.%s', $tenant, $doc->uuid, $doc->type)
);
return response()->download($pathToFile, $doc->path); return response()->download($pathToFile, $doc->path);
} }
/** /**
* @param \App\Http\Requests\Bo\UploadRequest $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
* @throws \Pion\Laravel\ChunkUpload\Exceptions\UploadMissingFileException Bla. * @throws \Pion\Laravel\ChunkUpload\Exceptions\UploadMissingFileException Bla.
*/ */
public function upload(UploadRequest $request) public function upload(Request $request)
{ {
if (isset($request->uuid)) {
$uuid = $request->uuid;
} else {
$uuid = false;
}
// Create the file receiver. // Create the file receiver.
$receiver = new FileReceiver("file", $request, HandlerFactory::classFromRequest($request)); $receiver = new FileReceiver("file", $request, HandlerFactory::classFromRequest($request));
// Check if the upload is success, throw exception or return response you need. // Check if the upload is success, throw exception or return response you need.
@ -241,31 +224,41 @@ class DocumentController extends Controller
if ($save->isFinished()) { if ($save->isFinished()) {
// Save the file and return any response you need, current example uses `move` function. // Save the file and return any response you need, current example uses `move` function.
// If you are not using move, you need to manually delete the file: unlink($save->getFile()->getPathname()). // If you are not using move, you need to manually delete the file: unlink($save->getFile()->getPathname()).
return $this->saveFile($save->getFile(), $request->uuid); return $this->saveFile($save->getFile(), $uuid);
} }
// We are in chunk mode, lets send the current progress. // We are in chunk mode, lets send the current progress.
/** @var AbstractHandler $handler */
$handler = $save->handler(); $handler = $save->handler();
return response()->json([ return response()->json([
"done" => $handler->getPercentageDone(), "done" => $handler->getPercentageDone(),
'status' => true 'status' => true
]); ]);
} }
/** /**
* @param \Illuminate\Http\UploadedFile $file * @param \Illuminate\Http\UploadedFile $file
* @param string|null $uuid * @param mixed $uuid
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Contracts\Container\BindingResolutionException Bla.
*/ */
protected function saveFile(UploadedFile $file, ?string $uuid): \Illuminate\Http\JsonResponse protected function saveFile(UploadedFile $file, $uuid)
{ {
/** @var \Illuminate\Filesystem\FilesystemAdapter */ if ($uuid === false) {
$disk = Storage::disk('docs'); $uuid = sprintf(
$uuid = $uuid ?: Str::uuid(); '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff),
/** @var \Illuminate\Http\UploadedFile $file */ mt_rand(0, 0xffff),
$filename = sprintf('%s/%s.%s', \tenant()->getKey(), $uuid, $file->guessExtension()); mt_rand(0, 0xffff),
$disk->putFileAs('', $file, $filename); mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0xffff)
);
}
$finalPath = storage_path('app/public/docs/');
// Move the file name.
$file->move($finalPath, $uuid . '.' . $file->getClientOriginalExtension());
return response()->json([ return response()->json([
'uid' => $uuid, 'uid' => $uuid,
]); ]);

View file

@ -2,38 +2,33 @@
namespace App\Http\Controllers\Bo; namespace App\Http\Controllers\Bo;
use App\Enums\Formularies\InputStatusEnum;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Formularies\StoreFormularyRequest; use App\Http\Requests\Bo\Formularies\StoreFormularyRequest;
use App\Http\Requests\Bo\Formularies\UpdateFormularyRequest; use App\Http\Requests\Bo\Formularies\UpdateFormularyRequest;
use App\Models\Contact; use App\Lib\Helpers\ToolboxHelper;
use App\Models\Formulary; use App\Models\Formulary;
use App\Traits\Controllers\ChangesModelOrder; use App\Traits\Controllers\ChangesModelOrder;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class FormularyController extends Controller class FormularyController extends Controller
{ {
use ChangesModelOrder; use ChangesModelOrder;
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Formulary::class);
}
/** /**
* Display a listing of the resource. * Display a listing of the resource.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View * @return \Illuminate\Http\Response
*/ */
public function index(Request $request): \Illuminate\View\View public function index(Request $request)
{ {
/** @var \Illuminate\Database\Eloquent\Builder $query */ /** @var \Illuminate\Database\Eloquent\Builder $query */
$query = Formulary::query(); $query = Formulary::query();
/** @var string $search Search field */
if ($request->search) { if ($request->search) {
$this->searchQuery( $this->searchQuery(
$query, $query,
@ -42,34 +37,33 @@ class FormularyController extends Controller
'name', 'name',
); );
} }
$searchFields = trans('validation.attributes.name'); $searchFields = trans('Nom');
/** Sort columns with a query */ /** Sort columns with a query */
$this->sortQuery($query); $this->sortQuery($query);
$formularyModels = $this->paginate($query); $formulariesModel = $this->paginate($query);
return view('bo.pages.formularies.index', compact('formularyModels', 'searchFields')); return view('bo.pages.formularies.index', compact('formulariesModel', 'searchFields'));
} }
/** /**
* Show the form for creating a new resource. * Show the form for creating a new resource.
* *
* @param \App\Models\Formulary $formulary * @return \Illuminate\Http\Response
* @return \Illuminate\View\View
*/ */
public function create(Formulary $formulary): \Illuminate\View\View public function create()
{ {
return view('bo.pages.formularies.create', ['formularyModel' => $formulary]); return view('bo.pages.formularies.create');
} }
/** /**
* Store a newly created resource in storage. * Store a newly created resource in storage.
* *
* @param \App\Http\Requests\Bo\Formularies\StoreFormularyRequest $request * @param \App\Http\Requests\Bo\Formularies\StoreFormularyRequest $request
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\Response
*/ */
public function store(StoreFormularyRequest $request): \Illuminate\Http\RedirectResponse public function store(StoreFormularyRequest $request)
{ {
return DB::transaction(function () use ($request) { return DB::transaction(function () use ($request) {
$formulary = new Formulary(); $formulary = new Formulary();
@ -77,17 +71,17 @@ class FormularyController extends Controller
if ($formulary->saveOrFail()) { if ($formulary->saveOrFail()) {
return redirect() return redirect()
->route('bo.formularies.edit', ['tenant' => \tenant(), 'formulary' => $formulary]) ->route('bo.formularies.edit', $formulary)
->with('success', trans( ->with('success', trans(
':model a été créé', ':model a été créé',
['model' => trans('models.classes.formulary')] ['model' => Str::singular(trans('models.classes.formularies'))]
)); ));
} }
return redirect() return redirect()
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été créé', ':model n\'a pas été créé',
['model' => trans('models.classes.formulary')] ['model' => Str::singular(trans('models.classes.formularies'))]
)); ));
}); });
} }
@ -96,40 +90,48 @@ class FormularyController extends Controller
* Show the form for editing the specified resource. * Show the form for editing the specified resource.
* *
* @param \App\Models\Formulary $formulary * @param \App\Models\Formulary $formulary
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Contracts\View\View * @return \Illuminate\Http\Response
*/ */
public function edit(Formulary $formulary): \Illuminate\Http\RedirectResponse|\Illuminate\Contracts\View\View public function edit(Formulary $formulary)
{ {
return view('bo.pages.formularies.edit', [ if ($formulary->status->value === InputStatusEnum::system()->value) {
'formularyModel' => $formulary return redirect()->route('bo.formularies.index')
]); ->with('error', trans(
'Administration du champs :name impossible car il est vérouillé !',
['name' => $formulary->name]
));
} else {
return view('bo.pages.formularies.edit', [
'formularyModel' => $formulary
]);
}
} }
/** /**
* Update the specified resource in storage. * Update the specified resource in storage.
* *
* @param \App\Http\Requests\Bo\Formularies\UpdateFormularyRequest $request * @param \App\Http\Requests\Bo\Formulary\UpdateFormularyRequest $request
* @param \App\Models\Formulary $formulary * @param \App\Models\Formulary $formulary
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\Response
*/ */
public function update(UpdateFormularyRequest $request, Formulary $formulary): \Illuminate\Http\RedirectResponse public function update(UpdateFormularyRequest $request, Formulary $formulary)
{ {
return DB::transaction(function () use ($request, $formulary) { return DB::transaction(function () use ($request, $formulary) {
$formulary->fill($request->validated()); $formulary->fill($request->validated());
if ($formulary->saveOrFail()) { if ($formulary->saveOrFail()) {
return redirect() return redirect()
->route('bo.formularies.edit', ['tenant' => \tenant(), 'formulary' => $formulary]) ->route('bo.formularies.edit', $formulary)
->with('success', trans( ->with('success', trans(
':model a été mis à jour', ':model a été mis à jour',
['model' => trans('models.classes.formulary')] ['model' => Str::singular(trans('models.classes.formularies'))]
)); ));
} }
return redirect() return redirect()
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été mis à jour', ':model n\'a pas été mis à jour',
['model' => trans('models.classes.formulary')] ['model' => Str::singular(trans('models.classes.formularies'))]
)); ));
}); });
} }
@ -137,41 +139,29 @@ class FormularyController extends Controller
/** /**
* Remove the specified resource from storage. * Remove the specified resource from storage.
* *
* @param \App\Models\Formulary $formulary * @param \App\Models\Formulary $formulary
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\Response
*/ */
public function destroy(Formulary $formulary): \Illuminate\Http\RedirectResponse public function destroy(Formulary $formulary)
{ {
/** @var array<string,string> */ return DB::transaction(function () use ($formulary) {
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
return DB::transaction(function () use ($formulary, $previousQueries) {
// * Remove the input on all contacts. // * Remove the input on all contacts.
Contact::removeDataInputOnAllContacts($formulary); ToolboxHelper::removeDataInputOnAllContacts($formulary);
if ($formulary->deleteOrFail()) { if ($formulary->deleteOrFail()) {
return redirect() return redirect()
->route('bo.formularies.index', array_merge(['tenant' => \tenant()], $previousQueries)) ->route('bo.formularies.index')
->with('warning', trans( ->with('warning', trans(
':model a bien été supprimé', ':model a bien été supprimé',
['model' => trans('models.classes.formulary')] ['model' => Str::singular(trans('models.classes.formularies'))]
)); ));
} }
return redirect()->back()->with('error', trans( return redirect()
':model n\'a pas été supprimé', ->back()
['model' => trans('models.classes.formulary')] ->with('error', trans(
)); ':model n\'a pas été supprimé',
['model' => Str::singular(trans('models.classes.formularies'))]
));
}); });
} }
/**
* Duplicate the specified resource.
*
* @param \App\Models\Formulary $formulary
* @return \Illuminate\Contracts\View\View
*/
public function duplicate(Formulary $formulary): \Illuminate\Contracts\View\View
{
return $this->create($formulary->replicate());
}
} }

View file

@ -6,62 +6,38 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Settings\UpdateSettingRequest; use App\Http\Requests\Bo\Settings\UpdateSettingRequest;
use App\Models\Directory; use App\Models\Directory;
use App\Models\Setting; use App\Models\Setting;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class SettingController extends Controller class SettingController extends Controller
{ {
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Setting::class);
}
/** /**
* Display a listing of the resource. * Display a listing of the resource.
* *
* @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/ */
public function index(Request $request): \Illuminate\Contracts\View\View public function index()
{ {
$directories = Directory::renderDirectories( $directories = Directory::renderDirectories(Directory::whereNull('directory_id')->get());
Directory::query()->whereNull('directory_id')->with('parent')->get(),
);
/** @var \Illuminate\Database\Eloquent\Builder $query */ /** @var \Illuminate\Database\Eloquent\Builder $settings */
$query = Setting::query(); $settings = Setting::query();
if ($request->search) { $this->sortQuery($settings);
$this->searchQuery( $settings = $settings->get();
$query,
$request->search,
null,
'key',
);
}
$searchFields = trans('validation.custom.key');
$this->sortQuery($query); return view('bo.pages.settings.index', compact('settings', 'directories'));
$settingModels = $query->get();
return view('bo.pages.settings.index', compact('settingModels', 'searchFields', 'directories'));
} }
/** /**
* Show the form for editing the specified resource. * Show the form for editing the specified resource.
* *
* @param \App\Models\Setting $setting * @param \App\Models\Setting $setting
* @return \Illuminate\Contracts\View\View * @return \Illuminate\Http\Response
*/ */
public function edit(Setting $setting): \Illuminate\Contracts\View\View public function edit(Setting $setting)
{ {
$directories = Directory::renderDirectories( $directories = Directory::renderDirectories(Directory::whereNull('directory_id')->get());
Directory::query()->whereNull('directory_id')->with('parent')->get(),
);
return view('bo.pages.settings.edit', ['settingModel' => $setting, 'directories' => $directories]); return view('bo.pages.settings.edit', ['settingModel' => $setting, 'directories' => $directories]);
} }
@ -69,27 +45,27 @@ class SettingController extends Controller
/** /**
* Update the specified resource in storage. * Update the specified resource in storage.
* *
* @param \App\Http\Requests\Bo\Settings\UpdateSettingRequest $request * @param \App\Http\Requests\Bo\Setting\UpdateSettingRequest $request
* @param \App\Models\Setting $setting * @param \App\Models\Setting $setting
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\Response
*/ */
public function update(UpdateSettingRequest $request, Setting $setting): \Illuminate\Http\RedirectResponse public function update(UpdateSettingRequest $request, Setting $setting)
{ {
return DB::transaction(function () use ($request, $setting) { return DB::transaction(function () use ($request, $setting) {
$setting->fill($request->validated()); $setting->fill($request->validated());
if ($setting->saveOrFail()) { if ($setting->saveOrFail()) {
return redirect() return redirect()
->route('bo.settings.edit', ['tenant' => \tenant(), 'setting' => $setting]) ->route('bo.settings.edit', $setting)
->with('success', trans( ->with('success', trans(
':model a été mis à jour', ':model a été mis à jour',
['model' => trans('models.classes.setting')] ['model' => Str::singular(trans('models.classes.settings'))]
)); ));
} }
return redirect() return redirect()
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été mis à jour', ':model n\'a pas été mis à jour',
['model' => trans('models.classes.setting')] ['model' => Str::singular(trans('models.classes.settings'))]
)); ));
}); });
} }
@ -97,26 +73,24 @@ class SettingController extends Controller
/** /**
* Remove the specified resource from storage. * Remove the specified resource from storage.
* *
* @param \App\Models\Setting $setting * @param \App\Models\Setting $setting
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\Response
*/ */
public function destroy(Setting $setting): \Illuminate\Http\RedirectResponse public function destroy(Setting $setting)
{ {
return DB::transaction(function () use ($setting) { if ($setting->deleteOrFail()) {
if ($setting->deleteOrFail()) {
return redirect()
->route('bo.settings.index', ['tenant' => \tenant()])
->with('warning', trans(
':model a bien été supprimé',
['model' => trans('models.classes.setting')]
));
}
return redirect() return redirect()
->back() ->route('bo.settings.index')
->with('error', trans( ->with('warning', trans(
':model n\'a pas été supprimé', ':model a bien été supprimé',
['model' => trans('models.classes.setting')] ['model' => Str::singular(trans('models.classes.settings'))]
)); ));
}); }
return redirect()
->back()
->with('error', trans(
':model n\'a pas été supprimé',
['model' => Str::singular(trans('models.classes.settings'))]
));
} }
} }

View file

@ -9,30 +9,24 @@ use App\Models\Tag;
use App\Traits\Controllers\ChangesModelOrder; use App\Traits\Controllers\ChangesModelOrder;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class TagController extends Controller class TagController extends Controller
{ {
use ChangesModelOrder; use ChangesModelOrder;
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Tag::class);
}
/** /**
* Display a listing of the resource. * Display a listing of the resource.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View * @return \Illuminate\Http\Response
*/ */
public function index(Request $request): \Illuminate\Contracts\View\View public function index(Request $request)
{ {
/** @var \Illuminate\Database\Eloquent\Builder $query */ /** @var \Illuminate\Database\Eloquent\Builder $query */
$query = Tag::query(); $query = Tag::query();
/** @var string $search Search field */
if ($request->search) { if ($request->search) {
$this->searchQuery( $this->searchQuery(
$query, $query,
@ -41,51 +35,50 @@ class TagController extends Controller
'name', 'name',
); );
} }
$searchFields = trans('validation.attributes.name'); $searchFields = trans('Nom');
/** Sort columns with a query */ /** Sort columns with a query */
$this->sortQuery($query); $this->sortQuery($query);
$tagModels = $this->paginate($query); $tags = $this->paginate($query);
return view('bo.pages.tags.index', compact('tagModels', 'searchFields')); return view('bo.pages.tags.index', compact('tags', 'searchFields'));
} }
/** /**
* Show the form for creating a new resource. * Show the form for creating a new resource.
* *
* @param \App\Models\Tag $tag * @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/ */
public function create(Tag $tag): \Illuminate\Contracts\View\View public function create()
{ {
return view('bo.pages.tags.create', ['tagModel' => $tag]); return view('bo.pages.tags.create');
} }
/** /**
* Store a newly created resource in storage. * Store a newly created resource in storage.
* *
* @param \App\Http\Requests\Bo\Tags\StoreTagRequest $request * @param \App\Http\Requests\Bo\Tag\StoreTagRequest $request
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\Response
*/ */
public function store(StoreTagRequest $request): \Illuminate\Http\RedirectResponse public function store(StoreTagRequest $request)
{ {
return DB::transaction(function () use ($request) { return DB::transaction(function () use ($request) {
$tag = new Tag(); $tag = new Tag();
$tag->fill($request->validated()); $tag->fill($request->validated());
if ($tag->saveOrFail()) { if ($tag->saveOrFail()) {
return redirect() return redirect()
->route('bo.tags.edit', ['tenant' => \tenant(), 'tag' => $tag]) ->route('bo.tags.edit', $tag)
->with('success', trans( ->with('success', trans(
':model a été créé', ':model a été créé',
['model' => trans('models.classes.tag')] ['model' => Str::singular(trans('models.classes.tags'))]
)); ));
} }
return redirect() return redirect()
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été créé', ':model n\'a pas été créé',
['model' => trans('models.classes.tag')] ['model' => Str::singular(trans('models.classes.tags'))]
)); ));
}); });
} }
@ -94,9 +87,9 @@ class TagController extends Controller
* Show the form for editing the specified resource. * Show the form for editing the specified resource.
* *
* @param \App\Models\Tag $tag * @param \App\Models\Tag $tag
* @return \Illuminate\Contracts\View\View * @return \Illuminate\Http\Response
*/ */
public function edit(Tag $tag): \Illuminate\Contracts\View\View public function edit(Tag $tag)
{ {
return view('bo.pages.tags.edit', ['tagModel' => $tag]); return view('bo.pages.tags.edit', ['tagModel' => $tag]);
} }
@ -104,28 +97,28 @@ class TagController extends Controller
/** /**
* Update the specified resource in storage. * Update the specified resource in storage.
* *
* @param \App\Http\Requests\Bo\Tags\UpdateTagRequest $request * @param \App\Http\Requests\Bo\Tag\UpdateTagRequest $request
* @param \App\Models\Tag $tag * @param \App\Models\Tag $tag
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\Response
*/ */
public function update(UpdateTagRequest $request, Tag $tag): \Illuminate\Http\RedirectResponse public function update(UpdateTagRequest $request, Tag $tag)
{ {
return DB::transaction(function () use ($request, $tag) { return DB::transaction(function () use ($request, $tag) {
$tag->fill($request->validated()); $tag->fill($request->validated());
if ($tag->saveOrFail()) { if ($tag->saveOrFail()) {
return redirect() return redirect()
->route('bo.tags.edit', ['tenant' => \tenant(), 'tag' => $tag]) ->route('bo.tags.edit', $tag)
->with('success', trans( ->with('success', trans(
':model a été mis à jour', ':model a été mis à jour',
['model' => trans('models.classes.tag')] ['model' => Str::singular(trans('models.classes.tags'))]
)); ));
} }
return redirect() return redirect()
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été mis à jour', ':model n\'a pas été mis à jour',
['model' => trans('models.classes.tag')] ['model' => Str::singular(trans('models.classes.tags'))]
)); ));
}); });
} }
@ -133,40 +126,24 @@ class TagController extends Controller
/** /**
* Remove the specified resource from storage. * Remove the specified resource from storage.
* *
* @param \App\Models\Tag $tag * @param \App\Models\Tag $tag
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\Response
*/ */
public function destroy(Tag $tag): \Illuminate\Http\RedirectResponse public function destroy(Tag $tag)
{ {
/** @var array<string,string> */ if ($tag->deleteOrFail()) {
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
return DB::transaction(function () use ($tag, $previousQueries) {
if ($tag->deleteOrFail()) {
return redirect()
->route('bo.tags.index', array_merge(['tenant' => \tenant()], $previousQueries))
->with('warning', trans(
':model a bien été supprimé',
['model' => trans('models.classes.tag')]
));
}
return redirect() return redirect()
->back() ->route('bo.tags.index')
->with('error', trans( ->with('warning', trans(
':model n\'a pas été supprimé', ':model a bien été supprimé',
['model' => trans('models.classes.tag')] ['model' => Str::singular(trans('models.classes.tags'))]
)); ));
}); }
} return redirect()
->back()
/** ->with('error', trans(
* Duplicate the specified resource. ':model n\'a pas été supprimé',
* ['model' => Str::singular(trans('models.classes.tags'))]
* @param \App\Models\Tag $tag ));
* @return \Illuminate\Contracts\View\View
*/
public function duplicate(Tag $tag): \Illuminate\Contracts\View\View
{
return $this->create($tag->replicate());
} }
} }

View file

@ -1,174 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Tenants\StoreTenantRequest;
use App\Http\Requests\Bo\Tenants\UpdateTenantRequest;
use App\Models\Tenant;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
class TenantController extends Controller
{
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Tenant::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 $query */
$query = Tenant::query();
if ($request->search) {
$this->searchQuery(
$query,
$request->search,
null,
'name',
);
}
$searchFields = trans('validation.attributes.name');
/** Sort columns with a query */
$this->sortQuery($query);
$tenantModels = $this->paginate($query);
return view('bo.pages.tenants.index', compact('tenantModels', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\Tenant $tenant
* @return \Illuminate\Contracts\View\View
*/
public function create(Tenant $tenant): \Illuminate\Contracts\View\View
{
return view('bo.pages.tenants.create', ['tenantModel' => $tenant]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Tenants\StoreTenantRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreTenantRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$tenant = new Tenant();
$tenant->fill($request->validated());
if ($tenant->saveOrFail()) {
return redirect()
->route('bo.tenants.edit', ['tenant' => $tenant])
->with('success', trans(
':model a été créé',
['model' => trans('models.classes.tenant')]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été créé',
['model' => trans('models.classes.tenant')]
));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Tenant $tenant
* @return \Illuminate\Contracts\View\View
*/
public function edit(Tenant $tenant): \Illuminate\Contracts\View\View
{
return view('bo.pages.tenants.edit', ['tenantModel' => $tenant]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Tenants\UpdateTenantRequest $request
* @param \App\Models\Tenant $tenant
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateTenantRequest $request, Tenant $tenant): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $tenant) {
$tenant->fill($request->validated());
if ($tenant->saveOrFail()) {
return redirect()
->route('bo.tenants.edit', ['tenant' => $tenant])
->with('success', trans(
':model a été mis à jour',
['model' => trans('models.classes.tenant')]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été mis à jour',
['model' => trans('models.classes.tenant')]
));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Tenant $tenant
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Tenant $tenant): \Illuminate\Http\RedirectResponse
{
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
return DB::transaction(function () use ($tenant, $previousQueries) {
if ($tenant->deleteOrFail()) {
/** @var string $tenantKey */
$tenantKey = $tenant->getKey();
Storage::disk('public')->deleteDirectory("modelfiles/{$tenantKey}");
Storage::disk('public')->deleteDirectory("docs/{$tenantKey}");
return redirect()
->route('bo.tenants.index', $previousQueries)
->with('warning', trans(
':model a bien été supprimé',
['model' => trans('models.classes.tenant')]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été supprimé',
['model' => trans('models.classes.tenant')]
));
});
}
/**
* Duplicate the specified resource.
*
* @param \App\Models\Tenant $tenant
* @return \Illuminate\Contracts\View\View
*/
public function duplicate(Tenant $tenant): \Illuminate\Contracts\View\View
{
return $this->create($tenant->replicate());
}
}

View file

@ -1,170 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Users\StoreUserRequest;
use App\Http\Requests\Bo\Users\UpdateUserRequest;
use App\Models\Tenant;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class UserController extends Controller
{
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(User::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 $userModels */
$userModels = User::query()->with('tenants');
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$userModels,
$search,
null,
'first_name',
'last_name',
'email',
'role',
);
}
$searchFields = \implode(', ', [
trans('validation.attributes.first_name'),
trans('validation.attributes.last_name'),
trans('validation.attributes.email'),
trans('validation.attributes.role'),
]);
/** Sort columns with a query */
$this->sortQuery($userModels);
/** Custom pagination */
$userModels = $this->paginate($userModels);
return view('bo.pages.users.index', compact('userModels', 'search', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\User $user
* @return \Illuminate\Contracts\View\View
*/
public function create(User $user): \Illuminate\Contracts\View\View
{
$tenants = Tenant::query()->orderBy('name', 'asc')->get();
return view('bo.pages.users.create', ['userModel' => $user, 'tenants' => $tenants]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Users\StoreUserRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreUserRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$user = new User();
$user->fill($request->validated());
if ($user->saveOrFail()) {
$user->tenants()->sync(collect($request->validated('tenants'))->pluck('id'));
return redirect()->route('bo.users.edit', ['tenant' => tenant(), 'user' => $user])
->with('success', __('crud.messages.has_been_created', [
'model' => Str::ucfirst(__('models.classes.user'))
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_created', [
'model' => Str::ucfirst(__('models.classes.user'))
]));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\User $user
* @return \Illuminate\Contracts\View\View
*/
public function edit(User $user): \Illuminate\Contracts\View\View
{
$tenants = Tenant::query()->orderBy('name', 'asc')->get();
return view('bo.pages.users.edit', ['userModel' => $user, 'tenants' => $tenants]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Users\UpdateUserRequest $request
* @param \App\Models\User $user
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateUserRequest $request, User $user): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $user) {
$user->fill($request->validated());
if ($user->saveOrFail()) {
$user->tenants()->sync(collect($request->validated('tenants'))->pluck('id'));
return redirect()->route('bo.users.edit', ['tenant' => tenant(), 'user' => $user])
->with('success', __('crud.messages.has_been_updated', [
'model' => Str::ucfirst(__('models.classes.user'))
]));
}
return redirect()->route('bo.users.edit', ['tenant' => tenant(), 'user' => $user])
->with('error', __('crud.messages.cannot_be_updated', [
'model' => Str::ucfirst(__('models.classes.user'))
]));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\User $user
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(User $user): \Illuminate\Http\RedirectResponse
{
if ($user->deleteOrFail()) {
return redirect()->route('bo.users.index', ['tenant' => tenant()])
->with('success', __('crud.messages.has_been_deleted', [
'model' => Str::ucfirst(__('models.classes.user'))
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_deleted', [
'model' => Str::ucfirst(__('models.classes.user'))
]));
}
/**
* Duplicate the specified resource.
*
* @param \App\Models\User $user
* @return \Illuminate\Contracts\View\View
*/
public function duplicate(User $user): \Illuminate\Contracts\View\View
{
return $this->create($user->replicate());
}
}

View file

@ -2,7 +2,9 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Enums\ItemsPerPaginationEnum; use App\Enums\Pagination\ItemsPerPaginationEnum;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Bus\DispatchesJobs;
@ -12,7 +14,6 @@ use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Session; use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Uwil\LaravelCommon\Helpers\ToolboxHelper;
class Controller extends BaseController class Controller extends BaseController
{ {
@ -26,7 +27,7 @@ class Controller extends BaseController
* *
* @param \Illuminate\Database\Eloquent\Builder $query The eloquent query builder. * @param \Illuminate\Database\Eloquent\Builder $query The eloquent query builder.
* @param string $search The query string. * @param string $search The query string.
* @param callable|null $searchQuery The query string. * @param callable $searchQuery The query string.
* @param string ...$fields Either a simple array or a string * @param string ...$fields Either a simple array or a string
* in case of a string the two fields * in case of a string the two fields
* would be concatenate to make the search. * would be concatenate to make the search.
@ -35,7 +36,7 @@ class Controller extends BaseController
protected function searchQuery( protected function searchQuery(
Builder $query, Builder $query,
string $search, string $search,
callable|null $searchQuery = null, callable $searchQuery = null,
string ...$fields string ...$fields
): Builder { ): Builder {
if (count($fields)) { if (count($fields)) {
@ -95,16 +96,16 @@ class Controller extends BaseController
* @param array $ignore * @param array $ignore
* @return \Illuminate\Database\Eloquent\Builder * @return \Illuminate\Database\Eloquent\Builder
*/ */
protected function sortQuery(Builder $query, array $ignore = []): \Illuminate\Database\Eloquent\Builder protected function sortQuery(Builder $query, array $ignore = []): Builder
{ {
$rName = request()->route()->getName(); $rName = request()->route()->getName();
$table = $query->getModel()->getTable(); $table = $query->getModel()->getTable();
if (request()->input('sort_col')) { if (request()->sort_col) {
Session::put("$rName.sort_col", request()->input('sort_col')); Session::put("$rName.sort_col", request()->sort_col);
Session::put("$rName.sorted", true); Session::put("$rName.sorted", true);
} }
if (request()->input('sort_way')) { if (request()->sort_way) {
Session::put("$rName.sort_way", request()->input('sort_way')); Session::put("$rName.sort_way", request()->sort_way);
Session::put("$rName.sorted", true); Session::put("$rName.sorted", true);
} }
if (request()->rst) { if (request()->rst) {
@ -112,29 +113,17 @@ class Controller extends BaseController
Session::remove("$rName.sort_col"); Session::remove("$rName.sort_col");
Session::remove("$rName.sort_way"); Session::remove("$rName.sort_way");
} }
if ( if (request()->sort_col and request()->sort_way and Schema::hasColumn($table, request()->sort_col)) {
request()->input('sort_col') and if (
request()->input('sort_way') and !isset($ignore) or
Schema::hasColumn($table, request()->input('sort_col')) (isset($ignore) and !in_array(request()->sort_col, $ignore))
) { ) {
if (!in_array(request()->input('sort_col'), $ignore)) { $query = $query->orderBy(\sprintf("$table.%s", request()->sort_col), request()->sort_way);
$query = $query->orderBy(
\sprintf("$table.%s", request()->input('sort_col')),
request()->input('sort_way')
);
} }
} elseif (Schema::hasColumn($table, 'order')) { } elseif (Schema::hasColumn($table, 'order')) {
$query = $query->orderBy('order', 'asc'); $query = $query->orderBy('order', 'ASC');
Session::put("$rName.sort_col", 'order'); Session::put("$rName.sort_col", 'order');
Session::put("$rName.sort_way", 'asc'); Session::put("$rName.sort_way", 'asc');
} elseif (Schema::hasColumn($table, 'updated_at')) {
$query = $query->orderBy('updated_at', 'desc');
Session::put("$rName.sort_col", 'updated_at');
Session::put("$rName.sort_way", 'desc');
} elseif (Schema::hasColumn($table, 'created_at')) {
$query = $query->orderBy('created_at', 'desc');
Session::put("$rName.sort_col", 'created_at');
Session::put("$rName.sort_way", 'desc');
} }
return $query; return $query;
} }
@ -142,21 +131,17 @@ class Controller extends BaseController
/** /**
* Customize pagination with cache or config (default). * Customize pagination with cache or config (default).
* *
* @param \Illuminate\Database\Eloquent\Builder $query * @param \Illuminate\Database\Eloquent\Builder $query
* @param \App\Enums\ItemsPerPaginationEnum|null $pagination * @param \App\Enums\Pagination\ItemsPerPaginationEnum|null $pagination
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*/ */
protected function paginate( protected function paginate(Builder $query, ItemsPerPaginationEnum $pagination = null): LengthAwarePaginator
Builder $query, {
ItemsPerPaginationEnum|null $pagination = null
): \Illuminate\Contracts\Pagination\LengthAwarePaginator {
$currentRoutePath = Str::slug(request()->route()->getName()); $currentRoutePath = Str::slug(request()->route()->getName());
$cacheKey = "pagination.{$currentRoutePath}"; $cacheKey = "pagination.{$currentRoutePath}";
$pagination = $pagination ?? ItemsPerPaginationEnum::twelve; $pagination = $pagination ?? ItemsPerPaginationEnum::twelve();
$pagination = ToolboxHelper::getValidatedEnum( $pagination = ToolboxHelper::getValidatedPaginate(
Cache::get($cacheKey, $pagination->value()), Cache::get($cacheKey, $pagination->value)
'pagination',
'\App\Enums\ItemsPerPaginationEnum',
); );
if (Cache::get($cacheKey) !== $pagination) { if (Cache::get($cacheKey) !== $pagination) {
Cache::put($cacheKey, $pagination); Cache::put($cacheKey, $pagination);

View file

@ -16,6 +16,7 @@ class Kernel extends HttpKernel
protected $middleware = [ protected $middleware = [
\App\Http\Middleware\TrustHosts::class, \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class, \App\Http\Middleware\TrustProxies::class,
\Fruitcake\Cors\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class, \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class, \App\Http\Middleware\TrimStrings::class,
@ -36,13 +37,7 @@ class Kernel extends HttpKernel
\Illuminate\View\Middleware\ShareErrorsFromSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class, \App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class, \Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\SecureHeaders::class, \App\Http\Middleware\SecureHeaders::class
],
'saml' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
], ],
'api' => [ 'api' => [
@ -59,17 +54,16 @@ class Kernel extends HttpKernel
* @var array<string, class-string|string> * @var array<string, class-string|string>
*/ */
protected $routeMiddleware = [ protected $routeMiddleware = [
'api.token' => \App\Http\Middleware\CheckApiToken::class, 'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth' => \App\Http\Middleware\Authenticate::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'htmlsanitize' => \App\Http\Middleware\HtmlSanitizer::class, 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'restrict_tenant_access' => \App\Http\Middleware\RestrictTenantAccess::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'htmlsanitize' => \App\Http\Middleware\HtmlSanitizer::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'api.token' => \App\Http\Middleware\CheckApiToken::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
]; ];
} }

View file

@ -13,7 +13,7 @@ class Authenticate extends Middleware
* @param mixed $request * @param mixed $request
* @return string|null * @return string|null
*/ */
protected function redirectTo(mixed $request): string|null protected function redirectTo($request)
{ {
$logins = [ $logins = [
'backend' => route('bo.login') 'backend' => route('bo.login')

View file

@ -14,7 +14,7 @@ class CheckApiToken
* @param \Closure $next * @param \Closure $next
* @return mixed * @return mixed
*/ */
public function handle(Request $request, Closure $next): mixed public function handle(Request $request, Closure $next)
{ {
if (isset($request->token) && $request->token == config('auth.API_TOKEN')) { if (isset($request->token) && $request->token == config('auth.API_TOKEN')) {
return $next($request); return $next($request);

View file

@ -11,5 +11,6 @@ class EncryptCookies extends Middleware
* *
* @var array<int, string> * @var array<int, string>
*/ */
protected $except = []; protected $except = [
];
} }

View file

@ -2,8 +2,8 @@
namespace App\Http\Middleware; namespace App\Http\Middleware;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Uwil\LaravelCommon\Helpers\ToolboxHelper;
class HtmlSanitizer class HtmlSanitizer
{ {
@ -15,7 +15,7 @@ class HtmlSanitizer
* @param mixed ...$params * @param mixed ...$params
* @return mixed * @return mixed
*/ */
public function handle(Request $request, \Closure $next, mixed ...$params): mixed public function handle(Request $request, \Closure $next, ...$params)
{ {
$inputs = $request->all(); $inputs = $request->all();
/** @var callable|null */ /** @var callable|null */

View file

@ -12,7 +12,6 @@ class RedirectIfAuthenticated
* Handle an incoming request. * Handle an incoming request.
* *
* @phpcs:disable Generic.Files.LineLength.TooLong * @phpcs:disable Generic.Files.LineLength.TooLong
*
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next * @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* @param string|null ...$guards * @param string|null ...$guards
@ -20,11 +19,8 @@ class RedirectIfAuthenticated
* @throws \RuntimeException If guard is unhandled. * @throws \RuntimeException If guard is unhandled.
* @phpcs:enable * @phpcs:enable
*/ */
public function handle( public function handle(Request $request, Closure $next, ...$guards)
Request $request, {
Closure $next,
string|null ...$guards
): \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse {
$guards = empty($guards) ? [null] : $guards; $guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) { foreach ($guards as $guard) {

View file

@ -1,48 +0,0 @@
<?php
namespace App\Http\Middleware;
use App\Models\Tenant;
use Closure;
use Illuminate\Http\Request;
class RestrictTenantAccess
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
* @return \Symfony\Component\HttpFoundation\Response
* @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint
*/
public function handle(Request $request, Closure $next): \Symfony\Component\HttpFoundation\Response
{
/** @var \App\Models\User|null $user */
$user = \auth('backend')->user();
if ($user) {
if (
!$request->routeIs(['bo.dashboard', 'bo.users.*', 'bo.tenants.*']) &&
$request->tenant &&
!\in_array($request->tenant, Tenant::get()->pluck('id')->toArray())
) {
// @phpstan-ignore argument.type
abort('404');
}
if (Tenant::count() && $request->routeIs(['bo.tenants.*', 'bo.users.*']) && $user->can('isAdmin')) {
$tenant = Tenant::first()->getKey();
tenancy()->initialize($tenant);
return $next($request);
}
if (
Tenant::count() && ($request->routeIs('bo.dashboard')
|| $request->routeIs('bo.users.edit', $user->getKey()))
) {
$tenant = ($user->tenants->count()) ? $user->tenants->first() : Tenant::first()->getKey();
tenancy()->initialize($tenant);
return $next($request);
}
} //end if
return $next($request);
}
}

View file

@ -6,7 +6,6 @@ use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\View; use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Vite;
class SecureHeaders class SecureHeaders
{ {
@ -27,13 +26,14 @@ class SecureHeaders
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \Closure $next * @param \Closure $next
* @return \Symfony\Component\HttpFoundation\Response * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/ */
public function handle(Request $request, Closure $next): \Symfony\Component\HttpFoundation\Response public function handle(Request $request, Closure $next)
{ {
View::share('nonce', $nonce = Vite::useCspNonce()); View::share('nonce', $nonce = \uniqid());
// * For VueJS dev tool to execute // * For VueJS dev tool to execute
$jsDev = config('app.debug') ? "'unsafe-eval' 'unsafe-inline'" : "'nonce-{$nonce}' 'strict-dynamic'"; $jsDev = config('app.debug') ? "'unsafe-eval' 'unsafe-inline'" : "'nonce-{$nonce}' 'strict-dynamic'";
$scheme = config('app.env') === 'local' ? 'http' : 'https';
$reportUri = Route::has('cspReportUri') ? $reportUri = Route::has('cspReportUri') ?
\sprintf(' report-uri %s ;', \parse_url(route('cspReportUri'), \PHP_URL_PATH)) : ''; \sprintf(' report-uri %s ;', \parse_url(route('cspReportUri'), \PHP_URL_PATH)) : '';
$this->removeUnwantedHeaders($this->unwantedHeaderList); $this->removeUnwantedHeaders($this->unwantedHeaderList);
@ -49,18 +49,19 @@ class SecureHeaders
// * CKEDITOR Specific https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/csp.html // * CKEDITOR Specific https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/csp.html
"connect-src 'self' blob:;", "connect-src 'self' blob:;",
"img-src * data: blob:;", "img-src * data: blob:;",
"object-src 'self' " . implode(' ', []) . ';',
'frame-src ' . implode(' ', []) . ';', 'frame-src ' . implode(' ', []) . ';',
"font-src 'self' data: ;", "font-src 'self' data: ;",
// ! Using CKEditor will must have unsafe-inline style // ! Using CKEditor will must have unsafe-inline style
// phpcs:ignore Generic.Files.LineLength.TooLong
"style-src 'self' 'unsafe-inline' " . \implode(' ', [ "style-src 'self' 'unsafe-inline' " . \implode(' ', [
// * File Manager // * File Manager
'https://cdn.jsdelivr.net', 'https://cdn.jsdelivr.net',
'https://use.fontawesome.com', 'https://use.fontawesome.com',
'https://fonts.googleapis.com', 'https://fonts.googleapis.com'
]) . ';', ]) . ';',
// ! If you dont use ckeditor use this // ! If you dont use ckeditor use this
// "style-src 'self' 'nonce-$nonce';", // "style-src 'self' 'nonce-$nonce';",
// phpcs:ignore Generic.Files.LineLength.TooLong
"script-src 'self' {$jsDev} " . \implode(' ', [ "script-src 'self' {$jsDev} " . \implode(' ', [
'https: blob:', 'https: blob:',
]) . ';' ]) . ';'
@ -75,12 +76,12 @@ class SecureHeaders
} }
/** /**
* Remove unwanted headers. * Remove unwanted headers
* *
* @param array $headerList * @param array $headerList
* @return void * @return void
*/ */
private function removeUnwantedHeaders(array $headerList): void private function removeUnwantedHeaders(array $headerList)
{ {
foreach ($headerList as $header) { foreach ($headerList as $header) {
header_remove($header); header_remove($header);

View file

@ -11,7 +11,7 @@ class TrustHosts extends Middleware
* *
* @return array<int, string|null> * @return array<int, string|null>
*/ */
public function hosts(): array public function hosts()
{ {
return [ return [
$this->allSubdomainsOfApplicationUrl(), $this->allSubdomainsOfApplicationUrl(),

View file

@ -6,15 +6,26 @@ use Illuminate\Foundation\Http\FormRequest;
class DestroyContactsRequest extends FormRequest class DestroyContactsRequest extends FormRequest
{ {
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
// * Check is done with api.token middleware
return true;
}
/** /**
* Prepare the data for validation. * Prepare the data for validation.
* *
* @return void * @return void
*/ */
protected function prepareForValidation(): void protected function prepareForValidation()
{ {
$this->merge([ $this->merge([
'contactUuidList' => \collect($this->contactUuidList)->filter()->all(), 'contactUuidList' => \collect($this->contactUuidList)->filter()->all()
]); ]);
} }
@ -23,10 +34,10 @@ class DestroyContactsRequest extends FormRequest
* *
* @return array * @return array
*/ */
public function rules(): array public function rules()
{ {
return [ return [
'contactUuidList' => 'present|array|min:0', 'contactUuidList' => 'present|array|min:0',
'contactUuidList.*' => 'uuid', 'contactUuidList.*' => 'uuid',
]; ];
} }

View file

@ -6,16 +6,27 @@ use App\Enums\Formularies\InputTypeEnum;
use App\Lib\Inputs\Definition\InputData; use App\Lib\Inputs\Definition\InputData;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use Illuminate\Validation\Rules\Enum; use Kwaadpepper\Enum\Rules\EnumIsValidRule;
class UpsertContactsRequest extends FormRequest class UpsertContactsRequest extends FormRequest
{ {
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
// * Check is done with api.token middleware
return true;
}
/** /**
* Prepare the data for validation. * Prepare the data for validation.
* *
* @return void * @return void
*/ */
protected function prepareForValidation(): void protected function prepareForValidation()
{ {
$this->merge([ $this->merge([
'contacts' => \collect($this->contacts)->filter() 'contacts' => \collect($this->contacts)->filter()
@ -50,35 +61,35 @@ class UpsertContactsRequest extends FormRequest
* *
* @return array * @return array
*/ */
public function rules(): array public function rules()
{ {
return [ return [
'contacts' => 'present|array', 'contacts' => 'present|array',
'contacts.*' => 'array', 'contacts.*' => 'array',
'contacts.*.id' => 'uuid', 'contacts.*.id' => 'uuid',
'contacts.*.lastname' => 'nullable|string|between:1,255', 'contacts.*.lastname' => 'nullable|string|max:255',
'contacts.*.firstname' => 'nullable|string|between:1,255', 'contacts.*.firstname' => 'nullable|string|max:255',
// * Dot not validate email here (soft validate), if it does not pass, app will be blocked. // * Dot not validate email here (soft validate), if it does not pass, app will be blocked.
'contacts.*.email' => 'required|string|between:1,255', 'contacts.*.email' => 'required|string|max:255',
'contacts.*.phone' => 'nullable|string|between:1,255', 'contacts.*.phone' => 'nullable|string|max:255',
'contacts.*.timestamp' => 'required|date', 'contacts.*.timestamp' => 'required|date',
// * DATA // * DATA
'contacts.*.data' => 'present|array', 'contacts.*.data' => 'present|array',
'contacts.*.dataParsed' => 'present', 'contacts.*.dataParsed' => 'present',
'contacts.*.data.*' => 'array', 'contacts.*.data.*' => 'array',
'contacts.*.data.*.title' => 'present|string|min:1|max:255', 'contacts.*.data.*.title' => 'present|string|min:1|max:255',
'contacts.*.data.*.formSlug' => 'present|string|min:1|max:255', 'contacts.*.data.*.formSlug' => 'present|string|min:1|max:255',
'contacts.*.data.*.type' => ['present', new Enum(InputTypeEnum::class)], 'contacts.*.data.*.type' => ['present', new EnumIsValidRule(InputTypeEnum::class)],
'contacts.*.data.*.value' => 'present', 'contacts.*.data.*.value' => 'present',
'contacts.*.data.*.required' => 'present|boolean', 'contacts.*.data.*.required' => 'present|boolean',
'contacts.*.data.*.send_to_app_user_id' => 'nullable|numeric', 'contacts.*.data.*.send_to_app_user_id' => 'nullable|numeric',
'contacts.*.notes' => 'nullable|string', 'contacts.*.notes' => 'nullable|string',
'contacts.*.visits' => 'present|array', 'contacts.*.visits' => 'present|array',
'contacts.*.visits.*' => 'required|array', 'contacts.*.visits.*' => 'required|array',
'contacts.*.visits.*.title' => 'present|string', 'contacts.*.visits.*.title' => 'present|string',
'contacts.*.visits.*.note' => 'present|string', 'contacts.*.visits.*.note' => 'present|string',
]; ];
} }
@ -90,7 +101,7 @@ class UpsertContactsRequest extends FormRequest
* @return mixed * @return mixed
* @throws \Illuminate\Validation\ValidationException In case of parse failure. * @throws \Illuminate\Validation\ValidationException In case of parse failure.
*/ */
private function filterDataLine(array $data, mixed $index): mixed private function filterDataLine(array $data, $index)
{ {
$data = \collect($data); $data = \collect($data);
try { try {
@ -106,15 +117,10 @@ class UpsertContactsRequest extends FormRequest
'error' => "wrong formSlug on data index '{$index}'" 'error' => "wrong formSlug on data index '{$index}'"
]); ]);
} }
$type = InputTypeEnum::make($data->get('type') ? $data->get('type') : null); $type = InputTypeEnum::make($data->get('type') ? $data->get('type') : null);
if (\is_null($type)) {
throw ValidationException::withMessages([
'error' => "wrong inputType on data index '{$index}'"
]);
}
$value = $data['value'] ?? null; $value = $data['value'] ?? null;
$required = $data->get('required') ? true : false; $required = $data->get('required') ? true : false;
$inputClass = $type->getInputClass(); $inputClass = $type->inputClass;
return $inputClass::from(InputData::make( return $inputClass::from(InputData::make(
$title, $title,
$formSlug, $formSlug,

View file

@ -1,56 +0,0 @@
<?php
namespace App\Http\Requests\Api;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
/** FIXME: currently not used but will be in the future. */
class SendFlyerRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'emails.email' => [
'required',
'email:rfc,strict,dns,spoof,filter',
Rule::exists('contacts', 'email')->where(function ($query) {
$query->whereIn('app_user_id', function ($subQuery) {
$subQuery->select('id')->from('app_users')->where('tenant_id', $this->tenant);
});
}),
],
'emails.subject' => 'required|string|between:3,255',
'emails.message' => 'required|string|min:3',
'emails.app_user_id' => [
'required',
'integer',
Rule::exists('app_users', 'id')->where('tenant_id', $this->tenant)
],
'emails.documents' => 'required|array|min:1',
'emails.documents.*.title' => 'required|string|min:3',
'emails.documents.*.uuid' => 'required|string|min:3',
'emails.documents.*.type' => 'required|string|min:1',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'emails.email' => trans('validation.attributes.email'),
'emails.subject' => trans('validation.attributes.subject'),
'emails.message' => trans('validation.attributes.message'),
'emails.app_user_id' => trans('models.classes.app_user'),
];
}
}

View file

@ -3,13 +3,8 @@
namespace App\Http\Requests\Bo\AppUser; namespace App\Http\Requests\Bo\AppUser;
use App\Enums\AppUserRolesEnum; use App\Enums\AppUserRolesEnum;
use App\Models\AppUser;
use Illuminate\Database\Query\Builder;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate; use Kwaadpepper\Enum\Rules\EnumIsValidRule;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Enum;
use Illuminate\Validation\Rules\Password; use Illuminate\Validation\Rules\Password;
class StoreAppUserRequest extends FormRequest class StoreAppUserRequest extends FormRequest
@ -19,9 +14,9 @@ class StoreAppUserRequest extends FormRequest
* *
* @return boolean * @return boolean
*/ */
public function authorize(): bool public function authorize()
{ {
return Gate::check('create', AppUser::class); return auth('backend')->check();
} }
/** /**
@ -29,10 +24,10 @@ class StoreAppUserRequest extends FormRequest
* *
* @return void * @return void
*/ */
protected function prepareForValidation(): void protected function prepareForValidation()
{ {
$this->merge([ $this->merge([
'status' => $this->status ? true : false, 'status' => $this->status ? true : false,
'authorizedFolders' => $this->authorizedFolders ? $this->authorizedFolders : [], 'authorizedFolders' => $this->authorizedFolders ? $this->authorizedFolders : [],
]); ]);
} }
@ -42,22 +37,14 @@ class StoreAppUserRequest extends FormRequest
* *
* @return array * @return array
*/ */
public function rules(): array public function rules()
{ {
return [ return [
'lastname' => 'required|min:3|max:255', 'lastname' => 'required|min:3|max:255',
'firstname' => 'required|min:3|max:255', 'firstname' => 'required|min:3|max:255',
'email' => [ 'email' => 'required|min:3|max:255|email:rfc,dns|unique:app_users,email',
'required', 'password' => [
'email:rfc,strict,dns,spoof,filter', 'required', 'required_with:password_confirmation', 'confirmed', 'max:255',
Rule::unique('app_users', 'email')
->where(fn(Builder $query) => $query->where('tenant_id', tenant('id')))
],
'password' => [
'required',
'required_with:password_confirmation',
'confirmed',
'max:255',
Password::min(8) Password::min(8)
->letters() ->letters()
->mixedCase() ->mixedCase()
@ -65,11 +52,11 @@ class StoreAppUserRequest extends FormRequest
->symbols() ->symbols()
], ],
'authorizedFolders' => 'present|array', 'authorizedFolders' => 'present|array',
'role' => [ 'role' => [
'required', 'required',
new Enum(AppUserRolesEnum::class) new EnumIsValidRule(AppUserRolesEnum::class)
], ],
'status' => 'required|boolean' 'status' => 'required|boolean'
]; ];
} }
@ -78,16 +65,16 @@ class StoreAppUserRequest extends FormRequest
* *
* @return array * @return array
*/ */
public function attributes(): array public function attributes()
{ {
return [ return [
'lastname' => \trans('validation.attributes.last_name'), 'lastname' => \trans('nom'),
'firstname' => \trans('validation.attributes.first_name'), 'firstname' => \trans('prénom'),
'email' => \trans('validation.attributes.email'), 'email' => \trans('email'),
'password' => \trans('validation.attributes.password'), 'password' => \trans('mot de passe'),
'authorizedFolders' => Str::of(\trans('models.classes.directory'))->plural(), 'authorizedFolders' => \trans('dossiers autorisés'),
'role' => \trans('validation.custom.role'), 'role' => \trans('rôle'),
'status' => \trans('validation.custom.status') 'status' => \trans('statut')
]; ];
} }
} }

View file

@ -2,29 +2,17 @@
namespace App\Http\Requests\Bo\AppUser; namespace App\Http\Requests\Bo\AppUser;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Password; use Illuminate\Validation\Rules\Password;
use Illuminate\Database\Query\Builder;
class UpdateAppUserRequest extends StoreAppUserRequest class UpdateAppUserRequest extends StoreAppUserRequest
{ {
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('app_user'));
}
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
* *
* @return array * @return array
*/ */
public function rules(): array public function rules()
{ {
$rules = [ $rules = [
'email' => [ 'email' => [
@ -32,9 +20,7 @@ class UpdateAppUserRequest extends StoreAppUserRequest
'min:3', 'min:3',
'max:255', 'max:255',
'email:rfc,dns', 'email:rfc,dns',
Rule::unique('app_users', 'email') Rule::unique('app_users', 'email')->ignore($this->route('app_user')->id),
->where(fn (Builder $query) => $query->where('tenant_id', tenant('id')))
->ignore($this->route('app_user')->getKey())
], ],
'password' => [ 'password' => [
'sometimes', 'nullable', 'required_with:password_confirmation', 'confirmed', 'max:255', 'sometimes', 'nullable', 'required_with:password_confirmation', 'confirmed', 'max:255',

View file

@ -4,14 +4,14 @@ namespace App\Http\Requests\Bo;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
class UpdateChangeOrderRequest extends FormRequest class ChangeOrderRequest extends FormRequest
{ {
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
* *
* @return boolean * @return boolean
*/ */
public function authorize(): bool public function authorize()
{ {
return auth('backend')->check(); return auth('backend')->check();
} }
@ -21,10 +21,10 @@ class UpdateChangeOrderRequest extends FormRequest
* *
* @return void * @return void
*/ */
protected function prepareForValidation(): void protected function prepareForValidation()
{ {
$this->merge([ $this->merge([
'direction' => $this->route('direction') === 'up' ? true : false 'direction' => $this->action === 'up' ? true : false
]); ]);
} }
@ -33,9 +33,10 @@ class UpdateChangeOrderRequest extends FormRequest
* *
* @return array * @return array
*/ */
public function rules(): array public function rules()
{ {
return [ return [
'action' => 'required|string|in:up,down',
'direction' => 'required|boolean', 'direction' => 'required|boolean',
]; ];
} }

View file

@ -3,9 +3,7 @@
namespace App\Http\Requests\Bo\Directories; namespace App\Http\Requests\Bo\Directories;
use App\Enums\Documents\LangEnum; use App\Enums\Documents\LangEnum;
use App\Models\Directory;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class StoreDirectoryRequest extends FormRequest class StoreDirectoryRequest extends FormRequest
{ {
@ -14,9 +12,9 @@ class StoreDirectoryRequest extends FormRequest
* *
* @return boolean * @return boolean
*/ */
public function authorize(): bool public function authorize()
{ {
return Gate::check('create', Directory::class); return auth('backend')->check();
} }
/** /**
@ -24,7 +22,7 @@ class StoreDirectoryRequest extends FormRequest
* *
* @return void * @return void
*/ */
protected function prepareForValidation(): void protected function prepareForValidation()
{ {
$this->merge([ $this->merge([
'needAuthorization' => $this->needAuthorization ? true : false, 'needAuthorization' => $this->needAuthorization ? true : false,
@ -39,8 +37,8 @@ class StoreDirectoryRequest extends FormRequest
public function rules() public function rules()
{ {
return [ return [
'name' => 'required|array', 'name' => 'required|array',
'name.*' => 'required|string|min:3|max:255', 'name.*' => 'required|string|min:3|max:255',
'needAuthorization' => 'required|boolean' 'needAuthorization' => 'required|boolean'
]; ];
} }
@ -50,15 +48,17 @@ class StoreDirectoryRequest extends FormRequest
* *
* @return array * @return array
*/ */
public function attributes(): array public function attributes()
{ {
$rules = [ $rules = [
'name' => \trans('validation.attributes.name'), 'name' => \trans('nom du dossier'),
'needAuthorization' => \trans('validation.custom.authorization') 'needAuthorization' => \trans('autorisation du dossier')
]; ];
/** @var Object $langEnum */
foreach (LangEnum::toArray() as $langEnum) { foreach (LangEnum::toArray() as $langEnum) {
$rules["name.{$langEnum->name}"] = sprintf('%s %s', trans('validation.attributes.name'), $langEnum->label); $def = $langEnum->getDefinition();
$rules["name.{$def}"] = \trans('nom du dossier :lang', [
'lang' => $langEnum->label
]);
} }
return $rules; return $rules;
} }

View file

@ -2,17 +2,6 @@
namespace App\Http\Requests\Bo\Directories; namespace App\Http\Requests\Bo\Directories;
use Illuminate\Support\Facades\Gate;
class UpdateDirectoryRequest extends StoreDirectoryRequest class UpdateDirectoryRequest extends StoreDirectoryRequest
{ {
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('directory'));
}
} }

View file

@ -3,14 +3,9 @@
namespace App\Http\Requests\Bo\Documents; namespace App\Http\Requests\Bo\Documents;
use App\Enums\Documents\LangEnum; use App\Enums\Documents\LangEnum;
use App\Enums\Documents\TypeEnum;
use App\Models\Document; use App\Models\Document;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate; use Kwaadpepper\Enum\Rules\EnumIsValidRule;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Enum;
use Uwil\LaravelCommon\Helpers\FileHelper;
class StoreDocumentRequest extends FormRequest class StoreDocumentRequest extends FormRequest
{ {
@ -19,9 +14,9 @@ class StoreDocumentRequest extends FormRequest
* *
* @return boolean * @return boolean
*/ */
public function authorize(): bool public function authorize()
{ {
return Gate::check('create', Document::class); return auth('backend')->check();
} }
/** /**
@ -29,28 +24,15 @@ class StoreDocumentRequest extends FormRequest
* *
* @return void * @return void
*/ */
protected function prepareForValidation(): void protected function prepareForValidation()
{ {
$this->merge([ $this->merge([
'status' => $this->status ? true : false, 'status' => $this->status ? true : false
]); ]);
// * Sanitize Path if (Document::query()->where('id', $this->document_id)->exists()) {
if (!empty($this->path)) {
$filename = $this->path;
$refMethod = new \ReflectionMethod(FileHelper::class, 'sanitizeFileName');
$refMethod->setAccessible(true);
$filename = \pathinfo(
$refMethod->invoke(null, htmlspecialchars($filename), false),
\PATHINFO_BASENAME
);
$this->merge([ $this->merge([
'path' => $filename 'directory_id' => Document::query()->where('id', $this->document_id)
]); ->value('directory_id')
}
if (Document::query()->where('id', $this->document_id)->exists() && $this->type) {
$this->merge([
'directory_id' => Document::query()->where('id', $this->document_id)->value('directory_id'),
'type' => TypeEnum::make($this->type, 'name')->name(),
]); ]);
} }
} }
@ -60,35 +42,19 @@ class StoreDocumentRequest extends FormRequest
* *
* @return array * @return array
*/ */
public function rules(): array public function rules()
{ {
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$disk = Storage::disk('docs');
return [ return [
'directory_id' => 'required|integer', 'directory_id' => 'required|integer',
'document_id' => 'sometimes:directory_id|nullable|integer', 'document_id' => 'sometimes:directory_id|nullable|integer',
'uuid' => [ 'title' => 'required|min:3|max:255',
'type' => 'required|min:3|max:255',
'path' => 'required|min:3|max:255',
'lang' => [
'required', 'required',
'uuid', new EnumIsValidRule(LangEnum::class)
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) use ($disk) {
if ($this->type and \array_column(TypeEnum::toArray(), 'name')) {
$extension = TypeEnum::make($this->type, 'name')->name();
$value = "{$value}.{$extension}";
}
if (!$disk->has(sprintf('%s/%s', \tenant()->getKey(), $value))) {
$path = $this->path ?? $this->value ?? 'not found';
$fail(\trans('Le fichier :filename n\'est pas présent dans le système de fichier', [
'filename' => $path
]));
}
}
], ],
'title' => 'required|min:3|max:255', 'status' => 'required|boolean'
'type' => ['required', 'string', Rule::in(array_column(TypeEnum::toArray(), 'name'))],
'path' => 'required|min:3|max:255',
'lang' => ['required', new Enum(LangEnum::class)],
'status' => 'required|boolean'
]; ];
} }
@ -97,17 +63,17 @@ class StoreDocumentRequest extends FormRequest
* *
* @return array * @return array
*/ */
public function attributes(): array public function attributes()
{ {
return [ return [
'directory_id' => \trans('models.classes.directory'), 'directory_id' => \trans('dossier à associer'),
'document_id' => \trans('models.classes.document'), 'document_id' => \trans('document (FR) à associer'),
'title' => \trans('validation.attributes.title'), 'title' => \trans('titre'),
'uuid' => \trans('models.classes.document'), 'uuid' => \trans('document'),
'type' => \trans('validation.custom.type'), 'type' => \trans('type du document'),
'path' => \trans('validation.custom.path'), 'path' => \trans('chemin du document'),
'lang' => \trans('validation.custom.lang'), 'lang' => \trans('langue du document'),
'status' => \trans('validation.custom.status'), 'status' => \trans('état de publication du document'),
]; ];
} }
} }

View file

@ -2,17 +2,6 @@
namespace App\Http\Requests\Bo\Documents; namespace App\Http\Requests\Bo\Documents;
use Illuminate\Support\Facades\Gate;
class UpdateDocumentRequest extends StoreDocumentRequest class UpdateDocumentRequest extends StoreDocumentRequest
{ {
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('document'));
}
} }

View file

@ -3,7 +3,6 @@
namespace App\Http\Requests\Bo; namespace App\Http\Requests\Bo;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class FilterCustomerRequest extends FormRequest class FilterCustomerRequest extends FormRequest
{ {
@ -12,9 +11,9 @@ class FilterCustomerRequest extends FormRequest
* *
* @return boolean * @return boolean
*/ */
public function authorize(): bool public function authorize()
{ {
return \auth('backend')->check(); return \auth()->check();
} }
/** /**
@ -22,15 +21,10 @@ class FilterCustomerRequest extends FormRequest
* *
* @return array * @return array
*/ */
public function rules(): array public function rules()
{ {
return [ return [
'customer' => [ 'customer' => 'sometimes|nullable|numeric|exists:app_users,id'
'sometimes',
'nullable',
'numeric',
Rule::unique('app_users', 'id')->where('tenant_id', tenant('id'))
],
]; ];
} }
} }

View file

@ -4,10 +4,8 @@ namespace App\Http\Requests\Bo\Formularies;
use App\Enums\Formularies\InputStatusEnum; use App\Enums\Formularies\InputStatusEnum;
use App\Enums\Formularies\InputTypeEnum; use App\Enums\Formularies\InputTypeEnum;
use App\Models\Formulary;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate; use Kwaadpepper\Enum\Rules\EnumIsValidRule;
use Illuminate\Validation\Rules\Enum;
class StoreFormularyRequest extends FormRequest class StoreFormularyRequest extends FormRequest
{ {
@ -17,15 +15,14 @@ class StoreFormularyRequest extends FormRequest
* @var string[] * @var string[]
*/ */
private $mobileAppLocales = ['fr', 'en', 'de', 'nl']; private $mobileAppLocales = ['fr', 'en', 'de', 'nl'];
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
* *
* @return boolean * @return boolean
*/ */
public function authorize(): bool public function authorize()
{ {
return Gate::check('create', Formulary::class); return auth('backend')->check();
} }
/** /**
@ -33,10 +30,10 @@ class StoreFormularyRequest extends FormRequest
* *
* @return void * @return void
*/ */
protected function prepareForValidation(): void protected function prepareForValidation()
{ {
$this->merge([ $this->merge([
'required' => $this->required ? true : false, 'required' => $this->required ? true : false,
'is_visible' => $this->is_visible ? true : false, 'is_visible' => $this->is_visible ? true : false,
]); ]);
} }
@ -46,21 +43,21 @@ class StoreFormularyRequest extends FormRequest
* *
* @return array * @return array
*/ */
public function rules(): array public function rules()
{ {
$rules = [ $rules = [
'type' => ['required', new Enum(InputTypeEnum::class)], 'type' => ['required', new EnumIsValidRule(InputTypeEnum::class)],
'placeholder' => 'present|array', 'placeholder' => 'present|array',
'details' => 'nullable|array', 'details' => 'nullable|array',
'details.*' => 'required|array', 'details.*' => 'required|array',
'required' => 'required|boolean', 'required' => 'required|boolean',
'is_visible' => 'required|boolean', 'is_visible' => 'required|boolean',
]; ];
foreach ($this->mobileAppLocales as $locale) { foreach ($this->mobileAppLocales as $locale) {
$rules["placeholder.{$locale}"] = 'required|string|min:3|max:100'; $rules["placeholder.{$locale}"] = 'required|string|min:3|max:100';
$rules["details.*.{$locale}"] = 'required|string|min:3|max:100'; $rules["details.*.{$locale}"] = 'required|string|min:3|max:100';
} }
if (intval($this->status) !== InputStatusEnum::locked->value()) { if (intval($this->status) !== InputStatusEnum::locked()->value) {
$rules = array_merge($rules, ['name' => 'required|string|min:3|max:70']); $rules = array_merge($rules, ['name' => 'required|string|min:3|max:70']);
} }
return $rules; return $rules;
@ -74,18 +71,17 @@ class StoreFormularyRequest extends FormRequest
public function attributes(): array public function attributes(): array
{ {
$attributes = [ $attributes = [
'type' => trans('validation.custom.type'), 'type' => trans('type'),
'name' => trans('validation.attributes.name'), 'name' => trans('nom'),
'placeholder' => trans('validation.custom.placeholder'), 'placeholder' => trans('placeholder'),
'details' => trans('validation.custom.detail'), 'details' => trans('détail'),
'details.*' => trans('validation.custom.detail'), 'details.*' => trans('détail 2'),
'details.*.value' => trans('validation.custom.valeur'), 'details.*.value' => trans('valeur d\'un élément dans la liste des détails du contenu'),
'required' => trans('validation.custom.required'), 'required' => trans('l\'état d\'obligation'),
'is_visible' => trans('validation.custom.visibility'), 'is_visible' => trans('l\'état de visibilité'),
]; ];
foreach ($this->mobileAppLocales as $locale) { foreach ($this->mobileAppLocales as $locale) {
$attributes["placeholder.{$locale}"] = trans('placeholder (:lang)', ['lang' => $locale]); $attributes["placeholder.{$locale}"] = trans('placeholder (:lang)', ['lang' => $locale]);
$attributes["details.*.{$locale}"] = trans('details (:lang)', ['lang' => $locale]);
} }
return $attributes; return $attributes;
} }

View file

@ -2,26 +2,14 @@
namespace App\Http\Requests\Bo\Formularies; namespace App\Http\Requests\Bo\Formularies;
use Illuminate\Support\Facades\Gate;
class UpdateFormularyRequest extends StoreFormularyRequest class UpdateFormularyRequest extends StoreFormularyRequest
{ {
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('formulary'));
}
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
* *
* @return array * @return array
*/ */
public function rules(): array public function rules()
{ {
$rules = [ $rules = [
'type' => 'nullable', 'type' => 'nullable',

View file

@ -11,7 +11,7 @@ class ImportRequest extends FormRequest
* *
* @return boolean * @return boolean
*/ */
public function authorize(): bool public function authorize()
{ {
return auth('backend')->check(); return auth('backend')->check();
} }
@ -21,7 +21,7 @@ class ImportRequest extends FormRequest
* *
* @return array * @return array
*/ */
public function rules(): array public function rules()
{ {
return [ return [
'import' => [ 'import' => [
@ -30,7 +30,7 @@ class ImportRequest extends FormRequest
'mimes:csv,txt', 'mimes:csv,txt',
// phpcs:ignore Generic.Files.LineLength.TooLong // phpcs:ignore Generic.Files.LineLength.TooLong
'mimetypes:text/csv,text/plain,application/csv,text/comma-separated-values,text/anytext,application/octet-stream,application/txt', 'mimetypes:text/csv,text/plain,application/csv,text/comma-separated-values,text/anytext,application/octet-stream,application/txt',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) { function ($attribute, $value, $fail) {
if ( if (
$this->import->getClientMimeType() !== 'text/csv' $this->import->getClientMimeType() !== 'text/csv'

View file

@ -2,11 +2,11 @@
namespace App\Http\Requests\Bo\Settings; namespace App\Http\Requests\Bo\Settings;
use App\Enums\UserRolesEnum;
use App\Models\Setting; use App\Models\Setting;
use App\Traits\Requests\HasPicture;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use Uwil\LaravelCommon\Traits\Requests\HasPicture;
class UpdateSettingRequest extends FormRequest class UpdateSettingRequest extends FormRequest
{ {
@ -17,18 +17,17 @@ class UpdateSettingRequest extends FormRequest
* *
* @return boolean * @return boolean
*/ */
public function authorize(): bool public function authorize()
{ {
if ( if (
$this->key === "Button1" || $this->key === "Button1" ||
$this->key === "Button2" || $this->key === "Button2" ||
$this->key === "Button3" $this->key === "Button3" ||
$this->key === "ShowIsActive"
) { ) {
return Gate::check('update', $this->route('setting')); return auth('backend')->check();
} else { } else {
/** @var \App\Models\User $user */ return (auth('backend')->check() && Auth::user()->role === UserRolesEnum::admin()->value);
$user = Auth::guard('backend')->user();
return $user->can('isAdmin');
} }
} }
@ -37,9 +36,11 @@ class UpdateSettingRequest extends FormRequest
* *
* @return void * @return void
*/ */
protected function prepareForValidation(): void protected function prepareForValidation()
{ {
if ( if ($this->key === "ShowIsActive") {
$this->merge(['value' => intval($this->value) ? "true" : "false"]);
} elseif (
$this->key === "Button1" || $this->key === "Button1" ||
$this->key === "Button2" || $this->key === "Button2" ||
$this->key === "Button3" $this->key === "Button3"
@ -60,7 +61,7 @@ class UpdateSettingRequest extends FormRequest
* *
* @return array * @return array
*/ */
public function rules(): array public function rules()
{ {
if ( if (
$this->key === "Button1" || $this->key === "Button1" ||
@ -68,11 +69,10 @@ class UpdateSettingRequest extends FormRequest
$this->key === "Button3" $this->key === "Button3"
) { ) {
$buttonsNullable = 0; $buttonsNullable = 0;
$buttons = Setting::query() $buttons = Setting::where('key', 'Button1')
->where('key', 'Button1') ->orWhere('key', 'Button2')
->orWhere('key', 'Button2') ->orWhere('key', 'Button3')
->orWhere('key', 'Button3') ->get();
->get();
foreach ($buttons as $button) { foreach ($buttons as $button) {
if ($button->value === null) { if ($button->value === null) {
if ($button->key !== $this->key) { if ($button->key !== $this->key) {
@ -82,7 +82,7 @@ class UpdateSettingRequest extends FormRequest
} }
return [ return [
'value' => [ 'value' => [
//phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed //phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) use ($buttonsNullable) { function ($attribute, $value, $fail) use ($buttonsNullable) {
if (!isset($value) && $buttonsNullable >= 2) { if (!isset($value) && $buttonsNullable >= 2) {
$fail(\trans('Au moins 1 bouton doît être défini.')); $fail(\trans('Au moins 1 bouton doît être défini.'));
@ -96,7 +96,7 @@ class UpdateSettingRequest extends FormRequest
$this->key === "BackgroundImage" || $this->key === "BackgroundImage" ||
$this->key === "HomeButtonImage" $this->key === "HomeButtonImage"
) { ) {
return $this->pictureRules('value', 100, 100, 1920, 1200, true); return $this->pictureRules('value', true, 100, 100, 1920, 1200);
} else { } else {
return ['value' => 'required|string|min:1']; return ['value' => 'required|string|min:1'];
} //end if } //end if
@ -107,12 +107,12 @@ class UpdateSettingRequest extends FormRequest
* *
* @return array * @return array
*/ */
public function attributes(): array public function attributes()
{ {
return [ return [
'key' => \trans('validation.custom.key'), 'key' => \trans('clé'),
'value' => \trans('validation.custom.value'), 'value' => \trans('valeur'),
'type' => \trans('validation.custom.type'), 'type' => \trans('type'),
]; ];
} }
} }

View file

@ -2,9 +2,8 @@
namespace App\Http\Requests\Bo\Tags; namespace App\Http\Requests\Bo\Tags;
use App\Models\Tag;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate; use Illuminate\Support\Str;
class StoreTagRequest extends FormRequest class StoreTagRequest extends FormRequest
{ {
@ -13,9 +12,21 @@ class StoreTagRequest extends FormRequest
* *
* @return boolean * @return boolean
*/ */
public function authorize(): bool public function authorize()
{ {
return Gate::check('create', Tag::class); return auth('backend')->check();
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation()
{
$this->merge([
'slug' => Str::slug($this->name),
]);
} }
/** /**
@ -23,7 +34,7 @@ class StoreTagRequest extends FormRequest
* *
* @return array * @return array
*/ */
public function rules(): array public function rules()
{ {
return [ return [
'name' => 'required|string|min:2|max:25', 'name' => 'required|string|min:2|max:25',
@ -38,7 +49,8 @@ class StoreTagRequest extends FormRequest
public function attributes(): array public function attributes(): array
{ {
return [ return [
'name' => trans('models.classes.tag'), 'name' => trans('name of the tag'),
'slug' => trans('slug of the tag'),
]; ];
} }
} }

View file

@ -2,17 +2,6 @@
namespace App\Http\Requests\Bo\Tags; namespace App\Http\Requests\Bo\Tags;
use Illuminate\Support\Facades\Gate;
class UpdateTagRequest extends StoreTagRequest class UpdateTagRequest extends StoreTagRequest
{ {
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('tag'));
}
} }

View file

@ -1,44 +0,0 @@
<?php
namespace App\Http\Requests\Bo\Tenants;
use App\Models\Tenant;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class StoreTenantRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::allows('create', Tenant::class);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'name' => 'required|string|min:2|max:25',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'name' => trans('validation.attributes.name'),
];
}
}

View file

@ -1,18 +0,0 @@
<?php
namespace App\Http\Requests\Bo\Tenants;
use Illuminate\Support\Facades\Gate;
class UpdateTenantRequest extends StoreTenantRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('tenant'));
}
}

View file

@ -1,30 +0,0 @@
<?php
namespace App\Http\Requests\Bo;
use Illuminate\Foundation\Http\FormRequest;
class UploadRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return \auth('backend')->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'uuid' => 'nullable|uuid'
];
}
}

View file

@ -1,77 +0,0 @@
<?php
namespace App\Http\Requests\Bo\Users;
use App\Enums\UserRolesEnum;
use App\Models\User;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Enum;
class StoreUserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('create', User::class);
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation(): void
{
if ($this->input('role') == UserRolesEnum::admin->value()) {
$this->merge([
'tenants' => null
]);
}
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
$isClient = $this->input('role') == UserRolesEnum::client->value();
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',
Rule::unique('users', 'email')
],
'role' => ['required', new Enum(UserRolesEnum::class)],
'tenants' => $isClient ? 'required|array' : 'nullable|array',
'tenants.*' => $isClient ? 'required|array' : 'nullable|array',
'tenants.*.id' => $isClient ? 'required|exists:tenants,id' : 'nullable|exists:tenants,id',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'first_name' => trans('validation.attributes.first_name'),
'last_name' => trans('validation.attributes.last_name'),
'email' => trans('validation.attributes.email'),
'role' => trans('validation.attributes.role'),
'tenants' => Str::of(trans('models.classes.tenant'))->plural(),
];
}
}

View file

@ -1,42 +0,0 @@
<?php
namespace App\Http\Requests\Bo\Users;
use App\Enums\UserRolesEnum;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rules\Enum;
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'));
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
/** @var \App\Models\User $userModel */
$userModel = request()->route()->parameter('user');
$rules = [
'email' => [
'required',
'string',
'unique:users,email,' . $userModel->getKey(),
'email:rfc,strict,dns,spoof,filter',
'max:255'
],
'role' => ['sometimes', new Enum(UserRolesEnum::class)],
];
return \array_merge(parent::rules(), $rules);
}
}

View file

@ -8,14 +8,12 @@ class ApiResponseResource extends JsonResource
{ {
/** /**
* Status * Status
*
* @var string * @var string
*/ */
private $status; private $status;
/** /**
* Datas * Datas
*
* @var array * @var array
*/ */
private $datas; private $datas;
@ -40,11 +38,11 @@ class ApiResponseResource extends JsonResource
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing * @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass * @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/ */
public function toArray($request): array public function toArray($request)
{ {
return [ return [
'status' => $this->status, 'status' => $this->status,
'datas' => $this->datas 'datas' => $this->datas
]; ];
} }
} }

View file

@ -2,7 +2,6 @@
namespace App\Http\Resources; namespace App\Http\Resources;
use App\Enums\AppUserRolesEnum;
use App\Models\Directory; use App\Models\Directory;
use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\JsonResource;
@ -12,34 +11,26 @@ class AppUserResource extends JsonResource
* Transform the resource into an array. * Transform the resource into an array.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable * @return array
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing * @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/ */
public function toArray($request) public function toArray($request)
{ {
/** //phpcs:enable
* @var \App\Models\AppUser
* @phpstan-ignore varTag.nativeType
*/
$appUser = $this;
return [ return [
'id' => $appUser->id, 'id' => $this->id,
'email' => $appUser->email, 'email' => $this->email,
'surname' => $appUser->lastname, 'surname' => $this->lastname,
'firstname' => $appUser->firstname, 'firstname' => $this->firstname,
'password' => $appUser->password, 'password' => $this->password,
'activated' => $appUser->status, 'activated' => $this->status,
'role' => ($appUser->role->equals(AppUserRolesEnum::total)) ? "TOTAL" : "USER", 'role' => ($this->role === 1) ? "TOTAL" : "USER",
'authorizedFolders' => json_encode( 'authorizedFolders' => json_encode($this->directories->each(function (Directory $directory) use ($request) {
$appUser->directories->each( return (new DirectoryResource($directory))->toArray($request);
// @phpstan-ignore argument.type })),
function (Directory $directory) use ($request) { 'created_at' => $this->created_at,
return (new DirectoryResource($directory))->toArray($request); 'updated_at' => $this->updated_at,
}
)
),
'created_at' => $appUser->created_at,
'updated_at' => $appUser->updated_at,
]; ];
} }
} }

View file

@ -16,25 +16,23 @@ class ContactResource extends JsonResource
*/ */
public function toArray($request) public function toArray($request)
{ {
/** //phpcs:enable
* @var \App\Models\Contact /** @var \App\Models\Contact */
* @phpstan-ignore varTag.nativeType
*/
$contact = $this; $contact = $this;
return [ return [
'id' => $contact->id, 'id' => $contact->id,
'lastname' => $contact->lastname, 'lastname' => $contact->lastname,
'firstname' => $contact->firstname, 'firstname' => $contact->firstname,
'email' => $contact->email, 'email' => $contact->email,
'phone' => $contact->phone, 'phone' => $contact->phone,
'data' => $contact->data, 'data' => $contact->data,
'notes' => $contact->notes, 'notes' => $contact->notes,
'visits' => $contact->visits, 'visits' => $contact->visits,
'timestamp' => $contact->timestamp->toISOString(), 'timestamp' => $contact->timestamp->toISOString(),
'app_user_id' => $contact->app_user_id, 'app_user_id' => $contact->app_user_id,
'send_to_app_user_id' => null, 'send_to_app_user_id' => null,
'created_at' => $contact->created_at, 'created_at' => $contact->created_at,
'updated_at' => $contact->updated_at, 'updated_at' => $contact->updated_at,
]; ];
} }
} }

View file

@ -16,19 +16,15 @@ class DirectoryResource extends JsonResource
*/ */
public function toArray($request) public function toArray($request)
{ {
/** //phpcs:enable
* @var \App\Models\Directory
* @phpstan-ignore varTag.nativeType
*/
$directory = $this;
return [ return [
'id' => $directory->id, 'id' => $this->id,
'name' => json_encode($directory->name), 'name' => json_encode($this->name),
'parent' => $directory->directory_id, 'parent' => $this->directory_id,
'order' => $directory->order, 'order' => $this->order,
'needAuthorization' => $directory->needAuthorization, 'needAuthorization' => $this->needAuthorization,
'created_at' => $directory->created_at, 'created_at' => $this->created_at,
'updated_at' => $directory->updated_at, 'updated_at' => $this->updated_at,
]; ];
} }
} }

View file

@ -16,23 +16,32 @@ class DocumentResource extends JsonResource
*/ */
public function toArray($request) public function toArray($request)
{ {
/** switch ($this->lang) {
* @var \App\Models\Document case 1:
* @phpstan-ignore varTag.nativeType $tmpLang = "fr";
*/ break;
$document = $this; case 2:
$tmpLang = "en";
break;
case 3:
$tmpLang = "de";
break;
case 4:
$tmpLang = "nl";
break;
}
return [ return [
'id' => $document->id, 'id' => $this->id,
'uuid' => $document->uuid, 'uuid' => $this->uuid,
'title' => $document->title, 'title' => $this->title,
'type' => $document->type, 'type' => $this->type,
'directory' => $document->directory_id, 'directory' => $this->directory_id,
'path' => $document->path, 'path' => $this->path,
'lang' => $document->lang->name, 'lang' => $tmpLang,
'published' => ($document->status) ? 1 : 0, 'published' => ($this->status) ? 1 : 0,
'parent' => $document->document_id, 'parent' => $this->document_id,
'created_at' => $document->created_at, 'created_at' => $this->created_at,
'updated_at' => $document->updated_at, 'updated_at' => $this->updated_at,
]; ];
} }
} }

View file

@ -11,30 +11,27 @@ class FormularyResource extends JsonResource
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing * @phpcs:disable: Squiz.Commenting.FunctionComment.TypeHintMissing
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass * @phpcs:disable: Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/ */
public function toArray($request) public function toArray($request)
{ {
/** /** @var \App\Models\Formulary */
* @var \App\Models\Formulary
* @phpstan-ignore varTag.nativeType
*/
$formulary = $this; $formulary = $this;
return [ return [
'id' => $formulary->id, 'id' => $formulary->id,
'type' => $formulary->type->value, 'type' => $formulary->type->value,
'name' => $formulary->name, 'name' => $formulary->name,
'slug' => $formulary->slug, 'slug' => $formulary->slug,
'placeholder' => $formulary->placeholder, 'placeholder' => $formulary->placeholder,
'details' => \collect($formulary->details)->filter()->map(function (array $detail) { 'details' => \collect($formulary->details)->filter()->map(function (array $detail) {
// * Realm can't hold more deeper dans Map<mixed,primitive> // * Realm can't hold more deeper dans Map<mixed,primitive>
return \json_encode($detail); return \json_encode($detail);
})->all(), })->all(),
'is_visible' => $formulary->is_visible, 'is_visible' => $formulary->is_visible,
'required' => $formulary->required, 'required' => $formulary->required,
'status' => $formulary->status->value, 'status' => $formulary->status->value,
'order' => $formulary->order, 'order' => $formulary->order,
]; ];
} }
} }

View file

@ -17,7 +17,7 @@ class AppUserImport implements ToModel, WithUpserts, WithCustomCsvSettings
/** /**
* @return string|array * @return string|array
*/ */
public function uniqueBy(): string|array public function uniqueBy()
{ {
return 'email'; return 'email';
} }
@ -37,27 +37,29 @@ class AppUserImport implements ToModel, WithUpserts, WithCustomCsvSettings
* *
* @return \Illuminate\Database\Eloquent\Model|null * @return \Illuminate\Database\Eloquent\Model|null
*/ */
public function model(array $row): \Illuminate\Database\Eloquent\Model|null public function model(array $row)
{ {
if (!isset($row[0]) || !isset($row[1]) || !isset($row[2]) || !isset($row[3])) { if (!isset($row[0]) || !isset($row[1]) || !isset($row[2]) || !isset($row[3])) {
return null; return null;
} }
$password = Hash::make(Str::random(10)); $user = new AppUser([
$user = new AppUser([ 'email' => $row[3],
'email' => $row[3], 'lastname' => ucfirst(strtolower($row[1])),
'lastname' => ucfirst(strtolower($row[1])),
'firstname' => ucfirst(strtolower($row[2])), 'firstname' => ucfirst(strtolower($row[2])),
'password' => $password, 'password' => '',
'status' => 1, 'status' => 1,
'role' => AppUserRolesEnum::user, 'role' => AppUserRolesEnum::user(),
]); ]);
if ($user) {
$password = Str::random(10);
$user->password = Hash::make($password);
$user->save();
Mail::to($user->email)->send(new AccountCreated($user, $password));
}
$user->saveOrFail(); $user->saveOrFail();
Mail::to($user->email)->send(new AccountCreated([
'email' => $user->email,
'lastname' => $user->lastname,
'firstname' => $user->firstname,
'password' => $user->password,
]));
return $user; return $user;
} }

View file

@ -1,46 +0,0 @@
<?php
namespace App\Jobs\Mails;
use App\Mail\TenantDeleted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Mail;
class SendTenantDeletedMail implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
/** @var string $author */
private $author;
/** @var string $name */
private $name;
/**
* Create a new job instance.
*
* @param string $author
* @param string $name
* @return void
*/
public function __construct(string $author, string $name)
{
$this->author = $author;
$this->name = $name;
}
/**
* Execute the job.
*
* @return void
*/
public function handle(): void
{
foreach (config('mail.admin.address') as $address) {
Mail::to($address)->send(new TenantDeleted($this->author, $this->name));
}
}
}

View file

@ -0,0 +1,190 @@
<?php
namespace App\Lib\Helpers;
class ExceptionsHelper
{
/**
* Handle sentry event
*
* @param \Sentry\Event $event
* @return null|\Sentry\Event
*/
public static function sentryEventHandler(\Sentry\Event $event): ?\Sentry\Event
{
// Remove unwanted reports.
$event->setModules([]);
$event->setOsContext(null);
/** @var \Sentry\ExceptionDataBag[] */
$exceptionBag = [];
foreach ($event->getExceptions() as $exceptionDataBag) {
$exceptionBag[] = self::getAnonymizedSentryException($exceptionDataBag);
}
$event->setExceptions($exceptionBag);
return $event;
}
/**
* Anonimize Sentry Exception bag
*
* @param \Sentry\ExceptionDataBag $exceptionDataBag
* @return \Sentry\ExceptionDataBag
*/
private static function getAnonymizedSentryException(
\Sentry\ExceptionDataBag $exceptionDataBag
): \Sentry\ExceptionDataBag {
$exceptionDataBag->setValue(self::getAnonymizedMessage($exceptionDataBag->getValue()));
if ($stackTrace = $exceptionDataBag->getStacktrace()) {
$exceptionDataBag->setStacktrace(self::getAnonymizedSentryStackTrace($stackTrace));
}
return $exceptionDataBag;
}
/**
* Anonymise sentry stacktrace
*
* @param \Sentry\Stacktrace $stackTrace
* @return \Sentry\Stacktrace
*/
private static function getAnonymizedSentryStackTrace(\Sentry\Stacktrace $stackTrace): \Sentry\Stacktrace
{
/** @var \Sentry\Frame[] */
$frames = \array_reverse($stackTrace->getFrames(), true);
$i = \count($frames) - 1;
// * Remove all but keep first (index.php).
while ($i-- > 0) {
$stackTrace->removeFrame(count($stackTrace->getFrames()) - 1);
}
foreach ($frames as $index => $frame) {
$frame->setPostContext([]);
$frame->setPreContext([]);
$frame->setContextLine('');
$frame->setVars(self::getAnonymizedSentryParams($frame->getVars()));
$stackTrace->addFrame($frame);
if ($index === 0) {
$stackTrace->removeFrame(count($stackTrace->getFrames()) - 1);
}
}
return $stackTrace;
}
/**
* Get anonymized sentry params
*
* @param mixed[] $params
* @return string[]
*/
private static function getAnonymizedSentryParams(array $params): array
{
foreach ($params as &$param) {
$type = \strtolower(\gettype($param));
if (\is_string($param)) {
$type = \explode(' ', \trim($param))[0] ?? null;
}
switch ($type) {
case 'Lambda':
case 'Object':
continue 2;
case 'array':
$param = sprintf('Array(%d)', \count($param));
continue 2;
default:
if (\is_numeric($param)) {
$param = \ucfirst($type);
continue 2;
}
if (\is_string($param)) {
$param = sprintf('String(%d)', \strlen($param));
continue 2;
}
} //end switch
} //end foreach
return $params;
}
/**
* Anonymize at least Sql queries
*
* @param string $message
* @return string
*/
public static function getAnonymizedMessage(string $message): string
{
$message = \preg_replace('/^(.*)( \(SQL:.*\))$/', '$1', $message) ?? $message;
if ($sqlPos = \strpos($message, 'to use near')) {
$message = \mb_strcut($message, 0, $sqlPos);
}
return $message;
}
/**
* Anonymize stack trace
*
* @param \Throwable $e
* @return string
*/
public static function getAnonymizedStackTrace(\Throwable $e): string
{
$projectPath = \realpath(\sprintf('%s/../', \app_path()));
$traceStack = $e->getTrace();
$traceStr = '';
$maxStackStage = 0;
foreach ($traceStack as $stackStage => $stack) {
$maxStackStage = $stackStage;
$traceStr .= \sprintf(
'#%s %s(%d): ',
$stackStage,
str_replace($projectPath, '', $stack['file'] ?? ''),
$stack['line'] ?? ''
);
if (isset($stack['class'])) {
$traceStr .= "{$stack['class']}->{$stack['function']}(";
if (isset($stack['args'])) {
$traceStr .= self::handleArgs($stack);
}
$traceStr .= ")\n";
} elseif (isset($stack['function'])) {
$traceStr .= "{$stack['function']}(";
if (isset($stack['args'])) {
$traceStr .= self::handleArgs($stack);
}
$traceStr .= ")\n";
}
} //end foreach
$maxStackStage++;
$traceStr .= "#$maxStackStage {main}";
return $traceStr;
}
/**
* Handle Args
*
* @param array $stack
* @return string
*/
public static function handleArgs(array $stack): string
{
$traceArgs = '';
$argsCount = count($stack['args']);
for ($i = 0; $i < $argsCount; $i++) {
$type = \gettype($stack['args'][$i]);
switch ($type) {
case 'object':
$traceArgs .= sprintf('Object(%s)', \get_class($stack['args'][$i]));
break;
case 'array':
$traceArgs .= sprintf('Array(%d)', \count($stack['args'][$i]));
break;
case 'string':
$traceArgs .= sprintf('String(%d)', \strlen($stack['args'][$i]));
break;
default:
$traceArgs .= \gettype($stack['args'][$i]);
}
$traceArgs .= ($i !== ($argsCount - 1) ? ',' : '');
}
return $traceArgs;
}
}

View file

@ -0,0 +1,444 @@
<?php
namespace App\Lib\Helpers;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
/**
* This class is used to handle files in LDC Laravel stack
*
* * The storage strategy will be using date.
* The file name may contain sufix timestamp highresolution time (hrtime or microtime) if is duplicate.
* (function() { return time() . ' ' . Str::slug(microtime(true)) . ' ' . hrtime(true); })();
*
* posts/ - The model table name
* 10/ - Year
* 04/ - Month
* 19/ - Day
* 5f/ - First two letters of md5 file
* black-cat-under-red-car.png - Slugified file name
* black-cat-under-red-car-286071690833400.png - Slugified file name that is duplicated
*
* The final path will be storage/modelfiles/posts/10/04/19/5f/black-cat-under-red-car-286071690833400.png
*
* ! No limits of file per folder will be handled.
*
* @author Munsch Jérémy <jeremy.munsch@ldc.fr>
*/
class FileStorageHelper
{
/**
* Destroy the file associate with the attribute.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $attribute
* @return void
*/
public static function removeFile(Model $model, string $attribute): void
{
$path = \ltrim(\urldecode($model->{$attribute}), '/');
if (\strpos($path, 'storage') !== 0) {
return;
}
$path = ToolboxHelper::mbReplace('storage/', '', $path);
if (Storage::disk('public')->exists($path)) {
Storage::disk('public')->delete($path);
}
}
/**
* Destroy the file associate with the attribute old value (before updated to DB).
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $attribute
* @param boolean $onlyIfUpdated
* @return void
*/
public static function removeOldFile(Model $model, string $attribute, bool $onlyIfUpdated = true): void
{
// * Ignore If removal has to be done only if the field has changed and it actually hasnt.
if ($onlyIfUpdated and $model->{$attribute} === $model->getOriginal($attribute)) {
return;
}
$originalPath = \ltrim(\urldecode($model->getOriginal($attribute)), '/');
if (\strpos($originalPath, 'storage') !== 0) {
return;
}
$originalPath = ToolboxHelper::mbReplace('storage/', '', $originalPath);
if (Storage::disk('public')->exists($originalPath)) {
Storage::disk('public')->delete($originalPath);
}
}
/**
* Store a file using optimized storage strategy
*
* You can use filename param to overload file name.
*
* @param \Illuminate\Database\Eloquent\Model $model The model that will hold the picture.
* @param \Illuminate\Http\UploadedFile|\SplFileInfo|string $file The file, will just return itself is string.
* @param boolean $slugify Force filename slugification.
* @param string|null $filename This string may be used to store the file.
* @param boolean $private To store the file in a private space.
* @return string
* @throws \RuntimeException If file copy fails.
*/
public static function storeFile(
Model $model,
$file,
bool $slugify = false,
string $filename = null,
bool $private = false
): string {
$tableName = $model->getTable();
if (\is_object($file) and $file instanceof \Illuminate\Http\UploadedFile) {
if ($private) {
throw new \RuntimeException('Private storage for uploaded files is unhandled');
}
/** @var \Illuminate\Http\UploadedFile $file */
$filename = $filename ?: $file->getClientOriginalName();
$filename = $slugify ? self::slugifyFileName($filename) : $filename;
$newPath = self::prepareMoveFile($tableName, $filename);
$newPath = str_replace(storage_path('app'), '', $newPath);
$file->storePubliclyAs(
pathinfo($newPath, \PATHINFO_DIRNAME),
pathinfo($newPath, \PATHINFO_BASENAME)
);
return 'storage' . str_replace('/public', '', $newPath);
}
if (\is_object($file) and $file instanceof \SplFileInfo) {
/** @var \SplFileInfo $file */
$filename = $filename ?: $file->getBaseName();
$filename = $slugify ? self::slugifyFileName($filename) : $filename;
$newPath = self::prepareMoveFile($tableName, $filename, $private);
if (!File::copy($file->getRealPath(), $newPath)) {
throw new \RuntimeException(sprintf(
'Failed to copy file %s to %s',
$file->getRealPath(),
$newPath
));
}
return $private ?
\ltrim(str_replace(\storage_path('app/private'), '/storage/app/private', $newPath), '/') :
\ltrim(str_replace(\storage_path('app/public'), '/storage', $newPath), '/');
}
return strval($file);
}
/**
* Store all files from string attribute html
* If oldHtmlString is passed, a diff is done with new and old files
* to keep only files that are present in the new html string.
*
*
* @param \Illuminate\Database\Eloquent\Model $model The model that will hold the picture.
* @param string $htmlString The html string to parse and modify files.
* @param string|null $oldHtmlString The old html string with alreadyParsed
* files using storeModelFilesFromHtml.
* @return string Html with files pathes replaced using storage strategy.
*/
public static function storeModelFilesFromHtml(
Model $model,
string $htmlString,
string $oldHtmlString = null
): string {
\collect(self::onlyUploadedFilesWithFileManager(self::findFilesInHtml($htmlString)))
->each(function (string $path) use ($model, &$htmlString) {
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$disk = Storage::disk('uploads');
$fileInfo = new \SplFileInfo($disk->path(
ToolboxHelper::mbReplace('/uploads/', '', \urldecode($path))
));
$htmlString = ToolboxHelper::mbReplace(
$path,
'/' . self::storeFile($model, $fileInfo, true),
$htmlString
);
});
if ($oldHtmlString) {
self::removeOldModelFilesFromHtml($htmlString, $oldHtmlString);
}
return $htmlString;
}
/**
* Remove all old files from string attribute html that are stored in public that
* are nove present anymore on new html string
*
* @param string $newHtmlString The html string with alreadyParsed files using storeModelFilesFromHtml.
* @param string $oldHtmlString The old html string with alreadyParsed files using storeModelFilesFromHtml.
* @return void
*/
public static function removeOldModelFilesFromHtml(string $newHtmlString, string $oldHtmlString): void
{
self::removeOldFiles(self::findFilesInHtml($oldHtmlString), self::findFilesInHtml($newHtmlString));
}
/**
* Remove all old files that are stored in public that
* are not present anymore on new array files
*
* @param string[] $oldFiles The old array files.
* @param string[] $newFiles The new array files.
* @return void
*/
public static function removeOldFiles(array $oldFiles, array $newFiles): void
{
$oldFiles = \collect(self::onlyUploadedFilesWithFileStorageHelper($oldFiles));
$filesToKeep = \collect(self::onlyUploadedFilesWithFileStorageHelper($newFiles));
$filesToRemove = $oldFiles->diff($filesToKeep);
$filesToRemove->each(function (string $publicStorageFilePath) {
$path = ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($publicStorageFilePath), '/'));
if (Storage::disk('public')->exists($path)) {
Storage::disk('public')->delete($path);
}
});
}
/**
* Remove all files from string attribute html that are stored in public
*
* @param string $htmlString The html string with alreadyParsed files using storeModelFilesFromHtml.
* @return void
*/
public static function removeModelFilesOfHtml(string $htmlString): void
{
$filesToRemove = \collect(self::onlyUploadedFilesWithFileStorageHelper(self::findFilesInHtml($htmlString)));
$filesToRemove->each(function (string $publicStorageFilePath) {
$path = ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($publicStorageFilePath), '/'));
if (Storage::disk('public')->exists($path)) {
Storage::disk('public')->delete($path);
}
});
}
/**
* Find all occurence of files in html
* @param string $html
* @return array
*/
public static function findFilesInHtml(string $html): array
{
preg_match_all('/href="(?P<href>.*)"|src="(?P<src>.*)"|url="(?P<url>.*)"/Um', $html, $m, PREG_SET_ORDER, 0);
return collect($m)->pluck('href')->filter()
->concat(collect($m)->pluck('url')->filter())
->concat(collect($m)->pluck('src')->filter())
->all();
}
/**
* Filter array to get only uploaded files that exists
* on disk 'public' (using helper storage strategy)
*
* @param array $urls
* @return array
*/
public static function onlyUploadedFilesWithFileStorageHelper(array $urls): array
{
return \collect($urls)->unique()
// * Filter to get only /uploads and using app url or empty
->filter(function (string $url) {
$path = \parse_url($url, \PHP_URL_PATH);
return \strpos(\ltrim($path, '/'), 'storage/modelfiles') === 0;
})->filter(function (string $url) {
$path = \parse_url($url, \PHP_URL_PATH);
return Storage::disk('public')
->exists(ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($path), '/')));
})->all();
}
/**
* Filter array to get only uploaded files that exists
* on disk 'uploads'
*
* @param array $urls
* @return array
*/
private static function onlyUploadedFilesWithFileManager(array $urls): array
{
return \collect($urls)->unique()
// * Filter to get only /uploads and using app url or empty
->filter(function (string $url) {
$host = \parse_url($url, \PHP_URL_HOST);
$path = \parse_url($url, \PHP_URL_PATH);
return ($host === \parse_url(config('app.url'), PHP_URL_HOST) or \is_null($host)) and
\strpos(\ltrim($path, '/'), 'uploads') === 0;
})->filter(function (string $url) {
$path = \parse_url($url, \PHP_URL_PATH);
return Storage::disk('uploads')
->exists(ToolboxHelper::mbReplace('uploads/', '', \ltrim(\urldecode($path), '/')));
})->all();
}
/**
* Prepare storage path and return
* the file full path
*
* @param string $tableName Model table name.
* @param string $filename The basename for the file.
* @param boolean $private May the file be stored in a private dir.
* @return string
*/
private static function prepareMoveFile(string $tableName, string $filename, bool $private = false): string
{
// * Security filter + beautifier
$filename = self::sanitizeFileName($filename);
// * Security filter
$filename = \filter_var($filename, \FILTER_SANITIZE_STRING);
// * Slugify filename
$ext = \pathinfo($filename, \PATHINFO_EXTENSION);
$filename = Str::slug(\pathinfo($filename, \PATHINFO_FILENAME)) .
(\strlen($ext) ? ".{$ext}" : '');
$folderPath = self::getStoragePath(
$private ? 'app/private/modelfiles' : 'app/public/modelfiles',
$tableName,
$filename,
$private
);
$filename = self::getFileUniqueName($folderPath, $filename);
return "{$folderPath}/{$filename}";
}
/**
* Create sub folders to store files using pattern
* storage/modelfiles/posts/10/04/19/5f
* year/month/day/md5 first 2 letters
*
* @param string $storage_dir
* @param string $table
* @param string $filename
* @return string
*/
private static function getStoragePath(string $storage_dir, string $table, string $filename): string
{
$filenameHash = \md5($filename);
$pathFolder = \storage_path(sprintf(
'%s/%s/%s/%s/%s/%s',
$storage_dir,
$table,
gmdate('y'),
gmdate('m'),
gmdate('d'),
substr($filenameHash, 0, 2)
));
if (!File::exists($pathFolder)) {
File::makeDirectory($pathFolder, 0755, true);
}
return $pathFolder;
}
/**
* Get unique filename if the path is already occupied
*
* @param string $folderPath
* @param string $filename
* @return string
* @throws \RuntimeException If a unique filename cannot be obtained.
*/
private static function getFileUniqueName(string $folderPath, string $filename): string
{
$attempts = 0;
$maxAttempts = 20;
while (File::exists("{$folderPath}/{$filename}")) {
if ($attempts++ >= $maxAttempts) {
throw new \RuntimeException("Failed to has a unique filename for {$folderPath}/{$filename}");
}
$filename = pathinfo($filename, \PATHINFO_FILENAME) .
\uniqid('-') . '.' .
pathinfo($filename, \PATHINFO_EXTENSION);
}
return $filename;
}
/**
* Slugify the filename
*
* @param string $filename
* @return string
*/
public static function slugifyFileName(string $filename): string
{
return Str::slug(pathinfo($filename, \PATHINFO_FILENAME)) . '.' . pathinfo($filename, \PATHINFO_EXTENSION);
}
/**
* Sanitize File name for uploaded files
*
* @param string $filename The original uploaded client file name.
* @param boolean $beautify Beautify the filename.
* @return string The sanitized file name.
*/
public static function sanitizeFileName(string $filename, bool $beautify = true): string
{
// * Sanitize filename
// phpcs:disable Generic.Files.LineLength.TooLong
$filename = preg_replace(
'~
[<>:"/\\\|?*]| # file system reserved https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
[\x00-\x1F]| # control characters http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
[\x7F\xA0\xAD]| # non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN
[#\[\]@!$&\'()+,;=]| # URI reserved https://tools.ietf.org/html/rfc3986#section-2.2
[{}^\~`] # URL unsafe characters https://www.ietf.org/rfc/rfc1738.txt
~x',
'-',
$filename
);
// phpcs:enable
// * Avoids '.' '..' or '.hiddenFiles' .
$filename = Str::of($filename)->ltrim('.-');
// * Optional beautification
if ($beautify) {
$filename = static::beautifyFilename($filename);
}
// * Maximize filename length to 255 bytes http://serverfault.com/a/9548/44086
/** @var string */
$extension = pathinfo($filename, \PATHINFO_EXTENSION);
/** @var string */
$filename = pathinfo($filename, \PATHINFO_FILENAME);
$encoding = mb_detect_encoding($filename);
$encoding = $encoding ? $encoding : null;
return \mb_strcut(
$filename,
0,
255 - (!empty($extension) ? strlen($extension) + 1 : 0),
$encoding
) . (!empty($extension) ? ".$extension" : '');
}
/**
* Beautify a filename
*
* @param string $filename
* @return string
*/
private static function beautifyFilename(string $filename): string
{
// Reduce consecutive characters.
$filename = preg_replace([
// * "file name.zip" becomes 'file-name.zip'
'/ +/',
// * "file___name.zip" becomes 'file-name.zip'
'/_+/',
// * "file---name.zip" becomes 'file-name.zip'
'/-+/'
], '-', $filename);
$filename = preg_replace([
// * "file--.--.-.--name.zip" becomes "file.name.zip"
'/-*\.-*/',
// * "file...name..zip" becomes "file.name.zip"
'/\.{2,}/'
], '.', $filename);
// Lowercase for windows/unix interoperability http://support.microsoft.com/kb/100625 .
$filename = mb_strtolower($filename, mb_detect_encoding($filename));
// * ".file-name.-" becomes "file-name"
$filename = trim($filename, '.-');
return $filename;
}
}

View file

@ -0,0 +1,298 @@
<?php
namespace App\Lib\Helpers;
use App\Enums\Pagination\ItemsPerPaginationEnum;
use App\Lib\Inputs\Definition\Input;
use App\Models\Contact;
use App\Models\Formulary;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Kwaadpepper\Enum\Rules\EnumIsValidRule;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class ToolboxHelper
{
/**
* Strip scheme (https) on oembed urls
* Only embed allowed are for MP4 self hosted videos right now
*
* https is force by ckeditor MediaElements which natively supports
* only cloud hosted medias (youtube, spotify etc..)
*
* @param string $input
* @return string
*/
public static function stripCkEditorSchemesOnMediaInputs(string $input): string
{
$input = \preg_replace('/(?<=<oembed url=")(https:\/\/)(?=.*?">)/m', '', $input);
return $input;
}
/**
* Assert That no Back Office url is present on txt
*
* @param string $field
* @param string $text
* @return string
* @throws \Illuminate\Validation\ValidationException If url is a bo url.
*/
public static function assertNoInternalBoUrls(string $field, string $text): string
{
$routes = Route::getRoutes();
// * Url replace and email replace.
$text = preg_replace_callback(
// phpcs:ignore Generic.Files.LineLength.TooLong
'/(?P<url>(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/',
function ($matches) use ($routes, $field) {
$match = collect($matches)->first() ?? '';
$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) &&
trim($routes->match($request)->getPrefix() ?? '', '/') === 'bo'
) {
// Removes the matched url.
throw ValidationException::withMessages([
$field => ':attribute : Il est interdit de coller une url du back office dans ce champ'
]);
}
return $match;
} catch (NotFoundHttpException $e) {
return $match;
}
},
$text
);
return $text;
}
/**
* Assert That only external url is present on txt
*
* @param string $field
* @param string $text
* @return string
* @throws \Illuminate\Validation\ValidationException If url is a bo url.
*/
public static function assertNoInternalUrls(string $field, string $text): string
{
self::assertNoInternalBoUrls($field, $text);
$routes = Route::getRoutes();
// * Url replace and email replace.
$text = preg_replace_callback(
// phpcs:ignore Generic.Files.LineLength.TooLong
'/(?P<url>(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/',
function ($matches) use ($routes, $field) {
$match = collect($matches)->first() ?? '';
$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)
) {
// Removes the matched url.
throw ValidationException::withMessages([
$field => sprintf(
':attribute : Il est interdit de coller une url de %s dans ce champ',
config('app.name')
)
]);
}
return $match;
} catch (NotFoundHttpException $e) {
return $match;
}
},
$text
);
return $text;
}
/**
* Mutltibyte string replace
* @param mixed $search
* @param mixed $replace
* @param mixed $subject
* @param integer $count
* @return mixed
*/
public static function mbReplace($search, $replace, $subject, int &$count = 0)
{
if (!is_array($search) && is_array($replace)) {
return false;
}
if (is_array($subject)) {
// Call mb_replace for each single string in $subject .
foreach ($subject as &$string) {
$string = &self::mbReplace($search, $replace, $string, $count);
}
} elseif (is_array($search)) {
if (!is_array($replace)) {
foreach ($search as &$string) {
$subject = self::mbReplace($string, $replace, $subject, $count);
}
} else {
$n = max(count($search), count($replace));
while ($n--) {
$subject = self::mbReplace(current($search), current($replace), $subject, $count);
next($search);
next($replace);
}
}
} else {
$parts = mb_split(preg_quote($search), $subject);
$count = count($parts) - 1;
$subject = implode($replace, $parts);
} //end if
return $subject;
}
/**
* Asserts that the field is unique
*
* @param string $table
* @param string $field
* @param mixed $value
* @param integer|null $id
* @param callable|null $query
* @return void
* @throws \Illuminate\Validation\ValidationException If the field is not unique.
*/
public static function assertFieldIsUnique(
string $table,
string $field,
$value,
?int $id = null,
?callable $query = null
) {
if (!$query) {
Validator::make([$field => $value], [
self::mbReplace('.', '\.', $field) => Rule::unique($table, $field)->ignore($id),
], [
self::mbReplace('.', '\.', "{$field}.unique")
=> trans('La valeur :value pour le champ :attribute est déjà utilisé', [
'attribute' => $field,
'value' => $value
])
])->validate();
return;
}
Validator::make([$field => $value], [
$field => Rule::unique($table)->where($query)->ignore($id),
], [
$field . '.unique' => trans('La valeur pour le champ :attribute est déjà utilisé', [
'attribute' => $field
])
])->validate();
}
/**
* Convert deeply an object to array
*
* @param array|object $input
* @return array
* @throws \RuntimeException If $input var is a scalar value.
*/
public static function objectToArray($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 {
throw new \RuntimeException('cannot handle $input of type ' . \gettype($input));
}
}
/**
* Convert deeply an array to object
*
* @param array|object $input
* @return object
* @throws \RuntimeException If $input var is a scalar value.
*/
public static function arrayToObject($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 {
throw new \RuntimeException('cannot handle $input of type ' . \gettype($input));
}
}
/**
* Get validated pagination from request
*
* @param integer $default
* @return integer
*/
public static function getValidatedPaginate(int $default): int
{
try {
return \intval(Validator::make(
['pagination' => \request()->get('pagination')],
['pagination' => new EnumIsValidRule(ItemsPerPaginationEnum::class)]
)->validated()['pagination']);
} catch (ValidationException $e) {
return $default;
}
}
/**
* Assert contact data is matching actual Formularies
*
* @param \App\Models\Contact $contact
* @return void
*/
public static function assertContactDataIsMatchingFormulary(Contact $contact)
{
/** @var \Illuminate\Support\Collection<string,integer> */
$formularies = Formulary::query()->pluck('slug')->flip();
\collect($contact->data)->each(function (Input $input) use ($formularies) {
if (!$formularies->has($input->data->formSlug)) {
throw new \ErrorException(
"Formulary {$input->data->formSlug} does not exist, do not store this contact data prop"
);
}
});
}
/**
* Remove Data Input on All contacts
*
* @param \App\Models\Formulary $formulary
* @return void
*/
public static function removeDataInputOnAllContacts(Formulary $formulary): void
{
DB::transaction(function () use ($formulary) {
Contact::query()->select(['id', 'data'])->each(function (Contact $contact) use ($formulary) {
$contact->data = \collect($contact->data)->filter(function (Input $input) use ($formulary) {
return $input->data->formSlug !== $formulary->slug;
});
$contact->saveOrFail();
});
});
}
}

View file

@ -3,6 +3,7 @@
namespace App\Lib\Inputs\Definition; namespace App\Lib\Inputs\Definition;
use App\Models\Formulary; use App\Models\Formulary;
use Illuminate\Database\Eloquent\Builder;
/** /**
* To manipulate Input * To manipulate Input
@ -62,11 +63,10 @@ abstract class Input implements InputInterface
/** /**
* Get the matching formulary for this object * Get the matching formulary for this object
* *
* @return \App\Models\Formulary|null * @return \App\Models/Formulary|null
*/ */
public function getFormulary(): \App\Models\Formulary|null public function getFormulary(): ?Formulary
{ {
// @phpstan-ignore return.type
return $this->formularyQuery()->first(); return $this->formularyQuery()->first();
} }
@ -85,7 +85,7 @@ abstract class Input implements InputInterface
* *
* @return \Illuminate\Database\Eloquent\Builder * @return \Illuminate\Database\Eloquent\Builder
*/ */
private function formularyQuery(): \Illuminate\Database\Eloquent\Builder private function formularyQuery(): Builder
{ {
return Formulary::query() return Formulary::query()
->where('slug', $this->data->formSlug) ->where('slug', $this->data->formSlug)
@ -102,12 +102,10 @@ abstract class Input implements InputInterface
* @return static * @return static
* @throws \RuntimeException If the input type is not recognized, or data is invalid. * @throws \RuntimeException If the input type is not recognized, or data is invalid.
*/ */
public static function from(InputData $data, bool $required = true): static public static function from(InputData $data, bool $required = true)
{ {
$inputTypeDataClass = static::class; $inputTypeDataClass = static::class;
$type = "";
try { try {
// @phpstan-ignore function.alreadyNarrowedType
$type = \is_numeric($data->type) ? intval($data->type) : strval($data->type); $type = \is_numeric($data->type) ? intval($data->type) : strval($data->type);
return new static( return new static(
InputData::make($data->title, $data->formSlug, $type, $data->value, $required), InputData::make($data->title, $data->formSlug, $type, $data->value, $required),
@ -133,7 +131,7 @@ abstract class Input implements InputInterface
* @return static * @return static
* @throws \RuntimeException If the input type is not recognized, or data is invalid. * @throws \RuntimeException If the input type is not recognized, or data is invalid.
*/ */
public static function make(string $title, string $formSlug, $data, bool $required = true): static public static function make(string $title, string $formSlug, $data, bool $required = true)
{ {
return static::from( return static::from(
InputData::make( InputData::make(
@ -167,9 +165,9 @@ abstract class Input implements InputInterface
* *
* @return array * @return array
*/ */
public function toArray(): array public function toArray()
{ {
return (array) $this->data; return (array)$this->data;
} }
/** /**
@ -179,7 +177,7 @@ abstract class Input implements InputInterface
* @return string * @return string
* @phpcs:disable Squiz.Commenting.FunctionComment.ScalarTypeHintMissing * @phpcs:disable Squiz.Commenting.FunctionComment.ScalarTypeHintMissing
*/ */
public function toJson($options = 0): string public function toJson($options = 0)
{ {
// phpcs:enable // phpcs:enable
return \json_encode($this->jsonSerialize(), $options); return \json_encode($this->jsonSerialize(), $options);
@ -190,7 +188,8 @@ abstract class Input implements InputInterface
* *
* @return array * @return array
*/ */
public function jsonSerialize(): array #[\ReturnTypeWillChange]
public function jsonSerialize()
{ {
return $this->toArray(); return $this->toArray();
} }

View file

@ -5,13 +5,13 @@ namespace App\Lib\Inputs\Definition;
use App\Enums\Formularies\InputTypeEnum; use App\Enums\Formularies\InputTypeEnum;
/** /**
* @property string $title The input type placeholder * @property-read string $title The input type placeholder
* @property string $formSlug The original Formulary slug. * @property-read string $formSlug The original Formulary slug.
* @property integer $type The input type value * @property-read integer $type The input type value
* @property string|array<integer,string> $value The input value, either a string or an array * @property-read string|array<integer,string> $value The input value, either a string or an array
* with the value index and label * with the value index and label
* (for Multiselect and checkboxes) * (for Multiselect and checkboxes)
* @property boolean $required The input is required ? * @property-read boolean $required The input is required ?
* @package App\Lib\Inputs * @package App\Lib\Inputs
*/ */
class InputData extends \stdClass class InputData extends \stdClass
@ -62,7 +62,7 @@ class InputData extends \stdClass
* @return void * @return void
* @throws \RuntimeException If $data is wrong. * @throws \RuntimeException If $data is wrong.
*/ */
public static function assertIsInputTypeData(InputData $data): void public static function assertIsInputTypeData(InputData $data)
{ {
$title = $data->title; $title = $data->title;
$type = $data->type; $type = $data->type;
@ -70,19 +70,15 @@ class InputData extends \stdClass
$value = $data->value; $value = $data->value;
$required = $data->required; $required = $data->required;
// @phpstan-ignore function.alreadyNarrowedType
if (!\is_string($title)) { if (!\is_string($title)) {
throw new \RuntimeException("InputTypeData 'title' should be a string"); throw new \RuntimeException("InputTypeData 'title' should be a string");
} }
// @phpstan-ignore function.alreadyNarrowedType
if (!\is_string($formSlug)) { if (!\is_string($formSlug)) {
throw new \RuntimeException("InputTypeData 'formSlug' should be a string"); throw new \RuntimeException("InputTypeData 'formSlug' should be a string");
} }
// @phpstan-ignore function.alreadyNarrowedType
if (!\is_integer($type)) { if (!\is_integer($type)) {
throw new \RuntimeException("InputTypeData 'type' should be an integer"); throw new \RuntimeException("InputTypeData 'type' should be an integer");
} }
// @phpstan-ignore function.alreadyNarrowedType
if (!\is_bool($required)) { if (!\is_bool($required)) {
throw new \RuntimeException("InputTypeData 'required' should be a boolean"); throw new \RuntimeException("InputTypeData 'required' should be a boolean");
} }
@ -93,7 +89,6 @@ class InputData extends \stdClass
throw new \RuntimeException($e->getMessage()); throw new \RuntimeException($e->getMessage());
} }
// @phpstan-ignore function.impossibleType, function.alreadyNarrowedType, booleanAnd.alwaysFalse
if (!\is_null($value) && !\is_string($value) && !\is_array($value)) { if (!\is_null($value) && !\is_string($value) && !\is_array($value)) {
$type = \gettype($value); $type = \gettype($value);
$value = \json_encode($value); $value = \json_encode($value);

View file

@ -31,7 +31,7 @@ interface InputInterface extends Jsonable, Arrayable, \JsonSerializable
* @return static * @return static
* @throws \RuntimeException If the input type is not recognized, or data is invalid. * @throws \RuntimeException If the input type is not recognized, or data is invalid.
*/ */
public static function from(InputData $data, bool $required = true): static; public static function from(InputData $data, bool $required = true);
/** /**
* Parse DB line to Object * Parse DB line to Object
@ -45,7 +45,7 @@ interface InputInterface extends Jsonable, Arrayable, \JsonSerializable
* @return static * @return static
* @throws \RuntimeException If the input type is not recognized, or data is invalid. * @throws \RuntimeException If the input type is not recognized, or data is invalid.
*/ */
public static function make(string $title, string $formSlug, $data, bool $required = true): static; public static function make(string $title, string $formSlug, $data, bool $required = true);
// * --- CHILDS Should implement --- // * --- CHILDS Should implement ---
@ -66,7 +66,7 @@ interface InputInterface extends Jsonable, Arrayable, \JsonSerializable
/** /**
* Get the matching formulary for this object * Get the matching formulary for this object
* *
* @return \App\Models\Formulary|null * @return \App\Models/Formulary|null
*/ */
public function getFormulary(): ?Formulary; public function getFormulary(): ?Formulary;
@ -91,8 +91,8 @@ interface InputInterface extends Jsonable, Arrayable, \JsonSerializable
* *
* @param string|array $data The line with data. * @param string|array $data The line with data.
* @param boolean $required The line is required. * @param boolean $required The line is required.
* @return void * @return string|array<integer,string>
* @throws \Illuminate\Validation\ValidationException If the data is invalid. * @throws \Illuminate\Validation\ValidationException If the data is invalid.
*/ */
public static function validateData($data, bool $required): void; public static function validateData($data, bool $required);
} }

View file

@ -4,6 +4,7 @@ namespace App\Lib\Inputs;
use App\Enums\Formularies\InputTypeEnum; use App\Enums\Formularies\InputTypeEnum;
use App\Lib\Inputs\Definition\Input; use App\Lib\Inputs\Definition\Input;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Support\Facades\Validator as FacadesValidator; use Illuminate\Support\Facades\Validator as FacadesValidator;
/** /**
@ -17,9 +18,9 @@ class InputCheckbox extends Input
* *
* @return \App\Enums\Formularies\InputTypeEnum * @return \App\Enums\Formularies\InputTypeEnum
*/ */
public static function getType(): \App\Enums\Formularies\InputTypeEnum public static function getType(): InputTypeEnum
{ {
return InputTypeEnum::checkbox; return InputTypeEnum::checkbox();
} }
@ -30,12 +31,12 @@ class InputCheckbox extends Input
* @param boolean $required The line is required. * @param boolean $required The line is required.
* @return \Illuminate\Contracts\Validation\Validator * @return \Illuminate\Contracts\Validation\Validator
*/ */
public static function getValidator($data, bool $required): \Illuminate\Contracts\Validation\Validator public static function getValidator($data, bool $required): Validator
{ {
return FacadesValidator::make( return FacadesValidator::make(
['value' => $data], ['value' => $data],
[ [
'value' => 'array' . ($required ? '|min:1' : ''), 'value' => 'array' . ($required ? '|min:1' : ''),
'value.*' => 'required|string|max:255' 'value.*' => 'required|string|max:255'
] ]
); );

View file

@ -4,6 +4,7 @@ namespace App\Lib\Inputs;
use App\Enums\Formularies\InputTypeEnum; use App\Enums\Formularies\InputTypeEnum;
use App\Lib\Inputs\Definition\Input; use App\Lib\Inputs\Definition\Input;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Support\Facades\Validator as FacadesValidator; use Illuminate\Support\Facades\Validator as FacadesValidator;
/** /**
@ -17,9 +18,9 @@ class InputEmail extends Input
* *
* @return \App\Enums\Formularies\InputTypeEnum * @return \App\Enums\Formularies\InputTypeEnum
*/ */
public static function getType(): \App\Enums\Formularies\InputTypeEnum public static function getType(): InputTypeEnum
{ {
return InputTypeEnum::email; return InputTypeEnum::email();
} }
@ -30,7 +31,7 @@ class InputEmail extends Input
* @param boolean $required The line is required. * @param boolean $required The line is required.
* @return \Illuminate\Contracts\Validation\Validator * @return \Illuminate\Contracts\Validation\Validator
*/ */
public static function getValidator($data, bool $required): \Illuminate\Contracts\Validation\Validator public static function getValidator($data, bool $required): Validator
{ {
return FacadesValidator::make( return FacadesValidator::make(
['value' => $data], ['value' => $data],

View file

@ -4,6 +4,7 @@ namespace App\Lib\Inputs;
use App\Enums\Formularies\InputTypeEnum; use App\Enums\Formularies\InputTypeEnum;
use App\Lib\Inputs\Definition\Input; use App\Lib\Inputs\Definition\Input;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Support\Facades\Validator as FacadesValidator; use Illuminate\Support\Facades\Validator as FacadesValidator;
/** /**
@ -17,9 +18,9 @@ class InputPhone extends Input
* *
* @return \App\Enums\Formularies\InputTypeEnum * @return \App\Enums\Formularies\InputTypeEnum
*/ */
public static function getType(): \App\Enums\Formularies\InputTypeEnum public static function getType(): InputTypeEnum
{ {
return InputTypeEnum::phone; return InputTypeEnum::phone();
} }
@ -30,7 +31,7 @@ class InputPhone extends Input
* @param boolean $required The line is required. * @param boolean $required The line is required.
* @return \Illuminate\Contracts\Validation\Validator * @return \Illuminate\Contracts\Validation\Validator
*/ */
public static function getValidator($data, bool $required): \Illuminate\Contracts\Validation\Validator public static function getValidator($data, bool $required): Validator
{ {
return FacadesValidator::make( return FacadesValidator::make(
['value' => $data], ['value' => $data],

View file

@ -4,6 +4,7 @@ namespace App\Lib\Inputs;
use App\Enums\Formularies\InputTypeEnum; use App\Enums\Formularies\InputTypeEnum;
use App\Lib\Inputs\Definition\Input; use App\Lib\Inputs\Definition\Input;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Support\Facades\Validator as FacadesValidator; use Illuminate\Support\Facades\Validator as FacadesValidator;
/** /**
@ -17,9 +18,9 @@ class InputRadio extends Input
* *
* @return \App\Enums\Formularies\InputTypeEnum * @return \App\Enums\Formularies\InputTypeEnum
*/ */
public static function getType(): \App\Enums\Formularies\InputTypeEnum public static function getType(): InputTypeEnum
{ {
return InputTypeEnum::radio; return InputTypeEnum::radio();
} }
@ -30,7 +31,7 @@ class InputRadio extends Input
* @param boolean $required The line is required. * @param boolean $required The line is required.
* @return \Illuminate\Contracts\Validation\Validator * @return \Illuminate\Contracts\Validation\Validator
*/ */
public static function getValidator($data, bool $required): \Illuminate\Contracts\Validation\Validator public static function getValidator($data, bool $required): Validator
{ {
return FacadesValidator::make( return FacadesValidator::make(
['value' => $data], ['value' => $data],

View file

@ -4,6 +4,7 @@ namespace App\Lib\Inputs;
use App\Enums\Formularies\InputTypeEnum; use App\Enums\Formularies\InputTypeEnum;
use App\Lib\Inputs\Definition\Input; use App\Lib\Inputs\Definition\Input;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Support\Facades\Validator as FacadesValidator; use Illuminate\Support\Facades\Validator as FacadesValidator;
/** /**
@ -17,9 +18,9 @@ class InputSelect extends Input
* *
* @return \App\Enums\Formularies\InputTypeEnum * @return \App\Enums\Formularies\InputTypeEnum
*/ */
public static function getType(): \App\Enums\Formularies\InputTypeEnum public static function getType(): InputTypeEnum
{ {
return InputTypeEnum::select; return InputTypeEnum::select();
} }
@ -30,7 +31,7 @@ class InputSelect extends Input
* @param boolean $required The line is required. * @param boolean $required The line is required.
* @return \Illuminate\Contracts\Validation\Validator * @return \Illuminate\Contracts\Validation\Validator
*/ */
public static function getValidator($data, bool $required): \Illuminate\Contracts\Validation\Validator public static function getValidator($data, bool $required): Validator
{ {
return FacadesValidator::make( return FacadesValidator::make(
['value' => $data], ['value' => $data],

View file

@ -15,8 +15,8 @@ class InputSelectMultiple extends InputCheckbox
* *
* @return \App\Enums\Formularies\InputTypeEnum * @return \App\Enums\Formularies\InputTypeEnum
*/ */
public static function getType(): \App\Enums\Formularies\InputTypeEnum public static function getType(): InputTypeEnum
{ {
return InputTypeEnum::selectMultiple; return InputTypeEnum::selectMultiple();
} }
} }

View file

@ -4,6 +4,7 @@ namespace App\Lib\Inputs;
use App\Enums\Formularies\InputTypeEnum; use App\Enums\Formularies\InputTypeEnum;
use App\Lib\Inputs\Definition\Input; use App\Lib\Inputs\Definition\Input;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Support\Facades\Validator as FacadesValidator; use Illuminate\Support\Facades\Validator as FacadesValidator;
/** /**
@ -16,9 +17,9 @@ class InputText extends Input
* *
* @return \App\Enums\Formularies\InputTypeEnum * @return \App\Enums\Formularies\InputTypeEnum
*/ */
public static function getType(): \App\Enums\Formularies\InputTypeEnum public static function getType(): InputTypeEnum
{ {
return InputTypeEnum::text; return InputTypeEnum::text();
} }
@ -29,7 +30,7 @@ class InputText extends Input
* @param boolean $required The line is required. * @param boolean $required The line is required.
* @return \Illuminate\Contracts\Validation\Validator * @return \Illuminate\Contracts\Validation\Validator
*/ */
public static function getValidator($data, bool $required): \Illuminate\Contracts\Validation\Validator public static function getValidator($data, bool $required): Validator
{ {
return FacadesValidator::make( return FacadesValidator::make(
['value' => $data], ['value' => $data],

View file

@ -2,6 +2,7 @@
namespace App\Mail; namespace App\Mail;
use App\Models\AppUser;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
@ -11,30 +12,36 @@ class AccountCreated extends Mailable
use Queueable; use Queueable;
use SerializesModels; use SerializesModels;
/** @var object */ /**
public $data; * The app user instance
*
* @var \App\Models\AppUser;
*/
public $user;
/** @var string */
public $password;
/** /**
* Create a new message instance. * Create a new message instance.
* *
* @param mixed $data * @param \App\Models\AppUser $user
* @param string $password
* @return void * @return void
*/ */
public function __construct(mixed $data) public function __construct(AppUser $user, string $password)
{ {
$this->data = (object) $data; $this->user = $user;
$this->password = $password;
} }
/** /**
* Build the message. * Build the message.
* *
* @return mixed * @return $this
*/ */
public function build(): mixed public function build()
{ {
return $this return $this->subject(__("Votre compte a été créé"))->view('emails.account-created');
->from($this->data->email, sprintf('%s %s', $this->data->lastname, $this->data->firstname))
->subject(__("Votre compte a été créé"))
->markdown('bo.mails.account-created');
} }
} }

View file

@ -3,11 +3,10 @@
namespace App\Mail; namespace App\Mail;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class SendFlyer extends Mailable implements ShouldQueue class SendFlyer extends Mailable
{ {
use Queueable; use Queueable;
use SerializesModels; use SerializesModels;
@ -21,20 +20,20 @@ class SendFlyer extends Mailable implements ShouldQueue
* @param mixed $data * @param mixed $data
* @return void * @return void
*/ */
public function __construct(mixed $data) public function __construct($data)
{ {
$this->data = (object) $data; $this->data = (object)$data;
} }
/** /**
* Build the message. * Build the message.
* *
* @return mixed * @return $this
*/ */
public function build(): mixed public function build()
{ {
return $this return $this
->from(config('mail.from.address'), $this->data->fromName) ->from($this->data->from, $this->data->fromName)
->subject($this->data->subject) ->subject($this->data->subject)
->markdown('bo.mails.sendflyer'); ->markdown('bo.mails.sendflyer');
} }

View file

@ -3,11 +3,10 @@
namespace App\Mail; namespace App\Mail;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class SendFlyerFailed extends Mailable implements ShouldQueue class SendFlyerFailed extends Mailable
{ {
use Queueable; use Queueable;
use SerializesModels; use SerializesModels;
@ -21,19 +20,20 @@ class SendFlyerFailed extends Mailable implements ShouldQueue
* @param mixed $data * @param mixed $data
* @return void * @return void
*/ */
public function __construct(mixed $data) public function __construct($data)
{ {
$this->data = (object) $data; $this->data = (object)$data;
} }
/** /**
* Build the message. * Build the message.
* *
* @return mixed * @return $this
*/ */
public function build(): mixed public function build()
{ {
return $this return $this
->from('noreply@ldc.fr', '[APP] ' . config('app.name'))
->subject('[MAIL] Une erreur est survenue') ->subject('[MAIL] Une erreur est survenue')
->view('bo.mails.sendflyerfailed', ['data' => $this->data]); ->view('bo.mails.sendflyerfailed', ['data' => $this->data]);
} }

View file

@ -1,48 +0,0 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class TenantDeleted extends Mailable
{
use Queueable;
use SerializesModels;
/** @var string $author */
private $author;
/** @var string $name */
private $name;
/**
* Create a new message instance.
*
* @param string $author
* @param string $name
* @return void
*/
public function __construct(string $author, string $name)
{
$this->author = $author;
$this->name = $name;
}
/**
* Build the message.
*
* @return mixed
*/
public function build(): mixed
{
return $this
->from(config('mail.from.address'), config('mail.from.name'))
->subject(sprintf('Suppression du tenant %s', $this->name))
->markdown('bo.mails.tenant-deleted', [
'author' => $this->author,
'name' => $this->name,
]);
}
}

View file

@ -3,35 +3,30 @@
namespace App\Models; namespace App\Models;
use App\Enums\AppUserRolesEnum; use App\Enums\AppUserRolesEnum;
use App\Traits\Models\IsRelatedToTenant; use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Stancl\Tenancy\Database\Concerns\BelongsToTenant; use Kwaadpepper\Enum\Traits\CastsEnums;
/** /**
* @property integer $id App user id. * @property integer $id
* @property string $lastname App user last name. * @property string $lastname
* @property string $firstname App user first name. * @property string $firstname
* @property string $email App user email. * @property string $email
* @property string $password App user password. * @property string $password
* @property string|null $tenant_id Associated tenant. * @property \App\Enums\AppUserRolesEnum $role
* @property boolean $status App user status. * @property boolean $status
* @property \App\Enums\AppUserRolesEnum $role App user role. * @property integer $order
* @property integer $order App user order.
* @property-read \Carbon\Carbon $created_at Created date.
* @property-read \Carbon\Carbon $updated_at Updated date.
*/ */
class AppUser extends Model class AppUser extends Model
{ {
use BelongsToTenant;
use IsRelatedToTenant;
use HasFactory; use HasFactory;
use CastsEnums;
/** /**
* The attributes that are mass assignable. * fillable
* * @var string[]
* @var list<string>
*/ */
protected $fillable = [ protected $fillable = [
'lastname', 'lastname',
@ -46,18 +41,17 @@ class AppUser extends Model
/** /**
* The attributes that should be hidden for serialization. * The attributes that should be hidden for serialization.
* *
* @var list<string> * @var array<int, string>
*/ */
protected $hidden = [ protected $hidden = [
'password', 'password',
]; ];
/** /**
* The attributes that should be cast. * cast enum
* * @var string[]
* @var array<string, string>
*/ */
protected $casts = [ protected $enumCasts = [
'role' => AppUserRolesEnum::class 'role' => AppUserRolesEnum::class
]; ];
@ -68,14 +62,15 @@ class AppUser extends Model
* *
* @return void * @return void
*/ */
protected static function booted(): void protected static function booted()
{ {
static::creating(function (self $model) { static::creating(function (self $model) {
self::assertFieldIsUnique('email', $model->email);
self::setOrder($model); self::setOrder($model);
self::updatePassword($model); static::updatePassword($model);
}); });
static::updating(function (self $model) { static::updating(function (self $model) {
self::updatePassword($model); static::updatePassword($model);
}); });
} }
@ -95,16 +90,28 @@ class AppUser extends Model
} }
/** /**
* Set order after the last element of the list. * Asserts using validation that the field is unique.
* *
* @param self $model * @param string $nameField
* @param string $valueField
* @param integer|null $id
* @return void * @return void
*/ */
private static function setOrder(self $model): void private static function assertFieldIsUnique(string $nameField, string $valueField, ?int $id = null)
{ {
$model->order = \intval( $table = (new self())->getTable();
self::query()->where(self::whereTenantQuery())->max('order') ToolboxHelper::assertFieldIsUnique($table, $nameField, $valueField, $id);
) + 1; }
/**
* Set order after the last element of the list.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
private static function setOrder(Model $model): void
{
$model->order = \intval(self::query()->max('order')) + 1;
} }
// * RELATIONSHIPS // * RELATIONSHIPS
@ -114,7 +121,7 @@ class AppUser extends Model
* *
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany * @return \Illuminate\Database\Eloquent\Relations\MorphToMany
*/ */
public function directories(): \Illuminate\Database\Eloquent\Relations\MorphToMany public function directories()
{ {
return $this->morphToMany(Directory::class, 'directorable'); return $this->morphToMany(Directory::class, 'directorable');
} }

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