Compare commits

..

No commits in common. "master" and "apiV4" have entirely different histories.

766 changed files with 106378 additions and 18691 deletions

View file

@ -14,9 +14,18 @@ DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=
DB_CONNECTION_OLD=mysql
DB_HOST_OLD=127.0.0.1
DB_PORT_OLD=3306
DB_DATABASE_OLD=ldccrm_12_2020
DB_USERNAME_OLD=root
DB_PASSWORD_OLD=
MIGRATIONPAGINATION=20000
BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=database
CACHE_DRIVER=apc
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
@ -25,27 +34,26 @@ REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=dev-mailpit
MAIL_PORT=1025
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"
MAIL_DIGITAL_SERVICE=""
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
# Sentry
SENTRY_LARAVEL_DSN=
# UWIL ADMIN SECURE
# - LIST SEPARATED WITH ',' (Required)
UWIL_ADMIN_SECURE_CIDR_LIST=""
# - LIST SEPARATED WITH ',' (Optional)
UWIL_ADMIN_SECURE_CIDR_PROJECT_ALLOWED_LIST=""
# SAML
SAML2_DEFAULT_TENANT_UUID=
SAML2_SP_CERT_x509=
SAML2_SP_CERT_PRIVATEKEY=
SAML2_CONTACT_TECHNICAL_EMAIL=
SAML2_CONTACT_SUPPORT_EMAIL=

View file

@ -14,6 +14,15 @@ DB_DATABASE=testing_ldccrm_12_2020
DB_USERNAME=root
DB_PASSWORD=DBPASSWORD
DB_CONNECTION_OLD=mysql
DB_HOST_OLD=127.0.0.1
DB_PORT_OLD=3306
DB_DATABASE_OLD="ldc-crm"
DB_USERNAME_OLD=root
DB_PASSWORD_OLD=
MIGRATIONPAGINATION=20000
BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=database

5
.gitignore vendored
View file

@ -9,7 +9,6 @@
/.phpunit.cache
/.vscode
/node_modules
/bootstrap
/public/build
/public/cache
/public/hot
@ -25,7 +24,3 @@ npm-debug.log
yarn-error.log
.npmrc
.phpdoc
.zshrc
.npm/
.composer/
tests/coverage

View file

@ -7,7 +7,7 @@ include:
file: "security/security.yml"
- project: "ldc-web/shared-ci"
ref: "main"
file: "build/docker-multistage.yml"
file: "build/build.yml"
- project: "ldc-web/shared-ci"
ref: "main"
file: "test/test.yml"

View file

@ -1,3 +0,0 @@
#!/bin/sh
npx --no -- commitlint --config node_modules/@uwil/husky-config/commitlint.config.cjs --edit $1

View file

@ -1,5 +1,6 @@
{
"phpcs.executablePath": "vendor/bin/phpcs",
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#8dc952",
"activityBar.activeBorder": "#3d7fc2",
@ -21,5 +22,5 @@
"commandCenter.border": "#15202b99"
},
"peacock.remoteColor": "#73b137",
"intelephense.environment.phpVersion": "8.5"
"intelephense.environment.phpVersion": "8.2"
}

View file

@ -1,50 +1,7 @@
ARG PHP_8_5_TAG=latest
FROM composer:2.8 AS vendor
WORKDIR /app
ARG GITLAB_USER
ARG GITLAB_PASSWORD
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/
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 config set @uwil:registry https://lab.s-web.fr/api/v4/groups/ldc-web/-/packages/npm/
RUN npm config set -- '//lab.s-web.fr/api/v4/groups/ldc-web/-/packages/npm/:_authToken' "${GITLAB_PASSWORD}"
RUN npm ci --no-audit --no-fund --loglevel=error && npm run prod
ARG PHP_8_5_TAG
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 --from=vendor --chown=${USER}:${USER} /app/vendor/ /app/srcs/vendor/
COPY --from=frontend --chown=${USER}:${USER} /app/public/build/ /app/srcs/public/build/
FROM registry.s-web.fr/ldc-web/projets/ldc-web-images-docker/ldcweb-production-8.2:latest
COPY --chown=www-data:www-data . /app/srcs
COPY --chown=www-data:www-data ./public /app/srcs/public
COPY --chown=www-data:www-data ./vendor /app/srcs/vendor
USER root
RUN chmod -R 755 /app/srcs/storage
USER ${USER}
USER www-data

View file

@ -0,0 +1,69 @@
<?php
use phpseclib3\Crypt\Rijndael;
class CPlatosChiffrement
{
private static $cipher = MCRYPT_RIJNDAEL_128; // Algorithme utilisé pour le cryptage des blocs
//private static $key = "yY75y2kLQ"; // Clé de cryptage PROD
private static $key = "123456789"; // Clé de cryptage DEV
private static $mode = 'cbc'; // Mode opératoire (traitement des blocs)
public static function crypt($data)
{
$keyHash = md5(self::$key);
$data = mcrypt_encrypt(self::$cipher, self::getKey($keyHash), $data, self::$mode, self::getIv($keyHash));
return base64_encode($data);
}
public static function decrypt($data)
{
$keyHash = md5(self::$key);
$data = base64_decode($data);
$data = mcrypt_decrypt(self::$cipher, self::getKey($keyHash), $data, self::$mode, self::getIv($keyHash));
return rtrim($data);
}
public static function getIv($keyHash)
{
$iv = substr($keyHash, 0, mcrypt_get_block_size(self::$cipher, self::$mode));
return $iv;
}
public static function getKey($keyHash)
{
$key = substr($keyHash, 0, mcrypt_get_key_size(self::$cipher, self::$mode));
return $key;
}
private function encryptRJ256($key, $iv, $text)
{
$cipher = new Rijndael('cbc');
$cipher->setBlockLength(128);
// keys are null-padded to the closest valid size
// longer than the longest key and it's truncated
$cipher->setKeyLength(128);
$cipher->setKey($key);
// the IV defaults to all-NULLs if not explicitly defined
$cipher->setIV($iv);
$cipher->disablePadding();
$length = strlen($text);
$pad = 32 - ($length % 32);
$text = str_pad($text, $length + $pad, chr(0));
return base64_encode($cipher->encrypt($text));
}
private function decryptRJ256($key, $iv, $text)
{
$cipher = new Rijndael('cbc'); // could use CRYPT_RIJNDAEL_MODE_CBC
$cipher->setBlockLength(128);
// keys are null-padded to the closest valid size
// longer than the longest key and it's truncated
$cipher->setKeyLength(128);
$cipher->setKey($key);
// the IV defaults to all-NULLs if not explicitly defined
$cipher->setIV($iv);
$cipher->disablePadding();
return $cipher->decrypt(base64_decode($text));
}
}

96
MIG/Db.php Normal file
View file

@ -0,0 +1,96 @@
<?php
class Database
{
static $instance;
private static $host;
private static $database;
private static $user;
private static $password;
public function __construct($host, $database, $user, $password)
{
self::$host = $host;
self::$database = $database;
self::$user = $user;
self::$password = $password;
}
public static function Instance()
{
if (!self::$instance) {
try {
self::$instance = new \PDO('mysql:host=' . self::$host . ';dbname=' . self::$database . ';charset=UTF8', self::$user, self::$password, array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION));
} catch (\Exception $e) {
die($e->getMessage());
}
}
return self::$instance;
}
public function request($statement, $params = false)
{
try {
$db = self::Instance();
$query = $db->prepare($statement);
if ($params) {
$query->execute($params);
} else {
$query->execute();
}
} catch (\PDOException $e) {
die($e->getMessage());
}
$query->setFetchMode(\PDO::FETCH_ASSOC);
$result = $query->fetch();
$query->closeCursor();
return ($result);
}
public function requestAll($statement, $params = false)
{
try {
$db = self::Instance();
$query = $db->prepare($statement);
if ($params) {
$query->execute($params);
} else {
$query->execute();
}
} catch (\PDOException $e) {
die($e->getMessage());
}
$query->setFetchMode(\PDO::FETCH_ASSOC);
$result = $query->fetchAll();
$query->closeCursor();
return ($result);
}
public function requestPush($statement, $params = false)
{
try {
$db = self::Instance();
$query = $db->prepare($statement);
if ($params)
$query->execute($params);
else
$query->execute();
return true;
} catch (\PDOException $e) {
die($e->getMessage());
}
}
public function requestPushInsert($statement, $params)
{
try {
$db = self::Instance();
$query = $db->prepare($statement);
$query->execute($params);
return $db->lastInsertId();
} catch (\PDOException $e) {
file_put_contents("errors_sql.txt", implode(',', $params) . PHP_EOL, FILE_APPEND);
}
}
}

91
MIG/export.php Normal file
View file

@ -0,0 +1,91 @@
<?php
ini_set("memory_limit", "-1");
set_time_limit(0);
error_reporting(E_ALL);
require_once 'Db.php';
require_once 'CPlatosChiffrement.class.php';
// FIXME: utiliser crm V2
require_once 'LDCCRM_V1.php';
$db = new Database('localhost', 'ldc-crm', 'ldc-crmadm', 'wai6dahB');
//$db = new Database('192.168.30.3', 'ldc-crm', 'ldc-crmadm', 'wai6dahB');
$apiCrm = new \LDCCRM\ApiV1('http://crm.test', 'giu6OCyG6FuJvM56zFntDcrtDI23GU7WNnkvOk9rPPTOzKArI8IV9nqgEAnsA8wEQUDnKheQ9FDfKUwG');
$siteId = 17;
$siteName = "REGHALAL";
$accounts = $db->requestAll("SELECT *
FROM useraccount
INNER JOIN siteuser su ON useraccount.user_id = su.siteuser_useraccountId
WHERE su.siteuser_siteId = :siteid
AND user_status = 1
AND user_lastUpdateDate >= '2018-03-01'
AND (user_emailCheckStatus <> 0 OR user_emailCheckStatus IS NULL)
AND (user_lastCnxDateHeure IS NOT NULL AND user_lastCnxDateHeure <> '0000-00-00 00:00:00')", ['siteid' => $siteId]);
foreach ($accounts as $account) {
$isSingleOptin = false;
$oldCRMid = $account['user_id'];
$civility = $account['user_civilite'];
$firstname = $account['user_prenom'];
$lastname = $account['user_nom'];
$birthdate = $account['user_dateNaissance'];
$address1 = !empty($account['user_adresse1']) ? $account['user_adresse1'] : null;
$address2 = !empty($account['user_adresse2']) ? $account['user_adresse2'] : null;
$postcode = (!empty($account['user_codePostal']) && $account['user_codePostal'] !== 0 ? $account['user_codePostal'] : null);
$city = !empty($account['user_ville']) ? $account['user_ville'] : null;
$optin = $account['user_inscritNewsletter'];
$haschcode = $account['user_hashcode'];
$status = $account['user_status'];
$created_at = $account['user_creationDate'];
$updated_at = $account['user_lastUpdateDate'];
$email = $account['user_email'];
$password = CPlatosChiffrement::decrypt($account['user_pwd']);
$last_cnx = $account['user_lastCnxDateHeure'];
if (empty($firstname) && empty($lastname))
$isSingleOptin = true;
// Récupération des champs complémentaires
$fields = [['fieldkey' => 'created_at', 'fieldvalue' => $created_at], ['fieldkey' => 'updated_at', 'fieldvalue' => $updated_at], ['fieldkey' => 'last_cnx', 'fieldvalue' => $last_cnx]];
if (!$isSingleOptin) {
$crmUser = $apiCrm->createCustomer($email, $password, $firstname, $lastname, $optin, $birthdate, null, $address1, $address2, $city, $postcode, "France");
if ($crmUser->status === 'OK') {
$newCRMId = $crmUser->user->crm_id;
$activate = $apiCrm->activateCustomer($newCRMId);
if ($activate->status === 'NOK') {
file_put_contents("Export_errors.txt", "OLD CRM ID : $oldCRMid - NEW CRM ID : $newCRMId - ACTIVATE API ERROR : " . $activate->code . " - " . $activate->message . PHP_EOL, FILE_APPEND);
}
} else {
file_put_contents("Export_errors.txt", "OLD CRM ID : $oldCRMid - API ERROR : " . $crmUser->code . " - " . $crmUser->message . PHP_EOL, FILE_APPEND);
continue;
}
} else {
$result = $apiCrm->createSingleOptin($email);
if ($result->status === 'OK') {
$newCRMId = "SINGLEOPTIN";
} else {
file_put_contents("Export_errors.txt", "OLD CRM ID : $oldCRMid - API ERROR : " . $crmUser->code . " - " . $crmUser->message . PHP_EOL, FILE_APPEND);
continue;
}
}
write("$oldCRMid;$newCRMId;$email;" . json_encode($fields) . PHP_EOL, $siteName);
}
function write($text, $siteName)
{
if (!file_exists("Export_$siteName.csv"))
file_put_contents("Export_$siteName.csv", "\xEF\xBB\xBF", FILE_APPEND); // BOM UTF-8
file_put_contents("Export_$siteName.csv", $text, FILE_APPEND);
}

9
MIG/make_tmp_table.sql Normal file
View file

@ -0,0 +1,9 @@
CREATE TABLE `legaulois_migration` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`user_id` BIGINT(20) NOT NULL DEFAULT '0',
`pwd` VARCHAR(255) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
)
COLLATE='utf8_unicode_ci'
ENGINE=InnoDB
AUTO_INCREMENT=0;

80
MIG/migrate.php Normal file
View file

@ -0,0 +1,80 @@
<?php
ini_set("memory_limit", "-1");
set_time_limit(0);
error_reporting(E_ALL);
require_once 'Db.php';
$db = new Database('192.168.31.102', 'ldccrm_12_2020', 'ldccrmadm', 'wai6dahB'); // INFOS NOUVELLE CRM
$newBrandId = 2; // BRAND ID de la migration
/**
* @return Generator
*/
$fileData = function() {
$file = fopen(__DIR__ . '/Export_REGHALAL.csv', 'r');
if (!$file)
die('file does not exist or cannot be opened');
while (($line = fgets($file)) !== false) {
yield $line;
}
fclose($file);
};
foreach ($fileData() as $line) {
$isSingleOptin = false;
$datas = explode(";", $line);
$oldCrmId = $datas[0];
$newCrmId = $datas[1];
$email = $datas[2];
$otherFields = json_decode($datas[3]);
if ($newCrmId === 'SINGLEOPTIN')
$isSingleOptin = true;
foreach ($otherFields as $otherField){
// created_at - updated_at - last_cnx ne bougent pas car génériques et db sur nouvelle CRM sinon DB sur nouveau site.
switch($otherField->fieldkey){
case 'created_at':
if (!$isSingleOptin) {
$query = "UPDATE customers SET created_at = :nvalue WHERE id = :newid";
$db->requestPush($query, ['nvalue' => $otherField->fieldvalue, 'newid' => $newCrmId]);
$query = "UPDATE customer_infos SET created_at = :nvalue WHERE customer_id = :newid AND brand_id = :brandid";
$db->requestPush($query, ['nvalue' => $otherField->fieldvalue, 'newid' => $newCrmId, 'brandid' => $newBrandId]);
$query = "UPDATE customer_login SET created_at = :nvalue WHERE customer_id = :newid AND brand_id = :brandid";
$db->requestPush($query, ['nvalue' => $otherField->fieldvalue, 'newid' => $newCrmId, 'brandid' => $newBrandId]);
$query = "UPDATE customer_opt_in_history SET created_at = :nvalue WHERE customer_id = :newid AND brand_id = :brandid";
$db->requestPush($query, ['nvalue' => $otherField->fieldvalue, 'newid' => $newCrmId, 'brandid' => $newBrandId]);
}else{
$query = "UPDATE single_optin SET created_at = :nvalue, updated_at = :nvalue WHERE email = :email AND brand_id = :brandid";
$db->requestPush($query, ['nvalue' => $otherField->fieldvalue, 'email' => $email, 'brandid' => $newBrandId]);
}
break;
case 'updated_at':
if (!$isSingleOptin) {
$query = "UPDATE customers SET updated_at = :nvalue WHERE id = :newid";
$db->requestPush($query, ['nvalue' => $otherField->fieldvalue, 'newid' => $newCrmId]);
$query = "UPDATE customer_infos SET updated_at = :nvalue WHERE customer_id = :newid AND brand_id = :brandid";
$db->requestPush($query, ['nvalue' => $otherField->fieldvalue, 'newid' => $newCrmId, 'brandid' => $newBrandId]);
$query = "UPDATE customer_login SET updated_at = :nvalue WHERE customer_id = :newid AND brand_id = :brandid";
$db->requestPush($query, ['nvalue' => $otherField->fieldvalue, 'newid' => $newCrmId, 'brandid' => $newBrandId]);
$query = "UPDATE customer_opt_in_history SET updated_at = :nvalue WHERE customer_id = :newid AND brand_id = :brandid";
$db->requestPush($query, ['nvalue' => $otherField->fieldvalue, 'newid' => $newCrmId, 'brandid' => $newBrandId]);
}
break;
case 'last_cnx':
if (!$isSingleOptin) {
$query = "UPDATE customer_login SET last_connection = :nvalue WHERE customer_id = :newid AND brand_id = :brandid";
$db->requestPush($query, ['nvalue' => $otherField->fieldvalue, 'newid' => $newCrmId, 'brandid' => $newBrandId]);
}
break;
}
//var_dump($otherField);
}
}

89
MIG/pwd.php Normal file
View file

@ -0,0 +1,89 @@
<?php
ini_set("memory_limit", "-1");
set_time_limit(0);
error_reporting(E_ALL);
require_once 'Db.php';
require_once 'CPlatosChiffrement.class.php';
$db = new Database('localhost', 'ldc-crm', 'root', '');
//$db = new Database('192.168.34.2', 'ldc-crm', 'ldc-crmadm', 'wai6dahB');
if (!isset($argv[1]) || !isset($argv[2]))
die('params missing');
$name = $argv[1];
$siteId = $argv[2];
$table = $name."_migration";
$maxdate = date("Y-m-d", strtotime("-36 month", time()));
$db->requestPush("CREATE TABLE IF NOT EXISTS $table (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`user_id` BIGINT(20) NOT NULL DEFAULT '0',
`firstname` VARCHAR(255) NOT NULL DEFAULT '0',
`lastname` VARCHAR(255) NOT NULL DEFAULT '0',
`birthdate` VARCHAR(255) NULL DEFAULT '0',
`address` VARCHAR(255) NULL DEFAULT '0',
`address2` VARCHAR(255) NULL DEFAULT '0',
`city` VARCHAR(255) NULL DEFAULT '0',
`postcode` VARCHAR(255) NULL DEFAULT '0',
`optin` INT(11) NOT NULL DEFAULT '0',
`email` VARCHAR(255) NOT NULL DEFAULT '0',
`pwd` VARCHAR(255) NOT NULL DEFAULT '0',
`created_at` TIMESTAMP NOT NULL,
`updated_at` TIMESTAMP NOT NULL,
`last_cnx` TIMESTAMP NOT NULL,
PRIMARY KEY (`id`)
) COLLATE='utf8_unicode_ci' ENGINE=InnoDB AUTO_INCREMENT=0;");
$getAccounts = function ($skip) use ($db, $siteId, $maxdate){
return $db->requestAll("SELECT *
FROM useraccount
INNER JOIN siteuser su ON useraccount.user_id = su.siteuser_useraccountId
WHERE su.siteuser_siteId = :siteid
AND user_id >= 701697
AND user_status = 1
AND user_lastUpdateDate >= :maxdate
AND (user_emailCheckStatus <> 0 OR user_emailCheckStatus IS NULL)
AND (user_lastCnxDateHeure IS NOT NULL AND user_lastCnxDateHeure <> '0000-00-00 00:00:00')
AND user_lastCnxDateHeure >= :maxdate
AND user_email IS NOT NULL LIMIT 4600 OFFSET ". ($skip * 4600), ['siteid' => $siteId, 'maxdate' => $maxdate]);
};
$k = 0;
while (count($accounts = $getAccounts($k++))) {
$sql = [];
$toto = "";
foreach ($accounts as $account){
$crm_id = $account['user_id'];
$civility = $account['user_civilite'];
$firstname = ucfirst($account['user_prenom']);
$lastname = ucfirst($account['user_nom']);
$birthdate = $account['user_dateNaissance'];
$address1 = !empty($account['user_adresse1']) ? $account['user_adresse1'] : null;
$address2 = !empty($account['user_adresse2']) ? $account['user_adresse2'] : null;
$postcode = (!empty($account['user_codePostal']) && $account['user_codePostal'] !== 0 ? $account['user_codePostal'] : null);
$city = !empty($account['user_ville']) ? $account['user_ville'] : null;
$optin = $account['user_inscritNewsletter'];
$haschcode = $account['user_hashcode'];
$status = $account['user_status'];
$created_at = $account['user_creationDate'];
$updated_at = $account['user_lastUpdateDate'];
$email = strtolower($account['user_email']);
//$password = CPlatosChiffrement::decrypt($account['user_pwd']);
$password = "toto";
$last_cnx = $account['user_lastCnxDateHeure'];
$datas = [
$crm_id,
$firstname,
$lastname,
$birthdate,
$address1,
$address2,
$city,
$postcode,
$optin,
$email,
$password,
$created_at,
$updated_at,
$last_cnx
];
$toto = "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
$db->requestPushInsert("INSERT INTO $table (user_id, firstname, lastname, birthdate, address, address2, city, postcode, optin, email, pwd, created_at, updated_at, last_cnx) VALUES $toto", $datas);
}
}

View file

@ -1,10 +1,52 @@
# CRM
# Comment lancer les tests unitaires ?
## Documentation
https://cdlfr.sharepoint.com/:b:/r/sites/LDCWEB/Documents%20partages/Documentations/Projets/CRM/FICHE%20FONCTIONNELLE%20OUTIL%20R%C3%89CLAMATIONS%20CONSO.pdf?csf=1&web=1&e=qktL4V
### Attention, si vous utilisez juste un .env à la place d'un .env.testing votre base de donnée de developpement sera effacée à chaque lancement des tests unitaires
## Tests unitaire
Etapes :
- cp .env.testing.example .env.testing (spécial tests unitaires)
- php artisan key:generate
- parameter les BDD dans le .env.testing
- php artisan config:cache
- git submodule init
- git submodule update (récupérer le client)
- composer install
- service start mysql (votre base de donnée accessible sur 127.0.0.1:3306)
- chmod u+x ./test.sh
- php artisan optimize
- composer run test
### Comment lancer les tests unitaires ?
Config Supervisor 2 Fichiers :
[Suivre le wiki dédié au tests unitaires](https://lab.s-web.fr/ldc-web/projets/crm/-/wikis/Tests-unitaires)
`ldccrm-worker.conf`
**Pour les jobs en général**
[program:ldccrm-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /u1/DATA01/webldc/Apps/ldccrm/ldccrm-12-2020/srcs/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
startasgroup=true
killasgroup=true
user=www-data
umask=077
numprocs=8
redirect_stderr=true
stdout_logfile=/u1/DATA01/webldc/Apps/ldccrm/ldccrm-12-2020/srcs/storage/logs/worker.log
stopwaitsecs=3600
`ldccrm-stats-worker.conf`
**Pour les jobs de stats uniquement**
[program:ldccrm-stats-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /u1/DATA01/webldc/Apps/ldccrm/ldccrm-12-2020/srcs/artisan queue:work --queue=stats --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
startasgroup=true
killasgroup=true
user=www-data
umask=077
numprocs=1
redirect_stderr=true
stdout_logfile=/u1/DATA01/webldc/Apps/ldccrm/ldccrm-12-2020/srcs/storage/logs/worker.log
stopwaitsecs=3600

View file

@ -0,0 +1,79 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
/**
* Command Name: LDC Web Status Checker
* Description: Envoi des infos Serveur/Laravel vers LDC Web
* Version: 1.1
* Author: Vincent BRIDIER
* Author URI: vincent.bridier@ldc.fr
*
* @codeCoverageIgnore
*
*/
class LdcWebStatusChecker extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'ldcweb:statuschecker';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Envoi des infos Serveur/Laravel vers LDC Web';
/**
* Execute the console command.
*
* @return mixed
*/
public function handle(): mixed
{
$data = [];
$data['uuid'] = config('ldcweb.project_uuid');
// Sitemap.xml ?
if (file_exists(public_path() . '/sitemap.xml')) {
$data['sitemap'] = 'true';
} else {
$data['sitemap'] = 'false';
}
// Robots.txt ?
if (file_exists(public_path() . '/robots.txt')) {
$data['robots'] = 'true';
} else {
$data['robots'] = 'false';
}
// Check logs.
$data['logs'] = 0;
$patherror = storage_path('/logs');
$files = glob($patherror . '/*.log');
if (count($files) > 0) {
foreach ($files as $file) {
$data['logs'] = $data['logs'] + filesize($file);
}
}
$data['code']['version'] = app()->version();
// Send by cURL.
$url = 'https://ldc-web.ldc.fr/api/status-checker';
$ch = curl_init($url);
$payload = ['token' => 'CE8S52Qq7stf5k8frF4T', 'datas' => json_encode($data)];
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_exec($ch);
curl_close($ch);
return 0;
}
}

View file

@ -0,0 +1,72 @@
<?php
namespace App\Console\Commands;
use App\Lib\MigrateOldData;
use App\Models\Brand;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
/**
* @codeCoverageIgnore
*/
class MigrateCrmUsers extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'migrate:crm';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Migrate crm users';
/**
* Execute the console command.
*
* @return integer
*/
public function handle(): int
{
try {
$table = $this->ask('What is the tmp table name with users data ?');
try {
DB::connection(config('database.oldDB'))->table($table)->exists();
} catch (\Exception $e) {
$this->error(sprintf('%s table not found on old crm database', $table));
return -1;
}
$brandname = $this->ask('What is the brand name ?');
$brand = Brand::query()->where('name', 'LIKE', "%$brandname%")->first();
if (!$brand) {
if (!$this->confirm(sprintf('Would you like to use %s ? ', $brandname))) {
$this->info('Command cancelled');
return 0;
}
if ($this->confirm('Would you like to create a new one ?')) {
$brand = new Brand();
$brand->name = $brandname;
$brand->save();
} else {
$this->info('Command cancelled');
return 0;
}
}
$mgOldData = new MigrateOldData();
$mgOldData->runMigration($brand, $table);
} catch (\Exception $e) {
$this->error(sprintf('Failed %s', $e));
return -1;
} //end try
return 0;
}
}

View file

@ -8,7 +8,6 @@ use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Str;
class UserCreate extends Command
{
@ -29,61 +28,74 @@ class UserCreate extends Command
/**
* The first name of the new user.
*
* @var string|null
* @var string
*/
protected $firstName;
protected $first_name;
/**
* The last name of the new user.
*
* @var string|null
* @var string
*/
protected $lastName;
protected $last_name;
/**
* The email of the new user.
*
* @var string|null
* @var string
*/
protected $email;
/**
* The password of the new user.
*
* @var string
*/
protected $password;
/**
* The role of the new user.
*
* @var string|null
* @var string
*/
protected $role;
/**
* Execute the console command.
*
* @return void
* @return integer
*/
public function handle(): void
public function handle(): int
{
$this->firstName = null;
$this->first_name = null;
$this->addFirstName();
$this->lastName = null;
$this->last_name = null;
$this->addLastName();
$this->email = null;
$this->addEmail();
$this->password = null;
$this->addPassword();
$this->role = null;
$this->addRole();
$user = new User([
'first_name' => $this->firstName,
'last_name' => $this->lastName,
'email' => $this->email,
'role' => $this->role,
'published' => true,
'first_name' => $this->first_name,
'last_name' => $this->last_name,
'email' => $this->email,
'password' => $this->password,
'role' => $this->role,
'published' => true,
'published_at' => Carbon::now(),
]);
$user->saveOrFail();
$this->info('User created 👌');
return 0;
}
/**
@ -93,22 +105,22 @@ class UserCreate extends Command
*/
private function addFirstName(): void
{
while (is_null($this->firstName)) {
while (is_null($this->first_name)) {
try {
$tmp = $this->ask('Type the wanted user first name', 'Admin');
$validator = Validator::make(['first_name' => $tmp], [
'first_name' => 'required|min:3|max:255'
]);
$validator->validate();
$this->firstName = $tmp;
$this->first_name = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid first name %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
}
}
} //end try
}; //end while
}
/**
@ -118,22 +130,22 @@ class UserCreate extends Command
*/
private function addLastName(): void
{
while (is_null($this->lastName)) {
while (is_null($this->last_name)) {
try {
$tmp = $this->ask('Type the wanted user last name', 'Saïtama');
$validator = Validator::make(['last_name' => $tmp], [
'last_name' => 'required|min:3|max:255'
]);
$validator->validate();
$this->lastName = $tmp;
$this->last_name = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid last_name %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
}
}
} //end try
}; //end while
}
/**
@ -157,8 +169,24 @@ class UserCreate extends Command
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
}
}
} //end try
}; //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
}
/**
@ -170,23 +198,21 @@ class UserCreate extends Command
{
while (is_null($this->role)) {
try {
$tmp = $this->choice(
$tmp = $this->choice(
'Select his role',
[
Str::ucFirst(RoleEnum::conceptor->label()),
Str::ucFirst(RoleEnum::admin->label()),
Str::ucFirst(RoleEnum::marketing->label())
],
Str::ucFirst(RoleEnum::marketing->label()),
[RoleEnum::conceptor->name, RoleEnum::admin->name, RoleEnum::visitor->name],
RoleEnum::visitor->name,
$maxAttempts = null,
$allowMultipleSelections = false
);
if ($tmp === Str::ucFirst(RoleEnum::conceptor->label())) {
if ($tmp === RoleEnum::conceptor->name) {
$tmp = RoleEnum::conceptor;
}
if ($tmp === Str::ucFirst(RoleEnum::admin->label())) {
if ($tmp === RoleEnum::admin->name) {
$tmp = RoleEnum::admin;
}
if ($tmp === Str::ucFirst(RoleEnum::marketing->label())) {
$tmp = RoleEnum::marketing;
if ($tmp === RoleEnum::visitor->name) {
$tmp = RoleEnum::visitor;
}
$validator = Validator::make(['role' => $tmp], [
'role' => 'required'
@ -195,11 +221,11 @@ class UserCreate extends Command
$this->role = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please choose from the selection %s',
'Please choose from the selection',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
}
}
} //end try
}; //end while
}
}

View file

@ -3,7 +3,6 @@
namespace App\Console;
use App\Console\Commands\DeleteUnconfirmedCustomers;
use App\Jobs\SendTransferredToDigitalCountMail;
use App\Jobs\StartStatJobs;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@ -27,7 +26,6 @@ class Kernel extends ConsoleKernel
{
// @codeCoverageIgnoreStart
$schedule->job(StartStatJobs::class)->dailyAt('03:30');
$schedule->job(SendTransferredToDigitalCountMail::class)->weekdays()->at('07:00');
$schedule->command(DeleteUnconfirmedCustomers::class)->dailyAt('05:30');
// @codeCoverageIgnoreEnd
}

View file

@ -2,7 +2,7 @@
namespace App\Enums\ActivityLogs;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
use App\Traits\Enums\BaseEnum;
enum ActivityLogsEventEnum: int
{

View file

@ -2,7 +2,7 @@
namespace App\Enums;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
use App\Traits\Enums\BaseEnum;
enum ApiError: int
{

View file

@ -2,7 +2,7 @@
namespace App\Enums;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
use App\Traits\Enums\BaseEnum;
enum ApiStatus: int
{

View file

@ -2,7 +2,7 @@
namespace App\Enums;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
use App\Traits\Enums\BaseEnum;
enum ArcadiaMailType: int
{

View file

@ -2,7 +2,7 @@
namespace App\Enums;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
use App\Traits\Enums\BaseEnum;
enum ArcadiaOrderType: int
{

View file

@ -1,21 +0,0 @@
<?php
namespace App\Enums\Claims;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
enum ClaimMessageType: int
{
use BaseEnum;
case system = 1;
case human = 2;
/**
* Optionnal labels definition
*/
private const LABELS = [
self::system->name => 'Système',
self::human->name => 'Humain',
];
}

View file

@ -1,51 +0,0 @@
<?php
namespace App\Enums\Claims;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
enum ClaimResponseType: int
{
use BaseEnum;
case none = 1;
case manual = 2;
case mail = 3;
case email = 4;
case phone = 5;
case other = 99;
/**
* Optionnal labels definition
*/
private const LABELS = [
self::none->name => 'Non défini',
self::manual->name => 'Manuelle',
self::mail->name => 'Courrier',
self::email->name => 'E-mail',
self::phone->name => 'Téléphone',
self::other->name => 'Autre',
];
/**
* Optionnal labels definition.
*/
private const COLORS = [
self::none->name => '#b8b8b8',
self::manual->name => '#a7a7a7',
self::mail->name => '#757575',
self::email->name => '#5e5e5e',
self::phone->name => '#414141',
self::other->name => '#2b2b2b',
];
/**
* Get Class.
*
* @return string
*/
public function color(): string
{
return self::COLORS[$this->name];
}
}

View file

@ -1,48 +0,0 @@
<?php
namespace App\Enums\Claims;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
enum ClaimStatus: int
{
use BaseEnum;
case untreated = 1;
case sentDigital = 2;
case sentMarketing = 3;
case treated = 4;
case refused = 5;
/**
* Optionnal labels definition
*/
private const LABELS = [
self::untreated->name => 'Non traitée',
self::sentDigital->name => 'Transférée au service Digital',
self::sentMarketing->name => 'Retournée au Marketing',
self::treated->name => 'Traitée',
self::refused->name => 'Refusé de répondre',
];
/**
* Optionnal labels definition.
*/
private const COLORS = [
self::untreated->name => '#5a5a5a',
self::sentDigital->name => '#0033c0',
self::sentMarketing->name => '#69077c',
self::treated->name => '#188618',
self::refused->name => '#dc143c',
];
/**
* Get Class.
*
* @return string
*/
public function color(): string
{
return self::COLORS[$this->name];
}
}

View file

@ -1,42 +0,0 @@
<?php
namespace App\Enums\Claims;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
enum ClaimType: int
{
use BaseEnum;
case classic = 1;
case br = 2;
case rgpd = 3;
/**
* Optionnal labels definition
*/
private const LABELS = [
self::classic->name => 'Classique',
self::br->name => 'BR',
self::rgpd->name => 'RGPD',
];
/**
* Optionnal labels definition.
*/
private const COLORS = [
self::classic->name => '#08818a',
self::br->name => '#9021f8',
self::rgpd->name => '#c2237a',
];
/**
* Get Class.
*
* @return string
*/
public function color(): string
{
return self::COLORS[$this->name];
}
}

View file

@ -2,7 +2,7 @@
namespace App\Enums;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
use App\Traits\Enums\BaseEnum;
enum Country: int
{
@ -520,9 +520,9 @@ enum Country: int
*/
public static function getValidCountriesLabel(): array
{
return \array_map(function (self $country) {
return collect(self::getValidCountries())->map(function (self $country) {
return $country->label();
}, self::getValidCountries());
})->all();
}
/**
@ -532,9 +532,9 @@ enum Country: int
*/
public static function getValidCountriesValues(): array
{
return \array_map(function (self $country) {
return collect(self::getValidCountries())->map(function (self $country) {
return $country->value();
}, self::getValidCountries());
})->all();
}
/**
@ -568,9 +568,7 @@ enum Country: int
Country::saintMartinFrenchPart,
Country::wallisAndFutuna,
Country::frenchPolynesia,
Country::newCaledonia,
Country::netherlands,
Country::germany,
Country::newCaledonia
];
}
@ -582,22 +580,20 @@ enum Country: int
protected static function isoCodes(): array
{
return [
'france' => 'FR',
'belgium' => 'BE',
'luxembourg' => 'LU',
'guadeloupe' => 'GP',
'martinique' => 'MQ',
'frenchGuiana' => 'GF',
'reunion' => 'RE',
'france' => 'FR',
'belgium' => 'BE',
'luxembourg' => 'LU',
'guadeloupe' => 'GP',
'martinique' => 'MQ',
'frenchGuiana' => 'GF',
'reunion' => 'RE',
'saintPierreAndMiquelon' => 'PM',
'mayotte' => 'YT',
'saintBarthelemy' => 'BL',
'saintMartinFrenchPart' => 'MF',
'wallisAndFutuna' => 'WF',
'frenchPolynesia' => 'PF',
'newCaledonia' => 'NC',
'netherlands' => 'NL',
'germany' => 'DE',
'mayotte' => 'YT',
'saintBarthelemy' => 'BL',
'saintMartinFrenchPart' => 'MF',
'wallisAndFutuna' => 'WF',
'frenchPolynesia' => 'PF',
'newCaledonia' => 'NC',
];
}

View file

@ -2,7 +2,7 @@
namespace App\Enums;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
use App\Traits\Enums\BaseEnum;
enum CustomerExportType: int
{
@ -16,9 +16,8 @@ enum CustomerExportType: int
* Optionnal labels definition
*/
private const LABELS = [
self::singleOptin->name => 'Les single-optins uniquement',
self::singleOptin->name => 'Les single optins uniquement',
self::customer->name => 'Les clients uniquement acceptant de recevoir les newsletters',
self::both->name => 'La totalité des single-optins ainsi que des clients acceptant
de recevoir les newsletters',
self::both->name => 'La totalité des client acceptant de recevoir les newsletters',
];
}

View file

@ -1,85 +0,0 @@
<?php
namespace App\Enums;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
enum ExportFormat: int
{
use BaseEnum;
case csv = 1;
case csvWithoutAttachment = 2;
case pdf = 3;
/**
* Optionnal labels definition
*/
private const LABELS = [
self::csv->name => 'csv',
self::csvWithoutAttachment->name => 'csv sans pièces jointes',
self::pdf->name => 'pdf',
];
/**
* Bootstrap color class to use for the export buttons.
*
* @return string
*/
public function class(): string
{
return match ($this) {
self::csv,
self::csvWithoutAttachment => 'success',
self::pdf => 'danger',
default => 'primary',
};
}
/**
* Font Awesome picto to use for the export buttons.
*
* @return string
*/
public function picto(): string
{
return match ($this) {
self::csv,
self::csvWithoutAttachment => 'fa-solid fa-file-csv',
self::pdf => 'fa-solid fa-file-pdf',
default => 'fa-solid fa-file',
};
}
/**
* File headers to use when the exported file is return with StreamedResponse.
*
* @return array
*/
public function fileHeaders(): array
{
return match ($this) {
self::csv,
self::csvWithoutAttachment => [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="export.csv"',
],
default => [],
};
}
/**
* Export file name.
*
* @return string
*/
public function fileName(): string
{
return match ($this) {
self::csv,
self::csvWithoutAttachment => 'export.csv',
self::pdf => 'export.pdf',
};
}
}

View file

@ -2,7 +2,7 @@
namespace App\Enums\Pagination;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
use App\Traits\Enums\BaseEnum;
enum ItemsPerPaginationEnum: int
{

View file

@ -2,7 +2,7 @@
namespace App\Enums;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
use App\Traits\Enums\BaseEnum;
enum QuestionType: int
{

View file

@ -2,7 +2,7 @@
namespace App\Enums\Theme;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
use App\Traits\Enums\BaseEnum;
enum BootstrapThemeEnum: int
{

View file

@ -2,7 +2,7 @@
namespace App\Enums\Users;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
use App\Traits\Enums\BaseEnum;
enum RoleEnum: int
{
@ -10,7 +10,7 @@ enum RoleEnum: int
case conceptor = 1;
case admin = 2;
case marketing = 3;
case visitor = 3;
/**
* Optionnal labels definition.
@ -18,6 +18,6 @@ enum RoleEnum: int
private const LABELS = [
self::conceptor->name => 'Concepteur',
self::admin->name => 'Administrateur',
self::marketing->name => 'Marketing',
self::visitor->name => 'Visiteur',
];
}

View file

@ -1,55 +0,0 @@
<?php
namespace App\Events;
use App\Models\Claim;
use App\Models\User;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ClaimModelChanged
{
use Dispatchable;
use InteractsWithSockets;
use SerializesModels;
/** @var \App\Models\Claim $claim Current claim modified. */
public $claim;
/** @var string $field Modified field. */
public $field;
/** @var string $label Label of the modified field. */
public $label;
/** @var \App\Models\User|null $user Current authenticated user. */
public $user;
/** @var string|null $content Custom content message. */
public $content;
/**
* Create a new event instance.
*
* @param \App\Models\Claim $claim
* @param string $field
* @param string $label
* @param \App\Models\User|null $user
* @param string|null $content
* @return void
*/
public function __construct(
Claim $claim,
string $field,
string $label,
User|null $user = null,
string|null $content = null,
) {
$this->claim = $claim;
$this->field = $field;
$this->label = $label;
$this->user = $user;
$this->content = $content;
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace App\Exceptions;
/**
* This class Exception is used to skip a row import from
* database migration (2016 -> 2020)
*
* @author Jérémy Munsch <jeremy.munsch@ldc.fr>
*/
class AbortRowImportException extends \RuntimeException
{
}

View file

@ -155,7 +155,7 @@ class Handler extends ExceptionHandler
// @codeCoverageIgnoreStart
if (config('app.env') === 'testing') {
// phpcs:ignore Squiz.PHP.DiscouragedFunctions.Found
print_r((string) $e);
print_r((string)$e);
}
return $this->getReponse($request, 500, \intval($e->getCode()), $e->getMessage());
// @codeCoverageIgnoreEnd
@ -180,13 +180,13 @@ class Handler extends ExceptionHandler
): JsonResponse {
if (Helper::isRequestForApiV2($request)) {
return (new V2ApiResponse(
ApiStatus::KO,
(object) array_merge($data, ['message' => $message, 'code' => $apiCode]),
(object)array_merge($data, ['message' => $message, 'code' => $apiCode]),
ApiStatus::KO
))->toResponse($request)->setStatusCode($httpCode);
}
return (new V4ApiResponse(
ApiStatus::KO,
(object) array_merge($data, ['message' => $message, 'code' => $apiCode]),
(object)array_merge($data, ['message' => $message, 'code' => $apiCode]),
ApiStatus::KO
))->toResponse($request)->setStatusCode($httpCode);
}
}

View file

@ -4,9 +4,10 @@ namespace App\Http\Controllers\Api\V2;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V2\StoreCustomerAnswers;
use App\Models\Answer;
use App\Models\Customer;
use App\Models\ModelAnswer;
use App\Models\Question;
use App\Models\Service;
use Illuminate\Http\JsonResponse;
class AnswerController extends Controller
{
@ -14,33 +15,21 @@ class AnswerController extends Controller
* Store the customer answers.
*
* @param \App\Http\Requests\Api\V2\StoreCustomerAnswers $request
* @param \App\Models\Service $service
* @param \App\Models\Customer $customer
* @return \Illuminate\Http\JsonResponse
*/
public function storeCustomerAnswers(
StoreCustomerAnswers $request,
Service $service,
Customer $customer
): \Illuminate\Http\JsonResponse {
): JsonResponse {
// * Make sure we are accessing a customer that belongs to our service.
$this->authorize('customerBelongsToService', $customer);
$question = Question::find($request->validated('question'));
// Column model_id is varchar because of singleoptin uuid, so force cast in string.
ModelAnswer::query()
->where('model_type', Customer::class)
->where('model_id', (string) $customer->getKey())
->whereIn('answer_id', $question->answers->modelKeys())
->delete();
foreach ($request->validated('answers') as $answerId) {
ModelAnswer::create([
'model_type' => Customer::class,
'model_id' => (string) $customer->getKey(),
'answer_id' => $answerId,
]);
}
$existingAnswerIds = $customer->getAnswers($service)->pluck('id');
$answersID = Answer::query()->whereIn('id', $request->answers)->pluck('id');
$customer->answers()->sync($answersID->merge($existingAnswerIds));
return response()->json();
}
}

View file

@ -34,7 +34,7 @@ class CustomerController extends Controller
*/
public function store(StoreCustomerRequest $request, Service $service): JsonResource
{
$data = (object) $request->validated();
$data = (object)$request->validated();
/** @var \App\Models\Customer */
$customer = Customer::query()->where('email', $data->email)->firstOrCreate([

View file

@ -11,6 +11,7 @@ use App\Http\Requests\Api\V2\SingleOptinRequest;
use App\Http\Requests\Api\V2\SingleOptinRemoveWithHashcodeRequest;
use App\Models\CustomerInfo;
use App\Models\Service;
use Illuminate\Http\JsonResponse;
use App\Models\SingleOptin;
class OptinController extends Controller
@ -25,31 +26,24 @@ class OptinController extends Controller
* if this email already has a full account.
* @throws \App\Exceptions\Api\V2\CrmException If the single optin could not be added or removed.
*/
public function optin(SingleOptinRequest $request, Service $service): \Illuminate\Http\JsonResponse
public function optin(SingleOptinRequest $request, Service $service): JsonResponse
{
$validatedData = $request->validated();
if (CustomerInfo::getFromEmail($service->brand, $validatedData['email'])->exists()) {
if (CustomerInfo::getFromEmail($service->brand, $request->email)->exists()) {
throw new ApiErrorException(ApiError::singleOptinCustomerExists);
}
/** @var \App\Models\SingleOptin */
$singleOptin = SingleOptin::query()
->where('email', $validatedData['email'])
$singleOptin = SingleOptin::query()->where('email', $request->email)
->whereHas('brand', function ($q) use ($service) {
$q->where('id', $service->brand->id);
})
->firstOrNew();
})->firstOrNew();
if (!$this->changeOptin($service, $request, \boolval($validatedData['opt_in']), $singleOptin)) {
if (!$this->changeOptin($service, $request, \boolval($request->opt_in), $singleOptin)) {
// @codeCoverageIgnoreStart
throw new CrmException('Api failed to update single optin.');
// @codeCoverageIgnoreEnd
}
if (isset($validatedData['answer_id'])) {
$singleOptin->answers()->attach($validatedData['answer_id']);
}
return response()->json();
}
@ -61,17 +55,13 @@ class OptinController extends Controller
* @return \Illuminate\Http\JsonResponse
* @throws \App\Exceptions\Api\V2\CrmException If the single optin could not be removed.
*/
public function optoutWithHashcode(
SingleOptinRemoveWithHashcodeRequest $request,
Service $service
): \Illuminate\Http\JsonResponse {
public function optoutWithHashcode(SingleOptinRemoveWithHashcodeRequest $request, Service $service): JsonResponse
{
/** @var \App\Models\SingleOptin */
$singleOptin = SingleOptin::query()
->where('hashcode', $request->hashcode)
$singleOptin = SingleOptin::query()->where('hashcode', $request->hashcode)
->whereHas('brand', function ($q) use ($service) {
$q->whereId($service->brand->id);
})
->firstOrFail();
})->firstOrFail();
if ($singleOptin and !$this->changeOptin($service, $request, \boolval($request->opt_in), $singleOptin)) {
// @codeCoverageIgnoreStart

View file

@ -8,9 +8,10 @@ use App\Http\Requests\Api\V2\GetQuestionStatsRequest;
use App\Http\Resources\Api\V2\BrandStatsResource;
use App\Http\Resources\Api\V2\QuestionStatsResource;
use App\Models\BrandStat;
use App\Models\ModelAnswer;
use App\Models\CustomerAnswer;
use App\Models\Question;
use App\Models\Service;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
@ -23,10 +24,8 @@ class StatsController extends Controller
* @param \App\Models\Service $service
* @return \Illuminate\Http\Resources\Json\ResourceCollection
*/
public function brandStats(
GetBrandStatsRequest $request,
Service $service
): \Illuminate\Http\Resources\Json\ResourceCollection {
public function brandStats(GetBrandStatsRequest $request, Service $service): ResourceCollection
{
$stats = BrandStat::query()->whereDate('created_at', '>=', $request->start)
->whereDate('created_at', '<=', $request->end)
->whereBrandId($service->brand->id)->get();
@ -40,10 +39,8 @@ class StatsController extends Controller
* @param \App\Models\Question $question
* @return \App\Http\Resources\Api\V2\QuestionStatsResource
*/
public function questionStats(
GetQuestionStatsRequest $request,
Question $question
): \App\Http\Resources\Api\V2\QuestionStatsResource {
public function questionStats(GetQuestionStatsRequest $request, Question $question): QuestionStatsResource
{
// * Make sure we are accessing a customer that belongs to our service.
$this->authorize('questionBelongsToService', $question);
@ -65,22 +62,18 @@ class StatsController extends Controller
* @param \Illuminate\Support\Carbon $end
* @return \Illuminate\Support\Collection
*/
private function answersByDays(Collection $answers, Carbon $start, Carbon $end): \Illuminate\Support\Collection
private function answersByDays(Collection $answers, Carbon $start, Carbon $end): Collection
{
// * Totalize by date all answers.
$answersByDays = ModelAnswer::query()
->whereDate('created_at', '>=', $start)
$answersByDays = CustomerAnswer::query()->whereDate('created_at', '>=', $start)
->whereDate('created_at', '<=', $end)
->whereIn('answer_id', $answers->pluck('id'))
->get()
->map(function ($custAnswer) {
->get()->map(function ($custAnswer) {
return [
'answer_id' => $custAnswer->answer_id,
'answer_id' => $custAnswer->answer_id,
'created_at' => $custAnswer->created_at->isoFormat('YYYY-MM-DD'),
];
})
->groupBy(['created_at', 'answer_id'])
->map(function ($day) {
})->groupBy(['created_at', 'answer_id'])->map(function ($day) {
return $day->map(function ($custAnswers) {
return $custAnswers->count();
});

View file

@ -4,9 +4,10 @@ namespace App\Http\Controllers\Api\V4;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V4\StoreCustomerAnswers;
use App\Models\Answer;
use App\Models\Customer;
use App\Models\ModelAnswer;
use App\Models\Question;
use App\Models\Service;
use Illuminate\Http\JsonResponse;
class AnswerController extends Controller
{
@ -14,33 +15,21 @@ class AnswerController extends Controller
* Store the customer answers.
*
* @param \App\Http\Requests\Api\V4\StoreCustomerAnswers $request
* @param \App\Models\Service $service
* @param \App\Models\Customer $customer
* @return \Illuminate\Http\JsonResponse
*/
public function storeCustomerAnswers(
StoreCustomerAnswers $request,
Service $service,
Customer $customer
): \Illuminate\Http\JsonResponse {
): JsonResponse {
// * Make sure we are accessing a customer that belongs to our service.
$this->authorize('customerBelongsToService', $customer);
$question = Question::find($request->validated('question'));
// Column model_id is varchar because of singleoptin uuid, so force cast in string.
ModelAnswer::query()
->where('model_type', Customer::class)
->where('model_id', (string) $customer->getKey())
->whereIn('answer_id', $question->answers->modelKeys())
->delete();
foreach ($request->validated('answers') as $answerId) {
ModelAnswer::create([
'model_type' => Customer::class,
'model_id' => (string) $customer->getKey(),
'answer_id' => $answerId,
]);
}
$existingAnswerIds = $customer->getAnswers($service)->pluck('id');
$answersID = Answer::query()->whereIn('id', $request->answers)->pluck('id');
$customer->answers()->sync($answersID->merge($existingAnswerIds));
return response()->json();
}
}

View file

@ -1,51 +0,0 @@
<?php
namespace App\Http\Controllers\Api\V4;
use App\Exceptions\Api\V4\CrmException;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V4\SendClaimRequest;
use App\Models\Claim;
use App\Models\CustomerInfo;
use App\Models\Service;
use Illuminate\Support\Arr;
use Uwil\LaravelCommon\Helpers\FileHelper;
class ClaimController extends Controller
{
/**
* Just returns HTTP 200 response.
*
* @param \App\Http\Requests\Api\V4\SendClaimRequest $request
* @param \App\Models\Service $service
* @return \Illuminate\Http\JsonResponse
* @throws \App\Exceptions\Api\V4\CrmException If the single optin could not be added or removed.
*/
public function store(SendClaimRequest $request, Service $service): \Illuminate\Http\JsonResponse
{
$claimModel = (new Claim())->fill(Arr::except($request->validated(), 'file'));
if ($request->filled('file')) {
$claimModel->file = FileHelper::storeBase64File(
tableName: 'claim',
fileBase64: $request->file,
private: true,
);
}
// Add brand id from service.
$claimModel->brand_id = $service->brand->getKey();
// Add customer relation if exist.
$crmCustomer = CustomerInfo::getFromEmail($claimModel->brand, $claimModel->email)->first();
if (!is_null($crmCustomer)) {
$claimModel->hashcode = $crmCustomer->hashcode;
}
if (!$claimModel->saveOrFail()) {
throw new CrmException('Api failed to store claim.');
}
$claimModel->brand()->associate($service->brand);
return response()->json();
}
}

View file

@ -34,7 +34,7 @@ class CustomerController extends Controller
*/
public function store(StoreCustomerRequest $request, Service $service): JsonResource
{
$data = (object) $request->validated();
$data = (object)$request->validated();
/** @var \App\Models\Customer */
$customer = Customer::query()->where('email', $data->email)->firstOrCreate([

View file

@ -2,6 +2,8 @@
namespace App\Http\Controllers\Api\V4;
use App\Enums\ApiError;
use App\Exceptions\Api\V4\ApiErrorException;
use App\Exceptions\Api\V4\CrmException;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V4\BaseApiRequest;
@ -9,6 +11,7 @@ use App\Http\Requests\Api\V4\SingleOptinRequest;
use App\Http\Requests\Api\V4\SingleOptinRemoveWithHashcodeRequest;
use App\Models\CustomerInfo;
use App\Models\Service;
use Illuminate\Http\JsonResponse;
use App\Models\SingleOptin;
class OptinController extends Controller
@ -19,49 +22,27 @@ class OptinController extends Controller
* @param \App\Http\Requests\Api\V4\SingleOptinRequest $request
* @param \App\Models\Service $service
* @return \Illuminate\Http\JsonResponse
* @throws \App\Exceptions\Api\V4\ApiErrorException With ApiError::singleOptinCustomerExists
* if this email already has a full account.
* @throws \App\Exceptions\Api\V4\CrmException If the single optin could not be added or removed.
*/
public function optin(SingleOptinRequest $request, Service $service): \Illuminate\Http\JsonResponse
public function optin(SingleOptinRequest $request, Service $service): JsonResponse
{
$validatedData = $request->validated();
$crmCustomer = CustomerInfo::getFromEmail($service->brand, $validatedData['email'])->first();
if ($crmCustomer) {
// Update the optin field.
$crmCustomer->opt_in = \boolval($validatedData['opt_in']);
if (CustomerInfo::getFromEmail($service->brand, $request->email)->exists()) {
throw new ApiErrorException(ApiError::singleOptinCustomerExists);
}
// Check the saving.
if (!$crmCustomer->saveOrFail()) {
// @codeCoverageIgnoreStart
throw new CrmException('Api failed to update customer optin.');
// @codeCoverageIgnoreEnd
}
/** @var \App\Models\SingleOptin */
$singleOptin = SingleOptin::query()->where('email', $request->email)
->whereHas('brand', function ($q) use ($service) {
$q->where('id', $service->brand->id);
})->firstOrNew();
// Associate answers if presents.
if (isset($validatedData['answer_id'])) {
$crmCustomer->answers()->attach($validatedData['answer_id']);
}
} else {
// Get the single optin if exist or create it.
/** @var \App\Models\SingleOptin */
$singleOptin = SingleOptin::query()
->where('email', $validatedData['email'])
->whereHas('brand', function ($q) use ($service) {
$q->whereKey($service->brand->getKey());
})
->firstOrNew();
// Update the optin field and check the saving.
if (!$this->changeOptin($service, $request, \boolval($validatedData['opt_in']), $singleOptin)) {
// @codeCoverageIgnoreStart
throw new CrmException('Api failed to update single optin.');
// @codeCoverageIgnoreEnd
}
// Associate answers if presents.
if (isset($validatedData['answer_id'])) {
$singleOptin->answers()->attach($validatedData['answer_id']);
}
} //end if
if (!$this->changeOptin($service, $request, \boolval($request->opt_in), $singleOptin)) {
// @codeCoverageIgnoreStart
throw new CrmException('Api failed to update single optin.');
// @codeCoverageIgnoreEnd
}
return response()->json();
}
@ -74,37 +55,19 @@ class OptinController extends Controller
* @return \Illuminate\Http\JsonResponse
* @throws \App\Exceptions\Api\V4\CrmException If the single optin could not be removed.
*/
public function optoutWithHashcode(
SingleOptinRemoveWithHashcodeRequest $request,
Service $service
): \Illuminate\Http\JsonResponse {
$validatedData = $request->validated();
$crmCustomer = CustomerInfo::getFromHashcode($service->brand, $validatedData['hashcode'])->first();
if ($crmCustomer) {
// Update the optin field.
$crmCustomer->opt_in = false;
public function optoutWithHashcode(SingleOptinRemoveWithHashcodeRequest $request, Service $service): JsonResponse
{
/** @var \App\Models\SingleOptin */
$singleOptin = SingleOptin::query()->where('hashcode', $request->hashcode)
->whereHas('brand', function ($q) use ($service) {
$q->whereId($service->brand->id);
})->firstOrFail();
// Check the saving.
if (!$crmCustomer->saveOrFail()) {
// @codeCoverageIgnoreStart
throw new CrmException('Api failed to update customer optin.');
// @codeCoverageIgnoreEnd
}
} else {
// Get the single optin if exist or create it.
/** @var \App\Models\SingleOptin */
$singleOptin = SingleOptin::query()->where('hashcode', $validatedData['hashcode'])
->whereHas('brand', function ($q) use ($service) {
$q->whereId($service->brand->getKey());
})->firstOrFail();
// Update the optin field and check the saving.
if ($singleOptin and !$this->changeOptin($service, $request, false, $singleOptin)) {
// @codeCoverageIgnoreStart
throw new CrmException('Api failed to update single optin.');
// @codeCoverageIgnoreEnd
}
} //end if
if ($singleOptin and !$this->changeOptin($service, $request, \boolval($request->opt_in), $singleOptin)) {
// @codeCoverageIgnoreStart
throw new CrmException('Api failed to update single optin.');
// @codeCoverageIgnoreEnd
}
return response()->json();
}

View file

@ -8,6 +8,8 @@ use App\Models\Answer;
use App\Models\Customer;
use App\Models\Question;
use App\Models\Service;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Http\Resources\Json\ResourceCollection;
class QuestionController extends Controller
{
@ -17,7 +19,7 @@ class QuestionController extends Controller
* @param \App\Models\Service $service
* @return \Illuminate\Http\Resources\Json\ResourceCollection
*/
public function index(Service $service): \Illuminate\Http\Resources\Json\ResourceCollection
public function index(Service $service): ResourceCollection
{
return new QuestionsResource($service->brand->questions);
}
@ -29,10 +31,8 @@ class QuestionController extends Controller
* @param \App\Models\Customer $customer
* @return \Illuminate\Http\Resources\Json\ResourceCollection
*/
public function indexWithResponse(
Service $service,
Customer $customer
): \Illuminate\Http\Resources\Json\ResourceCollection {
public function indexWithResponse(Service $service, Customer $customer): ResourceCollection
{
// * Make sure we are accessing a customer that belongs to our service.
$this->authorize('customerBelongsToService', $customer);
@ -47,22 +47,18 @@ class QuestionController extends Controller
* @param \App\Models\Customer $customer
* @return \Illuminate\Http\Resources\Json\JsonResource
*/
public function unAnsweredBy(Service $service, Customer $customer): \Illuminate\Http\Resources\Json\JsonResource
public function unAnsweredBy(Service $service, Customer $customer): JsonResource
{
// * Make sure we are accessing a customer that belongs to our service.
$this->authorize('customerBelongsToService', $customer);
$questions = Question::query()
->whereHas('brand', function ($q) use ($service) {
$q->where('id', $service->brand->id);
})
->whereDoesntHave('answers', function ($q) use ($customer) {
$q->whereIn('id', Answer::query()->whereHas('customers', function ($q) use ($customer) {
$q->where('id', $customer->id);
})->pluck('id'));
})
->with('answers')
->get();
$questions = Question::query()->whereHas('brand', function ($q) use ($service) {
$q->where('id', $service->brand->id);
})->whereDoesntHave('answers', function ($q) use ($customer) {
$q->whereIn('id', Answer::query()->whereHas('customers', function ($q) use ($customer) {
$q->where('id', $customer->id);
})->pluck('id'));
})->with('answers')->get();
return new QuestionsResource($questions);
}
}

View file

@ -8,9 +8,10 @@ use App\Http\Requests\Api\V4\GetQuestionStatsRequest;
use App\Http\Resources\Api\V4\BrandStatsResource;
use App\Http\Resources\Api\V4\QuestionStatsResource;
use App\Models\BrandStat;
use App\Models\ModelAnswer;
use App\Models\CustomerAnswer;
use App\Models\Question;
use App\Models\Service;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
@ -23,10 +24,8 @@ class StatsController extends Controller
* @param \App\Models\Service $service
* @return \Illuminate\Http\Resources\Json\ResourceCollection
*/
public function brandStats(
GetBrandStatsRequest $request,
Service $service
): \Illuminate\Http\Resources\Json\ResourceCollection {
public function brandStats(GetBrandStatsRequest $request, Service $service): ResourceCollection
{
$stats = BrandStat::query()->whereDate('created_at', '>=', $request->start)
->whereDate('created_at', '<=', $request->end)
->whereBrandId($service->brand->id)->get();
@ -40,10 +39,8 @@ class StatsController extends Controller
* @param \App\Models\Question $question
* @return \App\Http\Resources\Api\V4\QuestionStatsResource
*/
public function questionStats(
GetQuestionStatsRequest $request,
Question $question
): \App\Http\Resources\Api\V4\QuestionStatsResource {
public function questionStats(GetQuestionStatsRequest $request, Question $question): QuestionStatsResource
{
// * Make sure we are accessing a customer that belongs to our service.
$this->authorize('questionBelongsToService', $question);
@ -65,22 +62,18 @@ class StatsController extends Controller
* @param \Illuminate\Support\Carbon $end
* @return \Illuminate\Support\Collection
*/
private function answersByDays(Collection $answers, Carbon $start, Carbon $end): \Illuminate\Support\Collection
private function answersByDays(Collection $answers, Carbon $start, Carbon $end): Collection
{
// * Totalize by date all answers.
$answersByDays = ModelAnswer::query()
->whereDate('created_at', '>=', $start)
$answersByDays = CustomerAnswer::query()->whereDate('created_at', '>=', $start)
->whereDate('created_at', '<=', $end)
->whereIn('answer_id', $answers->pluck('id'))
->get()
->map(function ($custAnswer) {
->get()->map(function ($custAnswer) {
return [
'answer_id' => $custAnswer->answer_id,
'answer_id' => $custAnswer->answer_id,
'created_at' => $custAnswer->created_at->isoFormat('YYYY-MM-DD'),
];
})
->groupBy(['created_at', 'answer_id'])
->map(function ($day) {
})->groupBy(['created_at', 'answer_id'])->map(function ($day) {
return $day->map(function ($custAnswers) {
return $custAnswers->count();
});

View file

@ -13,14 +13,6 @@ class ActivityLogsController extends Controller
use ChangesModelOrder;
use UpdateModelPublished;
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(ActivityLog::class);
}
/**
* Display a listing of the resource.
*
@ -51,7 +43,7 @@ class ActivityLogsController extends Controller
$this->sortQuery($query);
/** Custom pagination */
$activitylogModels = $query->paginate($this->getPagination());
$activitylogModels = $this->paginate($query);
return view('bo.pages.activity_logs.index', compact('activitylogModels', 'search', 'searchFields'));
}

View file

@ -13,14 +13,6 @@ use Illuminate\Support\Str;
class AnswerController extends Controller
{
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Answer::class);
}
/**
* Display a listing of the resource.
*
@ -32,22 +24,22 @@ class AnswerController extends Controller
/** @var \App\Models\Question $questionModel */
$questionModel = $request->question;
/** @var \Illuminate\Database\Eloquent\Builder $query */
$query = Answer::query()->where('question_id', $questionModel->id);
/** @var \Illuminate\Database\Eloquent\Builder $answers */
$answers = Answer::query()->where('question_id', $questionModel->id);
if ($request->search) {
$this->searchQuery(
$query,
$answers,
$request->search,
null,
'answer',
);
}
$this->sortQuery($query);
$this->sortQuery($answers);
$searchFields = trans('Réponse');
$answerModels = $query->paginate($this->getPagination());
$answerModels = $this->paginate($answers);
return view('bo.pages.answers.index', compact('answerModels', 'questionModel', 'searchFields'));
}
@ -65,8 +57,8 @@ class AnswerController extends Controller
$questionModel = $request->question;
return view('bo.pages.answers.create', [
'answerModel' => $answer,
'questionModel' => $questionModel,
'answerModel' => $answer,
'questionModel' => $questionModel
]);
}
@ -81,11 +73,9 @@ class AnswerController extends Controller
return DB::transaction(function () use ($request) {
$answer = new Answer();
$answer->fill($request->validated());
$questionModel = $request->route()->parameter('question');
$answer->question()->associate($questionModel->getKey());
if ($answer->saveOrFail()) {
return redirect()
->route('bo.questions.answers.edit', ['question' => $questionModel, 'answer' => $answer])
->route('bo.answers.edit', $answer)
->with('success', trans(
':model a été créé.',
['model' => Str::singular(trans('models.classes.answers'))]
@ -110,8 +100,8 @@ class AnswerController extends Controller
public function edit(Question $question, Answer $answers): \Illuminate\Contracts\View\View
{
return view('bo.pages.answers.edit', [
'answerModel' => $answers,
'questionModel' => $question,
'answerModel' => $answers,
'questionModel' => $question
]);
}
@ -156,7 +146,6 @@ class AnswerController extends Controller
*/
public function destroy(Question $question, Answer $answer): \Illuminate\Http\RedirectResponse
{
$answer->singleOptins()->detach();
if ($answer->deleteOrFail()) {
return redirect()->route('bo.questions.answers.index', $question)
->with('success', trans(

View file

@ -5,24 +5,14 @@ namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Brands\StoreBrandRequest;
use App\Http\Requests\Bo\Brands\UpdateBrandRequest;
use App\Http\Requests\Bo\PaginateModelsRequest;
use App\Models\Brand;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Uwil\LaravelCommon\Helpers\CollectionHelper;
class BrandController extends Controller
{
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Brand::class);
}
/**
* Display a listing of the resource.
*
@ -50,7 +40,7 @@ class BrandController extends Controller
$q->where('opt_in', true);
}]);
$brandModels = $query->paginate($this->getPagination());
$brandModels = $this->paginate($query);
return view('bo.pages.brands.index', compact('brandModels', 'searchFields'));
}
@ -138,18 +128,16 @@ class BrandController extends Controller
}
/**
* Get a paginate resource.
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\PaginateModelsRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function jsonPaginateModels(PaginateModelsRequest $request): \Illuminate\Http\JsonResponse
public function jsonPaginate(): \Illuminate\Http\JsonResponse
{
$models = CollectionHelper::paginateManually(
Brand::getUserAssociatedBrands(),
$this->jsonItemPerPagination,
(int) $request->validated('page'),
);
return \response()->json($models);
return DB::transaction(function () {
/** @var \App\Models\Brand */
$brandModels = Brand::paginate(12);
return \response()->json($brandModels ?? 500);
});
}
}

View file

@ -1,298 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Enums\Claims\ClaimResponseType;
use App\Enums\Claims\ClaimStatus;
use App\Enums\ExportFormat;
use App\Enums\Users\RoleEnum;
use App\Events\ClaimModelChanged;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Claims\ExportClaimRequest;
use App\Http\Requests\Bo\Claims\SendClaimAnswerRequest;
use App\Http\Requests\Bo\Claims\UpdateClaimRequest;
use App\Http\Requests\Bo\Claims\ViewClaimAttachmentRequest;
use App\Http\Requests\Bo\PaginateModelsRequest;
use App\Lib\Helpers\FileHelper;
use App\Mail\ClaimAnswerMail;
use App\Models\Brand;
use App\Models\Claim;
use App\Models\ClaimMailTemplate;
use App\Models\ClaimMessage;
use App\Models\ClaimSubject;
use App\Traits\Controllers\ExportDatas;
use App\Traits\Controllers\UpdateModelPublished;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Uwil\LaravelCommon\Helpers\CollectionHelper;
class ClaimController extends Controller
{
use UpdateModelPublished;
use ExportDatas;
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Claim::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 \App\Models\User $userModel */
$userModel = auth('backend')->user();
/** @var \Illuminate\Database\Eloquent\Builder $query */
$query = Claim::query()
->when(
$request->filled('brand'),
fn($q) => $q->whereHas('brand', fn($q) => $q->where('id', $request->brand))
)->when(
$userModel->role->equals(RoleEnum::marketing),
fn($q) => $q->whereHas('brand', fn($q) => $q->whereIn('id', $userModel->brands->modelKeys()))
)
->when(
$request->filled('subject'),
fn($q) => $q->whereHas('claimSubject', fn($q) => $q->where('id', $request->subject))
)
->when(
$request->filled('status'),
fn($q) => $q->whereIn('status', $request->status),
fn($q) => $q->whereNotIn('status', [ClaimStatus::treated, ClaimStatus::refused])
);
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$query,
$search,
null,
'reference',
'first_name',
'last_name',
'email',
);
}
$searchFields = \implode(', ', [
trans('validation.custom.reference'),
trans('validation.attributes.first_name'),
trans('validation.attributes.last_name'),
trans('validation.attributes.email'),
]);
/** Sort columns with a query */
$this->sortQuery($query);
/** Custom pagination */
$claimModels = $query->simplePaginate($this->getPagination());
/** Define request brand filter if user only has 1 brand */
if (auth('backend')->user()->brands()->count() === 1) {
$request->merge(['brand' => auth('backend')->user()->brands()->value('id')]);
};
$claimSubjectModels = ClaimSubject::query()
->when(
$request->filled('brand'),
fn($q) => $q->whereHas('brand', fn($q) => $q->where('id', $request->brand)),
)
->when(
auth('backend')->user()->brands()->count() === 1,
fn($q) => $q->whereHas(
'brand',
fn($q) => $q->where('id', auth('backend')->user()->brands()->pluck('id'))
)
);
$claimSubjectModels = $request->filled('brand') || (auth('backend')->user()->brands()->count() === 1) ?
$claimSubjectModels->orderBy('label', 'ASC')->get() :
null;
return view('bo.pages.claims.index', compact('claimModels', 'search', 'searchFields', 'claimSubjectModels'));
}
/**
* Show the form for editing the specified resource.
*
* @param App\Models\Claim $claim
* @return \Illuminate\Contracts\View\View
*/
public function edit(Claim $claim): \Illuminate\Contracts\View\View
{
$customerClaimModels = !is_null($claim->customerInfos) ? Claim::query()
->where('hashcode', $claim->customerInfos->hashcode)
->whereHas('brand', fn($q) => $q->whereKey($claim->brand->getKey()))
->whereNot('id', $claim->getKey())
->paginate($this->jsonItemPerPagination) : collect();
$brandModels = CollectionHelper::paginate(
Brand::getUserAssociatedBrands(),
$this->jsonItemPerPagination,
);
$claimMessageModels = ClaimMessage::getAllModelsFromClaim($claim->getKey());
$claimSubjectModels = CollectionHelper::paginate(
ClaimSubject::getAllModelsFromBrand($claim->brand->getKey()),
$this->jsonItemPerPagination,
);
$claimTemplateModels = CollectionHelper::paginate(
ClaimMailTemplate::getAllModelsFromBrand($claim->brand->getKey()),
$this->jsonItemPerPagination,
);
return view('bo.pages.claims.edit', [
'claimModel' => $claim->load(['brand', 'claimSubject']),
'customerClaimModels' => $customerClaimModels,
'brandModels' => $brandModels,
'claimMessageModels' => $claimMessageModels,
'claimSubjectModels' => $claimSubjectModels,
'claimTemplateModels' => $claimTemplateModels,
'customerInfoModel' => $claim->customerInfos,
]);
}
/**
* Update the specified resource.
*
* @param \App\Http\Requests\Bo\Claims\UpdateClaimRequest $request
* @param \App\Models\Claim $claim
* @return \Illuminate\Http\JsonResponse
*/
public function update(UpdateClaimRequest $request, Claim $claim): \Illuminate\Http\JsonResponse
{
return DB::transaction(function () use ($request, $claim) {
$claim->fill($request->validated());
if ($claim->saveOrFail()) {
$claimMessageModels = ClaimMessage::getAllModelsFromClaim($claim->getKey());
return response()->json($claimMessageModels);
}
return response()->json([], 500);
});
}
/**
* Send the claim answer.
*
* @param \App\Http\Requests\Bo\Claims\SendClaimAnswerRequest $request
* @param \App\Models\Claim $claim
* @return \Illuminate\Http\JsonResponse
*/
public function answer(SendClaimAnswerRequest $request, Claim $claim): \Illuminate\Http\JsonResponse
{
return DB::transaction(function () use ($request, $claim) {
/** @var \App\Models\User $userModel */
$userModel = auth()->user();
$enum = ClaimResponseType::manual;
$claim->response_type = $enum;
$claim->status = ClaimStatus::treated;
if ($claim->saveOrFail()) {
// Get only the content of the response.
$content = $request->validated()['content'];
// Send the response to the claim author.
Mail::to($claim->email)->send(new ClaimAnswerMail($claim, $content));
// Create custom event with content message.
// @phpcs:disable Generic.Files.LineLength.TooLong
$content = sprintf(
"<b>%s %s</b> %s <i class='badge' style='background-color:{$enum->color()}'>%s</i><hr class='my-2'>%s",
$userModel->first_name,
$userModel->last_name,
trans('a modifié :field en', ['field' => trans('validation.custom.response_type')]),
$claim->response_type->label(),
clean($content),
);
event(new ClaimModelChanged($claim, '', '', null, $content));
// @phpcs:enable
// Return conversation.
$claimMessageModels = ClaimMessage::getAllModelsFromClaim($claim->getKey());
return response()->json($claimMessageModels);
}
return response()->json([], 500);
});
}
/**
* Undocumented function
*
* @param ViewClaimAttachmentRequest $request
* @param Claim $claim
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter
*/
public function viewAttachment(
ViewClaimAttachmentRequest $request,
Claim $claim
): \Symfony\Component\HttpFoundation\BinaryFileResponse {
// phpcs:enable
return response()->file(Storage::disk('private')->path(FileHelper::getPrivateFilePath($claim->file)));
}
/**
* Get a paginate resource.
* Needed to overheight the controller method and add a brand condition.
*
* @param \App\Http\Requests\Bo\PaginateModelsRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function jsonClaimPaginateModels(PaginateModelsRequest $request): \Illuminate\Http\JsonResponse
{
$data = $request->validated();
$brandModelKey = $request->route()->parameter('brand')->getKey();
$models = CollectionHelper::paginateManually(
$data['targetModel']::getAllModelsFromBrand($brandModelKey),
$this->jsonItemPerPagination,
$request->validated('page'),
);
return \response()->json($models);
}
// * EXPORT
/**
* Export claim datas to a given format
*
* @param ExportClaimRequest $request
* @return \Illuminate\Http\Response|Symfony\Component\HttpFoundation\StreamedResponse|\Illuminate\Http\RedirectResponse
*/
public function export(ExportClaimRequest $request)
{
/** @var \Illuminate\Database\Eloquent\Collection<int, \App\Models\Claim> $claims*/
$claims = Claim::query()->whereIn('id', $request->validated('claimList'))->get();
$format = ExportFormat::from($request->validated('format'));
// * CSV
if ($format->equals(ExportFormat::csvWithoutAttachment, ExportFormat::csv)) {
$csv = $this->generateCsv($claims);
if (!$this->needsZip($format, $claims)) {
return new StreamedResponse(function () use ($format, $csv) {
echo $csv;
}, 200, $this->getFileHeaders($format));
}
return $this->returnExportZip($format, $claims, $csv);
// * PDF
} elseif ($format->equals(ExportFormat::pdf)) {
$pdf = $this->generatePdf($claims);
if (!$this->needsZip(ExportFormat::pdf, $claims)) {
return $pdf->download('export.pdf');
}
return $this->returnExportZip($format, $claims, $pdf->output());
}
}
}

View file

@ -1,183 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Enums\Users\RoleEnum;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\ClaimMailTemplates\ClaimMailTemplateRequest;
use App\Models\Brand;
use App\Models\ClaimMailTemplate;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Uwil\LaravelCommon\Helpers\CollectionHelper;
class ClaimMailTemplateController extends Controller
{
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(ClaimMailTemplate::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 \App\Models\User $userModel */
$userModel = auth('backend')->user();
/** @var \Illuminate\Database\Eloquent\Builder $query */
$query = ClaimMailTemplate::query()
->when(
$request->filled('brand'),
fn($q) => $q->whereHas('brand', fn($q) => $q->where('id', $request->brand))
)->when(
$userModel->role->equals(RoleEnum::marketing),
fn($q) => $q->whereHas('brand', fn($q) => $q->whereIn('id', $userModel->brands->modelKeys()))
);
if ($request->search) {
$this->searchQuery(
$query,
$request->search,
null,
'label',
);
}
$this->sortQuery($query);
$searchFields = trans('validation.custom.label');
$claimMailTemplateModels = $query->paginate($this->getPagination());
return view('bo.pages.claim_mail_templates.index', compact('claimMailTemplateModels', 'searchFields'));
}
/**
* Show the specified resource.
*
* @param \App\Models\ClaimMailTemplate $claim_mail_template
* @return \Illuminate\Contracts\View\View
*/
public function show(ClaimMailTemplate $claim_mail_template): \Illuminate\Contracts\View\View
{
return view('bo.pages.claim_mail_templates.show', [
'claimMailTemplateModel' => $claim_mail_template,
]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\ClaimMailTemplate $claim_mail_template
* @return \Illuminate\Contracts\View\View
*/
public function create(ClaimMailTemplate $claim_mail_template): \Illuminate\Contracts\View\View
{
$brandModels = CollectionHelper::paginate(Brand::getUserAssociatedBrands(), $this->jsonItemPerPagination);
return view('bo.pages.claim_mail_templates.create', [
'claimMailTemplateModel' => $claim_mail_template,
'brandModels' => $brandModels,
]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\ClaimMailTemplates\ClaimMailTemplateRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(ClaimMailTemplateRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$claimMailTemplateModel = new ClaimMailTemplate();
$claimMailTemplateModel->fill($request->validated());
if ($claimMailTemplateModel->saveOrFail()) {
return redirect()
->route('bo.claim_mail_templates.edit', $claimMailTemplateModel)
->with('success', trans(
':model a été créé.',
['model' => Str::singular(trans('models.classes.claimmailtemplates'))]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été créé.',
['model' => Str::singular(trans('models.classes.claimmailtemplates'))]
));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\ClaimMailTemplate $claim_mail_template
* @return \Illuminate\Contracts\View\View
*/
public function edit(ClaimMailTemplate $claim_mail_template): \Illuminate\Contracts\View\View
{
$brandModels = CollectionHelper::paginate(Brand::getUserAssociatedBrands(), $this->jsonItemPerPagination);
return view('bo.pages.claim_mail_templates.edit', [
'claimMailTemplateModel' => $claim_mail_template,
'brandModels' => $brandModels,
]);
}
/**
* Update a spacific resource in storage.
*
* @param \App\Http\Requests\Bo\ClaimSubjects\ClaimMailTemplateRequest $request
* @param \App\Models\ClaimMailTemplate $claim_mail_template
* @return \Illuminate\Http\RedirectResponse
*/
public function update(
ClaimMailTemplateRequest $request,
ClaimMailTemplate $claim_mail_template
): \Illuminate\Http\RedirectResponse {
return DB::transaction(function () use ($request, $claim_mail_template) {
$claim_mail_template->fill($request->validated());
if ($claim_mail_template->saveOrFail()) {
return redirect()
->route('bo.claim_mail_templates.edit', $claim_mail_template)
->with('success', trans(
':model a été mis à jour.',
['model' => Str::singular(trans('models.classes.claimmailtemplates'))]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été mis à jour.',
['model' => Str::singular(trans('models.classes.claimmailtemplates'))]
));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\ClaimMailTemplate $claim_mail_template
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(ClaimMailTemplate $claim_mail_template): \Illuminate\Http\RedirectResponse
{
if ($claim_mail_template->deleteOrFail()) {
return redirect()->route('bo.claim_mail_templates.index')
->with('success', __('crud.messages.has_been_deleted', [
'model' => Str::of(__('models.classes.claimmailtemplates'))->singular()
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_deleted', [
'model' => Str::of(__('models.classes.claimmailtemplates'))->singular()
]));
}
}

View file

@ -1,53 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\ClaimMessages\StoreMessageRequest;
use App\Http\Requests\Bo\ClaimMessages\UpdateMessageRequest;
use App\Models\ClaimMessage;
use App\Traits\Controllers\UpdateModelPublished;
use Illuminate\Support\Facades\DB;
class ClaimMessageController extends Controller
{
use UpdateModelPublished;
/**
* Store a new specific resource.
*
* @param \App\Http\Requests\Bo\ClaimMessages\StoreMessageRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function store(StoreMessageRequest $request): \Illuminate\Http\JsonResponse
{
$this->authorize('store', ClaimMessage::class);
$validatedData = $request->validated();
return DB::transaction(function () use ($validatedData) {
$claimMessage = (new ClaimMessage())->fill($validatedData);
if ($claimMessage->saveOrFail()) {
$claimMessageModels = ClaimMessage::getAllModelsFromClaim($claimMessage->claim->getKey());
return response()->json($claimMessageModels);
}
return response()->json([], 500);
});
}
/**
* Update a specified resource.
*
* @param \App\Http\Requests\Bo\ClaimMessages\UpdateMessageRequest $request
* @param \App\Models\ClaimMessage $claim_message
* @return \Illuminate\Http\JsonResponse
*/
public function update(UpdateMessageRequest $request, ClaimMessage $claim_message): \Illuminate\Http\JsonResponse
{
$this->authorize('update', ClaimMessage::class);
$claim_message->content = $request->validated()['content'];
$claim_message->saveOrFail();
return response()->json($claim_message);
}
}

View file

@ -1,173 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\ClaimSubjects\ClaimSubjectRequest;
use App\Models\Brand;
use App\Models\ClaimSubject;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Uwil\LaravelCommon\Helpers\CollectionHelper;
class ClaimSubjectController extends Controller
{
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(ClaimSubject::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 = ClaimSubject::query()->when(
$request->filled('brand'),
fn($q) => $q->whereHas('brand', fn($q) => $q->where('id', $request->brand))
);
if ($request->search) {
$this->searchQuery(
$query,
$request->search,
null,
'label',
);
}
$this->sortQuery($query);
$searchFields = trans('validation.custom.label');
$claimSubjectModels = $query->paginate($this->getPagination());
return view('bo.pages.claim_subjects.index', compact('claimSubjectModels', 'searchFields'));
}
/**
* Show the specified resource.
*
* @param \App\Models\ClaimSubject $claim_subject
* @return \Illuminate\Contracts\View\View
*/
public function show(ClaimSubject $claim_subject): \Illuminate\Contracts\View\View
{
return view('bo.pages.claim_subjects.show', [
'claimSubjectModel' => $claim_subject
]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\ClaimSubject $claim_subject
* @return \Illuminate\Contracts\View\View
*/
public function create(ClaimSubject $claim_subject): \Illuminate\Contracts\View\View
{
$brandModels = CollectionHelper::paginate(Brand::getUserAssociatedBrands(), $this->jsonItemPerPagination);
return view('bo.pages.claim_subjects.create', [
'claimSubjectModel' => $claim_subject,
'brandModels' => $brandModels,
]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\ClaimSubjects\ClaimSubjectRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(ClaimSubjectRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$claimSubjectModel = new ClaimSubject();
$claimSubjectModel->fill($request->validated());
if ($claimSubjectModel->saveOrFail()) {
return redirect()
->route('bo.claim_subjects.edit', $claimSubjectModel)
->with('success', trans(
':model a été créé.',
['model' => Str::singular(trans('models.classes.claimsubjects'))]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été créé.',
['model' => Str::singular(trans('models.classes.claimsubjects'))]
));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\ClaimSubject $claim_subject
* @return \Illuminate\Contracts\View\View
*/
public function edit(ClaimSubject $claim_subject): \Illuminate\Contracts\View\View
{
$brandModels = CollectionHelper::paginate(Brand::getUserAssociatedBrands(), $this->jsonItemPerPagination);
return view('bo.pages.claim_subjects.edit', [
'claimSubjectModel' => $claim_subject,
'brandModels' => $brandModels,
]);
}
/**
* Update a spacific resource in storage.
*
* @param \App\Http\Requests\Bo\ClaimSubjects\ClaimSubjectRequest $request
* @param \App\Models\ClaimSubject $claim_subject
* @return \Illuminate\Http\RedirectResponse
*/
public function update(ClaimSubjectRequest $request, ClaimSubject $claim_subject): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $claim_subject) {
$claim_subject->fill($request->validated());
if ($claim_subject->saveOrFail()) {
return redirect()
->route('bo.claim_subjects.edit', $claim_subject)
->with('success', trans(
':model a été mis à jour.',
['model' => Str::singular(trans('models.classes.claimsubjects'))]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été mis à jour.',
['model' => Str::singular(trans('models.classes.claimsubjects'))]
));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\ClaimSubject $claim_subject
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(ClaimSubject $claim_subject): \Illuminate\Http\RedirectResponse
{
if ($claim_subject->deleteOrFail()) {
return redirect()->route('bo.claim_subjects.index')
->with('success', __('crud.messages.has_been_deleted', [
'model' => Str::of(__('models.classes.claimsubjects'))->singular()
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_deleted', [
'model' => Str::of(__('models.classes.claimsubjects'))->singular()
]));
}
}

View file

@ -5,18 +5,15 @@ namespace App\Http\Controllers\Bo;
use App\Enums\CustomerExportType;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\CustomerExport;
use App\Models\Answer;
use App\Models\Brand;
use App\Models\Customer;
use App\Models\CustomerInfo;
use App\Models\CustomerLogin;
use App\Models\Question;
use App\Models\SingleOptin;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Session;
@ -26,14 +23,6 @@ use Symfony\Component\HttpFoundation\StreamedResponse;
class CustomerController extends Controller
{
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Customer::class);
}
/**
* View all customers.
*
@ -42,41 +31,90 @@ class CustomerController extends Controller
*/
public function index(Request $request): \Illuminate\Contracts\View\View
{
$modelClass = Customer::class;
$modelTable = (new Customer())->getTable();
$questionModels = null;
$answerModels = null;
$searchFields = trans('Email, Prénom, Nom');
$modelTable = (new Customer())->getTable();
$modelClass = Customer::class;
$brands = Brand::query()->select('id', 'name')->get();
$columns = ['customer_infos.lastname', 'customer_infos.firstname'];
// Reset session sorts.
if ($request->rstSrts) {
$this->resetSessionSorts($modelTable);
}
// Reset session filters.
if ($request->rstFltrs) {
$this->resetSessionFilters($modelTable);
// * Jointure pour pouvoir trier les colonnes.
/** @var \Illuminate\Database\Eloquent\Builder */
$query = CustomerInfo::query()->join('customer_logins', function (JoinClause $join) {
$join->on('customer_logins.brand_id', '=', 'customer_infos.brand_id')
->on('customer_logins.customer_id', '=', 'customer_infos.customer_id');
})->join('customers', function (JoinClause $join) {
$join->on('customers.id', '=', 'customer_infos.customer_id');
})->join('brands', function (JoinClause $join) {
$join->on('brands.id', '=', 'customer_infos.brand_id');
});
if ($request->rst) {
Session::remove("crud.$modelTable.brand");
Session::remove("crud.$modelTable.search");
Session::remove("crud.$modelTable.sort_way");
Session::remove("crud.$modelTable.sort_col");
}
if ($request->filled('search') || Session::get("crud.$modelTable.search")) {
$columns = ['customer_infos.lastname', 'customer_infos.firstname'];
$query = $this->buildCustomerQuery();
if (request()->brand or Session::has("crud.$modelTable.brand")) {
$brand = $request->brand ?? Session::get("crud.$modelTable.brand");
Session::put("crud.$modelTable.brand", $brand);
$query = $query->where('customer_infos.brand_id', $brand);
}
// Apply filters.
$this->applyBrandFilter($request, $query, $modelTable, $questionModels, $answerModels);
$this->applySearchFilter($request, $query, $modelTable, $columns);
$this->applySort($request, $query, $modelTable);
// * Search on crud Label column.
if ($request->search or Session::has("crud.$modelTable.search")) {
$search = $request->search ?? Session::get("crud.$modelTable.search");
Session::put("crud.$modelTable.search", $search);
$query->where(function ($q) use ($columns, $search) {
foreach ($columns as $column) {
$q = $q->orWhere($column, 'LIKE', "%$search%");
}
$q = $q->orWhere('customers.email', 'LIKE', "%$search%");
$q = $q->orWhere('customer_logins.last_connection', 'LIKE', "%$search%");
});
}
$customerModels = $query->with(['customer', 'brand'])->simplePaginate($this->getPagination());
} //end if
// * Sort columns.
if ($request->sort_col) {
Session::put("crud.$modelTable.sort_col", $request->sort_col);
}
if ($request->sort_way) {
Session::put("crud.$modelTable.sort_way", $request->sort_way);
}
return view('bo.pages.customers.index', [
'modelClass' => $modelClass,
'modelTable' => $modelTable,
'customerModels' => $customerModels ?? collect(),
'searchFields' => $searchFields,
'questionModels' => $questionModels,
'answerModels' => $answerModels,
]);
if (
Session::has("crud.$modelTable.sort_col") and
Session::has("crud.$modelTable.sort_way") and
(Schema::hasColumn($modelTable, Session::get("crud.$modelTable.sort_col")) or
in_array(Session::get("crud.$modelTable.sort_col"), [
'brands.name',
'customers.email',
'customer_infos.firstname',
'customer_infos.lastname',
'customer_infos.opt_in',
'customer_logins.last_connection',
'customer_logins.status'
]))
) {
$query = $query->orderBy(
Session::get("crud.$modelTable.sort_col"),
Session::get("crud.$modelTable.sort_way")
);
}
$searchFields = trans('Email, Prénom, Nom');
$query->with(['customer', 'brand']);
/** @var \Illuminate\Pagination\AbstractPaginator */
$customerModels = $this->paginate($query);
return view('bo.pages.customers.index', compact(
'modelClass',
'modelTable',
'customerModels',
'brands',
'searchFields'
));
}
/**
@ -157,7 +195,6 @@ class CustomerController extends Controller
*/
public function export(): \Illuminate\Contracts\View\View
{
$this->authorize('export', Customer::class);
$brands = Brand::all();
return view('bo.pages.customers.export', compact(
'brands'
@ -266,157 +303,4 @@ class CustomerController extends Controller
});
return $response;
}
/**
* Reset all session sorts.
*
* @param string $table
* @return void
*/
private function resetSessionSorts(string $table): void
{
Session::forget([
"crud.$table.sort_col",
"crud.$table.sort_way",
]);
}
/**
* Reset all session filters.
*
* @param string $table
* @return void
*/
private function resetSessionFilters(string $table): void
{
Session::forget([
"crud.$table.brand",
"crud.$table.question",
"crud.$table.answer",
"crud.$table.search",
]);
}
/**
* Build the customer query.
*
* @return Builder
*/
private function buildCustomerQuery(): Builder
{
return CustomerInfo::query()
->join('customer_logins', function (JoinClause $join) {
$join->on('customer_logins.brand_id', '=', 'customer_infos.brand_id')
->on('customer_logins.customer_id', '=', 'customer_infos.customer_id');
})
->join('customers', fn($join) => $join->on('customers.id', '=', 'customer_infos.customer_id'))
->join('brands', fn($join) => $join->on('brands.id', '=', 'customer_infos.brand_id'));
}
/**
* Apply all filters (brand, question, answer).
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $table
* @param \Illuminate\Support\Collection|null $questionModels
* @param \Illuminate\Support\Collection|null $answerModels
* @return void
*/
private function applyBrandFilter(
Request $request,
Builder &$query,
string $table,
?Collection &$questionModels,
?Collection &$answerModels
): void {
$brand = $request->brand ?? Session::get("crud.$table.brand");
if (!(int) $brand) {
return;
}
Session::put("crud.$table.brand", $brand);
$query->where('customer_infos.brand_id', $brand);
$questionModels = Question::getAllModelsFromBrand($brand);
if (($request->filled('question') || Session::has("crud.$table.question")) && $questionModels->count()) {
$question = $request->question ?? Session::get("crud.$table.question");
Session::put("crud.$table.question", $question);
$answerModels = Answer::getAllAnswersFromQuestion($question);
$questionAnswers = Question::find($question)?->answers->modelKeys() ?? [];
$query->whereHas('customer.answers', fn($q) => $q->whereIn('answer_id', $questionAnswers));
if (
($request->filled('answer') || Session::has("crud.$table.answer"))
&& $questionModels->count()
&& $answerModels->count()
) {
$answer = $request->answer ?? Session::get("crud.$table.answer");
Session::put("crud.$table.answer", $answer);
$query->whereHas('customer.answers', fn($q) => $q->whereKey($answer));
}
}
}
/**
* Apply the search filter.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $table
* @param array $columns
* @return void
*/
private function applySearchFilter(Request $request, Builder &$query, string $table, array $columns): void
{
$search = $request->search ?? Session::get("crud.$table.search");
Session::put("crud.$table.search", $search);
$query->where(function ($q) use ($columns, $search) {
foreach ($columns as $column) {
$q->orWhere($column, 'LIKE', "%$search%");
}
$q->orWhere('customers.email', 'LIKE', "%$search%")
->orWhere('customer_logins.last_connection', 'LIKE', "%$search%");
});
}
/**
* Apply sorts.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $table
* @return void
*/
private function applySort(Request $request, Builder &$query, string $table): void
{
$sortCol = $request->sort_col ?? Session::get("crud.$table.sort_col");
$sortWay = $request->sort_way ?? Session::get("crud.$table.sort_way");
if ($sortCol) {
Session::put("crud.$table.sort_col", $sortCol);
}
if ($sortWay) {
Session::put("crud.$table.sort_way", $sortWay);
}
$sortable = [
'brands.name',
'customers.email',
'customer_infos.firstname',
'customer_infos.lastname',
'customer_infos.opt_in',
'customer_logins.last_connection',
'customer_logins.status',
];
if ($sortCol && $sortWay && (Schema::hasColumn($table, $sortCol) || in_array($sortCol, $sortable))) {
$query->orderBy($sortCol, $sortWay);
}
}
}

View file

@ -3,15 +3,16 @@
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\View\View;
class DashboardController extends Controller
{
/**
* Dashboard page.
* Dashboard Page
*
* @return \Illuminate\Contracts\View\View
*/
public function index(): \Illuminate\Contracts\View\View
public function index(): View
{
return view('bo.pages.dashboard');
}

View file

@ -7,7 +7,6 @@ use App\Http\Requests\Bo\QuestionExport;
use App\Http\Requests\Bo\Questions\StoreQuestionRequest;
use App\Http\Requests\Bo\Questions\UpdateQuestionRequest;
use App\Models\Brand;
use App\Models\Customer;
use App\Models\Question;
use Carbon\Carbon;
use Illuminate\Http\Request;
@ -18,14 +17,6 @@ use Symfony\Component\HttpFoundation\StreamedResponse;
class QuestionController extends Controller
{
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Question::class);
}
/**
* Display a listing of the resource.
*
@ -35,11 +26,11 @@ class QuestionController extends Controller
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $query */
$query = Question::query()->when(
$request->filled('brand'),
fn($q) => $q->whereHas('brand', fn($q) => $q->where('id', $request->brand))
);
$query = Question::query();
if ($request->brand) {
$query = $query->where('brand_id', '=', $request->brand);
}
if ($request->search) {
$this->searchQuery(
$query,
@ -55,7 +46,7 @@ class QuestionController extends Controller
$query->with(['brand', 'answers']);
/** @var \Illuminate\Pagination\AbstractPaginator */
$questionModels = $query->paginate($this->getPagination());
$questionModels = $this->paginate($query);
return view('bo.pages.questions.index', compact('questionModels', 'searchFields'));
}
@ -68,10 +59,11 @@ class QuestionController extends Controller
*/
public function create(Question $question): \Illuminate\Contracts\View\View
{
$brandModels = Brand::query()->orderBy('name', 'ASC')->paginate($this->jsonItemPerPagination);
$brandModels = Brand::all();
return view('bo.pages.questions.create', [
'questionModel' => $question,
'brandModels' => $brandModels
'brandModels' => $brandModels
]);
}
@ -111,8 +103,9 @@ class QuestionController extends Controller
*/
public function edit(Question $question): \Illuminate\Contracts\View\View
{
$brandModels = Brand::query()->orderBy('name', 'ASC')->paginate($this->jsonItemPerPagination);
$brandModels = Brand::all();
$question->load('brand');
return view('bo.pages.questions.edit', [
'questionModel' => $question,
'brandModels' => $brandModels
@ -189,7 +182,6 @@ class QuestionController extends Controller
*/
public function export(): \Illuminate\Contracts\View\View
{
$this->authorize('export', Question::class);
$brands = Brand::all();
return view('bo.pages.questions.export', compact('brands'));
}
@ -210,12 +202,10 @@ class QuestionController extends Controller
foreach ($questions as $question) {
$answersCounts = DB::query()
->selectRaw('count(*) as total, answer_id')
->from('model_answer')
->where('model_type', '=', Customer::class)
->whereIn('answer_id', $question->answers->modelKeys())
->from('customer_answer')
->whereIn('answer_id', $question->answers->pluck('id'))
->groupBy('answer_id')
->get()
->keyBy('answer_id');
->get()->keyBy('answer_id');
$results[$question->id] = [
'question' => $question->question,
@ -228,7 +218,7 @@ class QuestionController extends Controller
$answersCounts[$answer->id]->total ?? 0
];
}
} //end foreach
}
$w = Writer::createFromString('');
$w->setDelimiter(';');

View file

@ -14,18 +14,9 @@ use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
use Uwil\LaravelCommon\Helpers\CollectionHelper;
class ServiceController extends Controller
{
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Service::class);
}
/**
* Display a listing of the resource.
*
@ -35,11 +26,11 @@ class ServiceController extends Controller
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $query */
$query = Service::query()->when(
$request->filled('brand'),
fn($q) => $q->whereHas('brand', fn($q) => $q->where('id', $request->brand))
);
$query = Service::query();
if ($request->brand) {
$query = $query->where('brand_id', '=', $request->brand);
}
if ($request->search) {
$this->searchQuery(
$query,
@ -54,7 +45,7 @@ class ServiceController extends Controller
$query->with(['brand']);
$serviceModels = $query->paginate($this->getPagination());
$serviceModels = $this->paginate($query);
return view('bo.pages.services.index', compact('serviceModels', 'searchFields'));
}
@ -67,7 +58,8 @@ class ServiceController extends Controller
*/
public function create(Service $service): \Illuminate\Contracts\View\View
{
$brandModels = CollectionHelper::paginate(Brand::getUserAssociatedBrands(), $this->jsonItemPerPagination);
$brandModels = Brand::all();
return view('bo.pages.services.create', [
'serviceModel' => $service,
'brandModels' => $brandModels
@ -112,7 +104,8 @@ class ServiceController extends Controller
*/
public function edit(Service $service): \Illuminate\Contracts\View\View
{
$brandModels = CollectionHelper::paginate(Brand::getUserAssociatedBrands(), $this->jsonItemPerPagination);
$brandModels = Brand::all();
return view('bo.pages.services.edit', [
'serviceModel' => $service,
'brandModels' => $brandModels

View file

@ -3,24 +3,13 @@
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\Answer;
use App\Models\Question;
use App\Models\SingleOptin;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
class SingleOptinController extends Controller
{
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(SingleOptin::class);
}
/**
* Display a listing of the resource.
*
@ -29,29 +18,33 @@ class SingleOptinController extends Controller
*/
public function index(Request $request): \Illuminate\Contracts\View\View
{
$questionModels = null;
$answerModels = null;
$searchFields = trans('E-mail');
/** @var \Illuminate\Database\Eloquent\Builder $query */
$query = SingleOptin::query();
if ($request->filled('search')) {
$query = SingleOptin::query();
$this->applyBrandAndAnswerFilters($request, $query, $questionModels, $answerModels);
$this->applySearch($request, $query);
$this->sortQuery($query);
$query->with('brand');
$singleoptinModels = $query->simplePaginate($this->getPagination());
if ($request->brand) {
$query = $query->whereHas('brand', function (Builder $q) use ($request) {
$q->where('id', $request->brand);
});
}
if ($request->search) {
$this->searchQuery(
$query,
$request->search,
null,
'email',
);
}
return view('bo.pages.singleoptins.index', [
'singleoptinModels' => $singleoptinModels ?? collect(),
'searchFields' => $searchFields,
'questionModels' => $questionModels,
'answerModels' => $answerModels,
]);
}
$this->sortQuery($query);
$searchFields = trans('E-mail');
$query->with(['brand']);
/** @var \Illuminate\Pagination\AbstractPaginator */
$singleoptinModels = $this->paginate($query);
return view('bo.pages.singleoptins.index', compact('singleoptinModels', 'searchFields'));
}
/**
* Remove the specified resource from storage.
@ -61,7 +54,6 @@ class SingleOptinController extends Controller
*/
public function destroy(SingleOptin $singleoptin): \Illuminate\Http\RedirectResponse
{
$singleoptin->answers()->detach();
if ($singleoptin->deleteOrFail()) {
return redirect()->route('bo.single_optins.index')
->with('success', trans(
@ -75,59 +67,4 @@ class SingleOptinController extends Controller
['model' => Str::singular(trans('models.classes.singleoptins'))]
));
}
/**
* Undocumented function
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Support\Collection|null $questionModels
* @param \Illuminate\Support\Collection|null $answerModels
* @return void
*/
private function applyBrandAndAnswerFilters(
Request $request,
Builder $query,
?Collection &$questionModels,
?Collection &$answerModels
): void {
if (!$request->filled('brand') || !(int) $request->brand) {
return;
}
$brandId = $request->brand;
$query->whereHas('brand', fn($q) => $q->whereKey($brandId));
$questionModels = Question::getAllModelsFromBrand($brandId);
if (!$request->filled('question')) {
return;
}
$questionId = $request->question;
$question = Question::find($questionId);
if ($question) {
$questionAnswers = $question->answers->modelKeys();
$query->whereHas('answers', fn($q) => $q->whereIn('answer_id', $questionAnswers));
$answerModels = Answer::getAllAnswersFromQuestion($questionId);
}
if ($request->filled('answer')) {
$query->whereHas('answers', fn($q) => $q->whereKey($request->answer));
}
}
/**
* Apply search filter.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Database\Eloquent\Builder $query
* @return void
*/
private function applySearch(Request $request, Builder $query): void
{
if ($request->filled('search')) {
$this->searchQuery($query, $request->search, null, 'email');
}
}
}

View file

@ -7,11 +7,10 @@ use App\Http\Requests\Bo\StatsByBrandRequest;
use App\Http\Requests\Bo\StatsByQuestionRequest;
use App\Models\Brand;
use App\Models\BrandStat;
use App\Models\ModelAnswer;
use App\Models\CustomerAnswer;
use App\Models\Question;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Schema;
use League\Csv\Writer;
@ -25,7 +24,6 @@ class StatsController extends Controller
*/
public function viewStatsByBrand(StatsByBrandRequest $request)
{
Gate::authorize('viewStatsByBrand');
$stats = null;
if ($request->post()) {
$stats = BrandStat::query()->whereDate('created_at', '>=', $request->start)
@ -70,7 +68,7 @@ class StatsController extends Controller
}
$w->insertOne($cols);
}
return response((string) $w, 200, [
return response((string)$w, 200, [
'Content-Encoding' => 'none',
'Content-Type' => 'text/csv; charset=UTF-8',
// phpcs:ignore Generic.Files.LineLength.TooLong
@ -87,7 +85,6 @@ class StatsController extends Controller
*/
public function viewByQuestion(StatsByQuestionRequest $request): \Illuminate\Contracts\View\View
{
Gate::authorize('viewByQuestion');
$question = null;
$answers = null;
$answersByDays = null;
@ -135,7 +132,7 @@ class StatsController extends Controller
$dayAnswers->values()->all()
));
}
return response((string) $w, 200, [
return response((string)$w, 200, [
'Content-Encoding' => 'none',
'Content-Type' => 'text/csv; charset=UTF-8',
// phpcs:ignore Generic.Files.LineLength.TooLong
@ -155,12 +152,10 @@ class StatsController extends Controller
*/
private function answersByDays(Collection $answers, Carbon $start, Carbon $end): \Illuminate\Support\Collection
{
Gate::authorize('answersByDays');
// * Totalize by date all answers.
$answersByDays = ModelAnswer::query()
->whereDate('created_at', '>=', $start)
$answersByDays = CustomerAnswer::query()->whereDate('created_at', '>=', $start)
->whereDate('created_at', '<=', $end)
->whereIn('answer_id', $answers->modelKeys())
->whereIn('answer_id', $answers->pluck('id'))
->get()->map(function ($custAnswer) {
return [
'answer_id' => $custAnswer->answer_id,

View file

@ -5,15 +5,13 @@ 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\Brand;
use App\Models\User;
use App\Traits\Controllers\UpdateModelPublished;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Uwil\LaravelCommon\Helpers\CollectionHelper;
class UserController extends Controller
class UsersController extends Controller
{
use UpdateModelPublished;
@ -33,14 +31,14 @@ class UserController extends Controller
*/
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $query */
$query = User::query();
/** @var \Illuminate\Database\Eloquent\Builder $userModels */
$userModels = User::query();
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$query,
$userModels,
$search,
null,
'first_name',
@ -57,10 +55,10 @@ class UserController extends Controller
]);
/** Sort columns with a query */
$this->sortQuery($query);
$this->sortQuery($userModels);
/** Custom pagination */
$userModels = $query->paginate($this->getPagination());
$userModels = $this->paginate($userModels);
return view('bo.pages.users.index', compact('userModels', 'search', 'searchFields'));
}
@ -74,8 +72,7 @@ class UserController extends Controller
*/
public function create(User $user): \Illuminate\Contracts\View\View
{
$brandModels = CollectionHelper::paginate(Brand::getUserAssociatedBrands(), $this->jsonItemPerPagination);
return view('bo.pages.users.create', ['userModel' => $user, 'brandModels' => $brandModels]);
return view('bo.pages.users.create', ['userModel' => $user]);
}
/**
@ -91,7 +88,6 @@ class UserController extends Controller
$user->fill($request->validated());
if ($user->saveOrFail()) {
$user->brands()->sync(collect($request->validated()['brand_id'])->pluck('id')->toArray());
return redirect()->route('bo.users.edit', $user)
->with('success', __('crud.messages.has_been_created', [
'model' => Str::ucfirst(__('models.classes.user'))
@ -112,8 +108,7 @@ class UserController extends Controller
*/
public function edit(User $user): \Illuminate\Contracts\View\View
{
$brandModels = CollectionHelper::paginate(Brand::getUserAssociatedBrands(), $this->jsonItemPerPagination);
return view('bo.pages.users.edit', ['userModel' => $user, 'brandModels' => $brandModels]);
return view('bo.pages.users.edit', ['userModel' => $user]);
}
/**
@ -129,7 +124,6 @@ class UserController extends Controller
$user->fill($request->validated());
if ($user->saveOrFail()) {
$user->brands()->sync(collect($request->validated()['brand_id'])->pluck('id')->toArray());
return redirect()->route('bo.users.edit', $user)
->with('success', __('crud.messages.has_been_updated', [
'model' => Str::ucfirst(__('models.classes.user'))

View file

@ -4,12 +4,11 @@ namespace App\Http\Controllers;
use App\Enums\Pagination\ItemsPerPaginationEnum;
use App\Enums\Theme\BootstrapThemeEnum;
use App\Http\Requests\Bo\PaginateModelsRequest;
use App\Http\Requests\Bo\ThemeRequest;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Schema;
@ -21,9 +20,6 @@ class Controller extends BaseController
use AuthorizesRequests;
use ValidatesRequests;
/** @var integer $itemPerPagination */
public $jsonItemPerPagination = 5;
/**
* Build Search query on specified fields
* splitting search into words or using whole sentence
@ -160,7 +156,7 @@ class Controller extends BaseController
Session::put("$rName.sort_way", request()->sort_way);
Session::put("$rName.sorted", true);
}
if (request()->rstSrts) {
if (request()->rst) {
Session::remove("$rName.sorted");
Session::remove("$rName.sort_col");
Session::remove("$rName.sort_way");
@ -189,13 +185,16 @@ class Controller extends BaseController
}
/**
* Get the number of items per page.
* Customize pagination with cache or config (default).
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \App\Enums\Pagination\ItemsPerPaginationEnum|null $pagination
* @return integer
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*/
protected function getPagination(ItemsPerPaginationEnum $pagination = null): int
{
protected function paginate(
Builder $query,
ItemsPerPaginationEnum $pagination = null
): \Illuminate\Contracts\Pagination\LengthAwarePaginator {
$currentRoutePath = Str::slug(request()->route()->getName());
$cacheKey = "pagination.{$currentRoutePath}";
$pagination = $pagination ?? ItemsPerPaginationEnum::twelve;
@ -207,35 +206,20 @@ class Controller extends BaseController
if (Cache::get($cacheKey) !== $pagination) {
Cache::put($cacheKey, $pagination);
}
return $pagination;
}
/**
* Get a paginate resource.
*
* @param \App\Http\Requests\Bo\PaginateModelsRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function jsonPaginateModels(PaginateModelsRequest $request): \Illuminate\Http\JsonResponse
{
/** @var \Illuminate\Database\Eloquent\Model $models */
$models = $request->targetModel::query()
->where($request->input('fieldName') ?? 'name', 'like', "%{$request->input('search')}%")
->orderBy($request->input('fieldName') ?? 'name', 'ASC')
->paginate($request->input('paginate') ?? 10);
return \response()->json($models);
$query = $query->paginate($pagination);
return $query;
}
/**
* Set the bootstrap theme or light (default).
*
* @param \App\Http\Requests\Bo\ThemeRequest $request
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
protected function setTheme(ThemeRequest $request): \Illuminate\Http\RedirectResponse
protected function setTheme(Request $request): \Illuminate\Http\RedirectResponse
{
$theme = ToolboxHelper::getValidatedEnum(
$request->validated('theme') ?: Cache::get('theme', BootstrapThemeEnum::light->value()),
$request->theme ?: Cache::get('theme', BootstrapThemeEnum::light->value()),
'theme',
'\App\Http\Controllers\BootstrapThemeEnum',
);

View file

@ -20,6 +20,7 @@ class Kernel extends HttpKernel
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\App\Http\Middleware\HtmlSanitizer::class
];
/**
@ -38,6 +39,7 @@ class Kernel extends HttpKernel
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\SecureHeaders::class
],
'api' => [
\Illuminate\Routing\Middleware\ThrottleRequests::class . ':api',
\App\Http\Middleware\JsonOnly::class,
@ -45,11 +47,6 @@ class Kernel extends HttpKernel
\App\Http\Middleware\ApiCheckTimeStamps::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class
],
'saml' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
],
];
/**
@ -60,17 +57,16 @@ class Kernel extends HttpKernel
* @var array
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'htmlsanitize' => \App\Http\Middleware\HtmlSanitizer::class,
'logoutusers' => \App\Http\Middleware\LogoutUnpublishedUsers::class,
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'htmlsanitize' => \App\Http\Middleware\HtmlSanitizer::class,
];
}

View file

@ -57,8 +57,9 @@ class ApiResponse
$status = intval($response->getStatusCode() / 100) == 2 ? ApiStatus::OK : ApiStatus::KO;
if (Helper::isRequestForApiV2($request)) {
return $response->setData(new V2ApiResponse($status, (object) $data));
return $response->setData(new V2ApiResponse((object)$data, $status));
}
return $response->setData(new V4ApiResponse($status, (object) $data));
return $response->setData(new V4ApiResponse((object)$data, $status));
}
}

View file

@ -31,7 +31,7 @@ class Authenticate extends Middleware
}
}
}
return route('bo.login');
return route('login');
}
return null;

View file

@ -4,6 +4,7 @@ namespace App\Http\Middleware;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class HtmlSanitizer
{
@ -15,11 +16,8 @@ class HtmlSanitizer
* @param string|array ...$params
* @return \Symfony\Component\HttpFoundation\Response
*/
public function handle(
Request $request,
\Closure $next,
string|array ...$params
): \Symfony\Component\HttpFoundation\Response {
public function handle(Request $request, \Closure $next, string|array ...$params): Response
{
$inputs = $request->all();
/** @var callable|null */
$cleanInput = null;

View file

@ -1,28 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class LogoutUnpublishedUsers
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
/** @var \App\Models\User|null $userModel */
$userModel = Auth::user();
if (!is_null($userModel) && !$userModel->published) {
Auth::logout();
return redirect()->route('bo.login')->with('warning', trans('auth.unpublished_user'));
}
return $next($request);
}
}

View file

@ -15,7 +15,6 @@ class TrustHosts extends Middleware
{
return [
$this->allSubdomainsOfApplicationUrl(),
config('app.k8s_internal_url'),
];
}
}

View file

@ -22,7 +22,7 @@ class GetBrandStatsRequest extends BaseApiRequest
public function rules(): array
{
try {
if (Carbon::parse($this->end)->floatDiffInMonths(Carbon::parse($this->start), true) > 3) {
if (Carbon::parse($this->end)->floatDiffInMonths(Carbon::parse($this->start)) > 3) {
throw ValidationException::withMessages([
'Vous devez sélectionner une période de maximum 3 mois'
]);
@ -36,7 +36,7 @@ class GetBrandStatsRequest extends BaseApiRequest
throw $e;
}
return [
'start' => 'required|date|date_format:Y-m-d|before_or_equal:end',
'start' => 'required|date|date_format:Y-m-d',
'end' => 'required|date|date_format:Y-m-d|after_or_equal:start',
];
}

View file

@ -23,7 +23,7 @@ class GetQuestionStatsRequest extends BaseApiRequest
public function rules(): array
{
try {
if (Carbon::parse($this->end)->floatDiffInMonths(Carbon::parse($this->start), true) > 3) {
if (Carbon::parse($this->end)->floatDiffInMonths(Carbon::parse($this->start)) > 3) {
throw ValidationException::withMessages([
'Vous devez sélectionner une période de maximum 3 mois'
]);
@ -37,7 +37,7 @@ class GetQuestionStatsRequest extends BaseApiRequest
throw $e;
}
return [
'start' => 'required|date|date_format:Y-m-d|before_or_equal:end',
'start' => 'required|date|date_format:Y-m-d',
'end' => 'required|date|date_format:Y-m-d|after_or_equal:start',
];
}

View file

@ -15,10 +15,8 @@ class SingleOptinRequest extends BaseApiRequest
public function rules(): array
{
return [
'email' => 'required|email:rfc,dns,spoof,filter',
'opt_in' => 'required|boolean',
'answer_id' => 'sometimes|array|min:1',
'answer_id.*' => 'sometimes|integer|exists:answers,id',
'email' => 'required|email:rfc,dns,spoof,filter',
'opt_in' => 'required|boolean'
];
}
}

View file

@ -28,7 +28,7 @@ class StoreCustomerAnswers extends BaseApiRequest
'question' => [
'required',
"exists:$questionTableName,id",
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) use ($question, $brandId) {
if ($question->brand->id !== $brandId) {
$fail("$attribute does not belong to brand $brandId");

View file

@ -5,6 +5,7 @@ namespace App\Http\Requests\Api\V4;
use App\Http\Sanitizers\RemoveSpaces;
use ArondeParon\RequestSanitizer\Sanitizers\Capitalize;
use ArondeParon\RequestSanitizer\Sanitizers\Lowercase;
use ArondeParon\RequestSanitizer\Sanitizers\RemoveNonNumeric;
use ArondeParon\RequestSanitizer\Sanitizers\Trim;
use ArondeParon\RequestSanitizer\Sanitizers\TrimDuplicateSpaces;
use ArondeParon\RequestSanitizer\Traits\SanitizesInputs;
@ -28,7 +29,7 @@ abstract class BaseApiRequest extends FormRequest
'password' => Trim::class,
'phone' => [Trim::class, RemoveSpaces::class],
'email' => [Lowercase::class, Trim::class, RemoveSpaces::class],
'postcode' => [Trim::class]
'postcode' => [RemoveNonNumeric::class, Trim::class]
];
/**

View file

@ -19,7 +19,7 @@ class GetBrandStatsRequest extends BaseApiRequest
public function rules(): array
{
try {
if (Carbon::parse($this->end)->floatDiffInMonths(Carbon::parse($this->start), true) > 3) {
if (Carbon::parse($this->end)->floatDiffInMonths(Carbon::parse($this->start)) > 3) {
throw ValidationException::withMessages([
'Vous devez sélectionner une période de maximum 3 mois'
]);
@ -27,13 +27,13 @@ class GetBrandStatsRequest extends BaseApiRequest
} catch (\Exception $e) {
if (\get_class($e) !== ValidationException::class) {
throw ValidationException::withMessages([
'Les dates sélectionnées ne sembles pas être valides'
'Les dates séléectionnées ne sembles pas être valides'
]);
}
throw $e;
}
return [
'start' => 'required|date|date_format:Y-m-d|before_or_equal:end',
'start' => 'required|date|date_format:Y-m-d',
'end' => 'required|date|date_format:Y-m-d|after_or_equal:start',
];
}

View file

@ -19,7 +19,7 @@ class GetQuestionStatsRequest extends BaseApiRequest
public function rules(): array
{
try {
if (Carbon::parse($this->end)->floatDiffInMonths(Carbon::parse($this->start), true) > 3) {
if (Carbon::parse($this->end)->floatDiffInMonths(Carbon::parse($this->start)) > 3) {
throw ValidationException::withMessages([
'Vous devez sélectionner une période de maximum 3 mois'
]);
@ -33,7 +33,7 @@ class GetQuestionStatsRequest extends BaseApiRequest
throw $e;
}
return [
'start' => 'required|date|date_format:Y-m-d|before_or_equal:end',
'start' => 'required|date|date_format:Y-m-d',
'end' => 'required|date|date_format:Y-m-d|after_or_equal:start',
];
}

View file

@ -1,86 +0,0 @@
<?php
namespace App\Http\Requests\Api\V4;
use App\Enums\Country;
use App\Http\Sanitizers\RemoveSpaces;
use ArondeParon\RequestSanitizer\Sanitizers\Capitalize;
use ArondeParon\RequestSanitizer\Sanitizers\Lowercase;
use ArondeParon\RequestSanitizer\Sanitizers\Trim;
use ArondeParon\RequestSanitizer\Sanitizers\TrimDuplicateSpaces;
class SendClaimRequest extends BaseApiRequest
{
/**
* Using these sanitizers before proceeding the request.
*
* @var array
*/
protected $sanitizers = [
'first_name' => [Trim::class, TrimDuplicateSpaces::class, Capitalize::class],
'last_name' => [Trim::class, TrimDuplicateSpaces::class, Capitalize::class],
'email' => [Trim::class, RemoveSpaces::class, Lowercase::class],
'address' => [Trim::class, TrimDuplicateSpaces::class],
'city' => [Trim::class, TrimDuplicateSpaces::class],
'postcode' => [Trim::class],
'file' => [Trim::class],
'message' => [Trim::class],
];
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
$countryList = ($this->currentCountry && $this->currentCountry->get('iso')) ?
$this->currentCountry->get('iso') : implode(',', Country::getValidIsoAplha2Codes());
return [
'first_name' => 'required|string|between:3,255',
'last_name' => 'required|string|between:3,255',
'email' => 'required|email:rfc,dns,spoof,filter|max:255',
'address' => 'sometimes|nullable|string|max:255',
'city' => 'sometimes|nullable|string|between:2,255',
'postcode' => ['sometimes', 'nullable', 'string', "postal_code:{$countryList}"],
'file' => [
'sometimes',
'nullable',
'string',
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) {
// Base64 validation.
$decoded = base64_decode($value, true);
if ($decoded === false) {
return $fail('Le fichier doit être un encodage base64 valide.');
}
// Check the size (5 MB max = 5 * 1024 * 1024).
if (strlen($decoded) > 5 * 1024 * 1024) {
return $fail('Le fichier est trop volumineux (max 5 Mo).');
}
// Check the MIME type.
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_buffer($finfo, $decoded);
$allowed = ['image/jpeg', 'image/png', 'application/pdf'];
if (!in_array($mime, $allowed)) {
return $fail('Le fichier doit être une image JPG/PNG ou un PDF.');
}
}
],
'message' => 'required|string|min:3',
'subject_id' => 'required|integer|exists:claim_subjects,id',
];
}
}

View file

@ -12,10 +12,8 @@ class SingleOptinRequest extends BaseApiRequest
public function rules(): array
{
return [
'email' => 'required|email:rfc,dns,spoof,filter',
'opt_in' => 'required|boolean',
'answer_id' => 'sometimes|array|min:1',
'answer_id.*' => 'sometimes|integer|exists:answers,id',
'email' => 'required|email:rfc,dns,spoof,filter',
'opt_in' => 'required|boolean'
];
}
}

View file

@ -25,7 +25,7 @@ class StoreCustomerAnswers extends BaseApiRequest
'question' => [
'required',
"exists:$questionTableName,id",
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) use ($question, $brandId) {
if ($question->brand->id !== $brandId) {
$fail("$attribute does not belong to brand $brandId");

View file

@ -3,12 +3,9 @@
namespace App\Http\Requests\Bo\Brands;
use Illuminate\Foundation\Http\FormRequest;
use Uwil\LaravelCommon\Traits\Requests\HasPicture;
class StoreBrandRequest extends FormRequest
{
use HasPicture;
/**
* Determine if the user is authorized to make this request.
*
@ -19,16 +16,6 @@ class StoreBrandRequest extends FormRequest
return auth('backend')->check();
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation(): void
{
$this->mergePicture('logo');
}
/**
* Get the validation rules that apply to the request.
*
@ -36,16 +23,9 @@ class StoreBrandRequest extends FormRequest
*/
public function rules(): array
{
$rules = [
'name' => 'required|unique:brands,name|string|min:5|max:255',
'url' => 'required|url|min:5|max:255',
'url_claim' => 'required|url|min:5|max:255',
'domain' => 'required|unique:brands,domain|string|min:5|max:255',
return [
'name' => 'required|string|min:5|max:255',
];
return \array_merge(
$rules,
$this->pictureRules('logo', 550, 550, 550, 550),
);
}
/**
@ -56,11 +36,7 @@ class StoreBrandRequest extends FormRequest
public function attributes(): array
{
return [
'name' => trans('validation.attributes.name'),
'logo' => trans('validation.custom.logo'),
'url' => trans('validation.custom.url'),
'url_claim' => trans('validation.custom.url_claim'),
'domain' => trans('validation.custom.domain'),
'name' => trans('Nom de la marque'),
];
}
}

View file

@ -4,34 +4,4 @@ namespace App\Http\Requests\Bo\Brands;
class UpdateBrandRequest extends StoreBrandRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
/** @var integer $brandModelKey */
$brandModelKey = request()->route()->parameter('brand')->getKey();
$rules = [
'name' => [
'required',
"unique:brands,name,$brandModelKey",
'string',
'min:5',
'max:255',
],
'domain' => [
'required',
"unique:brands,domain,$brandModelKey",
'string',
'min:5',
'max:255',
],
];
return \array_merge(
parent::rules(),
$rules,
);
}
}

View file

@ -1,47 +0,0 @@
<?php
namespace App\Http\Requests\Bo\ClaimMailTemplates;
use Illuminate\Foundation\Http\FormRequest;
use Uwil\LaravelCommon\Rules\WysiwygLength;
class ClaimMailTemplateRequest 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 [
'brand_id' => 'required|exists:brands,id',
'label' => 'required|string|min:3|max:255',
'content' => ['required', 'string', new WysiwygLength()],
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'brand_id' => trans('models.classes.brands'),
'label' => trans('validation.custom.label'),
'content' => trans('validation.attributes.content'),
];
}
}

View file

@ -1,66 +0,0 @@
<?php
namespace App\Http\Requests\Bo\ClaimMessages;
use App\Enums\Claims\ClaimMessageType;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Enum;
class StoreMessageRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return auth('backend')->check();
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation(): void
{
/** @var \App\Models\User $userModel */
$userModel = auth('backend')->user();
$this->merge([
'user_id' => $userModel->getKey(),
'type' => ClaimMessageType::human->value(),
]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'claim_id' => 'required|numeric|exists:claims,id',
'user_id' => 'required|numeric|exists:users,id',
'content' => 'required|string|between:3,255',
'type' => ['required', new Enum(ClaimMessageType::class)],
];
}
/**
* Get custom attributes for validator errors.
*
* @return array<string,\Illuminate\Contracts\Translation\Translator|string|array<mixed>|null>
*/
public function attributes(): array
{
return [
'claim_id' => trans('models.classes.claims'),
'user_id' => trans('models.classes.users'),
'content' => trans('validation.attributes.content'),
'type' => trans('validation.custom.type'),
];
}
}

View file

@ -1,44 +0,0 @@
<?php
namespace App\Http\Requests\Bo\ClaimMessages;
use Illuminate\Foundation\Http\FormRequest;
class UpdateMessageRequest 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 [
'claim_message_id' => 'required|numeric|exists:claim_messages,id',
'content' => 'required|string|between:3,255',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array<string,\Illuminate\Contracts\Translation\Translator|string|array<mixed>|null>
*/
public function attributes(): array
{
return [
'claim_message_id' => trans('models.classes.claim_messages'),
'content' => trans('validation.attributes.content'),
];
}
}

View file

@ -1,44 +0,0 @@
<?php
namespace App\Http\Requests\Bo\ClaimSubjects;
use Illuminate\Foundation\Http\FormRequest;
class ClaimSubjectRequest 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 [
'brand_id' => 'required|exists:brands,id',
'label' => 'required|string|min:3|max:255',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'brand_id' => trans('models.classes.brands'),
'label' => trans('validation.custom.label'),
];
}
}

View file

@ -1,65 +0,0 @@
<?php
namespace App\Http\Requests\Bo\Claims;
use App\Enums\ExportFormat;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class ExportClaimRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return auth('backend')->check();
}
/**
* Prepare the data for validation.
*
* @return void
*/
public function prepareForValidation()
{
$this->merge([
'claimList' => json_decode($this->input('claimList'), true)
]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'format' => [
'required',
Rule::enum(ExportFormat::class)
],
'claimList' => 'required|array',
'claimList.*' => 'required|numeric|exists:claims,id',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array<string,\Illuminate\Contracts\Translation\Translator|string|array<mixed>|null>
*/
public function attributes(): array
{
return [
'brand_id' => trans('models.classes.brands'),
'subject_id' => trans('models.classes.claimsubjects'),
'type' => trans('validation.custom.type'),
'status' => trans('validation.custom.status'),
'response_type' => trans('validation.custom.response_type'),
];
}
}

View file

@ -1,44 +0,0 @@
<?php
namespace App\Http\Requests\Bo\Claims;
use Illuminate\Foundation\Http\FormRequest;
class SendClaimAnswerRequest 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 [
'claim_id' => 'required|numeric|exists:claims,id',
'content' => 'required|string|between:3,2000',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array<string,\Illuminate\Contracts\Translation\Translator|string|array<mixed>|null>
*/
public function attributes(): array
{
return [
'claim_id' => trans('models.classes.claims'),
'content' => trans('validation.attributes.content'),
];
}
}

View file

@ -1,54 +0,0 @@
<?php
namespace App\Http\Requests\Bo\Claims;
use App\Enums\Claims\ClaimResponseType;
use App\Enums\Claims\ClaimStatus;
use App\Enums\Claims\ClaimType;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Enum;
class UpdateClaimRequest 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 [
'brand_id' => 'required|numeric|exists:brands,id',
'subject_id' => 'required|numeric|exists:claim_subjects,id',
'type' => ['required', new Enum(ClaimType::class)],
'status' => ['required', new Enum(ClaimStatus::class)],
'response_type' => ['present', 'nullable', new Enum(ClaimResponseType::class)],
];
}
/**
* Get custom attributes for validator errors.
*
* @return array<string,\Illuminate\Contracts\Translation\Translator|string|array<mixed>|null>
*/
public function attributes(): array
{
return [
'brand_id' => trans('models.classes.brands'),
'subject_id' => trans('models.classes.claimsubjects'),
'type' => trans('validation.custom.type'),
'status' => trans('validation.custom.status'),
'response_type' => trans('validation.custom.response_type'),
];
}
}

View file

@ -1,35 +0,0 @@
<?php
namespace App\Http\Requests\Bo\Claims;
use App\Enums\Users\RoleEnum;
use App\Policies\Rules\UserStaticRules;
use Illuminate\Foundation\Http\FormRequest;
class ViewClaimAttachmentRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
/** @var \App\Models\User $user */
$user = auth('backend')->user();
return auth('backend')->check() &&
($user->brands->contains($this->claim->brand->id) ||
UserStaticRules::atLeastRole($user, RoleEnum::admin));
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [];
}
}

View file

@ -29,7 +29,7 @@ class CustomerExport extends FormRequest
'export_type' => [
'required',
'numeric',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) {
if (!\in_array($value, CustomerExportType::toValues())) {
$fail(trans('Le type d\'export invalide'));

View file

@ -1,52 +0,0 @@
<?php
namespace App\Http\Requests\Bo;
use Illuminate\Foundation\Http\FormRequest;
class PaginateModelsRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string,\Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'targetModel' => [
'required',
'string',
'starts_with:App\\Models\\',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) {
if (!class_exists($value)) {
$fail("Le modèle spécifié n'existe pas.");
return;
}
if (!is_subclass_of($value, \Illuminate\Database\Eloquent\Model::class)) {
$fail("Le modèle spécifié n'est pas un modèle Eloquent.");
}
},
],
'page' => 'required|integer|min:1',
'paginate' => 'required|integer|min:1',
'fieldName' => 'required|string|alpha_dash|max:255',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array<string,\Illuminate\Contracts\Translation\Translator|string|array<mixed>|null>
*/
public function attributes(): array
{
return [
'targetModel' => trans('validation.custom.targetModel'),
'page' => trans('validation.custom.page'),
'paginate' => trans('validation.custom.paginate'),
'fieldName' => trans('validation.custom.fieldName'),
];
}
}

View file

@ -18,6 +18,18 @@ class StoreQuestionRequest extends FormRequest
return auth('backend')->check();
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation(): void
{
$this->merge([
'brand_id' => collect($this->brand)->pluck('id')->first()
]);
}
/**
* Get the validation rules that apply to the request.
*

View file

@ -16,6 +16,18 @@ class StoreServiceRequest extends FormRequest
return auth('backend')->check();
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation(): void
{
$this->merge([
'brand_id' => collect($this->brand)->pluck('id')->first()
]);
}
/**
* Get the validation rules that apply to the request.
*

View file

@ -30,9 +30,9 @@ class StatsByBrandRequest extends FormRequest
{
if ($this->post()) {
try {
if ((Carbon::parse($this->end)->diffInDays(Carbon::parse($this->start)->addDay(), true)) > 365) {
if ((Carbon::parse($this->end)->diffInDays(Carbon::parse($this->start)) + 1) > 365) {
throw ValidationException::withMessages([
'Vous devez sélectionner une période de maximum 1 an'
'Vous devez sélectionner une période de maximum 1 ans'
]);
}
} catch (\Exception $e) {

View file

@ -30,7 +30,7 @@ class StatsByQuestionRequest extends FormRequest
{
if ($this->post()) {
try {
if ((Carbon::parse($this->end)->diffInDays(Carbon::parse($this->start)->addDay(), true)) > 365) {
if ((Carbon::parse($this->end)->diffInDays(Carbon::parse($this->start)) + 1) > 365) {
throw ValidationException::withMessages([
'Vous devez sélectionner une période de maximum 1 ans'
]);

View file

@ -1,34 +0,0 @@
<?php
namespace App\Http\Requests\Bo;
use App\Enums\Theme\BootstrapThemeEnum;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Enum;
class ThemeRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string,\Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'theme' => ['required', new Enum(BootstrapThemeEnum::class)],
];
}
/**
* Get custom attributes for validator errors.
*
* @return array<string,\Illuminate\Contracts\Translation\Translator|string|array<mixed>|null>
*/
public function attributes(): array
{
return [
'theme' => trans('validation.custom.theme'),
];
}
}

View file

@ -100,11 +100,11 @@ abstract class UpdateMiniCrudRequest extends FormRequest
}) : $input['dimensions'];
// * Adapt dimensions
$sizes = (object) [
$sizes = (object)[
'minHeight' => $dim['height'],
'maxHeight' => $dim['height'],
'minWith' => $dim['width'],
'maxWith' => $dim['width'],
'minWith' => $dim['width'],
'maxWith' => $dim['width'],
];
$rules = array_merge($rules, [
"data.$iptName.value" => [

View file

@ -5,6 +5,7 @@ namespace App\Http\Requests\Bo\Users;
use App\Enums\Users\RoleEnum;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Enum;
use Illuminate\Validation\Rules\Password;
class StoreUserRequest extends FormRequest
{
@ -23,11 +24,10 @@ class StoreUserRequest extends FormRequest
*
* @return void
*/
protected function prepareForValidation(): void
protected function prepareForValidation()
{
$this->merge([
'published' => $this->input('published') ? true : false,
'brand_id' => $this->input('brand_id') ?: [],
'published' => $this->published ? true : false
]);
}
@ -39,14 +39,19 @@ class StoreUserRequest extends FormRequest
public function rules(): array
{
return [
'first_name' => 'required|string|min:3|max:255',
'last_name' => 'required|string|min:3|max:255',
'email' => 'required|email:rfc,strict,dns,spoof,filter|unique:users,email',
'role' => ['required', new Enum(RoleEnum::class)],
'published' => 'required|boolean',
'brand_id' => 'present|array',
'brand_id.*' => 'sometimes|nullable|array',
'brand_id.*.id' => 'sometimes|nullable|numeric|exists:brands,id',
'first_name' => 'required|string|min:3|max:255',
'last_name' => 'required|string|min:3|max:255',
'email' => 'required|email:rfc,strict,dns,spoof,filter|unique:users,email',
'password' => [
'required', 'required_with:password_confirmation', 'confirmed', 'max:255', Password::min(8)
->letters()
->mixedCase()
->numbers()
->symbols()
->uncompromised()
],
'role' => ['required', new Enum(RoleEnum::class)],
'published' => 'required|boolean',
];
}
@ -58,14 +63,12 @@ class StoreUserRequest extends FormRequest
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'),
'published' => trans('validation.custom.publication'),
'brand_id' => trans('models.classes.brands'),
'brand_id.*' => trans('models.classes.brands'),
'brand_id.*.id' => trans('models.classes.brands'),
'first_name' => trans('validation.attributes.first_name'),
'last_name' => trans('validation.attributes.last_name'),
'email' => trans('validation.attributes.email'),
'password' => trans('validation.attributes.password'),
'role' => trans('validation.attributes.role'),
'published' => trans('validation.custom.publication')
];
}
}

View file

@ -2,6 +2,8 @@
namespace App\Http\Requests\Bo\Users;
use Illuminate\Validation\Rules\Password;
class UpdateUserRequest extends StoreUserRequest
{
/**
@ -9,11 +11,14 @@ class UpdateUserRequest extends StoreUserRequest
*
* @return void
*/
protected function prepareForValidation(): void
protected function prepareForValidation()
{
$this->merge([
'published' => $this->input('published') ? true : false,
'brand_id' => $this->input('brand_id') ?: [],
'published' => $this->published ? true : false,
'password' => $this->password ? $this->password : $this->route('user')->password,
'password_confirmation' => $this->password_confirmation ?
$this->password_confirmation :
$this->route('user')->password
]);
}
@ -24,15 +29,19 @@ class UpdateUserRequest extends StoreUserRequest
*/
public function rules(): array
{
/** @var \App\Models\User $userModel */
$userModel = request()->route()->parameter('user');
return array_merge(parent::rules(), [
'email' => [
'email' => [
'required',
'string',
'email:rfc,strict,dns,spoof,filter',
'unique:users,email,' . $userModel->getKey(),
'unique:users,email,' . $this->route('user')->id . ',id',
],
'password' => [
'required', 'required_with:password_confirmation', 'confirmed', 'max:255', Password::min(8)
->letters()
->mixedCase()
->numbers()
->symbols()
->uncompromised()
],
]);
}

View file

View file

@ -16,10 +16,10 @@ class ApiResponse extends JsonResource
protected $status;
/**
* @param \App\Enums\ApiStatus $status
* @param \stdClass|null $data
* @param \App\Enums\ApiStatus $status
*/
public function __construct(ApiStatus $status, stdClass|null $data = null)
public function __construct(stdClass|null $data = null, ApiStatus $status)
{
$this->status = $status;
$this->data = $data;
@ -36,8 +36,8 @@ class ApiResponse extends JsonResource
{
//phpcs:enable
return [
'status' => $this->status->value(),
'data' => (array) ($this->data ?? []),
'status' => $this->status->value(),
'data' => (array)($this->data ?? []),
'version' => '3.0.6'
];
}

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