Compare commits

..

64 commits

Author SHA1 Message Date
Jérémy Munsch
306ca38756
en validation youtube video if fix typo 2024-02-09 12:59:56 +01:00
Jérémy Munsch
d4403814bc
joinus form fix video helper string 2024-02-09 12:59:21 +01:00
Jérémy Munsch
cd7f460a3d
Join us add vimeo video possibility 2024-02-09 12:53:55 +01:00
Jérémy Munsch
831979966a
Fixes #32 Add Home video vimeo possibility playing as loop except for youtube (need playlist) 2024-02-09 12:25:35 +01:00
Vincent BRIDIER
95d552c429 Merge branch 'issue-63' into 'develop'
clean blades - #63

See merge request ldc-web/projets/agis!32
2024-02-08 15:00:43 +00:00
Alexis Gousseau
76b8b61288 clean blades - #63 2024-02-07 12:15:42 +01:00
Vincent BRIDIER
e8871467c0 Merge branch 'products-images' into 'develop'
fix product images dimensions - #62

See merge request ldc-web/projets/agis!31
2024-02-02 10:43:43 +00:00
Alexis Gousseau
77ad125ad2 fix product images dimensions - #62 2024-02-02 09:50:06 +01:00
Vincent BRIDIER
dcf995bdc6 Merge branch 'issue-38' into 'develop'
bump cookies notice to v4.0.1 - #38

See merge request ldc-web/projets/agis!29
2024-01-30 10:49:14 +00:00
Alexis Gousseau
29016ad3ba bump cookies notice to v4.0.1 - #38 2024-01-30 09:46:31 +01:00
Jérémy Munsch
e1961fab93
disable executeJavascript for sitemap as no puppeteer 2024-01-29 17:08:57 +01:00
Vincent BRIDIER
fa7d44d03d Merge branch 'mulitple-issues' into 'develop'
Mulitple issues

See merge request ldc-web/projets/agis!28
2024-01-29 11:58:53 +00:00
Jérémy Munsch
4d363f8fac
Fix navlink active on ranges 2024-01-29 12:36:34 +01:00
Jérémy Munsch
80a394691c
Closes #48 2024-01-29 12:33:12 +01:00
Jérémy Munsch
1648636bfa
npm update 2024-01-29 12:25:46 +01:00
Jérémy Munsch
159cbbebe7
Closes #56 Fix phone number 2024-01-29 12:13:48 +01:00
Vincent BRIDIER
a53cfc7d55 Merge branch 'issue-60-nutrionnal-values-labels' into 'develop'
Updated nutrionnal values labels

See merge request ldc-web/projets/agis!27
2024-01-29 11:11:31 +00:00
Noah Lancien
c498e298d8 Updated nutrionnal values labels 2024-01-29 11:24:12 +01:00
Vincent BRIDIER
e2d3d7ec28 Merge branch 'php-stan-issues-48-57' into 'develop'
Php stan issues 48 57

See merge request ldc-web/projets/agis!26
2024-01-29 08:40:19 +00:00
Noah Lancien
bed9d9dde6 phpstan 2024-01-26 16:08:12 +01:00
Noah Lancien
19f6454ea3 Merge branch 'issue-57' into php-stan-issues-48-57 2024-01-26 13:09:38 +01:00
Noah Lancien
ffdd886ddc Merge remote-tracking branch 'origin/issue-48' into php-stan-issues-48-57 2024-01-26 13:03:07 +01:00
Vincent BRIDIER
0d17174052 Merge branch 'issue-51' into 'develop'
Added a BackToTop button

See merge request ldc-web/projets/agis!25
2024-01-26 11:14:50 +00:00
Vincent BRIDIER
50a99c7344 Merge branch 'issue-58' into 'develop'
paËlla

See merge request ldc-web/projets/agis!23
2024-01-26 11:13:50 +00:00
Vincent BRIDIER
41dba737cb Merge branch 'issue-59' into 'develop'
Added youtube channel link in join-us page

See merge request ldc-web/projets/agis!22
2024-01-26 11:13:27 +00:00
Vincent BRIDIER
ae4ab562dc Merge branch 'issue-52' into 'develop'
Issue 52, 53, 54, 55, 56 - Typos + footer link

See merge request ldc-web/projets/agis!21
2024-01-26 11:12:29 +00:00
Vincent BRIDIER
a04a65c232 Merge branch 'issue-36-seo-package' into 'develop'
Added the SEO package

See merge request ldc-web/projets/agis!20
2024-01-26 11:11:44 +00:00
Vincent BRIDIER
fd987180bb Merge branch 'fix-news-show' into 'develop'
apply text-center for news subtitle in news show blade

See merge request ldc-web/projets/agis!19
2024-01-26 11:10:21 +00:00
Vincent BRIDIER
58d359155f Merge branch 'issue-44' into 'develop'
Decorative picture is now aligned with the cooking advice part in produt.show

See merge request ldc-web/projets/agis!17
2024-01-26 11:06:57 +00:00
Vincent BRIDIER
1532e3a31d Merge branch 'issue-41' into 'develop'
Ranges title in homepage are now with different colors

See merge request ldc-web/projets/agis!16
2024-01-26 11:05:41 +00:00
Vincent BRIDIER
8e9f7f8419 Merge branch 'issue-32' into 'develop'
Issue 32 Fix Trait HasPicture (Blame pas possible à cause du squash), les parametres...

See merge request ldc-web/projets/agis!15
2024-01-26 11:04:33 +00:00
Vincent BRIDIER
d5c0c06294 Merge branch 'issue-46' into 'develop'
The width of black strip is now the same as title in about-us page

See merge request ldc-web/projets/agis!14
2024-01-26 11:02:59 +00:00
Vincent BRIDIER
0333649f48 Merge branch 'issue-35' into 'develop'
Issue 35

See merge request ldc-web/projets/agis!13
2024-01-26 11:02:19 +00:00
Vincent BRIDIER
6d482c4631 Merge branch 'develop' into issue-35 2024-01-26 12:01:21 +01:00
Noah Lancien
14b0382cdb Added a BackToTop button 2024-01-23 11:53:45 +01:00
Noah Lancien
54e501cd03 In contact form, dates are now dates inpiuts instead of string 2024-01-22 09:56:19 +00:00
Noah Lancien
d162f87f3e Some wording in the contact form 2024-01-22 09:55:27 +00:00
Noah Lancien
1f1d691cbe paËlla 2024-01-22 09:01:28 +01:00
Noah Lancien
be80ca6c6a Added youtube channel link in join-us page 2024-01-22 08:59:16 +01:00
Noah Lancien
bf365eadca Fix #53, added 'convictions' link in footer 2024-01-19 09:51:43 +01:00
Noah Lancien
28a8390c41 Fix #55, wording on ranges page 2024-01-19 09:48:29 +01:00
Noah Lancien
6ee0986b48 Fix #56, renamed civilities 2024-01-19 09:47:34 +01:00
Noah Lancien
239307c1b1 Fixed footer typo 2024-01-19 09:46:41 +01:00
Noah Lancien
81089b8e12 Fixed header typo 2024-01-19 09:45:25 +01:00
Noah Lancien
30ee51d9d1 Added the SEO package 2024-01-08 10:54:20 +01:00
Nicolas Demangeat
398c762b33 apply text-center for news subtitle in news show blade 2023-12-29 15:18:09 +01:00
Jérémy Munsch
a952ea6ac6
Fixes #48 Add style to ck-content + fix all front WYSIWYG taht where missing ck-content 2023-12-22 11:46:21 +01:00
Jérémy Munsch
8318e83d0b
Update secure header with vite config form base project 2023-12-22 11:23:25 +01:00
Jérémy Munsch
25d9e1cac6
Fix commitments responsive on products.show 2023-12-22 11:05:14 +01:00
Jérémy Munsch
cffa0f34a8
Closes #37 add sitemap 2023-12-22 09:49:02 +01:00
Noah Lancien
9a8e0f8f9a Decorative picture is now aligned with the cooking advice part in produt.show 2023-12-21 16:10:31 +01:00
Noah Lancien
5854fc274b Ranges title in homepage are now with different colors 2023-12-21 15:51:00 +01:00
Jérémy Munsch
608629307f
Closes #32 Add youtube video on Homepage
+ remove Unused video Frame
+ FIX  Video Inlaid in Cookie Notice
2023-12-21 15:42:36 +01:00
Noah Lancien
b32f5161b3 The width of black strip is now the same as title in about-us page 2023-12-21 15:22:05 +01:00
Jérémy Munsch
f06e87daf5
Update HasPicture 2023-12-21 15:18:27 +01:00
Jérémy Munsch
13d998ac9c
Fix Trait HasPicture (Blame pas possible à cause du squash), les parametres ajoutés doivent être à la fin pour ne pas niquer le reste du code 2023-12-21 14:41:27 +01:00
Jérémy Munsch
fed7a112c2
Bump inputs form base project 2023-12-21 13:56:34 +01:00
Jérémy Munsch
f34bb70504
cookie notice bump to 4.0.0 2023-12-21 12:09:47 +01:00
Jérémy Munsch
53ccbb5af8
Closes #35 Change civility labels 2023-12-21 10:59:50 +01:00
Jérémy Munsch
8949c3b427
bump front stack + add favicon Fixes #40 2023-12-21 10:45:43 +01:00
Jérémy Munsch
1963b827d8
composer update 2023-12-21 09:28:30 +01:00
Vincent BRIDIER
256a8b5542 Add foreach for contactformé 2023-12-19 16:50:48 +01:00
Vincent BRIDIER
6c63d52996 Merge branch '2.0.0' into 'develop'
2.0.0

See merge request ldc-web/projets/agis!1
2023-12-19 15:09:12 +00:00
noah.lancien
b557533b72 2.0.0 with Laravel 2023-12-19 15:09:12 +00:00
220 changed files with 6844 additions and 7637 deletions

View file

@ -3,7 +3,6 @@ APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
ASSET_URL=http://localhost
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
@ -23,12 +22,6 @@ QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
@ -37,26 +30,7 @@ MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=mt1
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
MAIL_CONTACTFORM_RECEIVER=
# Sentry
SENTRY_LARAVEL_DSN=
@ -69,3 +43,6 @@ MATOMO_SITE_ID=
GOOGLE_RECAPTCHA_PUBLIC=
GOOGLE_RECAPTCHA_PRIVATE=
GOOGLE_RECAPTCHA_VERSION=
# Admin secure
LDC_WEB_API_KEY=

13
.gitignore vendored
View file

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

5
Dockerfile Normal file
View file

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

View file

@ -1 +1,7 @@
# Agis
## CRON
Put every min in PROD :
```
php artisan schedule:run
```

View file

@ -28,35 +28,35 @@ class UserCreate extends Command
/**
* The first name of the new user.
*
* @var string
* @var string|null
*/
protected $first_name;
/**
* The last name of the new user.
*
* @var string
* @var string|null
*/
protected $last_name;
/**
* The email of the new user.
*
* @var string
* @var string|null
*/
protected $email;
/**
* The password of the new user.
*
* @var string
* @var string|null
*/
protected $password;
/**
* The role of the new user.
*
* @var string
* @var string|null
*/
protected $role;
@ -219,7 +219,7 @@ class UserCreate extends Command
$this->role = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please choose from the selection',
'Please choose from the selection %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;

View file

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

View file

@ -13,9 +13,11 @@ enum CivilityEnum: int
/**
* Optionnal labels definition
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::man->name => 'Homme',
self::woman->name => 'Femme',
self::man->name => 'Monsieur',
self::woman->name => 'Madame',
];
}

View file

@ -15,6 +15,8 @@ enum ObjectEnum: int
/**
* Optionnal labels definition
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::customerService->name => 'Service consommateurs',

View file

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

View file

@ -16,6 +16,8 @@ enum CategoryEnum: int
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::enterprise->name => 'La vie d\'Entreprise',

View file

@ -16,6 +16,8 @@ enum CookingModeEnum: int
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::microwave->name => 'Micro-ondes',

View file

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

View file

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

View file

@ -13,6 +13,8 @@ enum BootstrapThemeEnum: int
/**
* Optionnal labels definition
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::light->name => 'Light',

View file

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

View file

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

View file

@ -98,7 +98,7 @@ class CommitmentController extends Controller
/**
* Show the form for editing the specified resource.
*
* @param App\Models\Commitment $commitment
* @param \App\Models\Commitment $commitment
* @return \Illuminate\Contracts\View\View
*/
public function edit(Commitment $commitment): \Illuminate\Contracts\View\View
@ -139,7 +139,7 @@ class CommitmentController extends Controller
{
$commitment->products()->detach();
if ($commitment->explorerpage()->count() > 3) {
if ($commitment->explorerpage()->count() === 0) {
if ($commitment->deleteOrFail()) {
return redirect()->route('bo.commitments.index')
->with('success', __('crud.messages.has_been_deleted', [

View file

@ -28,7 +28,7 @@ class JoinuspageController extends Controller
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Joinuspages\UpdateJoinuspageRequest $request
* @param \App\Http\Requests\Bo\Joinuspage\UpdateJoinuspageRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateJoinuspageRequest $request): \Illuminate\Http\RedirectResponse

View file

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

View file

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

View file

@ -104,7 +104,7 @@ class RangeController extends Controller
/**
* Show the form for editing the specified resource.
*
* @param App\Models\Range $range
* @param \App\Models\Range $range
* @return \Illuminate\Contracts\View\View
*/
public function edit(Range $range): \Illuminate\Contracts\View\View
@ -143,7 +143,9 @@ class RangeController extends Controller
*/
public function destroy(Range $range): \Illuminate\Http\RedirectResponse
{
if (!$range->products->isEmpty()) {
/** @var \Illuminate\Database\Eloquent\Collection $products*/
$products = $range->products;
if (!$products->isEmpty()) {
return redirect()->back()
->with('error', __('crud.messages.cannot_be_deleted_relation', [
'model' => Str::ucfirst(__('models.classes.range')),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -24,6 +24,7 @@ class NewsController extends Controller
/** @var \App\Models\News $newsModels */
$newsModels = News::query()->where('published', true)->orderBy('publish_at', 'desc')->paginate(12);
return view('fo.pages.news.index', \compact('staticPageModel', 'newsModels'));
}

View file

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
use App\Lib\Helpers\CollectionHelper;
use App\Models\Product;
class ProductController extends Controller
@ -10,16 +11,35 @@ class ProductController extends Controller
/**
* Display the product page.
*
* @param \App\Models\Product $product
* @param string $slug
* @return \Illuminate\Contracts\View\View
*/
public function show(Product $product): \Illuminate\Contracts\View\View
public function show(string $slug): \Illuminate\Contracts\View\View
{
$otherProductModels = $product->range->products()
->where('id', '!=', $product->id)
$productModel = Product::query()->where('slug', $slug)->firstOrFail();
$otherProductModels = $productModel->range->products()
->where('id', '!=', $productModel->id)
->where('published', true)
->inRandomOrder()
->get();
return view('fo.pages.products.show', compact('product', 'otherProductModels'));
return view('fo.pages.products.show', compact('productModel', 'otherProductModels'));
}
/**
* Get filtered products.
*
* @return \Illuminate\Http\JsonResponse
*/
public function productsFiltered(): \Illuminate\Http\JsonResponse
{
$productModels = Product::query()
->where('published', true)
->where('range_id', request()->range_id)
->orderBy('order', 'asc')
->get();
$productModels = CollectionHelper::paginateManually($productModels, 12, request()->page);
return response()->json($productModels);
}
}

View file

@ -4,6 +4,7 @@ namespace App\Http\Controllers\Fo;
use App\Enums\StaticPageType;
use App\Http\Controllers\Controller;
use App\Lib\Helpers\CollectionHelper;
use App\Models\Product;
use App\Models\Range;
use App\Models\StaticPage;
@ -17,12 +18,12 @@ class RangeController extends Controller
*/
public function index(): \Illuminate\Contracts\View\View
{
/** @var \App\Models\Range $rangeModels */
$rangeModels = Range::query()->where('published', true)->orderBy('order')->get();
/** @var \App\Models\StaticPage $staticPageModel */
$staticPageModel = StaticPage::query()->where('type', StaticPageType::ranges)->firstOrFail();
/** @var \App\Models\Range $rangeModels */
$rangeModels = Range::query()->where('published', true)->orderBy('order')->paginate(12);
return view('fo.pages.ranges.index', compact('rangeModels', 'staticPageModel'));
}
@ -38,8 +39,27 @@ class RangeController extends Controller
$rangeModel = Range::query()->where('published', true)->where('slug', $slug)->firstOrFail();
/** @var \App\Models\Product $productModels */
$productModels = Product::query()->where('range_id', $rangeModel->id)->where('published', true)->get();
$productModels = Product::query()
->where('range_id', $rangeModel->id)
->where('published', true)
->orderBy('order')
->paginate(12);
return view('fo.pages.ranges.show', compact('rangeModel', 'productModels'));
}
/**
* Get filtered ranges.
*
* @return \Illuminate\Http\JsonResponse
*/
public function rangesFiltered(): \Illuminate\Http\JsonResponse
{
$productModels = Range::query()
->where('published', true)
->orderBy('order', 'asc')
->get();
$productModels = CollectionHelper::paginateManually($productModels, 12, request()->page);
return response()->json($productModels);
}
}

View file

@ -110,7 +110,10 @@ class StaticPageController extends Controller
*/
public function contactForm(ContactRequest $request): \Illuminate\Http\RedirectResponse
{
Mail::to(\config('mail.from.address'))->send(new SendContactMail((object)$request->validated()));
$receivers = \config('mail.contactform.receiver');
foreach ($receivers as $email) {
Mail::to($email)->send(new SendContactMail((object)$request->validated()));
}
return redirect()->route('fo.contact.show')
->with(
@ -184,7 +187,10 @@ class StaticPageController extends Controller
*/
public function joinusForm(JoinusRequest $request): \Illuminate\Http\RedirectResponse
{
Mail::to(\config('mail.from.address'))->send(new SendJoinusMail((object)$request->validated()));
$receivers = \config('mail.contactform.receiver');
foreach ($receivers as $email) {
Mail::to($email)->send(new SendJoinusMail((object)$request->validated()));
}
return redirect()->route('fo.joinus.show')
->with(

View file

@ -65,5 +65,6 @@ class Kernel extends HttpKernel
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'htmlsanitize' => \App\Http\Middleware\HtmlSanitizer::class,
'local-development' => \App\Http\Middleware\LocalDevelopment::class,
];
}

View file

@ -0,0 +1,25 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class LocalDevelopment
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
if (app()->environment() != 'local') {
return abort(403, 'You are not authorized to access this');
}
return $next($request);
}
}

View file

@ -59,12 +59,16 @@ class SecureHeaders
config('app.debug') ? "https://{$host}:4443" : '',
'https://yl.s-web.fr',
]) . ';',
"img-src * https://yl.s-web.fr data: blob:;",
"img-src 'self' data: blob: " . implode(' ', [
// * VITE DEV
config('app.debug') ? "https://{$host}:4443" : ''
]) . ';',
'frame-src ' . implode(' ', [
'https://www.google.com/recaptcha/',
'https://recaptcha.google.com/recaptcha/',
'https://www.google.com/maps/',
'https://www.youtube.com',
'https://player.vimeo.com',
]) . ';',
"font-src 'self' data: " . \implode(' ', [
// * VITE DEV

View file

@ -27,14 +27,14 @@ class UpdateConvictionpageRequest extends FormRequest
public function rules()
{
return [
'key_feed' => 'required|numeric',
'key_earth_1' => 'required|numeric',
'key_earth_2' => 'required|numeric',
'key_together_1' => 'required|numeric',
'key_together_2' => 'required|numeric',
'key_together_3' => 'required|numeric',
'key_together_4' => 'required|numeric',
'key_territory' => 'required|numeric',
'key_feed' => 'required|numeric|max:2147483647',
'key_earth_1' => 'required|numeric|max:2147483647',
'key_earth_2' => 'required|numeric|max:2147483647',
'key_together_1' => 'required|numeric|max:2147483647',
'key_together_2' => 'required|numeric|max:2147483647',
'key_together_3' => 'required|numeric|max:2147483647',
'key_together_4' => 'required|numeric|max:2147483647',
'key_territory' => 'required|numeric|max:2147483647',
];
}

View file

@ -2,6 +2,8 @@
namespace App\Http\Requests\Bo\Homepage;
use App\Rules\IsValidVimeoId;
use App\Rules\IsValidYoutubeId;
use App\Traits\Requests\HasPicture;
use Illuminate\Foundation\Http\FormRequest;
@ -26,6 +28,9 @@ class UpdateHomepageRequest extends FormRequest
*/
protected function prepareForValidation()
{
$this->merge([
'display_video' => $this->boolean('display_video')
]);
$this->mergePicture('picture');
}
@ -44,10 +49,20 @@ class UpdateHomepageRequest extends FormRequest
'picture_title' => 'required|string|between:3,255',
'presentation_title' => 'required|string|between:3,255',
'presentation_content' => 'required|string',
'display_video' => 'present|boolean',
'video_id' => [
'nullable', 'required_if:display_video,true',
function (string $attribute, mixed $value, \Closure $fail) {
if (!IsValidYoutubeId::validates($value) and !IsValidVimeoId::validates($value)) {
(new IsValidYoutubeId())->validate($attribute, $value, $fail);
(new IsValidVimeoId())->validate($attribute, $value, $fail);
}
}
],
];
return \array_merge(
$rules,
$this->pictureRules('picture', 640, 390, 640, 390, false),
$this->pictureRules('picture', 640, 390, 640, 390, true, requiredIf: 'display_video,false'),
);
}
@ -59,6 +74,7 @@ class UpdateHomepageRequest extends FormRequest
public function attributes(): array
{
return [
'video_id' => trans('validation.custom.video_id'),
'title_part1' => trans('validation.custom.title_part1'),
'title_part2' => trans('validation.custom.title_part2'),
'title_part3' => trans('validation.custom.title_part3'),

View file

@ -2,6 +2,8 @@
namespace App\Http\Requests\Bo\Joinuspage;
use App\Rules\IsValidVimeoId;
use App\Rules\IsValidYoutubeId;
use Illuminate\Foundation\Http\FormRequest;
class UpdateJoinuspageRequest extends FormRequest
@ -24,7 +26,15 @@ class UpdateJoinuspageRequest extends FormRequest
public function rules()
{
return [
'video_id' => 'required|string|regex:%([^"&?\/\s]{11})%i',
'video_id' => [
'required', 'string',
function (string $attribute, mixed $value, \Closure $fail) {
if (!IsValidYoutubeId::validates($value) and !IsValidVimeoId::validates($value)) {
(new IsValidYoutubeId())->validate($attribute, $value, $fail);
(new IsValidVimeoId())->validate($attribute, $value, $fail);
}
}
],
];
}

View file

@ -54,7 +54,7 @@ class StoreNewsRequest extends FormRequest
'subtitle' => 'required|string|between:3,255',
'content' => 'required|string|min:15',
'published' => 'required|boolean',
'publish_at' => 'nullable|date',
'publish_at' => 'required|date',
'unpublish_at' => 'nullable|date|after:publish_at',
];

View file

@ -68,8 +68,8 @@ class StoreProductRequest extends FormRequest
return \array_merge(
$rules,
$this->pictureRules('picture_thumbnail', 332, 252, 332, 252, false),
$this->pictureRules('picture_ambiance', 774, 515, 774, 515, false),
$this->pictureRules('picture_packshot', 327, 252, 327, 252, false),
$this->pictureRules('picture_ambiance', 1115, 790, 1115, 790, false),
$this->pictureRules('picture_packshot', 560, 480, 560, 480, false),
);
}
@ -89,35 +89,35 @@ class StoreProductRequest extends FormRequest
// phpcs:enable
$nutritionnal_values = json_encode([
"energyKj" => [
"label" => "Energie en Kj",
"label" => "ÉNERGIE (kj)",
"value" => $this->nutritionnal_values['energy_kj'],
],
"energyKcal" => [
"label" => "Energie en kcal",
"label" => "ÉNERGIE (kcal)",
"value" => $this->nutritionnal_values['energy_kcal'],
],
"fat" => [
"label" => "Matières grasses",
"label" => "MATIÈRES GRASSES (g)",
"value" => $this->nutritionnal_values['fat'],
],
"saturated-fat" => [
"label" => "dont acides gras saturés",
"label" => "ACIDES GRAS SATURÉS (g)",
"value" => $this->nutritionnal_values['saturated_fat'],
],
"carbohydrate" => [
"label" => "Glucides",
"label" => "GLUCIDES (g)",
"value" => $this->nutritionnal_values['carbohydrate'],
],
"sugar" => [
"label" => "dont sucres",
"label" => "SUCRES (g)",
"value" => $this->nutritionnal_values['sugar'],
],
"proteins" => [
"label" => "Protéines",
"label" => "PROTÉINES (g)",
"value" => $this->nutritionnal_values['proteins'],
],
"salt" => [
"label" => "Sel",
"label" => "SEL (g)",
"value" => $this->nutritionnal_values['salt'],
]
]);

View file

@ -59,7 +59,7 @@ class StoreRangeRequest extends FormRequest
return \array_merge(
$rules,
$this->pictureRules('picture', 466, 440, 466, 440, false),
$this->pictureRules('picture_productsheet', 332, 442, 332, 442, false),
$this->pictureRules('picture_productsheet', 680, 300, 680, 300, false),
$this->pictureRules('picture_thumbnail', 246, 337, 246, 337, false),
);
}

View file

@ -24,8 +24,10 @@ class UpdateStaticPageRequest extends FormRequest
*/
public function prepareForValidation(): void
{
/** @var \App\Models\StaticPage $staticPageModel */
$staticPageModel = $this->route('static_page');
if (
\in_array($this->route('static_page')->type, [
\in_array($staticPageModel->type, [
\App\Enums\StaticPageType::confidentiality,
\App\Enums\StaticPageType::legals
])

View file

@ -1,229 +0,0 @@
<?php
namespace App\Http\Requests\Bo;
use App\Enums\InputType;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\ValidationException;
abstract class UpdateMiniCrudRequest extends FormRequest
{
/**
* The model parameter name
*
* @var string
*/
protected $modelName = null;
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return auth('backend')->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
* @phpcs:disable Generic.Metrics.NestingLevel.TooHigh
*/
public function rules()
{
// phpcs:enable
$rules = [];
foreach ($this->{$this->modelName}->data as $iptName => $input) {
$input = \json_decode(\json_encode($input), true);
switch ($input['inputType']) {
case InputType::number->value:
$rules = array_merge($rules, [
"data.$iptName.value" => $this->compileValidate('required|numeric', $input)
]);
break;
case InputType::videoYoutube->value:
$rules = array_merge($rules, [
"data.$iptName.value" => $this->compileValidate('regex:%([^"&?\/\s]{11})%i', $input)
]);
break;
case InputType::videoVimeo->value:
$rules = array_merge($rules, [
"data.$iptName.value" => $this->compileValidate('regex:%([0-9]{8,10})%i', $input)
]);
break;
case InputType::video->value:
$rules = array_merge($rules, [
"data.$iptName.value" => $this->compileValidate([
'regex:%([0-9]{8,10}|[^"&?\/\s]{11})%i'
], $input)
]);
break;
case InputType::string->value:
$rules = array_merge($rules, [
"data.$iptName.value" => $this->compileValidate('required|string|min:3', $input)
]);
break;
case InputType::text->value:
$mergeRules = $this->compileValidate('required|string|min:15', $input);
$mergeRules = collect($mergeRules)
->map(function ($rule) {
if (\strpos($rule, 'max:') !== false) {
$max = \intval(str_replace('max:', '', $rule));
return function ($attribute, $value, $fail) use ($max) {
if (\strlen(\strip_tags($value)) > $max) {
$fail("$attribute ne doit pas excéder $max caractères");
}
};
}
return $rule;
});
$rules = array_merge($rules, [
"data.$iptName.value" => $mergeRules
]);
break;
case InputType::image->value:
$required = $this->isUpdate() ? 'sometimes' : 'required';
$ipt = $this->data[$iptName];
$max = isset($ipt['dimensionsMax']);
$dim = \is_string($input['dimensions']) ? collect(explode(
',',
(isset($ipt['dimensionsMax']) and is_string($ipt['dimensionsMax'])) ?
$ipt['dimensionsMax'] : $input['dimensions']
))->mapWithKeys(function ($v) {
$v = explode(' ', $v);
return [$v[0] => $v[1]];
}) : $input['dimensions'];
// * Adapt dimensions
$sizes = (object)[
'minHeight' => $dim['height'],
'maxHeight' => $dim['height'],
'minWith' => $dim['width'],
'maxWith' => $dim['width'],
];
$rules = array_merge($rules, [
"data.$iptName.value" => [
function (string $attribute, $value, callable $fail) use ($sizes) {
return ToolboxHelper::validatePicture($attribute, $value, $fail, $sizes);
}
],
$this->compileValidate(
implode('|', [
$required,
'image',
'mimes:png,jpg,jpeg',
sprintf(
'dimensions:%s=%d,%s=%d',
$max ? 'max_height' : 'height',
$dim['height'],
$max ? 'max_width' : 'width',
$dim['width']
)
]),
$input
),
"data.$iptName.alt" => $this->compileValidate("$required|string|min:5", $input),
"data.$iptName.title" => $this->compileValidate("$required|string|min:5", $input)
]);
break;
case InputType::url->value:
$rules = array_merge($rules, [
"data.$iptName.value" => [
'required', 'url',
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) {
try {
ToolboxHelper::assertNoInternalUrls($attribute, $value);
ToolboxHelper::assertNoInternalBoUrls($attribute, $value);
} catch (ValidationException $e) {
$fail(collect($e->errors())->first()[0]);
}
}
]
]);
break;
case InputType::boolean->value:
$all = $this->all();
$nested = ToolboxHelper::dotStringToNestedArray(
"data.$iptName.value",
$this->input("data.$iptName.value") ? true : false
);
$all = ToolboxHelper::arrayMergeRecursiveDistinct($all, $nested);
$this->merge($all);
$rules = array_merge($rules, [
"data.$iptName.value" => 'boolean'
]);
break;
} //end switch
} //end foreach
return $rules;
}
/**
* Undocumented function
*
* @param string|array $inputTypeValidate
* @param array $input
* @return array
*/
public function compileValidate(string|array $inputTypeValidate, array $input): array
{
if (\is_string($inputTypeValidate)) {
$inputTypeValidate = explode('|', $inputTypeValidate);
}
/** @var array|string */
$inputValidate = \collect($input)->get('validate', $input);
$inputValidate = \is_string($inputValidate) ? explode('|', $inputValidate) : $inputValidate;
foreach ($inputValidate as $validate) {
if (!\in_array($validate, $inputTypeValidate)) {
$inputTypeValidate[] = $validate;
}
}
$test = \collect($inputTypeValidate)->all();
$test = \collect($inputTypeValidate)->sort(function ($valueA, $valueB) {
$valueA = $this->ruleHasToComeFirst($valueA) ? 0 : 2;
$valueB = $this->ruleHasToComeFirst($valueB) ? 1 : 2;
return $valueA - $valueB;
})->unique()->all();
return \collect($inputTypeValidate)->unique()->sort(function ($valueA, $valueB) {
$valueA = $this->ruleHasToComeFirst($valueA) ? 0 : 2;
$valueB = $this->ruleHasToComeFirst($valueB) ? 1 : 2;
return $valueA - $valueB;
})->all();
}
/**
* Does the rulle need to come first ?
*
* @param string $rule
* @return boolean
*/
private function ruleHasToComeFirst(string $rule): bool
{
$rule = \collect(\explode(':', $rule, 1))->filter()->first(null, '');
return \collect([
'nullable', 'present', 'required', 'required_if',
'required_unless', 'required_with', 'required_with_all',
'required_without', 'required_without_all', 'required_with_all',
'missing', 'missing_if', 'missing_unless',
'missing_with', 'missing_with_all',
])->first(fn (string $firstRule) => \strpos($rule, $firstRule) === 0) ? true : false;
}
/**
* Is this request an update request ?
*
* @return boolean
*/
private function isUpdate(): bool
{
return $this->isMethod('PUT');
}
}

View file

@ -3,7 +3,9 @@
namespace App\Http\Requests\Bo\Users;
use App\Enums\Users\RoleEnum;
use App\Models\User;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rules\Enum;
use Illuminate\Validation\Rules\Password;
@ -16,7 +18,7 @@ class StoreUserRequest extends FormRequest
*/
public function authorize(): bool
{
return auth('backend')->check();
return Gate::check('create', User::class);
}
/**
@ -24,10 +26,10 @@ class StoreUserRequest extends FormRequest
*
* @return void
*/
protected function prepareForValidation()
protected function prepareForValidation(): void
{
$this->merge([
'published' => $this->published ? true : false
'published' => $this->boolean('published')
]);
}

View file

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

View file

@ -21,8 +21,8 @@ class ContactRequest extends FormRequest
protected function prepareForValidation()
{
$this->merge([
'accept_rules' => $this->accept_rules ? true : false,
'send_data' => $this->send_data ? true : false,
'accept_rules' => $this->boolean('accept_rules'),
'send_data' => $this->boolean('send_data'),
]);
$this->mergePicture('file');
}
@ -31,9 +31,11 @@ class ContactRequest extends FormRequest
* Get the validation rules that apply to the request.
*
* @return array
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
*/
public function rules()
{
// phpcs:enable
ToolboxHelper::validateRecaptcha($this, \config('services.google.recaptcha.version'));
$rules = [
@ -43,104 +45,96 @@ class ContactRequest extends FormRequest
'society' => 'sometimes|nullable|string|between:3,255',
'email' => 'required|email:rfc,strict,dns,spoof,filter|max:255',
'phone' => 'sometimes|nullable|numeric|min:0',
'address' => 'sometimes|nullable|string|between:3,255',
'postal_code' => 'sometimes|nullable|numeric|min:0',
'city' => 'sometimes|nullable|string|between:3,255',
'address' => 'required|string|between:3,255',
'postal_code' => 'required|numeric|min:0',
'city' => 'required|string|between:3,255',
'object' => ['required', new Enum(ObjectEnum::class)],
'product_name' => [
'nullable', 'string', 'between:3,255',
// @phpcs:ignore Generic.Files.LineLength.TooLong
'required_with:product_date_limit,product_barcode,product_lot,product_date_consumption,product_location',
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) {
if ($this->get("object") != ObjectEnum::customerService->value()) {
$fail(\trans('le Champs :field doit être vide lorsque le champs objet
n\'est pas égale à :enumValue.', [
'field' => trans('validation.custom.product_name'),
'enumValue' => ObjectEnum::customerService->label(),
]));
}
}
],
'product_date_limit' => [
'sometimes', 'nullable', 'string', 'between:3,255',
// @phpcs:ignore Generic.Files.LineLength.TooLong
'required_with:product_name,product_barcode,product_lot,product_date_consumption,product_location',
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) {
if ($this->get("object") != ObjectEnum::customerService->value()) {
$fail(\trans('le Champs :field doit être vide lorsque le champs objet
n\'est pas égale à :enumValue.', [
'field' => trans('validation.custom.product_date_limit'),
'enumValue' => ObjectEnum::customerService->label(),
]));
}
}
],
'product_barcode' => [
'sometimes', 'nullable', 'numeric', 'min:0',
// @phpcs:ignore Generic.Files.LineLength.TooLong
'required_with:product_name,product_date_limit,product_lot,product_date_consumption,product_location',
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) {
if ($this->get("object") != ObjectEnum::customerService->value()) {
$fail(\trans('le Champs :field doit être vide lorsque le champs objet
n\'est pas égale à :enumValue.', [
'field' => trans('validation.custom.product_barcode'),
'enumValue' => ObjectEnum::customerService->label(),
]));
}
}
],
'product_lot' => [
'sometimes', 'nullable', 'numeric', 'min:0',
// @phpcs:ignore Generic.Files.LineLength.TooLong
'required_with:product_name,product_date_limit,product_barcode,product_date_consumption,product_location',
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) {
if ($this->get("object") != ObjectEnum::customerService->value()) {
$fail(\trans('le Champs :field doit être vide lorsque le champs objet
n\'est pas égale à :enumValue.', [
'field' => trans('validation.custom.product_lot'),
'enumValue' => ObjectEnum::customerService->label(),
]));
}
}
],
'product_date_consumption' => [
'sometimes', 'nullable', 'string', 'between:3,255',
// @phpcs:ignore Generic.Files.LineLength.TooLong
'required_with:product_name,product_date_limit,product_barcode,product_lot,product_location',
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) {
if ($this->get("object") != ObjectEnum::customerService->value()) {
$fail(\trans('le Champs :field doit être vide lorsque le champs objet
n\'est pas égale à :enumValue.', [
'field' => trans('validation.custom.product_date_consumption'),
'enumValue' => ObjectEnum::customerService->label(),
]));
}
}
],
'product_location' => [
'sometimes', 'nullable', 'string', 'between:3,255',
// @phpcs:ignore Generic.Files.LineLength.TooLong
'required_with:product_name,product_date_limit,product_barcode,product_lot,product_date_consumption',
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) {
if ($this->get("object") != ObjectEnum::customerService->value()) {
$fail(\trans('le Champs :field doit être vide lorsque le champs objet
n\'est pas égale à :enumValue.', [
'field' => trans('validation.custom.product_location'),
'enumValue' => ObjectEnum::customerService->label(),
]));
}
}
],
'message' => 'required|string|between:3,255',
'message' => 'required|string|between:3,500',
'accept_rules' => 'required|boolean',
'send_data' => 'required|boolean',
];
if ($this->object === strval(ObjectEnum::customerService->value())) {
$rules = \array_merge($rules, [
'product_name' => [
'string', 'between:3,255', 'required',
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) {
if ($this->get("object") != ObjectEnum::customerService->value()) {
$fail(\trans('le Champs :field doit être vide lorsque le champs objet
n\'est pas égale à :enumValue.', [
'field' => trans('validation.custom.product_name'),
'enumValue' => ObjectEnum::customerService->label(),
]));
}
}
],
'product_date_limit' => [
'required', 'date',
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) {
if ($this->get("object") != ObjectEnum::customerService->value()) {
$fail(\trans('le Champs :field doit être vide lorsque le champs objet
n\'est pas égale à :enumValue.', [
'field' => trans('validation.custom.product_date_limit'),
'enumValue' => ObjectEnum::customerService->label(),
]));
}
}
],
'product_barcode' => [
'numeric', 'min:0', 'required',
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) {
if ($this->get("object") != ObjectEnum::customerService->value()) {
$fail(\trans('le Champs :field doit être vide lorsque le champs objet
n\'est pas égale à :enumValue.', [
'field' => trans('validation.custom.product_barcode'),
'enumValue' => ObjectEnum::customerService->label(),
]));
}
}
],
'product_lot' => [
'numeric', 'min:0', 'required',
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) {
if ($this->get("object") != ObjectEnum::customerService->value()) {
$fail(\trans('le Champs :field doit être vide lorsque le champs objet
n\'est pas égale à :enumValue.', [
'field' => trans('validation.custom.product_lot'),
'enumValue' => ObjectEnum::customerService->label(),
]));
}
}
],
'product_date_consumption' => [
'required', 'date',
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) {
if ($this->get("object") != ObjectEnum::customerService->value()) {
$fail(\trans('le Champs :field doit être vide lorsque le champs objet
n\'est pas égale à :enumValue.', [
'field' => trans('validation.custom.product_date_consumption'),
'enumValue' => ObjectEnum::customerService->label(),
]));
}
}
],
'product_location' => [
'string', 'between:3,255', 'required',
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) {
if ($this->get("object") != ObjectEnum::customerService->value()) {
$fail(\trans('le Champs :field doit être vide lorsque le champs objet
n\'est pas égale à :enumValue.', [
'field' => trans('validation.custom.product_location'),
'enumValue' => ObjectEnum::customerService->label(),
]));
}
}
],
]);
} //end if
if (
isset(request()->file) and
(request()->file->getClientMimeType() === "image/jpeg" ||
@ -152,8 +146,8 @@ class ContactRequest extends FormRequest
0,
1920,
1080,
3000,
true,
3000,
'product_name,product_date_limit,product_barcode,product_lot,product_date_consumption,product_location'
));
} elseif (isset(request()->file) and (request()->file->getClientMimeType() === "application/pdf")) {
@ -161,6 +155,20 @@ class ContactRequest extends FormRequest
//phpcs:ignore Generic.Files.LineLength.TooLong
'file' => 'sometimes|nullable|mimes:pdf|required_with:product_name,product_date_limit,product_barcode,product_lot,product_date_consumption,product_location'
]);
} elseif (
isset(request()->file) and
(request()->file->getClientMimeType() !== "application/pdf") and
(request()->file->getClientMimeType() !== "image/jpeg") and
(request()->file->getClientMimeType() !== "image/png")
) {
$rules = \array_merge($rules, [
'file' => [
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) {
$fail(\trans('Le format du fichier doit être .jpg, .png ou .pdf'));
}
],
]);
} //end if
return $rules;
}

View file

@ -21,7 +21,7 @@ class JoinusRequest extends FormRequest
'first_name' => 'required|string|between:3,255',
'email' => 'required|email:rfc,strict,dns,spoof,filter|max:255',
'phone' => 'required|numeric|min:0',
'message' => 'required|string|between:3,255',
'message' => 'required|string|between:3,500',
"file" => "sometimes|nullable|mimes:pdf|max:10000"
];
}

View file

@ -19,7 +19,7 @@ class SeasonalSectionStatusUpdate
*/
public function handle()
{
/** @var \Illuminate\Database\Eloquent\Collection<array-key, \App\Models\SeasonalSections> */
/** @var \Illuminate\Database\Eloquent\Collection<array-key, \App\Models\SeasonalSection> */
$seasonalsectionsListToPublish = SeasonalSection::query()
->where('publish_at', '<=', Carbon::now()->toDateTime())
->whereNotNull('publish_at')
@ -30,7 +30,7 @@ class SeasonalSectionStatusUpdate
$seasonalsections->published = true;
$seasonalsections->saveOrFail();
}
/** @var \Illuminate\Database\Eloquent\Collection<array-key, \App\Models\SeasonalSections> */
/** @var \Illuminate\Database\Eloquent\Collection<array-key, \App\Models\SeasonalSection> */
$seasonalsectionsListToUnPublish = SeasonalSection::query()
->where('unpublish_at', '<=', Carbon::now()->toDateTime())
->whereNotNull('unpublish_at')

View file

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

View file

@ -118,7 +118,7 @@ class FileStorageHelper
);
return str_replace('/private/', '', $newPath);
}//end if
} //end if
if (\is_object($file) and $file instanceof \SplFileInfo) {
/** @var \SplFileInfo $file */
$filename = $filename ?: $file->getBaseName();
@ -309,7 +309,6 @@ class FileStorageHelper
$private ? 'app/private/modelfiles' : 'app/public/modelfiles',
$tableName,
$filename,
$private
);
$filename = self::getFileUniqueName($folderPath, $filename);
return "{$folderPath}/{$filename}";
@ -404,7 +403,7 @@ class FileStorageHelper
$filename = Str::of($filename)->ltrim('.-');
// * Optional beautification
if ($beautify) {
$filename = static::beautifyFilename($filename);
$filename = self::beautifyFilename($filename);
}
// * Maximize filename length to 255 bytes http://serverfault.com/a/9548/44086
/** @var string */

View file

@ -175,11 +175,10 @@ class ToolboxHelper
$url = $match;
try {
$request = Request::create($url);
// If a route is match then it do not throws exception.
if (
\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST) &&
$routes->match($request)
) {
// Assert route matches existing routes.
$routes->match($request);
// ? Is url internal ?
if (\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST)) {
// Removes the matched url.
throw ValidationException::withMessages([
$field => trans(
@ -205,14 +204,14 @@ class ToolboxHelper
* @param array|string $replace
* @param array|string $subject
* @param integer $count
* @return array|string
* @return array|string|false
*/
public static function mbReplace(
array|string $search,
array|string $replace,
array|string $subject,
int &$count = 0
): array|string {
): array|string|false {
if (!is_array($search) && is_array($replace)) {
return false;
}
@ -289,9 +288,12 @@ class ToolboxHelper
public static function getOriginal(Model $model, string $attribute): mixed
{
if (\in_array('Spatie\Translatable\HasTranslations', \class_uses_recursive($model))) {
// @phpstan-ignore-next-line
return $model->getTranslation(
$attribute,
// @phpstan-ignore-next-line
$model->getLocale(),
// @phpstan-ignore-next-line
$model->useFallbackLocale()
);
}
@ -361,47 +363,45 @@ class ToolboxHelper
/**
* Convert deeply an object to array
*
* @param array|object $input
* @param mixed $input
* @return array
* @throws \RuntimeException If $input var is a scalar value.
*/
public static function objectToArray($input): array
public static function objectToArray(mixed $input): array
{
// Only process if it's an object or array being passed to the function.
if (is_object($input) or is_array($input)) {
$ret = (array)$input;
foreach ($ret as &$item) {
// Recursively process EACH element only if not scalar.
$item = (is_object($item) or is_array($item)) ?
self::objectToArray($item) : $item;
}
return $ret;
} else {
if (!(is_object($input) or is_array($input))) {
throw new \RuntimeException('Cannot handle $input of type ' . \gettype($input));
}
$ret = (array)$input;
foreach ($ret as &$item) {
// Recursively process EACH element only if not scalar.
$item = (is_object($item) or is_array($item)) ?
self::objectToArray($item) : $item;
}
return $ret;
}
/**
* Convert deeply an array to object
*
* @param array|object $input
* @param mixed $input
* @return object
* @throws \RuntimeException If $input var is a scalar value.
*/
public static function arrayToObject($input): object
public static function arrayToObject(mixed $input): object
{
// Only process if it's an object or array being passed to the function.
if (is_object($input) or is_array($input)) {
$ret = (object)$input;
foreach ($ret as &$item) {
// Recursively process EACH element only if not scalar.
$item = (is_object($item) or is_array($item)) ?
self::arrayToObject($item) : $item;
}
return $ret;
} else {
if (!(is_object($input) or is_array($input))) {
throw new \RuntimeException('Cannot handle $input of type ' . \gettype($input));
}
$ret = (object)$input;
foreach ($ret as &$item) {
// Recursively process EACH element only if not scalar.
$item = (is_object($item) or is_array($item)) ?
self::arrayToObject($item) : $item;
}
return $ret;
}
/**
@ -409,11 +409,11 @@ class ToolboxHelper
*
* @param string $attribute
* @param string|null $value
* @param callback $fail
* @param callable $fail
* @param object $sizes
* @return void
*/
public static function validatePicture(string $attribute, string|null $value, callable $fail, object $sizes)
public static function validatePicture(string $attribute, string|null $value, callable $fail, object $sizes): void
{
if (!($useStorage = Storage::exists($value)) and !File::exists($value)) {
$fail(trans(':attribute n\'existe pas dans le système de fichier', [
@ -432,7 +432,7 @@ class ToolboxHelper
'attribute' => $attribute
]));
}
if (!($dimensions = \getimagesize($fileFullPath)) or !isset($dimensions[0]) or !isset($dimensions[1])) {
if (!($dimensions = \getimagesize($fileFullPath))) {
$fail(trans(':attribute impossible de valider les dimensions', [
'attribute' => $attribute
]));

View file

@ -5,7 +5,6 @@ namespace App\Mail;
use App\Enums\Contact\ObjectEnum;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Attachment;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
@ -17,7 +16,7 @@ class SendContactMail extends Mailable
use SerializesModels;
/**
* @var \App\Mail\Contact
* @var \stdClass
*/
private \stdClass $data;
@ -40,16 +39,11 @@ class SendContactMail extends Mailable
*/
public function envelope(): \Illuminate\Mail\Mailables\Envelope
{
$subject = ObjectEnum::make($this->data->object) ?
ObjectEnum::make($this->data->object)->label() :
__('Nouveau message');
return new Envelope(
from: new Address(
$this->data->email,
($this->data->first_name !== null || $this->data->last_name !== null) ?
(($this->data->first_name ?? '') . " " . ($this->data->last_name ?? '')) :
null
),
to: [new Address(config('mail.from.address'), config('mail.from.name'))],
subject: sprintf('%s - %s', ObjectEnum::make($this->data->object)->label() ??
__('Nouveau message'), config('app.name')),
subject: sprintf('%s - %s', $subject, config('app.name')),
);
}

View file

@ -4,7 +4,6 @@ namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Attachment;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
@ -16,7 +15,7 @@ class SendJoinusMail extends Mailable
use SerializesModels;
/**
* @var \App\Mail\Joinus
* @var \stdClass
*/
private \stdClass $data;
@ -25,7 +24,6 @@ class SendJoinusMail extends Mailable
*
* @param \stdClass $data
* @return void
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
*/
public function __construct(\stdClass $data)
{
@ -40,13 +38,6 @@ class SendJoinusMail extends Mailable
public function envelope(): \Illuminate\Mail\Mailables\Envelope
{
return new Envelope(
from: new Address(
$this->data->email,
($this->data->first_name !== null || $this->data->last_name !== null) ?
(($this->data->first_name ?? '') . " " . ($this->data->last_name ?? '')) :
null
),
to: [new Address(config('mail.from.address'), config('mail.from.name'))],
subject: sprintf('%s - %s', __('Candidature spontanée'), config('app.name')),
);
}

View file

@ -3,18 +3,17 @@
namespace App\Models;
use App\Enums\ActivityLogs\ActivityLogsEventEnum;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Schema;
/**
* @property string $model Target model.
* @property string $model_id Id of the target model.
* @property string $event Event of this activity (ActivityLogsEventEnum).
* @property string $data Changes.
* @property-read \Illuminate\Support\Carbon $created_at Created date.
* @property string $model Target model.
* @property string $model_id Id of the target model.
* @property \App\Enums\ActivityLogs\ActivityLogsEventEnum $event Event of this activity (ActivityLogsEventEnum).
* @property array $data Changes.
* @property-read \Illuminate\Support\Carbon $created_at Created date.
*
* @property-read \App\Models\User $user User BelongsTo relation.
*/
@ -82,19 +81,20 @@ class ActivityLog extends Model
$activity->model_id = $model->getKey();
$activity->event = $eventEnum;
$activity->data = static::getChangedColumns($activity, $model, $eventEnum);
$activity->created_at = Carbon::now();
// @phpstan-ignore-next-line
$activity->created_at = now();
$activity->saveOrFail();
}
/**
* Get old, new columns that has changed.
*
* @param \self $activity
* @param self $activity
* @param \Illuminate\Database\Eloquent\Model $model
* @param \App\Enums\ActivityLogs\ActivityLogsEventEnum $eventEnum
* @return array|null
* @return array
*/
public static function getChangedColumns(self $activity, Model $model, ActivityLogsEventEnum $eventEnum): array|null
public static function getChangedColumns(self $activity, Model $model, ActivityLogsEventEnum $eventEnum): array
{
$modelClassName = $activity->model_class;
/** Get type of each fields of the target model */
@ -103,16 +103,17 @@ class ActivityLog extends Model
$activity->model_id
)->first();
if ($targetModel != null) {
// phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
$targetModelTypes = collect($targetModel->toArray())
// phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
->map(function ($field, $fieldIndex) use ($targetModel) {
return Schema::getColumnType($targetModel->getTable(), $fieldIndex);
})->toArray();
// phpcs:enable
/** Return old, new and type of values changed */
return ($eventEnum === ActivityLogsEventEnum::updated) ?
array_intersect_key($targetModelTypes, $model->getChanges()) : [];
}
/** Return old, new and type of values changed */
return ($eventEnum === ActivityLogsEventEnum::updated) ?
array_intersect_key($targetModelTypes, $model->getChanges()) : null;
return [];
}
// * RELATIONS
@ -122,7 +123,7 @@ class ActivityLog extends Model
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(User::class);
}

View file

@ -11,9 +11,11 @@ use Illuminate\Database\Eloquent\Model;
* @property string $title_part1 Title part 1.
* @property string $title_part2 Title part 2.
* @property string $title_part3 Title part 3.
* @property string $picture Path of the picture (640px * 390px).
* @property string|null $picture Path of the picture (640px * 390px).
* @property string $picture_alt Picture alt attribute.
* @property string $picture_title Picture title attribute.
* @property string $video_id The youtube or vimeo video Id.
* @property boolean $display_video Wether to display video or picture.
* @property string $presentation_title Presentation title.
* @property string $presentation_content WYSIWYG - Presentation content.
* @property-read \Carbon\Carbon $created_at Created date.
@ -38,6 +40,17 @@ class Homepage extends Model
'picture_title',
'presentation_title',
'presentation_content',
'video_id',
'display_video',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'display_video' => 'bool'
];
/**

View file

@ -9,6 +9,9 @@ use App\Traits\Models\ActivityLog;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Uwil\Seo\Laravel\Interfaces\SeoInterface;
use Uwil\Seo\Laravel\Objects\SeoItem;
use Uwil\Seo\Laravel\Traits\SeoTrait;
/**
* @property string $seo_title SEO title.
@ -31,10 +34,11 @@ use Illuminate\Support\Str;
* @property-read \Carbon\Carbon $created_at Created date.
* @property-read \Carbon\Carbon $updated_at Updated date.
*/
class News extends Model
class News extends Model implements SeoInterface
{
use ActivityLog;
use HasFactory;
use SeoTrait;
/**
* The attributes that are mass assignable.
@ -198,4 +202,22 @@ class News extends Model
}
return false;
}
// * SEO TRAIT OVERLOAD
/**
* Transform to seoModel
*
* @return \Uwil\Seo\Laravel\Objects\SeoItem
*/
public function toSeoItem(): SeoItem
{
return new SeoItem(
\trans('models.classes.' . Str::singular($this->getTable())),
$this->seo_title,
$this->seo_description,
$this->published ?? false,
\route(\config('seo.bo_route_prefix') . $this->getTable() . '.edit', $this),
$this->published ? \route('fo.' . $this->getTable() . '.show', $this) : ''
);
}
}

View file

@ -9,6 +9,9 @@ use App\Traits\Models\ActivityLog;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Uwil\Seo\Laravel\Interfaces\SeoInterface;
use Uwil\Seo\Laravel\Objects\SeoItem;
use Uwil\Seo\Laravel\Traits\SeoTrait;
/**
* @phpcs:disable Generic.Files.LineLength.TooLong
@ -41,10 +44,11 @@ use Illuminate\Support\Str;
* @property-read \App\Models\Range $range Range Belongs to relationship.
* @phpcs:enable
*/
class Product extends Model
class Product extends Model implements SeoInterface
{
use ActivityLog;
use HasFactory;
use SeoTrait;
/**
* The attributes that are mass assignable.
@ -189,4 +193,22 @@ class Product extends Model
{
return $this->belongsToMany(Commitment::class);
}
// * SEO TRAIT OVERLOAD
/**
* Transform to seoModel
*
* @return \Uwil\Seo\Laravel\Objects\SeoItem
*/
public function toSeoItem(): SeoItem
{
return new SeoItem(
\trans('models.classes.' . Str::singular($this->getTable())),
$this->seo_title,
$this->seo_description,
$this->published ?? false,
\route(\config('seo.bo_route_prefix') . $this->getTable() . '.edit', $this),
$this->published ? \route('fo.' . $this->getTable() . '.show', $this) : ''
);
}
}

View file

@ -9,6 +9,9 @@ use App\Traits\Models\ActivityLog;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Uwil\Seo\Laravel\Interfaces\SeoInterface;
use Uwil\Seo\Laravel\Objects\SeoItem;
use Uwil\Seo\Laravel\Traits\SeoTrait;
// phpcs:disable Generic.Files.LineLength.TooLong
/**
@ -22,7 +25,7 @@ use Illuminate\Support\Str;
* @property string $picture Path of the picture (466px * 440px).
* @property string $picture_alt Picture alt attribute.
* @property string $picture_title Picture title attribute.
* @property string $picture_productsheet Path of the picture of the product sheet (332px * 442px).
* @property string $picture_productsheet Path of the picture of the product sheet (680px * 300px).
* @property string $picture_productsheet_alt Picture alt attribute of the product sheet.
* @property string $picture_productsheet_title Picture title attribute of the product sheet.
* @property string $picture_thumbnail Path of the thumbnail (246px * 337px).
@ -36,10 +39,11 @@ use Illuminate\Support\Str;
* @property-read \App\Models\Products $products Products Has Many relationship.
*/
// phpcs:enable
class Range extends Model
class Range extends Model implements SeoInterface
{
use ActivityLog;
use HasFactory;
use SeoTrait;
/**
* The attributes that are mass assignable.
@ -170,4 +174,22 @@ class Range extends Model
{
return $this->hasMany(Product::class);
}
// * SEO TRAIT OVERLOAD
/**
* Transform to seoModel
*
* @return \Uwil\Seo\Laravel\Objects\SeoItem
*/
public function toSeoItem(): SeoItem
{
return new SeoItem(
\trans('models.classes.' . Str::singular($this->getTable())),
$this->seo_title,
$this->seo_description,
$this->published ?? false,
\route(\config('seo.bo_route_prefix') . $this->getTable() . '.edit', $this),
$this->published ? \route('fo.' . $this->getTable() . '.show', $this) : ''
);
}
}

View file

@ -5,6 +5,7 @@ namespace App\Models;
use App\Lib\Helpers\FileStorageHelper;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
/**
* @property string $picture Path of the picture (120px * 120px).
@ -54,9 +55,14 @@ class Stamp extends Model
});
static::updated(function (self $model) {
FileStorageHelper::removeOldFile($model, 'picture');
Cache::forget('randomStampsFooter');
});
static::deleted(function (self $model) {
FileStorageHelper::removeFile($model, 'picture');
Cache::forget('randomStampsFooter');
});
static::created(function () {
Cache::forget('randomStampsFooter');
});
}
}

View file

@ -8,6 +8,9 @@ use App\Traits\Models\ActivityLog;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Uwil\Seo\Laravel\Interfaces\SeoInterface;
use Uwil\Seo\Laravel\Objects\SeoItem;
use Uwil\Seo\Laravel\Traits\SeoTrait;
/**
* @property \App\Enums\StaticPageType $type Page type (StaticPageEnum).
@ -19,10 +22,11 @@ use Illuminate\Support\Str;
* @property-read \Illuminate\Support\Carbon $created_at Created date.
* @property-read \Illuminate\Support\Carbon $updated_at Updated date.
*/
class StaticPage extends Model
class StaticPage extends Model implements SeoInterface
{
use ActivityLog;
use HasFactory;
use SeoTrait;
/**
* The attributes that are mass assignable.
@ -57,7 +61,7 @@ class StaticPage extends Model
});
static::updating(function (self $model) {
// * ASSERTIONS
static::assertTitleIsUnique($model->title, $model->id);
self::assertTitleIsUnique($model->title, $model->id);
// * CHANGES
$model->slug = Str::slug($model->title);
@ -83,4 +87,22 @@ class StaticPage extends Model
ToolboxHelper::assertFieldIsUnique($table, 'title', $title, $id);
ToolboxHelper::assertFieldIsUnique($table, 'slug', Str::slug($title), $id);
}
// * SEO TRAIT OVERLOAD
/**
* Transform to seoModel
*
* @return \Uwil\Seo\Laravel\Objects\SeoItem
*/
public function toSeoItem(): SeoItem
{
return new SeoItem(
\trans('models.classes.' . Str::singular($this->getTable())),
$this->seo_title,
$this->seo_description,
true,
\route(\config('seo.bo_route_prefix') . $this->getTable() . '.edit', $this),
''
);
}
}

View file

@ -4,6 +4,7 @@ namespace App\Models;
use App\Enums\Users\RoleEnum;
use App\Traits\Models\ActivityLog;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
@ -72,7 +73,7 @@ class User extends Authenticatable
protected static function booted(): void
{
static::creating(function (self $model) {
static::updatePassword($model);
self::updatePassword($model);
// * Prevent user elevation privileges
throw_if(
auth('backend')->user() and auth('backend')->user()->role->value() > $model->role->value(),
@ -80,7 +81,7 @@ class User extends Authenticatable
);
});
static::updating(function (self $model) {
static::updatePassword($model);
self::updatePassword($model);
if (optional(auth('backend')->user())->getKey() === $model->getKey()) {
\validator(
['published' => $model->published],

View file

@ -32,7 +32,7 @@ class TranslationServiceProvider extends ServiceProvider
/**
* Gather provided langs checking from files
*
* @return \Illuminate\Support\Collection<string, string>
* @return \Illuminate\Support\Collection<int, string>
*/
private function collectLocalesStrings(): Collection
{

View file

@ -4,6 +4,7 @@ namespace App\Providers;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
@ -20,6 +21,7 @@ class ViewServiceProvider extends ServiceProvider
$appInfos = Cache::remember('composer', 360, function () {
return json_decode(File::get(\app_path('../composer.json')));
});
// Share composer.json infos.
if (isset($appInfos->name) and isset($appInfos->version)) {
View::share('cpsr_name', $appInfos->name);
@ -28,12 +30,33 @@ class ViewServiceProvider extends ServiceProvider
if (!app()->runningInConsole()) {
// * FORCE BOOTSTRAP PAGINATOR (or custom if in front)
// Early boot, request wont be filled as expected.
if (collect(explode('/', \request()->getPathInfo()))->get(1) === 'bo') {
// For the moment, we exclude the seo page because the new pagination system doesn't work in the package.
if (
collect(explode('/', \request()->getPathInfo()))->get(1) === 'bo' &&
collect(explode('/', \request()->getPathInfo()))->last() !== 'seo'
) {
Paginator::defaultView('bo.modules.pagination');
} else {
Paginator::useBootstrapFive();
}
}
// Get all the stamps that are pubished.
$stamps = Cache::remember('randomStampsFooter', 360, function () {
return DB::table('stamps')
->select('picture', 'picture_alt', 'picture_title')
->where('published', true)
->get();
});
/**
* Share a collection of 1 or 2 random stamps for footer.
* First, check if $stamps is not empty, if not,
* we count if the number of stamp in $stamps is greater than 2.
* Then, pick 1 or 2 stamp(s) depending on the number of stamps.
*/
View::share(
'randomStampsFooter',
$stamps->isNotEmpty() ? $stamps->random($stamps->count() >= 2 ? 2 : 1) : []
);
} //end if
}
}

View file

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

View file

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

View file

@ -37,18 +37,16 @@ trait ChangesModelOrder
return DB::transaction(function () use ($request, $className, $model) {
/** @var \Illuminate\Database\Eloquent\Model|null */
$tmp = $className::where('order', $request->direction ? '<' : '>', $model->order)
$tmp = $className::where('order', $request->direction ? '<' : '>', $model->getOriginal('order'))
->orderBy('order', $request->direction ? 'DESC' : 'ASC')->first();
if (!$tmp) {
return back()->with('error', trans('crud.messages.order_not_changed'));
}
$newOrder = $tmp->order;
$tmp->order = $model->order;
$model->order = $newOrder;
$tmp->saveOrFail();
$model->saveOrFail();
$newOrder = $tmp->getOriginal('order');
$tmp->update(['order' => $model->getOriginal('order')]);
$model->update(['order' => $newOrder]);
Session::flash('success', trans('crud.messages.order_changed'));
return back();

View file

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

View file

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

View file

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

View file

@ -1,180 +0,0 @@
<?php
namespace App\Traits\Models;
use App\Enums\InputType;
use App\Lib\Helpers\FileStorageHelper;
use App\Lib\Helpers\ToolboxHelper;
trait MiniCrudModel
{
/**
* Fill the model with an array of attributes.
*
* @param array $attributes
* @return void
* @throws \Illuminate\Database\Eloquent\MassAssignmentException If fill prop is not allowed.
* @phpcs:disable Squiz.Commenting.FunctionComment.EmptyThrows
*/
public function fill(array $attributes)
{
// phpcs:enable
parent::fill($attributes);
if (isset($attributes['data'])) {
$this->setData($attributes['data']);
}
}
/**
* Apply function on data typed text while on save event
*
* @return void
*/
public function minicrudCreating()
{
/** @var array<string,array> */
$data = $this->data;
foreach ($data as &$input) {
switch ($input['inputType']) {
case InputType::text->value:
$input['text'] = clean($input['text']);
$input['text'] = FileStorageHelper::storeModelFilesFromHtml($this, $input['text']);
break;
} //end switch
} //end foreach
$this->data = $data;
}
/**
* Apply function on data typed text while on update event
*
* @return void
*/
public function minicrudUpdating()
{
$this->minicrudCreating();
}
/**
* Apply function on data typed text while on updated event
*
* @return void
*/
public function minicrudUpdated()
{
/** @var array */
foreach ($this->data as $index => $input) {
switch ($input['inputType']) {
case InputType::text->value:
$original = ToolboxHelper::getOriginal($this, 'data');
FileStorageHelper::removeOldModelFilesFromHtml(
$input['text'],
$original[$index]['text'] ?? ''
);
break;
} //end switch
} //end foreach
}
/**
* Apply function on data typed text while on deleted event
*
* @return void
*/
public function minicrudDeleted()
{
/** @var array */
foreach ($this->data as $input) {
switch ($input['inputType']) {
case InputType::text->value:
FileStorageHelper::removeModelFilesOfHtml($input['text']);
break;
} //end switch
} //end foreach
}
/**
* Set a specific prop
*
* @param array $data
* @return void
*/
private function setData(array $data): void
{
$attrs = $this->attributes;
if (isset($attrs['data'])) {
unset($attrs['data']);
}
$this->setAttribute('data', $data);
}
/**
* Set a specific prop
*
* @param array $value
* @return void
*/
protected function setDataAttribute(array $value): void
{
$this->attributes['data'] = $this->castAttributeAsJson(
'data',
$this->serializeData($value)
);
}
/**
* Set a specific prop
*
* @param string|null $data
* @return array
*/
protected function getDataAttribute(string|null $data): array
{
return $this->fromJson($data ?? $this->attributes['data'] ?? '[]');
}
/**
* Serialize properties
*
* @param array $data
* @return array
* @throws \ErrorException If input type is no handled.
* @throws \Illuminate\Validation\ValidationException If url is a bo url.
* @phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
*/
private function serializeData(array $data): array
{
foreach ($data as $o => $input) {
switch ($input['inputType']) {
case InputType::image->value:
$data[$o]['value'] = \preg_replace(
'/^\/|^/',
'',
FileStorageHelper::storeFile($this, $input['value'], true)
);
break;
case InputType::selectEnum->value:
case InputType::boolean->value:
case InputType::password->value:
case InputType::email->value:
case InputType::number->value:
case InputType::videoVimeo->value:
case InputType::videoYoutube->value:
case InputType::video->value:
break;
case InputType::string->value:
case InputType::text->value:
case InputType::url->value:
ToolboxHelper::assertNoInternalBoUrls('content', $input['value']);
break;
default:
throw new \ErrorException(sprintf(
'Unknown input Type %s',
$input['inputType']
));
} //end switch
} //end foreach
return $data;
}
}

View file

@ -45,9 +45,11 @@ trait HasPicture
* @param integer|null $minHeight
* @param integer|null $maxWith
* @param integer|null $maxHeight
* @param integer|null $size
* @param boolean $nullable
* @param integer|null $size
* @param string|null $requiredWith
* @param string|null $requiredWithout
* @param string|null $requiredIf
* @return array
*/
public function pictureRules(
@ -56,9 +58,11 @@ trait HasPicture
?int $minHeight = null,
?int $maxWith = null,
?int $maxHeight = null,
?int $size = null,
bool $nullable = false,
?int $size = null,
?string $requiredWith = null,
?string $requiredWithout = null,
?string $requiredIf = null,
): array {
$dimensions = (object)[
'minWith' => $minWith ?? $this->minWith,
@ -69,10 +73,12 @@ trait HasPicture
return [
$name => [
!$nullable ? 'required' : 'nullable',
(strlen($requiredWith)) ? 'required_with:' . $requiredWith : '',
($size) ? 'max:' . $size : '',
(!empty($requiredWith)) ? "required_with:{$requiredWith}" : '',
(!empty($requiredWithout)) ? "required_without:{$requiredWithout}" : '',
(!empty($requiredIf)) ? "required_if:{$requiredIf}" : '',
($size) ? "max:{$size}" : '',
function (string $attribute, $value, callable $fail) use ($dimensions) {
return ToolboxHelper::validatePicture($attribute, $value, $fail, $dimensions);
ToolboxHelper::validatePicture($attribute, $value, $fail, $dimensions);
}
]
];
@ -107,7 +113,7 @@ trait HasPicture
"{$name}Validate.*" => [
'required',
function (string $attribute, $value, callable $fail) use ($sizes) {
return $this->validatePicture($attribute, $value, $fail, $sizes);
ToolboxHelper::validatePicture($attribute, $value, $fail, $sizes);
}
]
];

View file

@ -1,22 +1,26 @@
{
"name": "uwil/agis",
"version": "1.0.0",
"version": "2.0.0",
"type": "project",
"description": "Agis website.",
"require": {
"php": "^8.2",
"diglactic/laravel-breadcrumbs": "^8.0",
"doctrine/dbal": "^3.7",
"guzzlehttp/guzzle": "^7.2",
"laravel/framework": "^10.0",
"laravel/tinker": "^2.8",
"laravel/ui": "^4.2",
"ldc/sitemap-generator": "^2.0",
"ldc/web-admin-secure-laravel": "^2.0",
"mews/purifier": "^3.4",
"sentry/sentry-laravel": "*"
"sentry/sentry-laravel": "*",
"uwil/k8s-laravel": "^1.2",
"uwil/seo-laravel": "^2.0"
},
"require-dev": {
"fakerphp/faker": "^1.9.1",
"kitloong/laravel-migrations-generator": "^6.8",
"krlove/eloquent-model-generator": "dev-l10-compatibility",
"larastan/larastan": "^2.8",
"laravel-lang/common": "^4.0",
"laravel/pint": "^1.0",
"laravel/sail": "^1.18",
@ -24,8 +28,7 @@
"nunomaduro/collision": "^7.0",
"phpunit/phpunit": "^10.0",
"slevomat/coding-standard": "^8.10",
"spatie/laravel-ignition": "^2.0",
"thedoctor0/laravel-factory-generator": "^1.2"
"spatie/laravel-ignition": "^2.0"
},
"autoload": {
"psr-4": {
@ -36,8 +39,8 @@
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/laravel-shift/eloquent-model-generator.git"
"type": "composer",
"url": "https://lab.s-web.fr/api/v4/group/23/-/packages/composer/packages.json"
}
],
"autoload-dev": {

4764
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -96,6 +96,10 @@ return [
'name' => env('MAIL_FROM_NAME', 'Example'),
],
'contactform' => [
'receiver' => explode(',', env('MAIL_CONTACTFORM_RECEIVER', 'hello@example.com'))
],
/*
|--------------------------------------------------------------------------
| Markdown Mail Settings

12
config/seo.php Normal file
View file

@ -0,0 +1,12 @@
<?php
return [
'bo_route_prefix' => 'bo.',
'blade' => 'bo.pages.seo.index',
'middlewares' => [
'web',
'admin-secure',
'auth:backend'
],
'guard' => 'backend'
];

View file

@ -38,4 +38,8 @@ return [
'version' => env('GOOGLE_RECAPTCHA_VERSION', "3") === "3" ? true : false
]
],
'matomo' => [
'site_id' => env('MATOMO_SITE_ID')
]
];

View file

@ -0,0 +1,101 @@
<?php
use Spatie\Sitemap\Tags\Url;
return [
/**
* A list of url to ignore
* These are hard coded
* Each url does not have to be exhaustive
*
* For example, if you want to ignore all routes
* of the backoffice group
* just add 'bo' into this table
*
* * Note that this can take route names (both partially or full)
* * You can use a wild card just as for routeIs method
*/
'ignoreRoutes' => [
'back.*',
'bo.*',
'fo.verification.*',
'fo.password.*',
'front.frame',
'cspReportUri',
],
/**
* This should be filled using the command `php artisan sitemap:install-completer`
*
* 'completeWith' => [CLASS, 'METHOD'],
* eg : 'completeWith' => [CompleteSitemapWith::class, 'append'],
*
* This should point to a static method which will be called with the folowing parameter
* (LDC\SitemapGenerator\Lib\Sitemap $sitemap)
*
* You may now use $sitemap->addLinks('route.myRouteName', string[] $links)
*/
'completeWith' => null,
/**
* The default route frequency to use with sitemap generator
*/
'routeDefaultFrequency' => Url::CHANGE_FREQUENCY_DAILY,
/**
* The default route priority to use with sitemap generator
*
* priority should be a number between 0 and 1
* https://www.sitemaps.org/protocol.html
*/
'routeDefaultPriority' => 0.5,
/**
* Frequencies for routes
* use the full or partial route name as index
* and a frequency within \Spatie\Sitemap\Tags\Url constants
*
* eg: 'engagments.*' => \Spatie\Sitemap\Tags\Url::CHANGE_FREQUENCY_HOURLY
*/
'routeFrequencies' => [],
/**
* Priorities for routes
* use the full or partial route name as index
* and a priority in 1.0, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, and 0.0.
*
* 1.0 is the Highest
*
* eg: 'engagments.*' => 1.0
*/
'routePriorities' => [],
/**
* Enable or disable schedule for the job
* If set to true the sitemap generator will be run
* using the 'cron' instruction
*/
'schedule' => true,
/**
* If you want to set specific
* instruction for when to schedule job
* you can set this with a cron instruction
* You can generate one using https://crontab.guru/
*
* schedule must be set to true
*
* Default is every day at 15:45
*/
'cron' => '45 15 * * *',
/**
* The sitemap generator can execute JavaScript on each page so it will
* discover links that are generated by your JS scripts. This feature
* is powered by headless Chrome.
*
* ! This needs pupetter installed with npm : npm i puppeteer (not to put in dev dependencies)
*/
'executeJavascript' => false,
];

View file

@ -20,35 +20,35 @@ class ProductFactory extends Factory
$nutritionnal_values = [
'energyKj' => [
'label' => 'Energie en Kj',
'label' => 'ÉNERGIE (kj)',
'value' => fake()->randomFloat(2, 0, 70)
],
'energyKcal' => [
'label' => 'Energie en kcal',
'label' => 'ÉNERGIE (kcal)',
'value' => fake()->randomFloat(2, 0, 70)
],
'fat' => [
'label' => 'Matières grasses',
'label' => 'MATIÈRES GRASSES (g)',
'value' => fake()->randomFloat(2, 0, 70)
],
'saturated-fat' => [
'label' => 'dont acides gras saturés',
'label' => 'ACIDES GRAS SATURÉS (g)',
'value' => fake()->randomFloat(2, 0, 70)
],
'carbohydrate' => [
'label' => 'Glucides',
'label' => 'GLUCIDES (g)',
'value' => fake()->randomFloat(2, 0, 70)
],
'sugar' => [
'label' => 'dont sucres',
'label' => 'SUCRES (g)',
'value' => fake()->randomFloat(2, 0, 70)
],
'proteins' => [
'label' => 'Protéines',
'label' => 'PROTÉINES (g)',
'value' => fake()->randomFloat(2, 0, 70)
],
'salt' => [
'label' => 'Sel',
'label' => 'SEL (g)',
'value' => fake()->randomFloat(2, 0, 70)
],
];

View file

@ -36,7 +36,7 @@ class UserFactory extends Factory
*/
public function unverified(): static
{
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
return $this->state(fn (array $attributes) => [
'email_verified_at' => null,
]);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View file

@ -15,14 +15,14 @@ return new class extends Migration
{
Schema::create('convictionpages', function (Blueprint $table) {
$table->id();
$table->float('key_feed')->comment('Chiffre clé \'bien nourrir\'');
$table->float('key_earth_1')->comment('Chiffre clé \'respecter la terre\' 1');
$table->float('key_earth_2')->comment('Chiffre clé \'respecter la terre\' 2');
$table->float('key_together_1')->comment('Chiffre clé \'vivre ensemble\' 1');
$table->float('key_together_2')->comment('Chiffre clé \'vivre ensemble\' 2');
$table->float('key_together_3')->comment('Chiffre clé \'vivre ensemble\' 3');
$table->float('key_together_4')->comment('Chiffre clé \'vivre ensemble\' 4');
$table->float('key_territory')->comment('Chiffre clé \'intégrer son territoire\'');
$table->float('key_feed', 12)->comment('Chiffre clé \'bien nourrir\'');
$table->float('key_earth_1', 12)->comment('Chiffre clé \'respecter la terre\' 1');
$table->float('key_earth_2', 12)->comment('Chiffre clé \'respecter la terre\' 2');
$table->float('key_together_1', 12)->comment('Chiffre clé \'vivre ensemble\' 1');
$table->float('key_together_2', 12)->comment('Chiffre clé \'vivre ensemble\' 2');
$table->float('key_together_3', 12)->comment('Chiffre clé \'vivre ensemble\' 3');
$table->float('key_together_4', 12)->comment('Chiffre clé \'vivre ensemble\' 4');
$table->float('key_territory', 12)->comment('Chiffre clé \'intégrer son territoire\'');
$table->timestamps();
});
}

View file

@ -26,7 +26,7 @@ return new class extends Migration
$table->string('picture_alt')->comment('Attribut alt pour l\'image picture');
$table->string('picture_title')->comment('Attribut title pour l\'image picture');
$table->string('picture_productsheet')
->comment('Path de l\'image à afficher sur les fiches produits de cette gamme (332px * 442px)');
->comment('Path de l\'image à afficher sur les fiches produits de cette gamme (680px * 300px)');
$table->string('picture_productsheet_alt')
->comment('Attribut alt de l\'image à afficher sur les fiches produits');
$table->string('picture_productsheet_title')

View file

@ -0,0 +1,43 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::table('homepages', function (Blueprint $table) {
$table->string('picture')->nullable()->change()
->comment('Path de l\'image de la page d\'accueil (640px * 390px)');
$table->string('youtube_video_id')->after('picture_title')
->nullable()
->default(null)
->comment('L\ídentifiant de la vidéo Youtube');
$table->boolean('display_video')->after('youtube_video_id')
->default(false)
->comment('Afficher la video ou l\'image');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Schema::table('homepages', function (Blueprint $table) {
$table->string('picture')->change()
->comment('Path de l\'image de la page d\'accueil (640px * 390px)');
$table->dropColumn('youtube_video_id');
$table->dropColumn('display_video');
});
}
};

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::table('homepages', function (Blueprint $table) {
$table->renameColumn('youtube_video_id', 'video_id');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Schema::table('homepages', function (Blueprint $table) {
$table->renameColumn('video_id', 'youtube_video_id');
});
}
};

View file

@ -21,7 +21,7 @@ class RangeSeeder extends Seeder
'../database/factories/assets/ranges/default-picture-466x440.png'
)));
$range->picture_productsheet = FileStorageHelper::storeFile($range, new \SplFileInfo(\resource_path(
'../database/factories/assets/ranges/default-picture-332x442.png'
'../database/factories/assets/ranges/default-picture-680x300.png'
)));
$range->picture_thumbnail = FileStorageHelper::storeFile($range, new \SplFileInfo(\resource_path(
'../database/factories/assets/ranges/default-picture-246x337.png'

177
manifest.yaml Normal file
View file

@ -0,0 +1,177 @@
---
apiVersion: v1
kind: ConfigMap
metadata:
name: ${CI_PROJECT_PATH_SLUG}-config-map
data:
APP_NAME: "${APP_APP_NAME}"
APP_ENV: "${APP_APP_ENV}"
APP_KEY: "${APP_APP_KEY}"
APP_DEBUG: "${APP_APP_DEBUG}"
APP_URL: "https://www.${APP_DOMAIN}"
LOG_CHANNEL: "k8s"
DB_HOST: "mariadb-service.common-services"
DB_DATABASE: "${APP_DB_DATABASE}"
DB_USERNAME: "${APP_DB_USERNAME}"
DB_PASSWORD: "${APP_DB_PASSWORD}"
MAIL_HOST: "${APP_MAIL_HOST}"
MAIL_PORT: "${APP_MAIL_PORT}"
MAIL_USERNAME: "${APP_MAIL_USERNAME}"
MAIL_PASSWORD: "${APP_MAIL_PASSWORD}"
MAIL_ENCRYPTION: "${APP_MAIL_ENCRYPTION}"
MAIL_FROM_ADDRESS: "${APP_MAIL_FROM_ADDRESS}"
MAIL_FROM_NAME: "${APP_APP_NAME}"
MAIL_CONTACTFORM_RECEIVER: "${APP_MAIL_CONTACTFORM_RECEIVER}"
LDC_WEB_API_KEY: "${APP_LDC_WEB_API_KEY}"
GOOGLE_RECAPTCHA_PRIVATE: "${APP_GOOGLE_RECAPTCHA_PRIVATE}"
GOOGLE_RECAPTCHA_PUBLIC: "${APP_GOOGLE_RECAPTCHA_PUBLIC}"
GOOGLE_RECAPTCHA_VERSION: "${APP_GOOGLE_RECAPTCHA_VERSION}"
SENTRY_LARAVEL_DSN: "${APP_SENTRY_LARAVEL_DSN}"
MATOMO_SITE_ID: "${APP_MATOMO_SITE_ID}"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ${CI_PROJECT_PATH_SLUG}-pvc-storage
spec:
storageClassName: "csi-cinder-classic"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: $APP_PVC_SIZE
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${CI_PROJECT_PATH_SLUG}-deployment
spec:
replicas: 1
selector:
matchLabels:
app: ${CI_PROJECT_PATH_SLUG}-app
template:
metadata:
labels:
app: ${CI_PROJECT_PATH_SLUG}-app
spec:
containers:
- name: ${CI_PROJECT_PATH_SLUG}-container
image: $IMAGE_TAG
envFrom:
- configMapRef:
name: ${CI_PROJECT_PATH_SLUG}-config-map
volumeMounts:
- name: storage
mountPath: /app/srcs/storage
imagePullSecrets:
- name: docker-registry-credentials
securityContext:
runAsUser: 33
runAsGroup: 33
fsGroup: 33
fsGroupChangePolicy: "OnRootMismatch"
volumes:
- name: storage
persistentVolumeClaim:
claimName: ${CI_PROJECT_PATH_SLUG}-pvc-storage
---
apiVersion: v1
kind: Service
metadata:
name: ${CI_PROJECT_PATH_SLUG}-service
spec:
selector:
app: ${CI_PROJECT_PATH_SLUG}-app
ports:
- port: 80
targetPort: 443
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: ${CI_PROJECT_PATH_SLUG}-ingress-route
spec:
entryPoints:
- web
- websecure
routes:
- match: Host(`${APP_DOMAIN}`, `www.${APP_DOMAIN}`)
kind: Rule
services:
- name: ${CI_PROJECT_PATH_SLUG}-service
port: 80
middlewares:
- name: redirect-no-www-to-www
namespace: common-services
tls:
secretName: $APP_DOMAIN
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: $APP_DOMAIN
spec:
dnsNames:
- $APP_DOMAIN
- www.${APP_DOMAIN}
secretName: $APP_DOMAIN
issuerRef:
name: $APP_CERT_ISSUER
kind: ClusterIssuer
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: ${CI_PROJECT_PATH_SLUG}-cronjob
spec:
schedule: "* * * * *"
jobTemplate:
spec:
template:
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- ${CI_PROJECT_PATH_SLUG}-app
topologyKey: "kubernetes.io/hostname"
namespaces: [$KUBE_NAMESPACE]
containers:
- name: ${CI_PROJECT_PATH_SLUG}-scheduled
image: $IMAGE_TAG
imagePullPolicy: IfNotPresent
command:
- "php"
args:
- "artisan"
- "schedule:run"
envFrom:
- configMapRef:
name: ${CI_PROJECT_PATH_SLUG}-config-map
volumeMounts:
- name: storage
mountPath: /app/srcs/storage
imagePullSecrets:
- name: docker-registry-credentials
volumes:
- name: storage
persistentVolumeClaim:
claimName: ${CI_PROJECT_PATH_SLUG}-pvc-storage
securityContext:
runAsUser: 33
runAsGroup: 33
fsGroup: 33
fsGroupChangePolicy: "OnRootMismatch"
restartPolicy: OnFailure

2914
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,6 @@
{
"private": true,
"name": "srcs",
"scripts": {
"vite": "vite --clearScreen false",
"prod": "vite build --mode production",
@ -15,12 +16,11 @@
"devDependencies": {
"@babel/core": "^7.22.11",
"@babel/types": "^7.21.2",
"@fortawesome/fontawesome-svg-core": "latest",
"@fortawesome/free-brands-svg-icons": "latest",
"@fortawesome/free-regular-svg-icons": "latest",
"@fortawesome/free-solid-svg-icons": "latest",
"@fortawesome/vue-fontawesome": "^3.0.3",
"@ldc-web/cookie-notice": "^3.0.9",
"@fortawesome/fontawesome-svg-core": "^6.5.1",
"@fortawesome/free-regular-svg-icons": "^6.5.1",
"@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/vue-fontawesome": "^3.0.5",
"@ldc-web/cookie-notice": "^4.0.1",
"@sipec/vue3-tags-input": "^3.0.4",
"@types/bootstrap": "^5.2.7",
"@types/node": "^20.8.2",
@ -28,11 +28,10 @@
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.36.2",
"@vitejs/plugin-basic-ssl": "^1.0.1",
"@vitejs/plugin-legacy": "latest",
"@vitejs/plugin-vue": "latest",
"@vitejs/plugin-vue": "^4.6.2",
"@vue/compiler-sfc": "latest",
"@vue/eslint-config-typescript": "latest",
"@vue/tsconfig": "latest",
"@vue/tsconfig": "^0.4.0",
"axios": "^1.5.1",
"bootstrap": "^5.3.2",
"chart.js": "^4.4.0",
@ -40,33 +39,34 @@
"eslint": "latest",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-vue": "latest",
"laravel-vite-plugin": "latest",
"laravel-vite-plugin": "^0.8.1",
"postcss-discard-comments": "^6.0.0",
"prettier": "latest",
"print-js": "^1.6.0",
"resolve-url-loader": "^5.0.0",
"rollup-plugin-copy": "^3.5.0",
"sass": "latest",
"slugify": "^1.6.6",
"stylelint": "latest",
"stylelint": "^15.11.0",
"stylelint-config-idiomatic-order": "github:ream88/stylelint-config-idiomatic-order",
"stylelint-config-sass-guidelines": "latest",
"stylelint-config-standard-scss": "latest",
"stylelint-scss": "latest",
"stylelint-config-sass-guidelines": "^10.0.0",
"stylelint-config-standard-scss": "^11.1.0",
"stylelint-scss": "^5.3.2",
"sweetalert2": "^11.7.31",
"swiper": "^10.2.0",
"swiper": "^11.0.5",
"terser": "latest",
"toastify-js": "^1.12.0",
"ts-loader": "^9.3.1",
"typescript": ">=3.3.1 <5.2.0",
"typescript-eslint": "latest",
"typescript": "latest",
"vite": "^4.5.1",
"vite-plugin-babel": "latest",
"vite-plugin-checker": "latest",
"vite-plugin-eslint": "latest",
"vite-plugin-stylelint": "latest",
"vite": "^4.0.0",
"vue-eslint-parser": "^9.1.0",
"vue-loader": "^16.8.3",
"vue": "latest",
"vue-tsc": "1.6.0"
"vue-eslint-parser": "^9.3.2",
"vue-loader": "^17.3.1",
"vue-tsc": "1.8.11"
}
}
}

10
phpstan.neon Normal file
View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 0 B

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#ffc40d</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 KiB

View file

@ -0,0 +1,173 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="330.000000pt" height="330.000000pt" viewBox="0 0 330.000000 330.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,330.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1565 3286 c-113 -31 -202 -130 -219 -240 -4 -22 -8 -41 -10 -43 -1
-2 -38 -14 -82 -27 -464 -136 -823 -506 -947 -976 -25 -94 -32 -134 -43 -240
-3 -30 -7 -59 -8 -65 -2 -6 -35 -10 -75 -9 l-71 0 0 -38 0 -38 75 0 75 0 1
-32 c3 -160 55 -361 139 -533 80 -164 171 -288 303 -411 159 -149 398 -281
583 -324 50 -11 52 -13 58 -53 17 -105 94 -200 192 -236 184 -69 376 45 409
242 3 20 12 37 19 37 54 0 306 106 417 175 137 86 292 233 392 373 147 204
248 496 256 740 1 21 5 22 76 22 l75 0 0 38 0 39 -71 -1 c-39 -1 -72 2 -74 5
-2 3 -6 26 -9 52 -14 116 -16 134 -22 162 -51 281 -199 551 -413 754 -150 143
-371 267 -582 326 -54 15 -57 18 -64 53 -18 100 -91 195 -177 232 -60 26 -145
32 -203 16z m135 -70 c0 11 80 -38 108 -67 27 -28 68 -116 59 -126 -2 -2 -37
0 -78 5 -88 9 -220 8 -306 -2 -71 -9 -75 -4 -50 57 39 92 148 155 235 136 17
-4 32 -5 32 -3z m120 -271 c136 -20 253 -55 390 -117 105 -48 262 -161 358
-257 214 -216 348 -505 376 -814 4 -51 3 -57 -12 -53 -9 3 -93 28 -187 56 -93
28 -173 54 -176 58 -3 4 -19 48 -34 97 -93 304 -313 529 -615 628 -181 60
-429 54 -605 -15 -285 -110 -495 -341 -578 -633 l-23 -79 -187 -57 c-103 -31
-188 -53 -188 -50 -2 9 -2 9 5 89 14 152 79 353 161 497 131 229 321 408 560
525 152 75 266 109 425 126 30 4 56 8 58 9 6 5 226 -3 272 -10z m35 -464 c244
-65 441 -225 555 -451 33 -66 74 -179 67 -186 -1 -1 -36 8 -77 21 -41 13 -113
35 -160 49 -47 14 -200 61 -341 104 l-255 78 -205 -62 c-112 -34 -300 -91
-416 -127 -140 -42 -213 -61 -213 -52 0 51 95 247 158 325 130 163 346 292
527 316 28 3 52 8 54 10 13 12 235 -7 306 -25z m-251 -543 c-1 -35 -2 -66 -3
-69 -1 -4 -15 -9 -33 -13 -41 -8 -119 -88 -134 -138 -34 -111 38 -255 139
-275 15 -3 27 -8 28 -12 4 -23 3 -134 -1 -136 -6 -4 -156 41 -695 205 -148 45
-316 96 -372 113 -57 17 -103 34 -103 37 0 4 10 10 23 12 12 3 195 58 407 123
619 188 735 223 741 219 3 -1 4 -31 3 -66z m118 63 c13 -6 264 -82 558 -171
294 -88 546 -166 560 -172 23 -10 19 -12 -55 -35 -44 -13 -87 -26 -95 -28 -16
-4 -540 -163 -992 -301 -16 -5 -18 3 -18 64 l0 70 55 27 c30 14 67 41 80 60
90 117 42 286 -97 342 l-38 15 0 69 c0 71 3 76 42 60z m-12 -226 c46 -24 73
-71 73 -125 0 -139 -174 -194 -258 -82 -14 19 -20 43 -20 82 0 62 21 100 70
124 38 20 98 20 135 1z m-1187 -226 c95 -29 178 -55 186 -59 8 -4 25 -44 37
-89 75 -258 253 -471 499 -594 64 -32 169 -64 253 -78 65 -10 226 -14 267 -5
11 2 40 7 64 11 70 12 175 50 259 95 216 116 386 329 455 567 13 46 26 85 29
88 6 7 357 115 371 115 22 0 -19 -238 -63 -372 -59 -176 -136 -304 -290 -481
-34 -39 -180 -157 -245 -199 -318 -205 -738 -257 -1106 -137 -436 141 -764
501 -869 954 -20 87 -36 235 -26 235 4 0 84 -23 179 -51z m922 -285 c99 -30
187 -55 195 -55 17 -1 71 15 525 154 171 52 311 94 313 93 1 -2 -7 -32 -19
-67 -81 -248 -264 -443 -504 -538 -44 -17 -91 -34 -105 -36 -204 -40 -390 -24
-552 48 -142 63 -283 183 -369 314 -47 73 -101 191 -113 249 l-7 31 228 -69
c125 -38 309 -94 408 -124z m200 -998 c44 0 112 3 151 7 l72 7 -8 -35 c-41
-181 -301 -224 -405 -67 -13 20 -27 52 -31 69 l-6 32 73 -7 c41 -3 110 -6 154
-6z"/>
<path d="M1589 2881 c-11 -5 -14 -32 -14 -115 0 -106 1 -109 23 -111 14 -1 22
4 23 14 1 9 2 25 3 36 2 31 26 24 45 -14 10 -21 25 -36 39 -38 26 -3 27 3 8
48 -13 30 -13 36 2 52 21 23 21 83 -1 104 -17 17 -104 33 -128 24z m98 -67 c5
-17 -14 -35 -41 -38 -23 -3 -26 0 -22 23 2 14 5 29 5 33 1 9 54 -6 58 -18z"/>
<path d="M1320 2843 c-42 -13 -60 -29 -74 -63 -40 -100 51 -197 147 -157 89
38 97 157 14 207 -31 20 -57 23 -87 13z m53 -52 c23 -9 36 -31 43 -68 4 -21
-29 -52 -59 -57 -19 -3 -33 1 -47 17 -11 11 -20 23 -21 26 0 3 -2 14 -4 25 -4
18 35 66 54 66 5 0 21 -4 34 -9z"/>
<path d="M1890 2808 c-14 -23 -32 -54 -41 -69 -8 -15 -23 -39 -33 -53 -9 -15
-14 -31 -11 -36 9 -15 43 -12 52 5 13 22 67 5 71 -22 2 -13 13 -23 29 -26 l25
-5 -5 116 c-5 116 -11 132 -47 132 -8 0 -26 -19 -40 -42z m40 -63 c0 -28 -4
-35 -20 -35 -11 0 -20 3 -20 6 0 7 33 64 37 64 2 0 3 -16 3 -35z"/>
<path d="M2096 2775 c-9 -23 -9 -23 18 -37 l24 -13 -27 -55 c-52 -108 -51
-105 -30 -118 17 -11 23 -4 59 68 22 44 44 80 48 80 4 0 14 -4 22 -10 16 -10
40 4 40 25 0 9 -69 46 -136 72 -6 2 -14 -3 -18 -12z"/>
<path d="M1026 2717 c-17 -13 -16 -18 34 -108 29 -52 54 -95 56 -97 1 -2 27
11 58 28 49 28 54 34 46 51 -12 21 -21 24 -35 10 -6 -5 -20 -12 -32 -15 -19
-6 -26 1 -52 51 -45 88 -53 96 -75 80z"/>
<path d="M2290 2573 c-36 -46 -66 -87 -68 -92 -1 -4 22 -27 52 -50 55 -41 55
-41 67 -19 10 18 8 24 -12 39 -13 10 -27 19 -31 19 -13 0 -9 27 5 39 11 8 18
6 32 -9 22 -25 41 -26 50 -3 4 12 -2 24 -19 38 -19 16 -23 24 -15 38 10 16 12
16 40 -8 17 -14 34 -25 39 -25 14 0 33 31 24 39 -5 4 -29 24 -54 43 l-45 35
-65 -84z"/>
<path d="M832 2579 c-23 -17 -42 -36 -42 -42 0 -7 20 -32 43 -57 23 -26 54
-61 69 -79 25 -29 30 -31 48 -18 l20 14 -21 27 -21 26 26 31 c29 34 31 47 15
82 -12 26 -47 47 -78 47 -9 0 -36 -14 -59 -31z m87 -38 c8 -14 6 -23 -10 -37
-20 -18 -21 -18 -39 6 -17 22 -17 25 -2 37 24 18 40 16 51 -6z"/>
<path d="M2490 2423 c-30 -25 -60 -53 -67 -62 -36 -47 18 -140 75 -126 28 6
73 35 130 83 30 25 31 29 19 47 -14 18 -17 17 -82 -33 -56 -43 -70 -50 -87
-41 -30 16 -19 38 48 95 48 40 59 55 52 68 -14 24 -28 19 -88 -31z"/>
<path d="M680 2425 c-11 -13 -10 -22 3 -51 l17 -34 -39 6 c-33 5 -41 3 -50
-15 -7 -11 -10 -21 -9 -22 6 -1 53 -9 80 -13 38 -6 41 -9 68 -65 13 -28 26
-51 29 -51 4 0 12 9 19 20 10 17 10 26 -3 50 -20 39 -20 40 17 29 27 -7 36 -5
52 11 25 24 22 27 -34 35 -71 10 -75 12 -100 65 -25 54 -31 58 -50 35z"/>
<path d="M2630 2175 c-76 -34 -94 -47 -92 -61 6 -28 10 -29 44 -16 45 17 56
-4 21 -40 -20 -21 -24 -33 -19 -54 l7 -27 33 32 c27 26 38 31 65 26 26 -4 37
0 62 25 26 26 30 35 23 62 -8 38 -35 98 -43 97 -3 0 -49 -20 -101 -44z m94
-40 c15 -39 -25 -66 -49 -32 -14 20 -14 23 2 34 23 17 40 16 47 -2z"/>
<path d="M515 2154 c-14 -31 -25 -59 -25 -63 0 -7 153 -79 185 -87 18 -4 25 4
48 57 21 48 24 65 15 71 -24 15 -35 8 -50 -31 -13 -33 -20 -39 -37 -34 -25 6
-25 6 -6 43 14 27 14 30 -4 40 -27 14 -28 14 -40 -20 -10 -31 -17 -35 -39 -21
-11 6 -10 15 5 45 15 32 15 38 3 46 -25 16 -29 12 -55 -46z"/>
<path d="M1170 2213 l0 -93 48 0 c52 0 73 14 90 60 13 35 -4 87 -34 106 -13 9
-42 17 -64 18 l-40 2 0 -93z m98 3 c4 -32 -13 -56 -39 -56 -16 0 -19 8 -19 48
0 27 3 52 7 55 12 13 48 -20 51 -47z"/>
<path d="M1374 2302 c-5 -4 -9 -45 -9 -92 l0 -85 58 -3 c52 -3 57 -1 57 18 0
17 -6 20 -40 20 -37 0 -40 2 -31 19 7 12 18 17 33 14 18 -3 24 0 25 16 1 17
-4 21 -28 21 -22 0 -29 5 -29 19 0 12 7 18 23 18 42 1 47 3 47 21 0 15 -9 19
-48 19 -27 1 -53 -2 -58 -5z"/>
<path d="M1541 2300 c-4 -3 -8 -45 -8 -93 0 -84 1 -88 21 -85 15 2 20 9 19 26
-2 12 1 23 5 23 4 1 18 3 31 4 39 4 61 39 50 77 -7 27 -21 45 -39 49 -18 4
-72 4 -79 -1z m79 -60 c0 -25 -38 -33 -45 -9 -4 10 -4 23 -1 28 10 15 46 0 46
-19z"/>
<path d="M1700 2234 c0 -66 3 -78 24 -99 18 -18 31 -23 53 -19 47 8 57 27 59
106 1 84 0 88 -24 82 -14 -4 -17 -17 -18 -70 0 -45 -5 -68 -14 -74 -25 -16
-39 10 -42 77 -2 54 -6 67 -20 69 -16 2 -18 -7 -18 -72z"/>
<path d="M1903 2302 c-6 -4 -11 -45 -11 -94 0 -79 2 -88 19 -88 13 0 19 7 20
23 4 134 3 157 -7 161 -6 2 -15 1 -21 -2z"/>
<path d="M2011 2293 c-13 -9 -21 -25 -21 -43 0 -24 7 -33 40 -50 22 -11 40
-25 40 -30 0 -17 -16 -19 -42 -6 -22 12 -26 11 -36 -4 -8 -14 -6 -20 11 -29
40 -21 66 -21 93 2 22 19 24 26 15 50 -6 15 -25 34 -46 44 -19 9 -35 20 -35
24 0 16 20 20 41 8 17 -9 23 -8 30 2 20 33 -52 59 -90 32z"/>
<path d="M2616 1392 c-2 -4 -13 -33 -24 -65 l-21 -58 27 -10 c37 -15 176 -59
184 -59 3 0 16 28 27 62 16 48 18 64 8 70 -23 15 -34 8 -46 -32 -12 -42 -17
-45 -40 -31 -12 8 -13 14 -2 37 10 22 10 29 -1 35 -23 15 -36 10 -47 -21 -6
-17 -17 -30 -27 -30 -17 0 -16 13 2 76 4 16 0 23 -15 27 -12 3 -23 2 -25 -1z"/>
<path d="M600 1355 c-9 -11 -23 -13 -50 -9 -65 11 -100 -51 -66 -114 8 -15 17
-39 21 -54 6 -32 21 -37 29 -10 4 12 15 17 38 16 49 -2 60 4 47 28 -6 11 -17
17 -24 14 -20 -7 -28 9 -11 24 10 7 21 8 33 2 58 -30 105 37 63 91 -24 30 -61
35 -80 12z m53 -50 c1 -5 -6 -11 -15 -13 -11 -2 -18 3 -18 13 0 17 30 18 33 0z
m-99 -17 c-18 -32 -27 -39 -35 -27 -11 18 1 39 23 39 12 0 17 -4 12 -12z"/>
<path d="M2521 1164 c-22 -28 -21 -33 19 -74 3 -3 18 -20 34 -38 l30 -32 -55
17 c-74 23 -99 22 -116 -4 -14 -21 -11 -24 72 -85 47 -35 90 -64 95 -66 5 -2
13 5 19 16 9 17 3 24 -39 53 -28 19 -50 37 -50 41 0 3 25 -2 55 -13 67 -23 72
-24 84 0 11 19 10 19 -63 100 l-31 34 50 -28 c28 -16 56 -33 63 -38 12 -10 42
7 42 24 0 4 -33 27 -72 50 -40 23 -83 49 -94 56 -20 13 -24 11 -43 -13z"/>
<path d="M689 1085 c-38 -21 -59 -58 -59 -106 0 -35 6 -47 36 -75 42 -40 68
-42 108 -10 l30 23 -23 36 c-25 41 -33 44 -51 22 -10 -12 -10 -18 -1 -29 19
-22 -7 -31 -30 -10 -40 36 -8 114 48 114 30 0 63 -34 58 -59 -2 -14 3 -25 16
-32 26 -14 31 -9 32 34 2 84 -87 134 -164 92z"/>
<path d="M2311 903 c-67 -35 -85 -120 -37 -176 53 -61 133 -57 185 9 26 33 27
79 1 121 -33 55 -95 74 -149 46z m72 -44 c37 -17 53 -53 39 -84 -26 -56 -105
-50 -120 9 -10 35 8 72 36 79 9 3 18 5 19 6 1 0 13 -4 26 -10z"/>
<path d="M880 844 c-47 -30 -89 -59 -94 -65 -15 -14 26 -40 41 -27 21 17 66
-18 59 -46 -4 -15 1 -29 13 -40 17 -16 20 -12 64 84 26 55 47 104 47 109 0 8
-33 41 -41 41 -2 0 -42 -25 -89 -56z m56 -43 c-11 -20 -21 -28 -31 -25 -21 8
-19 15 13 40 34 28 38 24 18 -15z"/>
<path d="M1079 759 c-25 -25 -20 -56 18 -106 18 -24 23 -37 16 -44 -14 -14
-53 6 -53 27 0 22 -44 14 -48 -8 -4 -23 38 -65 74 -74 39 -10 78 14 82 50 3
20 -6 42 -27 71 -17 24 -31 46 -31 49 0 15 40 4 54 -15 18 -26 52 -22 48 6 -5
30 -49 65 -83 65 -16 0 -38 -9 -50 -21z"/>
<path d="M2123 769 l-22 -8 25 -56 c14 -31 24 -58 23 -60 -2 -2 -29 16 -60 39
-44 34 -60 41 -77 35 -25 -9 -24 -12 30 -124 48 -99 46 -97 68 -83 17 11 17
13 -5 65 -13 29 -22 53 -19 53 3 0 28 -16 57 -35 54 -37 64 -40 85 -27 10 7 1
33 -35 109 -44 93 -50 100 -70 92z"/>
<path d="M1265 693 c-3 -10 -2 -19 3 -21 5 -1 17 -9 27 -16 16 -12 16 -17 -8
-94 -24 -78 -24 -82 -6 -87 22 -7 39 1 39 18 0 6 10 42 21 79 20 63 23 66 45
58 19 -8 26 -5 34 10 5 10 10 21 10 23 0 5 -132 47 -148 47 -5 0 -13 -8 -17
-17z"/>
<path d="M1783 655 c-82 -35 -81 -163 1 -207 46 -25 122 -4 149 41 35 59 12
137 -49 166 -39 18 -59 18 -101 0z m96 -61 c41 -52 18 -104 -44 -104 -58 0
-86 69 -45 110 28 28 64 25 89 -6z"/>
<path d="M1486 642 c-3 -9 -8 -61 -11 -114 l-6 -98 25 0 c23 0 26 4 26 35 0
23 5 35 13 35 14 0 37 -40 37 -65 0 -10 10 -15 30 -15 l29 0 -14 41 c-14 37
-13 42 5 62 42 45 6 126 -57 128 -15 0 -38 2 -50 5 -14 2 -24 -2 -27 -14z
m101 -58 c9 -23 -18 -44 -44 -34 -15 6 -20 15 -16 29 6 25 51 29 60 5z"/>
<path d="M1279 1166 c-1 -1 -3 -13 -5 -28 -2 -19 2 -28 12 -28 17 0 16 7 18
-110 l1 -95 28 -3 28 -3 -3 132 -3 133 -37 1 c-21 1 -39 1 -39 1z"/>
<path d="M1456 1149 c-13 -10 -27 -30 -30 -43 -11 -46 25 -105 67 -106 21 0
21 -2 -3 -50 -11 -22 -20 -41 -20 -43 0 -2 14 -4 31 -5 28 -1 33 5 65 71 39
81 43 131 14 165 -26 32 -90 37 -124 11z m82 -51 c14 -14 -2 -48 -22 -48 -20
0 -37 25 -30 44 7 19 35 21 52 4z"/>
<path d="M1703 1158 c-30 -14 -48 -70 -33 -98 8 -15 8 -25 1 -34 -49 -59 23
-150 103 -130 49 12 89 99 55 119 -6 4 -8 22 -4 46 9 51 -1 76 -35 94 -33 17
-56 18 -87 3z m70 -62 c8 -19 -16 -38 -37 -30 -16 6 -21 25 -9 37 12 12 41 7
46 -7z m7 -110 c0 -28 -33 -41 -51 -20 -17 20 -3 44 26 44 20 0 25 -5 25 -24z"/>
<path d="M1883 1163 c-7 -3 -13 -16 -13 -29 0 -22 4 -24 45 -24 30 0 45 -4 45
-13 0 -7 -13 -52 -29 -99 -16 -48 -27 -89 -23 -92 12 -13 50 -5 57 12 15 37
72 237 69 241 -5 5 -137 9 -151 4z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -0,0 +1,19 @@
{
"name": "Agis",
"short_name": "Agis",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-256x256.png",
"sizes": "256x256",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

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