Compare commits
No commits in common. "laravel-8-fix-image-input" and "develop" have entirely different histories.
laravel-8-
...
develop
15
.env.example
|
|
@ -31,9 +31,16 @@ MAIL_ENCRYPTION=null
|
|||
MAIL_FROM_ADDRESS=null
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
# SENTRY
|
||||
SENTRY_LARAVEL_DSN=
|
||||
|
||||
# LDC WEB STATUS CHECKER
|
||||
# Exception Handler
|
||||
MAIL_LIST_SERVICEWEB=
|
||||
# Google Analytics
|
||||
GOOGLE_ANALYTICS_ID=
|
||||
# Google Recaptcha
|
||||
GOOGLE_RECAPTCHA_PUBLIC=
|
||||
GOOGLE_RECAPTCHA_PRIVATE=
|
||||
GOOGLE_RECAPTCHA_VERSION=
|
||||
# LDC Web Status Checker
|
||||
LDC_STATUS_CHECKER_API_KEY="MY_KEY"
|
||||
LDC_STATUS_CHECKER_UUID="UUID_PROJECT"
|
||||
# LDC Web Admin Secure
|
||||
LDC_WEB_API_KEY="MY_KEY"
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:vue/vue3-strongly-recommended",
|
||||
"@vue/eslint-config-typescript"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.vue"],
|
||||
"rules": {
|
||||
"no-undef": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["*.vue"],
|
||||
"rules": {
|
||||
"indent": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"parser": "vue-eslint-parser",
|
||||
"parserOptions": {
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["vue", "@typescript-eslint"],
|
||||
"ignorePatterns": ["webpack.mix.js", "**/vendor/*.js", "**/vendor/**/*.js"],
|
||||
"rules": {
|
||||
"indent": ["error", 4, { "SwitchCase": 1 }],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"quotes": ["error", "double"],
|
||||
"semi": ["error", "always"],
|
||||
"vue/script-indent": ["error", 2, { "baseIndent": 0, "switchCase": 1 }],
|
||||
"vue/one-component-per-file": "off"
|
||||
}
|
||||
}
|
||||
9
.gitignore
vendored
|
|
@ -1,14 +1,19 @@
|
|||
/node_modules
|
||||
/public/hot
|
||||
/public/storage
|
||||
/public/build
|
||||
/public/css
|
||||
/public/js
|
||||
/public/images
|
||||
/public/fonts
|
||||
/public/medias
|
||||
/public/uploads
|
||||
/public/assets
|
||||
/public/cache
|
||||
/public/sitemap.xml
|
||||
/public/mix-manifest.json
|
||||
/storage/*.key
|
||||
/vendor
|
||||
.env
|
||||
.env.backup
|
||||
.phpunit.result.cache
|
||||
docker-compose.override.yml
|
||||
Homestead.json
|
||||
|
|
|
|||
118
.gitlab-ci.yml
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
stages:
|
||||
- Security
|
||||
- CodingStandards
|
||||
- Builds
|
||||
|
||||
composer-security:
|
||||
stage: Security
|
||||
image: $CI_REGISTRY/service-web-lab/ldc-web-images-docker/ldcweb-qa:php7.3-1.0
|
||||
script:
|
||||
- local-php-security-checker --format=json --path=./composer.lock > composer-security-checker-report.json
|
||||
artifacts:
|
||||
when: on_failure
|
||||
expose_as: 'composer-security-checker'
|
||||
paths:
|
||||
- composer-security-checker-report.json
|
||||
allow_failure: true
|
||||
needs: []
|
||||
|
||||
npm-security:
|
||||
stage: Security
|
||||
image: node:17.9.1
|
||||
before_script:
|
||||
- npm install --location=global audit-ci@^6
|
||||
script:
|
||||
- audit-ci -p npm -o json > npm-security-checker-report.json
|
||||
artifacts:
|
||||
when: on_failure
|
||||
expose_as: 'npm-security-checker'
|
||||
paths:
|
||||
- npm-security-checker-report.json
|
||||
allow_failure: true
|
||||
needs: []
|
||||
|
||||
phpcs:
|
||||
stage: CodingStandards
|
||||
image: $CI_REGISTRY/service-web-lab/ldc-web-images-docker/ldcweb-qa:php7.3-1.0
|
||||
before_script:
|
||||
- composer config gitlab-token.gitlab.com $CI_DEPLOY_USER $CI_DEPLOY_PASSWORD
|
||||
- composer install
|
||||
cache:
|
||||
paths:
|
||||
- vendor/
|
||||
script:
|
||||
- vendor/bin/phpcs --report=junit --report-file=phpcs-report.xml
|
||||
artifacts:
|
||||
when: on_failure
|
||||
expose_as: 'phpcs'
|
||||
paths:
|
||||
- phpcs-report.xml
|
||||
reports:
|
||||
junit: phpcs-report.xml
|
||||
allow_failure: false
|
||||
needs:
|
||||
- composer-security
|
||||
- npm-security
|
||||
|
||||
eslint:
|
||||
stage: CodingStandards
|
||||
image: node:17.9.1
|
||||
before_script:
|
||||
- npm install
|
||||
cache:
|
||||
paths:
|
||||
- node_modules/
|
||||
script:
|
||||
- npm run eslint-ci --silent 2>/dev/null 1> eslint-report.xml
|
||||
artifacts:
|
||||
when: on_failure
|
||||
expose_as: 'eslint'
|
||||
paths:
|
||||
- eslint-report.xml
|
||||
reports:
|
||||
junit: eslint-report.xml
|
||||
allow_failure: false
|
||||
needs:
|
||||
- composer-security
|
||||
- npm-security
|
||||
|
||||
stylelint:
|
||||
stage: CodingStandards
|
||||
image: node:17.9.1
|
||||
before_script:
|
||||
- npm install
|
||||
cache:
|
||||
paths:
|
||||
- node_modules/
|
||||
script:
|
||||
- npm run stylelint-ci --silent 2>/dev/null 1> stylelint-report.json
|
||||
artifacts:
|
||||
when: on_failure
|
||||
expose_as: 'stylelint'
|
||||
paths:
|
||||
- stylelint-report.json
|
||||
allow_failure: false
|
||||
needs:
|
||||
- composer-security
|
||||
- npm-security
|
||||
|
||||
front-build:
|
||||
stage: Builds
|
||||
image: node:17.9.1
|
||||
before_script:
|
||||
- npm install
|
||||
cache:
|
||||
paths:
|
||||
- node_modules/
|
||||
script:
|
||||
- npm run prod > front-build-report.json
|
||||
artifacts:
|
||||
when: on_failure
|
||||
expose_as: 'front-build-report'
|
||||
paths:
|
||||
- front-build-report.json
|
||||
allow_failure: false
|
||||
needs:
|
||||
- phpcs
|
||||
- eslint
|
||||
- stylelint
|
||||
14
.styleci.yml
|
|
@ -1,14 +0,0 @@
|
|||
php:
|
||||
preset: laravel
|
||||
version: 8
|
||||
disabled:
|
||||
- no_unused_imports
|
||||
finder:
|
||||
not-name:
|
||||
- index.php
|
||||
- server.php
|
||||
js:
|
||||
finder:
|
||||
not-name:
|
||||
- webpack.mix.js
|
||||
css: true
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
{
|
||||
"extends": [
|
||||
"stylelint-config-standard-scss",
|
||||
"stylelint-config-sass-guidelines",
|
||||
"stylelint-config-idiomatic-order"
|
||||
],
|
||||
"plugins": ["stylelint-scss"],
|
||||
"files": ["**/*.css", "**/*.scss"],
|
||||
"ignoreFiles": [
|
||||
"**/vendor/*.css",
|
||||
"**/vendor/**/*.css",
|
||||
"**/vendor/*.scss",
|
||||
"**/vendor/**/*.scss",
|
||||
"**/vendor/*.sass",
|
||||
"**/vendor/**/*.sass",
|
||||
"**/vendor/*.less",
|
||||
"**/vendor/**/*.less"
|
||||
],
|
||||
"customSyntax": "postcss-scss",
|
||||
"rules": {
|
||||
"indentation": 4,
|
||||
"string-quotes": "double",
|
||||
"selector-max-id": null,
|
||||
"color-no-invalid-hex": true,
|
||||
"order/properties-alphabetical-order": null,
|
||||
"max-nesting-depth": 5,
|
||||
"selector-max-compound-selectors": 5,
|
||||
"scss/double-slash-comment-empty-line-before": [
|
||||
"always",
|
||||
{
|
||||
"ignore": [
|
||||
"between-comments",
|
||||
"stylelint-commands",
|
||||
"inside-block"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
22
.vscode/settings.json
vendored
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"workbench.colorCustomizations": {
|
||||
"activityBar.activeBackground": "#27b2e7",
|
||||
"activityBar.background": "#27b2e7",
|
||||
"activityBar.foreground": "#15202b",
|
||||
"activityBar.inactiveForeground": "#15202b99",
|
||||
"activityBarBadge.background": "#fce2f5",
|
||||
"activityBarBadge.foreground": "#15202b",
|
||||
"commandCenter.border": "#e7e7e799",
|
||||
"sash.hoverBorder": "#27b2e7",
|
||||
"statusBar.background": "#1695c5",
|
||||
"statusBar.foreground": "#e7e7e7",
|
||||
"statusBarItem.hoverBackground": "#27b2e7",
|
||||
"statusBarItem.remoteBackground": "#1695c5",
|
||||
"statusBarItem.remoteForeground": "#e7e7e7",
|
||||
"titleBar.activeBackground": "#1695c5",
|
||||
"titleBar.activeForeground": "#e7e7e7",
|
||||
"titleBar.inactiveBackground": "#1695c599",
|
||||
"titleBar.inactiveForeground": "#e7e7e799"
|
||||
},
|
||||
"peacock.remoteColor": "#1695c5"
|
||||
}
|
||||
10
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# 01/08/2022
|
||||
init
|
||||
with packages :
|
||||
- kwaadpepper/exception-handler
|
||||
- kwaadpepper/refresh-sitemap
|
||||
- ldc/web-admin-secure-laravel
|
||||
- ldc/web-status-checker-laravel
|
||||
|
||||
with Google Services keys
|
||||
with gitlab-ci config file
|
||||
34
README.md
|
|
@ -1,7 +1,29 @@
|
|||
## reste à faire
|
||||
# Laravel Base Project
|
||||
## Installation
|
||||
```
|
||||
cp .env.example .env
|
||||
php artisan key:generate
|
||||
```
|
||||
|
||||
FO :
|
||||
ts
|
||||
view
|
||||
route
|
||||
les fichiers à la racine
|
||||
## Crons
|
||||
Put every min in PROD :
|
||||
```
|
||||
php artisan schedule:run
|
||||
```
|
||||
|
||||
### LDC Web Status Report
|
||||
#### Available Commands
|
||||
```
|
||||
ldc:status-report
|
||||
ldc:send-status-report
|
||||
```
|
||||
### LDC Web Admin Secure
|
||||
#### Available Commands
|
||||
```
|
||||
ldcweb:admin-secure-sync
|
||||
```
|
||||
### Refresh-Sitemap
|
||||
#### Available Commands
|
||||
```
|
||||
sitemap:refresh --dry-run
|
||||
```
|
||||
|
|
@ -7,6 +7,9 @@ use Illuminate\Console\Command;
|
|||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
/**
|
||||
* Creates a new user
|
||||
*/
|
||||
class CreateAdmin extends Command
|
||||
{
|
||||
/**
|
||||
|
|
@ -26,7 +29,7 @@ class CreateAdmin extends Command
|
|||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
* @return integer
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
|
@ -47,52 +50,41 @@ class CreateAdmin extends Command
|
|||
continue;
|
||||
}
|
||||
}
|
||||
$firstname = null;
|
||||
while (is_null($firstname)) {
|
||||
$name = null;
|
||||
while (is_null($name)) {
|
||||
try {
|
||||
$tmp = $this->ask('Type the wanted user firstname', 'Admin');
|
||||
$validator = Validator::make(['firstname' => $tmp], [
|
||||
'firstname' => 'required|min:5,max:255'
|
||||
$tmp = $this->ask('Type the wanted user name', 'Admin');
|
||||
$validator = Validator::make(['name' => $tmp], [
|
||||
'name' => 'required|min:5,max:255'
|
||||
]);
|
||||
$validator->validate();
|
||||
$firstname = $tmp;
|
||||
$name = $tmp;
|
||||
} catch (ValidationException $e) {
|
||||
$this->error(sprintf(
|
||||
'Please provide a valid firstname %s',
|
||||
'Please provide a valid name %s',
|
||||
\implode(',', collect($e->errors())->flatten()->all())
|
||||
));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$lastname = null;
|
||||
while (is_null($lastname)) {
|
||||
try {
|
||||
$tmp = $this->ask('Type the wanted user lastname', 'Saïtama');
|
||||
$validator = Validator::make(['lastname' => $tmp], [
|
||||
'lastname' => 'required|min:5,max:255'
|
||||
]);
|
||||
$validator->validate();
|
||||
$lastname = $tmp;
|
||||
} catch (ValidationException $e) {
|
||||
$this->error(sprintf(
|
||||
'Please provide a valid lastname %s',
|
||||
\implode(',', collect($e->errors())->flatten()->all())
|
||||
));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$password = null;
|
||||
while (
|
||||
($password = $this->secret(
|
||||
(!\is_null($password) ? 'Failed to confirm, ' : '') . 'Type the wanted user password'
|
||||
)) !== $this->secret('Confirm the password')
|
||||
) {
|
||||
continue;
|
||||
};
|
||||
|
||||
$password = $this->secret('Type the wanted user password');
|
||||
|
||||
$user = new User();
|
||||
$user->firstname = $firstname;
|
||||
$user->lastname = $lastname;
|
||||
$user->email = $email;
|
||||
$user->password = $password;
|
||||
$user->email_verified_at = now();
|
||||
$user->saveOrFail();
|
||||
$admin = new User([
|
||||
'name' => $name,
|
||||
'email' => $email,
|
||||
'password' => $password
|
||||
]);
|
||||
$admin->saveOrFail();
|
||||
|
||||
$this->info('User created.');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Kwaadpepper\ExceptionHandler\Exceptions\ExceptionHandler;
|
||||
use Throwable;
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
|
|
|
|||
|
|
@ -3,12 +3,24 @@
|
|||
namespace App\Http\Controllers\Bo\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Providers\RouteServiceProvider;
|
||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||
use Illuminate\Foundation\Auth\ThrottlesLogins;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
|
||||
class LoginController extends Controller
|
||||
{
|
||||
use AuthenticatesUsers;
|
||||
use ThrottlesLogins;
|
||||
|
||||
/**
|
||||
* Where to redirect users after login.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $redirectTo = RouteServiceProvider::HOME;
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
|
|
@ -20,16 +32,6 @@ class LoginController extends Controller
|
|||
$this->middleware('guest:backend')->except('logout');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the guard to be used during authentication.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Auth\StatefulGuard
|
||||
*/
|
||||
protected function guard()
|
||||
{
|
||||
return Auth::guard('backend');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the application's login form.
|
||||
*
|
||||
|
|
@ -41,11 +43,57 @@ class LoginController extends Controller
|
|||
}
|
||||
|
||||
/**
|
||||
* Where to redirect users after login.
|
||||
* Attempt to log the user into the application.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return boolean
|
||||
*/
|
||||
protected function attemptLogin(Request $request)
|
||||
{
|
||||
return Auth::guard('backend')->attempt(
|
||||
$this->credentials($request),
|
||||
$request->filled('remember')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The user has been authenticated.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param null $user
|
||||
* @return mixed
|
||||
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing, Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
|
||||
*/
|
||||
protected function authenticated(Request $request, $user)
|
||||
{
|
||||
/** @var \App\Models\User|null */
|
||||
$user = Auth::guard('backend')->user();
|
||||
if ($user) {
|
||||
$user->last_connection = now();
|
||||
$user->saveOrFail();
|
||||
Session::flash('info', trans('Vous êtes à présent connecté'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The user has logged out of the application.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return mixed
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
|
||||
*/
|
||||
protected function loggedOut(Request $request)
|
||||
{
|
||||
Session::flash('info', trans('Vous êtes à présent déconnecté'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the post register / login redirect path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function redirectTo(): string
|
||||
public function redirectPath()
|
||||
{
|
||||
return \route('bo.dashboard');
|
||||
return response()->redirectToRoute('bo.dashboard')->getTargetUrl();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class ResetPasswordController extends Controller
|
||||
{
|
||||
use ResetsPasswords;
|
||||
|
||||
/**
|
||||
* Get the guard to be used during authentication.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Auth\StatefulGuard
|
||||
*/
|
||||
protected function guard()
|
||||
{
|
||||
return Auth::guard('backend');
|
||||
}
|
||||
|
||||
/**
|
||||
* Where to redirect users after login.
|
||||
* @return string
|
||||
*/
|
||||
protected function redirectTo(): string
|
||||
{
|
||||
return \route('bo.login');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
/**
|
||||
* Dashboard Page
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return view('bo.pages.dashboard');
|
||||
}
|
||||
}
|
||||
18
app/Http/Controllers/Bo/HomeController.php
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class HomeController extends Controller
|
||||
{
|
||||
/**
|
||||
* Dashboard view
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
|
||||
*/
|
||||
public function dashboard()
|
||||
{
|
||||
return view('bo.dashboard');
|
||||
}
|
||||
}
|
||||
|
|
@ -96,14 +96,11 @@ class Controller extends BaseController
|
|||
$table = $query->getModel()->getTable();
|
||||
if (request()->sort_col) {
|
||||
Session::put("$rName.sort_col", request()->sort_col);
|
||||
Session::put("$rName.sorted", true);
|
||||
}
|
||||
if (request()->sort_way) {
|
||||
Session::put("$rName.sort_way", request()->sort_way);
|
||||
Session::put("$rName.sorted", true);
|
||||
}
|
||||
if (request()->rst) {
|
||||
Session::remove("$rName.sorted");
|
||||
Session::remove("$rName.sort_col");
|
||||
Session::remove("$rName.sort_way");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Fo;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class HomeController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return view('fo.pages.home');
|
||||
}
|
||||
}
|
||||
|
|
@ -34,16 +34,12 @@ class Kernel extends HttpKernel
|
|||
\App\Http\Middleware\EncryptCookies::class,
|
||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
\Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
\App\Http\Middleware\SecureHeaders::class
|
||||
],
|
||||
|
||||
'api' => [
|
||||
'throttle:api',
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -63,6 +59,6 @@ class Kernel extends HttpKernel
|
|||
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
|
||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||
'htmlsanitize' => \App\Http\Middleware\HtmlSanitizer::class,
|
||||
'htmlsanitize' => \App\Http\Middleware\HtmlSanitizer::class
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ class Authenticate extends Middleware
|
|||
/**
|
||||
* Get the path the user should be redirected to when they are not authenticated.
|
||||
*
|
||||
* @param mixed $request
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return string|null
|
||||
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
|
||||
*/
|
||||
protected function redirectTo($request)
|
||||
{
|
||||
$logins = [
|
||||
'frontend' => route('login'),
|
||||
'backend' => route('bo.login')
|
||||
];
|
||||
if (!$request->expectsJson()) {
|
||||
|
|
@ -31,7 +31,7 @@ class Authenticate extends Middleware
|
|||
}
|
||||
}
|
||||
}
|
||||
return route('login');
|
||||
return collect($logins)->first() ?? '/';
|
||||
}
|
||||
abort(401);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
use App\Lib\ToolboxHelper;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class HtmlSanitizer
|
||||
|
|
@ -18,17 +18,11 @@ class HtmlSanitizer
|
|||
public function handle(Request $request, \Closure $next, ...$params)
|
||||
{
|
||||
$inputs = $request->all();
|
||||
/** @var callable|null */
|
||||
$cleanInput = null;
|
||||
$cleanInput = function (&$input, $name, $rootName = null) use ($params, &$cleanInput) {
|
||||
// * Nested element array
|
||||
if (is_array($input)) {
|
||||
array_walk_recursive($input, $cleanInput, $name);
|
||||
}
|
||||
array_walk_recursive($inputs, function (&$input, $name) use ($params) {
|
||||
if (!is_string($input)) {
|
||||
return;
|
||||
}
|
||||
if (in_array($name, $params) or in_array($rootName ?? $name, $params)) {
|
||||
if (in_array($name, $params)) {
|
||||
// ? Is a json input ?
|
||||
if ($isJson = \json_decode($input, true) and is_array($isJson)) {
|
||||
// ! Decode each json field recursive.
|
||||
|
|
@ -45,8 +39,7 @@ class HtmlSanitizer
|
|||
} else {
|
||||
$input = strip_tags($input);
|
||||
}
|
||||
};
|
||||
\array_walk($inputs, $cleanInput);
|
||||
});
|
||||
$request->merge($inputs);
|
||||
return $next($request);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,5 +11,6 @@ class PreventRequestsDuringMaintenance extends Middleware
|
|||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [];
|
||||
protected $except = [
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
|
|
@ -11,28 +10,21 @@ class RedirectIfAuthenticated
|
|||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @phpcs:disable Generic.Files.LineLength.TooLong
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
||||
* @param string|null ...$guards
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @param string|null ...$guards
|
||||
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
||||
* @throws \RuntimeException If guard is unhandled.
|
||||
* @phpcs:enable
|
||||
*/
|
||||
public function handle(Request $request, Closure $next, ...$guards)
|
||||
public function handle(Request $request, \Closure $next, ...$guards)
|
||||
{
|
||||
$logins = [
|
||||
'backend' => route('bo.dashboard')
|
||||
];
|
||||
$guards = empty($guards) ? [null] : $guards;
|
||||
|
||||
foreach ($guards as $guard) {
|
||||
if (Auth::guard($guard)->check()) {
|
||||
switch ($guard) {
|
||||
case 'frontend':
|
||||
return \redirect()->route('home');
|
||||
case 'backend':
|
||||
return \redirect()->route('bo.dashboard');
|
||||
default:
|
||||
throw new \RuntimeException("Unhandled guard `{$guard}` .");
|
||||
}
|
||||
return \redirect()->to($logins[$guard]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,10 +32,9 @@ class SecureHeaders
|
|||
{
|
||||
View::share('nonce', $nonce = \uniqid());
|
||||
// * For VueJS dev tool to execute
|
||||
$jsDev = config('app.debug') ? "'unsafe-eval' 'unsafe-inline'" : "'nonce-{$nonce}' 'strict-dynamic'";
|
||||
$scheme = config('app.env') === 'local' ? 'http' : 'https';
|
||||
$jsDev = config('app.debug') ? "'unsafe-eval' 'unsafe-inline'" : "'nonce-$nonce'";
|
||||
$reportUri = Route::has('cspReportUri') ?
|
||||
\sprintf(' report-uri %s ;', \parse_url(route('cspReportUri'), \PHP_URL_PATH)) : '';
|
||||
\sprintf('report-uri %s', \parse_url(route('cspReportUri'), \PHP_URL_PATH)) : '';
|
||||
$this->removeUnwantedHeaders($this->unwantedHeaderList);
|
||||
$response = $next($request);
|
||||
$response->headers->set('Referrer-Policy', 'no-referrer-when-downgrade');
|
||||
|
|
@ -49,24 +48,16 @@ class SecureHeaders
|
|||
// * CKEDITOR Specific https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/csp.html
|
||||
"connect-src 'self' blob:;",
|
||||
"img-src * data: blob:;",
|
||||
'frame-src ' . implode(' ', []) . ';',
|
||||
"font-src 'self' data: ;",
|
||||
// "frame-src https://www.youtube.com;",
|
||||
"font-src 'self' data:;",
|
||||
// ! Using CKEditor will must have unsafe-inline style
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
"style-src 'self' 'unsafe-inline' " . \implode(' ', [
|
||||
// * File Manager
|
||||
'https://cdn.jsdelivr.net',
|
||||
'https://use.fontawesome.com',
|
||||
'https://fonts.googleapis.com'
|
||||
]) . ';',
|
||||
"style-src 'self' 'unsafe-inline';",
|
||||
// ! If you dont use ckeditor use this
|
||||
// "style-src 'self' 'nonce-$nonce';",
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
"script-src 'self' {$jsDev} " . \implode(' ', [
|
||||
'https: blob:',
|
||||
]) . ';'
|
||||
"script-src 'self' $jsDev;"
|
||||
];
|
||||
|
||||
$response->headers->set(
|
||||
'Content-Security-Policy',
|
||||
implode(' ', $cspPolicies) . $reportUri
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class TrustProxies extends Middleware
|
|||
* @var integer
|
||||
*/
|
||||
protected $headers =
|
||||
Request::HEADER_X_FORWARDED_FOR |
|
||||
Request::HEADER_X_FORWARDED_FOR |
|
||||
Request::HEADER_X_FORWARDED_HOST |
|
||||
Request::HEADER_X_FORWARDED_PORT |
|
||||
Request::HEADER_X_FORWARDED_PROTO |
|
||||
|
|
|
|||
|
|
@ -11,5 +11,6 @@ class VerifyCsrfToken extends Middleware
|
|||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = ['/csp-report'];
|
||||
protected $except = [
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,190 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib\Helpers;
|
||||
|
||||
class ExceptionsHelper
|
||||
{
|
||||
/**
|
||||
* Handle sentry event
|
||||
*
|
||||
* @param \Sentry\Event $event
|
||||
* @return null|\Sentry\Event
|
||||
*/
|
||||
public static function sentryEventHandler(\Sentry\Event $event): ?\Sentry\Event
|
||||
{
|
||||
// Remove unwanted reports.
|
||||
$event->setModules([]);
|
||||
$event->setOsContext(null);
|
||||
|
||||
/** @var \Sentry\ExceptionDataBag[] */
|
||||
$exceptionBag = [];
|
||||
foreach ($event->getExceptions() as $exceptionDataBag) {
|
||||
$exceptionBag[] = self::getAnonymizedSentryException($exceptionDataBag);
|
||||
}
|
||||
$event->setExceptions($exceptionBag);
|
||||
return $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Anonimize Sentry Exception bag
|
||||
*
|
||||
* @param \Sentry\ExceptionDataBag $exceptionDataBag
|
||||
* @return \Sentry\ExceptionDataBag
|
||||
*/
|
||||
private static function getAnonymizedSentryException(
|
||||
\Sentry\ExceptionDataBag $exceptionDataBag
|
||||
): \Sentry\ExceptionDataBag {
|
||||
$exceptionDataBag->setValue(self::getAnonymizedMessage($exceptionDataBag->getValue()));
|
||||
if ($stackTrace = $exceptionDataBag->getStacktrace()) {
|
||||
$exceptionDataBag->setStacktrace(self::getAnonymizedSentryStackTrace($stackTrace));
|
||||
}
|
||||
return $exceptionDataBag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Anonymise sentry stacktrace
|
||||
*
|
||||
* @param \Sentry\Stacktrace $stackTrace
|
||||
* @return \Sentry\Stacktrace
|
||||
*/
|
||||
private static function getAnonymizedSentryStackTrace(\Sentry\Stacktrace $stackTrace): \Sentry\Stacktrace
|
||||
{
|
||||
/** @var \Sentry\Frame[] */
|
||||
$frames = \array_reverse($stackTrace->getFrames(), true);
|
||||
$i = \count($frames) - 1;
|
||||
// * Remove all but keep first (index.php).
|
||||
while ($i-- > 0) {
|
||||
$stackTrace->removeFrame(count($stackTrace->getFrames()) - 1);
|
||||
}
|
||||
foreach ($frames as $index => $frame) {
|
||||
$frame->setPostContext([]);
|
||||
$frame->setPreContext([]);
|
||||
$frame->setContextLine('');
|
||||
$frame->setVars(self::getAnonymizedSentryParams($frame->getVars()));
|
||||
$stackTrace->addFrame($frame);
|
||||
if ($index === 0) {
|
||||
$stackTrace->removeFrame(count($stackTrace->getFrames()) - 1);
|
||||
}
|
||||
}
|
||||
return $stackTrace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get anonymized sentry params
|
||||
*
|
||||
* @param mixed[] $params
|
||||
* @return string[]
|
||||
*/
|
||||
private static function getAnonymizedSentryParams(array $params): array
|
||||
{
|
||||
foreach ($params as &$param) {
|
||||
$type = \strtolower(\gettype($param));
|
||||
if (\is_string($param)) {
|
||||
$type = \explode(' ', \trim($param))[0] ?? null;
|
||||
}
|
||||
switch ($type) {
|
||||
case 'Lambda':
|
||||
case 'Object':
|
||||
continue 2;
|
||||
case 'array':
|
||||
$param = sprintf('Array(%d)', \count($param));
|
||||
continue 2;
|
||||
default:
|
||||
if (\is_numeric($param)) {
|
||||
$param = \ucfirst($type);
|
||||
continue 2;
|
||||
}
|
||||
if (\is_string($param)) {
|
||||
$param = sprintf('String(%d)', \strlen($param));
|
||||
continue 2;
|
||||
}
|
||||
} //end switch
|
||||
} //end foreach
|
||||
return $params;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Anonymize at least Sql queries
|
||||
*
|
||||
* @param string $message
|
||||
* @return string
|
||||
*/
|
||||
public static function getAnonymizedMessage(string $message): string
|
||||
{
|
||||
$message = \preg_replace('/^(.*)( \(SQL:.*\))$/', '$1', $message) ?? $message;
|
||||
if ($sqlPos = \strpos($message, 'to use near')) {
|
||||
$message = \mb_strcut($message, 0, $sqlPos);
|
||||
}
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Anonymize stack trace
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @return string
|
||||
*/
|
||||
public static function getAnonymizedStackTrace(\Throwable $e): string
|
||||
{
|
||||
$projectPath = \realpath(\sprintf('%s/../', \app_path()));
|
||||
$traceStack = $e->getTrace();
|
||||
$traceStr = '';
|
||||
$maxStackStage = 0;
|
||||
foreach ($traceStack as $stackStage => $stack) {
|
||||
$maxStackStage = $stackStage;
|
||||
$traceStr .= \sprintf(
|
||||
'#%s %s(%d): ',
|
||||
$stackStage,
|
||||
str_replace($projectPath, '', $stack['file'] ?? ''),
|
||||
$stack['line'] ?? ''
|
||||
);
|
||||
if (isset($stack['class'])) {
|
||||
$traceStr .= "{$stack['class']}->{$stack['function']}(";
|
||||
if (isset($stack['args'])) {
|
||||
$traceStr .= self::handleArgs($stack);
|
||||
}
|
||||
$traceStr .= ")\n";
|
||||
} elseif (isset($stack['function'])) {
|
||||
$traceStr .= "{$stack['function']}(";
|
||||
if (isset($stack['args'])) {
|
||||
$traceStr .= self::handleArgs($stack);
|
||||
}
|
||||
$traceStr .= ")\n";
|
||||
}
|
||||
} //end foreach
|
||||
$maxStackStage++;
|
||||
$traceStr .= "#$maxStackStage {main}";
|
||||
return $traceStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Args
|
||||
*
|
||||
* @param array $stack
|
||||
* @return string
|
||||
*/
|
||||
public static function handleArgs(array $stack): string
|
||||
{
|
||||
$traceArgs = '';
|
||||
$argsCount = count($stack['args']);
|
||||
for ($i = 0; $i < $argsCount; $i++) {
|
||||
$type = \gettype($stack['args'][$i]);
|
||||
switch ($type) {
|
||||
case 'object':
|
||||
$traceArgs .= sprintf('Object(%s)', \get_class($stack['args'][$i]));
|
||||
break;
|
||||
case 'array':
|
||||
$traceArgs .= sprintf('Array(%d)', \count($stack['args'][$i]));
|
||||
break;
|
||||
case 'string':
|
||||
$traceArgs .= sprintf('String(%d)', \strlen($stack['args'][$i]));
|
||||
break;
|
||||
default:
|
||||
$traceArgs .= \gettype($stack['args'][$i]);
|
||||
}
|
||||
$traceArgs .= ($i !== ($argsCount - 1) ? ',' : '');
|
||||
}
|
||||
return $traceArgs;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,444 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib\Helpers;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* This class is used to handle files in LDC Laravel stack
|
||||
*
|
||||
* * The storage strategy will be using date.
|
||||
* The file name may contain sufix timestamp highresolution time (hrtime or microtime) if is duplicate.
|
||||
* (function() { return time() . ' ' . Str::slug(microtime(true)) . ' ' . hrtime(true); })();
|
||||
*
|
||||
* posts/ - The model table name
|
||||
* 10/ - Year
|
||||
* 04/ - Month
|
||||
* 19/ - Day
|
||||
* 5f/ - First two letters of md5 file
|
||||
* black-cat-under-red-car.png - Slugified file name
|
||||
* black-cat-under-red-car-286071690833400.png - Slugified file name that is duplicated
|
||||
*
|
||||
* The final path will be storage/modelfiles/posts/10/04/19/5f/black-cat-under-red-car-286071690833400.png
|
||||
*
|
||||
* ! No limits of file per folder will be handled.
|
||||
*
|
||||
* @author Munsch Jérémy <jeremy.munsch@ldc.fr>
|
||||
*/
|
||||
class FileStorageHelper
|
||||
{
|
||||
/**
|
||||
* Destroy the file associate with the attribute.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param string $attribute
|
||||
* @return void
|
||||
*/
|
||||
public static function removeFile(Model $model, string $attribute): void
|
||||
{
|
||||
$path = \ltrim(\urldecode($model->{$attribute}), '/');
|
||||
if (\strpos($path, 'storage') !== 0) {
|
||||
return;
|
||||
}
|
||||
$path = ToolboxHelper::mbReplace('storage/', '', $path);
|
||||
if (Storage::disk('public')->exists($path)) {
|
||||
Storage::disk('public')->delete($path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the file associate with the attribute old value (before updated to DB).
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param string $attribute
|
||||
* @param boolean $onlyIfUpdated
|
||||
* @return void
|
||||
*/
|
||||
public static function removeOldFile(Model $model, string $attribute, bool $onlyIfUpdated = true): void
|
||||
{
|
||||
// * Ignore If removal has to be done only if the field has changed and it actually hasnt.
|
||||
if ($onlyIfUpdated and $model->{$attribute} === $model->getOriginal($attribute)) {
|
||||
return;
|
||||
}
|
||||
$originalPath = \ltrim(\urldecode($model->getOriginal($attribute)), '/');
|
||||
if (\strpos($originalPath, 'storage') !== 0) {
|
||||
return;
|
||||
}
|
||||
$originalPath = ToolboxHelper::mbReplace('storage/', '', $originalPath);
|
||||
if (Storage::disk('public')->exists($originalPath)) {
|
||||
Storage::disk('public')->delete($originalPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a file using optimized storage strategy
|
||||
*
|
||||
* You can use filename param to overload file name.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model The model that will hold the picture.
|
||||
* @param \Illuminate\Http\UploadedFile|\SplFileInfo|string $file The file, will just return itself is string.
|
||||
* @param boolean $slugify Force filename slugification.
|
||||
* @param string|null $filename This string may be used to store the file.
|
||||
* @param boolean $private To store the file in a private space.
|
||||
* @return string
|
||||
* @throws \RuntimeException If file copy fails.
|
||||
*/
|
||||
public static function storeFile(
|
||||
Model $model,
|
||||
$file,
|
||||
bool $slugify = false,
|
||||
string $filename = null,
|
||||
bool $private = false
|
||||
): string {
|
||||
$tableName = $model->getTable();
|
||||
if (\is_object($file) and $file instanceof \Illuminate\Http\UploadedFile) {
|
||||
if ($private) {
|
||||
throw new \RuntimeException('Private storage for uploaded files is unhandled');
|
||||
}
|
||||
/** @var \Illuminate\Http\UploadedFile $file */
|
||||
$filename = $filename ?: $file->getClientOriginalName();
|
||||
$filename = $slugify ? self::slugifyFileName($filename) : $filename;
|
||||
$newPath = self::prepareMoveFile($tableName, $filename);
|
||||
$newPath = str_replace(storage_path('app'), '', $newPath);
|
||||
$file->storePubliclyAs(
|
||||
pathinfo($newPath, \PATHINFO_DIRNAME),
|
||||
pathinfo($newPath, \PATHINFO_BASENAME)
|
||||
);
|
||||
return 'storage' . str_replace('/public', '', $newPath);
|
||||
}
|
||||
if (\is_object($file) and $file instanceof \SplFileInfo) {
|
||||
/** @var \SplFileInfo $file */
|
||||
$filename = $filename ?: $file->getBaseName();
|
||||
$filename = $slugify ? self::slugifyFileName($filename) : $filename;
|
||||
$newPath = self::prepareMoveFile($tableName, $filename, $private);
|
||||
if (!File::copy($file->getRealPath(), $newPath)) {
|
||||
throw new \RuntimeException(sprintf(
|
||||
'Failed to copy file %s to %s',
|
||||
$file->getRealPath(),
|
||||
$newPath
|
||||
));
|
||||
}
|
||||
return $private ?
|
||||
\ltrim(str_replace(\storage_path('app/private'), '/storage/app/private', $newPath), '/') :
|
||||
\ltrim(str_replace(\storage_path('app/public'), '/storage', $newPath), '/');
|
||||
}
|
||||
return strval($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store all files from string attribute html
|
||||
* If oldHtmlString is passed, a diff is done with new and old files
|
||||
* to keep only files that are present in the new html string.
|
||||
*
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model The model that will hold the picture.
|
||||
* @param string $htmlString The html string to parse and modify files.
|
||||
* @param string|null $oldHtmlString The old html string with alreadyParsed
|
||||
* files using storeModelFilesFromHtml.
|
||||
* @return string Html with files pathes replaced using storage strategy.
|
||||
*/
|
||||
public static function storeModelFilesFromHtml(
|
||||
Model $model,
|
||||
string $htmlString,
|
||||
string $oldHtmlString = null
|
||||
): string {
|
||||
\collect(self::onlyUploadedFilesWithFileManager(self::findFilesInHtml($htmlString)))
|
||||
->each(function (string $path) use ($model, &$htmlString) {
|
||||
/** @var \Illuminate\Filesystem\FilesystemAdapter */
|
||||
$disk = Storage::disk('uploads');
|
||||
$fileInfo = new \SplFileInfo($disk->path(
|
||||
ToolboxHelper::mbReplace('/uploads/', '', \urldecode($path))
|
||||
));
|
||||
$htmlString = ToolboxHelper::mbReplace(
|
||||
$path,
|
||||
'/' . self::storeFile($model, $fileInfo, true),
|
||||
$htmlString
|
||||
);
|
||||
});
|
||||
|
||||
if ($oldHtmlString) {
|
||||
self::removeOldModelFilesFromHtml($htmlString, $oldHtmlString);
|
||||
}
|
||||
|
||||
return $htmlString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all old files from string attribute html that are stored in public that
|
||||
* are nove present anymore on new html string
|
||||
*
|
||||
* @param string $newHtmlString The html string with alreadyParsed files using storeModelFilesFromHtml.
|
||||
* @param string $oldHtmlString The old html string with alreadyParsed files using storeModelFilesFromHtml.
|
||||
* @return void
|
||||
*/
|
||||
public static function removeOldModelFilesFromHtml(string $newHtmlString, string $oldHtmlString): void
|
||||
{
|
||||
self::removeOldFiles(self::findFilesInHtml($oldHtmlString), self::findFilesInHtml($newHtmlString));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all old files that are stored in public that
|
||||
* are not present anymore on new array files
|
||||
*
|
||||
* @param string[] $oldFiles The old array files.
|
||||
* @param string[] $newFiles The new array files.
|
||||
* @return void
|
||||
*/
|
||||
public static function removeOldFiles(array $oldFiles, array $newFiles): void
|
||||
{
|
||||
$oldFiles = \collect(self::onlyUploadedFilesWithFileStorageHelper($oldFiles));
|
||||
$filesToKeep = \collect(self::onlyUploadedFilesWithFileStorageHelper($newFiles));
|
||||
$filesToRemove = $oldFiles->diff($filesToKeep);
|
||||
$filesToRemove->each(function (string $publicStorageFilePath) {
|
||||
$path = ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($publicStorageFilePath), '/'));
|
||||
if (Storage::disk('public')->exists($path)) {
|
||||
Storage::disk('public')->delete($path);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all files from string attribute html that are stored in public
|
||||
*
|
||||
* @param string $htmlString The html string with alreadyParsed files using storeModelFilesFromHtml.
|
||||
* @return void
|
||||
*/
|
||||
public static function removeModelFilesOfHtml(string $htmlString): void
|
||||
{
|
||||
$filesToRemove = \collect(self::onlyUploadedFilesWithFileStorageHelper(self::findFilesInHtml($htmlString)));
|
||||
$filesToRemove->each(function (string $publicStorageFilePath) {
|
||||
$path = ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($publicStorageFilePath), '/'));
|
||||
if (Storage::disk('public')->exists($path)) {
|
||||
Storage::disk('public')->delete($path);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all occurence of files in html
|
||||
* @param string $html
|
||||
* @return array
|
||||
*/
|
||||
public static function findFilesInHtml(string $html): array
|
||||
{
|
||||
preg_match_all('/href="(?P<href>.*)"|src="(?P<src>.*)"|url="(?P<url>.*)"/Um', $html, $m, PREG_SET_ORDER, 0);
|
||||
return collect($m)->pluck('href')->filter()
|
||||
->concat(collect($m)->pluck('url')->filter())
|
||||
->concat(collect($m)->pluck('src')->filter())
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter array to get only uploaded files that exists
|
||||
* on disk 'public' (using helper storage strategy)
|
||||
*
|
||||
* @param array $urls
|
||||
* @return array
|
||||
*/
|
||||
public static function onlyUploadedFilesWithFileStorageHelper(array $urls): array
|
||||
{
|
||||
return \collect($urls)->unique()
|
||||
// * Filter to get only /uploads and using app url or empty
|
||||
->filter(function (string $url) {
|
||||
$path = \parse_url($url, \PHP_URL_PATH);
|
||||
return \strpos(\ltrim($path, '/'), 'storage/modelfiles') === 0;
|
||||
})->filter(function (string $url) {
|
||||
$path = \parse_url($url, \PHP_URL_PATH);
|
||||
return Storage::disk('public')
|
||||
->exists(ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($path), '/')));
|
||||
})->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter array to get only uploaded files that exists
|
||||
* on disk 'uploads'
|
||||
*
|
||||
* @param array $urls
|
||||
* @return array
|
||||
*/
|
||||
private static function onlyUploadedFilesWithFileManager(array $urls): array
|
||||
{
|
||||
return \collect($urls)->unique()
|
||||
// * Filter to get only /uploads and using app url or empty
|
||||
->filter(function (string $url) {
|
||||
$host = \parse_url($url, \PHP_URL_HOST);
|
||||
$path = \parse_url($url, \PHP_URL_PATH);
|
||||
return ($host === \parse_url(config('app.url'), PHP_URL_HOST) or \is_null($host)) and
|
||||
\strpos(\ltrim($path, '/'), 'uploads') === 0;
|
||||
})->filter(function (string $url) {
|
||||
$path = \parse_url($url, \PHP_URL_PATH);
|
||||
return Storage::disk('uploads')
|
||||
->exists(ToolboxHelper::mbReplace('uploads/', '', \ltrim(\urldecode($path), '/')));
|
||||
})->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare storage path and return
|
||||
* the file full path
|
||||
*
|
||||
* @param string $tableName Model table name.
|
||||
* @param string $filename The basename for the file.
|
||||
* @param boolean $private May the file be stored in a private dir.
|
||||
* @return string
|
||||
*/
|
||||
private static function prepareMoveFile(string $tableName, string $filename, bool $private = false): string
|
||||
{
|
||||
// * Security filter + beautifier
|
||||
$filename = self::sanitizeFileName($filename);
|
||||
// * Security filter
|
||||
$filename = \filter_var($filename, \FILTER_SANITIZE_STRING);
|
||||
// * Slugify filename
|
||||
$ext = \pathinfo($filename, \PATHINFO_EXTENSION);
|
||||
$filename = Str::slug(\pathinfo($filename, \PATHINFO_FILENAME)) .
|
||||
(\strlen($ext) ? ".{$ext}" : '');
|
||||
|
||||
$folderPath = self::getStoragePath(
|
||||
$private ? 'app/private/modelfiles' : 'app/public/modelfiles',
|
||||
$tableName,
|
||||
$filename,
|
||||
$private
|
||||
);
|
||||
$filename = self::getFileUniqueName($folderPath, $filename);
|
||||
return "{$folderPath}/{$filename}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Create sub folders to store files using pattern
|
||||
* storage/modelfiles/posts/10/04/19/5f
|
||||
* year/month/day/md5 first 2 letters
|
||||
*
|
||||
* @param string $storage_dir
|
||||
* @param string $table
|
||||
* @param string $filename
|
||||
* @return string
|
||||
*/
|
||||
private static function getStoragePath(string $storage_dir, string $table, string $filename): string
|
||||
{
|
||||
$filenameHash = \md5($filename);
|
||||
$pathFolder = \storage_path(sprintf(
|
||||
'%s/%s/%s/%s/%s/%s',
|
||||
$storage_dir,
|
||||
$table,
|
||||
gmdate('y'),
|
||||
gmdate('m'),
|
||||
gmdate('d'),
|
||||
substr($filenameHash, 0, 2)
|
||||
));
|
||||
if (!File::exists($pathFolder)) {
|
||||
File::makeDirectory($pathFolder, 0755, true);
|
||||
}
|
||||
return $pathFolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unique filename if the path is already occupied
|
||||
*
|
||||
* @param string $folderPath
|
||||
* @param string $filename
|
||||
* @return string
|
||||
* @throws \RuntimeException If a unique filename cannot be obtained.
|
||||
*/
|
||||
private static function getFileUniqueName(string $folderPath, string $filename): string
|
||||
{
|
||||
$attempts = 0;
|
||||
$maxAttempts = 20;
|
||||
while (File::exists("{$folderPath}/{$filename}")) {
|
||||
if ($attempts++ >= $maxAttempts) {
|
||||
throw new \RuntimeException("Failed to has a unique filename for {$folderPath}/{$filename}");
|
||||
}
|
||||
$filename = pathinfo($filename, \PATHINFO_FILENAME) .
|
||||
\uniqid('-') . '.' .
|
||||
pathinfo($filename, \PATHINFO_EXTENSION);
|
||||
}
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Slugify the filename
|
||||
*
|
||||
* @param string $filename
|
||||
* @return string
|
||||
*/
|
||||
public static function slugifyFileName(string $filename): string
|
||||
{
|
||||
return Str::slug(pathinfo($filename, \PATHINFO_FILENAME)) . '.' . pathinfo($filename, \PATHINFO_EXTENSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize File name for uploaded files
|
||||
*
|
||||
* @param string $filename The original uploaded client file name.
|
||||
* @param boolean $beautify Beautify the filename.
|
||||
* @return string The sanitized file name.
|
||||
*/
|
||||
public static function sanitizeFileName(string $filename, bool $beautify = true): string
|
||||
{
|
||||
// * Sanitize filename
|
||||
// phpcs:disable Generic.Files.LineLength.TooLong
|
||||
$filename = preg_replace(
|
||||
'~
|
||||
[<>:"/\\\|?*]| # file system reserved https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
|
||||
[\x00-\x1F]| # control characters http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
|
||||
[\x7F\xA0\xAD]| # non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN
|
||||
[#\[\]@!$&\'()+,;=]| # URI reserved https://tools.ietf.org/html/rfc3986#section-2.2
|
||||
[{}^\~`] # URL unsafe characters https://www.ietf.org/rfc/rfc1738.txt
|
||||
~x',
|
||||
'-',
|
||||
$filename
|
||||
);
|
||||
// phpcs:enable
|
||||
// * Avoids '.' '..' or '.hiddenFiles' .
|
||||
$filename = Str::of($filename)->ltrim('.-');
|
||||
// * Optional beautification
|
||||
if ($beautify) {
|
||||
$filename = static::beautifyFilename($filename);
|
||||
}
|
||||
// * Maximize filename length to 255 bytes http://serverfault.com/a/9548/44086
|
||||
/** @var string */
|
||||
$extension = pathinfo($filename, \PATHINFO_EXTENSION);
|
||||
/** @var string */
|
||||
$filename = pathinfo($filename, \PATHINFO_FILENAME);
|
||||
|
||||
$encoding = mb_detect_encoding($filename);
|
||||
$encoding = $encoding ? $encoding : null;
|
||||
return \mb_strcut(
|
||||
$filename,
|
||||
0,
|
||||
255 - (!empty($extension) ? strlen($extension) + 1 : 0),
|
||||
$encoding
|
||||
) . (!empty($extension) ? ".$extension" : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Beautify a filename
|
||||
*
|
||||
* @param string $filename
|
||||
* @return string
|
||||
*/
|
||||
private static function beautifyFilename(string $filename): string
|
||||
{
|
||||
// Reduce consecutive characters.
|
||||
$filename = preg_replace([
|
||||
// * "file name.zip" becomes 'file-name.zip'
|
||||
'/ +/',
|
||||
// * "file___name.zip" becomes 'file-name.zip'
|
||||
'/_+/',
|
||||
// * "file---name.zip" becomes 'file-name.zip'
|
||||
'/-+/'
|
||||
], '-', $filename);
|
||||
$filename = preg_replace([
|
||||
// * "file--.--.-.--name.zip" becomes "file.name.zip"
|
||||
'/-*\.-*/',
|
||||
// * "file...name..zip" becomes "file.name.zip"
|
||||
'/\.{2,}/'
|
||||
], '.', $filename);
|
||||
// Lowercase for windows/unix interoperability http://support.microsoft.com/kb/100625 .
|
||||
$filename = mb_strtolower($filename, mb_detect_encoding($filename));
|
||||
// * ".file-name.-" becomes "file-name"
|
||||
$filename = trim($filename, '.-');
|
||||
return $filename;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,237 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib\Helpers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
class ToolboxHelper
|
||||
{
|
||||
/**
|
||||
* Strip scheme (https) on oembed urls
|
||||
* Only embed allowed are for MP4 self hosted videos right now
|
||||
*
|
||||
* https is force by ckeditor MediaElements which natively supports
|
||||
* only cloud hosted medias (youtube, spotify etc..)
|
||||
*
|
||||
* @param string $input
|
||||
* @return string
|
||||
*/
|
||||
public static function stripCkEditorSchemesOnMediaInputs(string $input): string
|
||||
{
|
||||
$input = \preg_replace('/(?<=<oembed url=")(https:\/\/)(?=.*?">)/m', '', $input);
|
||||
return $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert That no Back Office url is present on txt
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $text
|
||||
* @return string
|
||||
* @throws \Illuminate\Validation\ValidationException If url is a bo url.
|
||||
*/
|
||||
public static function assertNoInternalBoUrls(string $field, string $text): string
|
||||
{
|
||||
$routes = Route::getRoutes();
|
||||
// * Url replace and email replace.
|
||||
$text = preg_replace_callback(
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
'/(?P<url>(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/',
|
||||
function ($matches) use ($routes, $field) {
|
||||
$match = collect($matches)->first() ?? '';
|
||||
$url = $match;
|
||||
try {
|
||||
$request = Request::create($url);
|
||||
// If a route is match then it do not throws exception.
|
||||
if (
|
||||
\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST) &&
|
||||
trim($routes->match($request)->getPrefix() ?? '', '/') === 'bo'
|
||||
) {
|
||||
// Removes the matched url.
|
||||
throw ValidationException::withMessages([
|
||||
$field => ':attribute : Il est interdit de coller une url du back office dans ce champ'
|
||||
]);
|
||||
}
|
||||
return $match;
|
||||
} catch (NotFoundHttpException $e) {
|
||||
return $match;
|
||||
}
|
||||
},
|
||||
$text
|
||||
);
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert That only external url is present on txt
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $text
|
||||
* @return string
|
||||
* @throws \Illuminate\Validation\ValidationException If url is a bo url.
|
||||
*/
|
||||
public static function assertNoInternalUrls(string $field, string $text): string
|
||||
{
|
||||
self::assertNoInternalBoUrls($field, $text);
|
||||
$routes = Route::getRoutes();
|
||||
// * Url replace and email replace.
|
||||
$text = preg_replace_callback(
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
'/(?P<url>(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/',
|
||||
function ($matches) use ($routes, $field) {
|
||||
$match = collect($matches)->first() ?? '';
|
||||
$url = $match;
|
||||
try {
|
||||
$request = Request::create($url);
|
||||
// If a route is match then it do not throws exception.
|
||||
if (
|
||||
\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST) &&
|
||||
$routes->match($request)
|
||||
) {
|
||||
// Removes the matched url.
|
||||
throw ValidationException::withMessages([
|
||||
$field => sprintf(
|
||||
':attribute : Il est interdit de coller une url de %s dans ce champ',
|
||||
config('app.name')
|
||||
)
|
||||
]);
|
||||
}
|
||||
return $match;
|
||||
} catch (NotFoundHttpException $e) {
|
||||
return $match;
|
||||
}
|
||||
},
|
||||
$text
|
||||
);
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutltibyte string replace
|
||||
* @param mixed $search
|
||||
* @param mixed $replace
|
||||
* @param mixed $subject
|
||||
* @param integer $count
|
||||
* @return mixed
|
||||
*/
|
||||
public static function mbReplace($search, $replace, $subject, int &$count = 0)
|
||||
{
|
||||
if (!is_array($search) && is_array($replace)) {
|
||||
return false;
|
||||
}
|
||||
if (is_array($subject)) {
|
||||
// Call mb_replace for each single string in $subject .
|
||||
foreach ($subject as &$string) {
|
||||
$string = &self::mbReplace($search, $replace, $string, $count);
|
||||
}
|
||||
} elseif (is_array($search)) {
|
||||
if (!is_array($replace)) {
|
||||
foreach ($search as &$string) {
|
||||
$subject = self::mbReplace($string, $replace, $subject, $count);
|
||||
}
|
||||
} else {
|
||||
$n = max(count($search), count($replace));
|
||||
while ($n--) {
|
||||
$subject = self::mbReplace(current($search), current($replace), $subject, $count);
|
||||
next($search);
|
||||
next($replace);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$parts = mb_split(preg_quote($search), $subject);
|
||||
$count = count($parts) - 1;
|
||||
$subject = implode($replace, $parts);
|
||||
} //end if
|
||||
return $subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the field is unique
|
||||
*
|
||||
* @param string $table
|
||||
* @param string $field
|
||||
* @param mixed $value
|
||||
* @param integer|null $id
|
||||
* @param callable|null $query
|
||||
* @return void
|
||||
* @throws \Illuminate\Validation\ValidationException If the field is not unique.
|
||||
*/
|
||||
public static function assertFieldIsUnique(
|
||||
string $table,
|
||||
string $field,
|
||||
$value,
|
||||
?int $id = null,
|
||||
?callable $query = null
|
||||
) {
|
||||
if (!$query) {
|
||||
Validator::make([$field => $value], [
|
||||
self::mbReplace('.', '\.', $field) => Rule::unique($table, $field)->ignore($id),
|
||||
], [
|
||||
self::mbReplace('.', '\.', "{$field}.unique")
|
||||
=> trans('La valeur :value pour le champ :attribute est déjà utilisé', [
|
||||
'attribute' => $field,
|
||||
'value' => $value
|
||||
])
|
||||
])->validate();
|
||||
return;
|
||||
}
|
||||
Validator::make([$field => $value], [
|
||||
$field => Rule::unique($table)->where($query)->ignore($id),
|
||||
], [
|
||||
$field . '.unique' => trans('La valeur pour le champ :attribute est déjà utilisé', [
|
||||
'attribute' => $field
|
||||
])
|
||||
])->validate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert deeply an object to array
|
||||
*
|
||||
* @param array|object $input
|
||||
* @return array
|
||||
* @throws \RuntimeException If $input var is a scalar value.
|
||||
*/
|
||||
public static function objectToArray($input): array
|
||||
{
|
||||
// Only process if it's an object or array being passed to the function.
|
||||
if (is_object($input) or is_array($input)) {
|
||||
$ret = (array)$input;
|
||||
foreach ($ret as &$item) {
|
||||
// Recursively process EACH element only if not scalar.
|
||||
$item = (is_object($item) or is_array($item)) ?
|
||||
self::objectToArray($item) : $item;
|
||||
}
|
||||
return $ret;
|
||||
} else {
|
||||
throw new \RuntimeException('cannot handle $input of type ' . \gettype($input));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert deeply an array to object
|
||||
*
|
||||
* @param array|object $input
|
||||
* @return object
|
||||
* @throws \RuntimeException If $input var is a scalar value.
|
||||
*/
|
||||
public static function arrayToObject($input): object
|
||||
{
|
||||
// Only process if it's an object or array being passed to the function.
|
||||
if (is_object($input) or is_array($input)) {
|
||||
$ret = (object)$input;
|
||||
foreach ($ret as &$item) {
|
||||
// Recursively process EACH element only if not scalar.
|
||||
$item = (is_object($item) or is_array($item)) ?
|
||||
self::arrayToObject($item) : $item;
|
||||
}
|
||||
return $ret;
|
||||
} else {
|
||||
throw new \RuntimeException('cannot handle $input of type ' . \gettype($input));
|
||||
}
|
||||
}
|
||||
}
|
||||
499
app/Lib/ToolboxHelper.php
Normal file
|
|
@ -0,0 +1,499 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
class ToolboxHelper
|
||||
{
|
||||
/**
|
||||
* Strip scheme (https) on oembed urls
|
||||
* Only embed allowed are for MP4 self hosted videos right now
|
||||
*
|
||||
* https is force by ckeditor MediaElements which natively supports
|
||||
* only cloud hosted medias (youtube, spotify etc..)
|
||||
*
|
||||
* @param string $input
|
||||
* @return string
|
||||
*/
|
||||
public static function stripCkEditorSchemesOnMediaInputs(string $input): string
|
||||
{
|
||||
$input = \preg_replace('/(?<=<oembed url=")(https:\/\/)(?=.*?">)/m', '', $input);
|
||||
return $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert That no Back Office url is present on txt
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $text
|
||||
* @return string
|
||||
* @throws \Illuminate\Validation\ValidationException If url is a bo url.
|
||||
*/
|
||||
public static function assertNoInternalBoUrls(string $field, string $text): string
|
||||
{
|
||||
$routes = Route::getRoutes();
|
||||
// * Url replace and email replace.
|
||||
$text = preg_replace_callback(
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
'/(?P<url>(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/',
|
||||
function ($matches) use ($routes, $field) {
|
||||
$match = collect($matches)->first() ?? '';
|
||||
$url = $match;
|
||||
try {
|
||||
$request = Request::create($url);
|
||||
// If a route is match then it do not throws exception.
|
||||
if (
|
||||
\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST) &&
|
||||
trim($routes->match($request)->getPrefix() ?? '', '/') === 'bo'
|
||||
) {
|
||||
// Removes the matched url.
|
||||
throw ValidationException::withMessages([
|
||||
$field => sprintf(
|
||||
'%s : Il est interdit de coller une url du back office dans ce champ',
|
||||
trans($field)
|
||||
)
|
||||
]);
|
||||
}
|
||||
return $match;
|
||||
} catch (NotFoundHttpException $e) {
|
||||
return $match;
|
||||
}
|
||||
},
|
||||
$text
|
||||
);
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get files path uploaded with ckeditor custom upload plugin (crud-policies)
|
||||
*
|
||||
* @param string $text
|
||||
* @param array $detectExtensions
|
||||
* @return array
|
||||
*/
|
||||
public static function getCkEditorLocalUploadedFiles(
|
||||
string $text,
|
||||
array $detectExtensions = ['png', 'svg', 'jpg', 'gif']
|
||||
): array {
|
||||
$out = [];
|
||||
\preg_match_all(
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
'/src="(?P<url>\/storage\/images\/crud-policies\/uploads\/\d{4}-\d{2}-\d{2}\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}-[-a-zA-Z0-9@%._\+~#=]{2,256}\.(?:' . implode('|', $detectExtensions) . '))"/',
|
||||
$text,
|
||||
$out
|
||||
);
|
||||
return collect($out['url'] ?? [])->map(function (string $urlEncodedFilePath) {
|
||||
return \urldecode($urlEncodedFilePath);
|
||||
})->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert That only external url is present on txt
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $text
|
||||
* @return string
|
||||
* @throws \Illuminate\Validation\ValidationException If url is a bo url.
|
||||
*/
|
||||
public static function assertNoInternalUrls(string $field, string $text): string
|
||||
{
|
||||
self::assertNoInternalBoUrls($field, $text);
|
||||
$routes = Route::getRoutes();
|
||||
// * Url replace and email replace.
|
||||
$text = preg_replace_callback(
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
'/(?P<url>(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/',
|
||||
function ($matches) use ($routes, $field) {
|
||||
$match = collect($matches)->first() ?? '';
|
||||
$url = $match;
|
||||
try {
|
||||
$request = Request::create($url);
|
||||
// If a route is match then it do not throws exception.
|
||||
if (
|
||||
\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST) &&
|
||||
$routes->match($request)
|
||||
) {
|
||||
// Removes the matched url.
|
||||
throw ValidationException::withMessages([
|
||||
$field => sprintf(
|
||||
'%s : Il est interdit de coller une url de %s dans ce champ',
|
||||
trans($field),
|
||||
config('app.name')
|
||||
)
|
||||
]);
|
||||
}
|
||||
return $match;
|
||||
} catch (NotFoundHttpException $e) {
|
||||
return $match;
|
||||
}
|
||||
},
|
||||
$text
|
||||
);
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array from a dot separated string
|
||||
*
|
||||
* @param string $path
|
||||
* @param mixed $value
|
||||
* @param string $separator
|
||||
* @return array
|
||||
*/
|
||||
public static function dotStringToNestedArray(string $path, $value, string $separator = '.'): array
|
||||
{
|
||||
$arr = $value;
|
||||
$keys = array_reverse(explode($separator, $path));
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$arr = [$key => $arr];
|
||||
}
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy image
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param string $attribute
|
||||
* @return void
|
||||
*/
|
||||
public static function removeImage(Model $model, string $attribute)
|
||||
{
|
||||
$path = ltrim($model->{$attribute}, '/');
|
||||
if (File::exists($path) and \strpos($path, 'storage') === 0) {
|
||||
File::delete($path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy image
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param string $attribute
|
||||
* @return void
|
||||
*/
|
||||
public static function removeOldImage(Model $model, string $attribute)
|
||||
{
|
||||
$originalPath = ltrim($model->getOriginal($attribute), '/');
|
||||
if (File::exists($originalPath) and \strpos($originalPath, 'storage') === 0) {
|
||||
File::delete($originalPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove pictures in ckeditor
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param string $attribute
|
||||
* @return void
|
||||
*/
|
||||
public static function removeImagesOnCkEditor(Model $model, string $attribute)
|
||||
{
|
||||
// * Remove inline uploaded images
|
||||
collect(ToolboxHelper::getCkEditorLocalUploadedFiles($model->getOriginal($attribute)))
|
||||
->map(function (string $originalPicturePath) {
|
||||
// * If a picture is not used anymore
|
||||
if (File::exists(\public_path($originalPicturePath))) {
|
||||
File::delete(\public_path($originalPicturePath));
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Remove unused pictures in ckeditor
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param string $attribute
|
||||
* @return void
|
||||
*/
|
||||
public static function removeUnusedImagesOnCkEditor(Model $model, string $attribute)
|
||||
{
|
||||
// * Remove unused inline uploaded images
|
||||
if ($model->{$attribute} != $model->getOriginal($attribute)) {
|
||||
$updatedPicturesPath = collect(
|
||||
ToolboxHelper::getCkEditorLocalUploadedFiles($model->{$attribute})
|
||||
);
|
||||
collect(ToolboxHelper::getCkEditorLocalUploadedFiles($model->getOriginal($attribute)))
|
||||
->map(function (string $originalPicturePath) use ($updatedPicturesPath) {
|
||||
// * If a picture is not used anymore
|
||||
if (!$updatedPicturesPath->contains($originalPicturePath)) {
|
||||
if (File::exists(\public_path($originalPicturePath))) {
|
||||
File::delete(\public_path($originalPicturePath));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store an image of different types
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param \Illuminate\Http\UploadedFile|\SplFileInfo|string $image
|
||||
* @return string
|
||||
* @throws \ErrorException If file copy fails.
|
||||
*/
|
||||
public static function storeImage(Model $model, $image): string
|
||||
{
|
||||
$table = $model->getTable();
|
||||
if (\is_object($image) and $image instanceof \Illuminate\Http\UploadedFile) {
|
||||
/** @var \Illuminate\Http\UploadedFile $image */
|
||||
$newPath = self::prepareMoveFile($table, $image->getClientOriginalName());
|
||||
$newPath = str_replace(storage_path('app'), '', $newPath);
|
||||
$image->storePubliclyAs(
|
||||
pathinfo($newPath, \PATHINFO_DIRNAME),
|
||||
pathinfo($newPath, \PATHINFO_BASENAME)
|
||||
);
|
||||
return 'storage' . str_replace('/public', '', $newPath);
|
||||
}
|
||||
if (\is_object($image) and $image instanceof \SplFileInfo) {
|
||||
/** @var \SplFileInfo $image */
|
||||
$newPath = self::prepareMoveFile($table, $image->getBaseName());
|
||||
if (!File::copy($image->getRealPath(), $newPath)) {
|
||||
throw new \ErrorException(sprintf(
|
||||
'Failed to copy file %s to %s',
|
||||
$image->getRealPath(),
|
||||
$newPath
|
||||
));
|
||||
}
|
||||
return \ltrim(str_replace(\storage_path('app/public'), '/storage', $newPath), '/');
|
||||
}
|
||||
return strval($image);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare storage path and return
|
||||
* the image full path
|
||||
*
|
||||
* @param string $table Model table name.
|
||||
* @param string $filename The basename for the file.
|
||||
* @return string
|
||||
*/
|
||||
private static function prepareMoveFile(string $table, string $filename): string
|
||||
{
|
||||
// * Security filter + beautifier
|
||||
$filename = self::sanitizeFileName($filename);
|
||||
// * Security filter
|
||||
$filename = \filter_var($filename, \FILTER_SANITIZE_STRING);
|
||||
// * Slugify filename
|
||||
$ext = \pathinfo($filename, \PATHINFO_EXTENSION);
|
||||
$filename = Str::slug(\pathinfo($filename, \PATHINFO_FILENAME)) .
|
||||
(\strlen($ext) ? ".{$ext}" : '');
|
||||
|
||||
$pathFolder = \storage_path(sprintf('app/public/images/%s', $table));
|
||||
if (!File::exists($pathFolder)) {
|
||||
File::makeDirectory($pathFolder, 0755, true);
|
||||
}
|
||||
$filename = sprintf('%s-%s', Str::uuid(), $filename);
|
||||
$fullPath = sprintf('%s/%s', $pathFolder, $filename);
|
||||
if (File::exists($fullPath)) {
|
||||
File::delete($fullPath);
|
||||
}
|
||||
return $fullPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the field is unique
|
||||
*
|
||||
* @param string $table
|
||||
* @param string $field
|
||||
* @param mixed $value
|
||||
* @param integer|null $id
|
||||
* @param callable|null $query
|
||||
* @return void
|
||||
* @throws \Illuminate\Validation\ValidationException If the field is not unique.
|
||||
*/
|
||||
public static function assertFieldIsUnique(
|
||||
string $table,
|
||||
string $field,
|
||||
$value,
|
||||
?int $id = null,
|
||||
?callable $query = null
|
||||
) {
|
||||
if (!$query) {
|
||||
Validator::make([$field => $value], [
|
||||
$field => Rule::unique($table, $field)->ignore($id),
|
||||
], [
|
||||
$field . '.unique' => trans('La valeur :value pour le champ :attribute est déjà utilisé', [
|
||||
'attribute' => $field,
|
||||
'value' => $value
|
||||
])
|
||||
])->validate();
|
||||
return;
|
||||
}
|
||||
Validator::make([$field => $value], [
|
||||
$field => Rule::unique($table)->where($query)->ignore($id),
|
||||
], [
|
||||
$field . '.unique' => trans('La valeur pour le champ :attribute est déjà utilisé', [
|
||||
'attribute' => $field
|
||||
])
|
||||
])->validate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate recaptcha V3
|
||||
*
|
||||
* @param Request $request
|
||||
* @return void
|
||||
*/
|
||||
public static function validateRecaptchaV3(Request $request)
|
||||
{
|
||||
Validator::validate($request->all(), [
|
||||
'g-recaptcha-response' => [
|
||||
'required', 'string',
|
||||
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed, Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
|
||||
function ($attribute, $value, $fail) {
|
||||
try {
|
||||
$response = Http::asForm()->post('https://www.google.com/recaptcha/api/siteverify', [
|
||||
'secret' => config('recaptcha.private'),
|
||||
'response' => \strval($value),
|
||||
'remoteip' => app('clientIp')
|
||||
]);
|
||||
if ($e = $response->toException()) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
if (
|
||||
!$response->json('success') or
|
||||
!$response->json('challenge_ts') or
|
||||
Carbon::parse($response->json('challenge_ts'))->lessThan(\now('utc')->subMinutes(5)) or
|
||||
$response->json('hostname') !== \parse_url(config('app.url'), \PHP_URL_HOST) or
|
||||
!$response->json('score')
|
||||
) {
|
||||
throw new \ErrorException();
|
||||
}
|
||||
if (\floatval($response->json('score')) < 0.8) {
|
||||
$fail('Vous n\'avez pas obtenu un score assez élevé lors de la validation du recaptcha');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$fail('Erreur lors de la validation du formulaire veuillez réssayer');
|
||||
} //end try
|
||||
}
|
||||
]
|
||||
], [], ['g-recaptcha-response' => 'reCaptcha']);
|
||||
}
|
||||
|
||||
/**
|
||||
* * Register CSP report URI
|
||||
*
|
||||
* Put this function inside RouteServiceProvider,
|
||||
* and call it in RouteServiceProvider::boot
|
||||
*
|
||||
* ! Use this in conjonction with kwaadpepper/exception-handler
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function cspReportRoute()
|
||||
{
|
||||
// * CSP REPORT URI, Please add /csp-report to VerifyCsrfToken $except middleware
|
||||
\Illuminate\Support\Facades\Route::post('/csp-report', function () {
|
||||
$isCspRequest = request()->header('Content-Type') === 'application/csp-report';
|
||||
if (!$isCspRequest or !request()->json('csp-report') or !is_array(request()->json('csp-report'))) {
|
||||
abort(401);
|
||||
}
|
||||
// * Flatten if needed
|
||||
$cspReport = collect(request()->json('csp-report'))
|
||||
->flatMap(function ($v, $k) {
|
||||
return [$k => is_array($v) ? collect($v)->flatten()->implode(', ') : $v];
|
||||
})->all();
|
||||
// * Filter array keys
|
||||
$cspReport = collect(array_intersect_key($cspReport, [
|
||||
'blocked-uri' => '', 'document-uri' => '', 'original-policy' => '',
|
||||
'referrer' => '', 'violated-directive' => ''
|
||||
]));
|
||||
report(new \Illuminate\Http\Client\HttpClientException(sprintf(
|
||||
"CSP REPORT for %s : \n %s",
|
||||
$cspReport->get('document-uri'),
|
||||
$cspReport->implode("\n")
|
||||
)));
|
||||
})->middleware('throttle:5,2')
|
||||
->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class)
|
||||
->name('cspReportUri');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize File name for uploaded files
|
||||
*
|
||||
* @param string $filename The original uploaded client file name.
|
||||
* @param boolean $beautify Beautify the filename.
|
||||
* @return string The sanitized file name.
|
||||
*/
|
||||
public static function sanitizeFileName(string $filename, bool $beautify = true): string
|
||||
{
|
||||
// * Sanitize filename
|
||||
// phpcs:disable Generic.Files.LineLength.TooLong
|
||||
$filename = preg_replace(
|
||||
'~
|
||||
[<>:"/\\\|?*]| # file system reserved https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
|
||||
[\x00-\x1F]| # control characters http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
|
||||
[\x7F\xA0\xAD]| # non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN
|
||||
[#\[\]@!$&\'()+,;=]| # URI reserved https://tools.ietf.org/html/rfc3986#section-2.2
|
||||
[{}^\~`] # URL unsafe characters https://www.ietf.org/rfc/rfc1738.txt
|
||||
~x',
|
||||
'-',
|
||||
$filename
|
||||
);
|
||||
// phpcs:enable
|
||||
// * Avoids '.' '..' or '.hiddenFiles' .
|
||||
$filename = Str::of($filename)->ltrim('.-');
|
||||
// * Optional beautification
|
||||
if ($beautify) {
|
||||
$filename = static::beautifyFilename($filename);
|
||||
}
|
||||
// * Maximize filename length to 255 bytes http://serverfault.com/a/9548/44086
|
||||
/** @var string */
|
||||
$extension = pathinfo($filename, \PATHINFO_EXTENSION);
|
||||
/** @var string */
|
||||
$filename = pathinfo($filename, \PATHINFO_FILENAME);
|
||||
|
||||
$encoding = mb_detect_encoding($filename);
|
||||
$encoding = $encoding ? $encoding : null;
|
||||
return \mb_strcut(
|
||||
$filename,
|
||||
0,
|
||||
255 - (!empty($extension) ? strlen($extension) + 1 : 0),
|
||||
$encoding
|
||||
) . (!empty($extension) ? ".$extension" : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Beautify a filename
|
||||
*
|
||||
* @param string $filename
|
||||
* @return string
|
||||
*/
|
||||
private static function beautifyFilename(string $filename): string
|
||||
{
|
||||
// Reduce consecutive characters.
|
||||
$filename = preg_replace([
|
||||
// * "file name.zip" becomes 'file-name.zip'
|
||||
'/ +/',
|
||||
// * "file___name.zip" becomes 'file-name.zip'
|
||||
'/_+/',
|
||||
// * "file---name.zip" becomes 'file-name.zip'
|
||||
'/-+/'
|
||||
], '-', $filename);
|
||||
$filename = preg_replace([
|
||||
// * "file--.--.-.--name.zip" becomes "file.name.zip"
|
||||
'/-*\.-*/',
|
||||
// * "file...name..zip" becomes "file.name.zip"
|
||||
'/\.{2,}/'
|
||||
], '.', $filename);
|
||||
// Lowercase for windows/unix interoperability http://support.microsoft.com/kb/100625 .
|
||||
$filename = mb_strtolower($filename, mb_detect_encoding($filename));
|
||||
// * ".file-name.-" becomes "file-name"
|
||||
$filename = trim($filename, '.-');
|
||||
return $filename;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,10 +2,12 @@
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Lib\ToolboxHelper;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
|
|
@ -18,8 +20,7 @@ class User extends Authenticatable
|
|||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'firstname',
|
||||
'lastname',
|
||||
'name',
|
||||
'email',
|
||||
'password',
|
||||
];
|
||||
|
|
@ -41,6 +42,7 @@ class User extends Authenticatable
|
|||
*/
|
||||
protected $casts = [
|
||||
'email_verified_at' => 'datetime',
|
||||
'last_connection' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -51,15 +53,34 @@ class User extends Authenticatable
|
|||
protected static function booted()
|
||||
{
|
||||
// * EVENT CLOSURES.
|
||||
static::creating(function (User $user) {
|
||||
static::creating(function (self $user) {
|
||||
static::assertEmailIsUnique($user->email);
|
||||
if (Hash::needsRehash($user->password)) {
|
||||
$user->password = Hash::make($user->password);
|
||||
}
|
||||
$user->remember_token = Str::random(10);
|
||||
});
|
||||
static::updating(function (User $user) {
|
||||
static::updating(function (self $user) {
|
||||
static::assertEmailIsUnique($user->email, $user->id);
|
||||
if (Hash::needsRehash($user->password)) {
|
||||
$user->password = Hash::make($user->password);
|
||||
}
|
||||
$user->remember_token = Str::random(10);
|
||||
});
|
||||
}
|
||||
|
||||
// * METHODS
|
||||
|
||||
/**
|
||||
* Asserts using validation that the email is unique
|
||||
*
|
||||
* @param mixed $email
|
||||
* @param integer|null $id
|
||||
* @return void
|
||||
* @throws \Illuminate\Validation\ValidationException If user email as already used.
|
||||
*/
|
||||
private static function assertEmailIsUnique($email, ?int $id = null)
|
||||
{
|
||||
ToolboxHelper::assertFieldIsUnique((new self())->getTable(), 'email', $email, $id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,20 @@ namespace App\Providers;
|
|||
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*
|
||||
|
|
@ -17,47 +25,34 @@ class AppServiceProvider extends ServiceProvider
|
|||
*/
|
||||
public function boot()
|
||||
{
|
||||
// * HTTPS FIX
|
||||
URL::forceScheme('https');
|
||||
|
||||
// * HA PROXY FIX
|
||||
App::singleton('clientIp', function () {
|
||||
if (config('app.env') === 'local') {
|
||||
return request()->getClientIp();
|
||||
} else {
|
||||
return request()->server('HTTP_X_FORWARDED_FOR') ??
|
||||
request()->getClientIp();
|
||||
}
|
||||
});
|
||||
|
||||
// * LOCAL FORCE eg: for Carbon
|
||||
setlocale(LC_ALL, \sprintf(
|
||||
'%s_%s.UTF-8',
|
||||
config('app.locale'),
|
||||
\strtoupper(config('app.locale'))
|
||||
));
|
||||
// * Remove numeric to prevent float seperator to breaking numeric transactions
|
||||
\collect([\LC_TIME, \LC_MESSAGES, \LC_COLLATE, \LC_CTYPE, \LC_MONETARY])
|
||||
->map(function ($contant) {
|
||||
setlocale(
|
||||
$contant,
|
||||
\sprintf(
|
||||
'%s_%s.UTF-8',
|
||||
config('app.locale'),
|
||||
\strtoupper(config('app.locale'))
|
||||
)
|
||||
);
|
||||
});
|
||||
Carbon::setLocale(config('app.locale'));
|
||||
|
||||
$this->sharetoView();
|
||||
}
|
||||
// * Force bootstrap paginator from Alexis with tooltips.
|
||||
Paginator::defaultView('bo.modules.pagination');
|
||||
|
||||
/**
|
||||
* Share Vars to view
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function sharetoView()
|
||||
{
|
||||
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') {
|
||||
Paginator::useBootstrap();
|
||||
} else {
|
||||
// FIXME: change this if needed.
|
||||
Paginator::useBootstrap();
|
||||
// Paginator::defaultview('fo.modules.pagination'); .
|
||||
// * HA PROXY FIX
|
||||
app()->singleton('clientIp', function () {
|
||||
if (
|
||||
config('app.env') === 'local'
|
||||
) {
|
||||
return request()->getClientIp();
|
||||
}
|
||||
}
|
||||
return request()->server('HTTP_X_FORWARDED_FOR') ?? request()->getClientIp();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,13 @@ use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvid
|
|||
|
||||
class AuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* The policy mappings for the application.
|
||||
*
|
||||
* @var array<class-string, class-string>
|
||||
*/
|
||||
protected $policies = [];
|
||||
|
||||
/**
|
||||
* Register any authentication / authorization services.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Lib\ToolboxHelper;
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
use Illuminate\Http\Request;
|
||||
|
|
@ -17,7 +18,7 @@ class RouteServiceProvider extends ServiceProvider
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
public const HOME = '/';
|
||||
public const HOME = '/home';
|
||||
|
||||
/**
|
||||
* Define your route model bindings, pattern filters, etc.
|
||||
|
|
@ -28,16 +29,16 @@ class RouteServiceProvider extends ServiceProvider
|
|||
{
|
||||
$this->configureRateLimiting();
|
||||
|
||||
$this->cspReportRoute();
|
||||
ToolboxHelper::cspReportRoute();
|
||||
|
||||
$this->routes(function () {
|
||||
Route::middleware('web')
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/web-bo.php'));
|
||||
->group(base_path('routes/web-fo.php'));
|
||||
|
||||
Route::middleware('web')
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/web-fo.php'));
|
||||
->group(base_path('routes/web-bo.php'));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -48,50 +49,8 @@ class RouteServiceProvider extends ServiceProvider
|
|||
*/
|
||||
protected function configureRateLimiting()
|
||||
{
|
||||
RateLimiter::for('api', function (Request $request) {
|
||||
return Limit::perMinute(150)->by(optional($request->user())->id ?: app('clientIp'));
|
||||
});
|
||||
RateLimiter::for('web', function (Request $request) {
|
||||
return Limit::perMinute(150)->by(optional($request->user())->id ?: app('clientIp'));
|
||||
});
|
||||
RateLimiter::for('credentials', function (Request $request) {
|
||||
return Limit::perMinute(30)->by(optional($request->user())->id ?: $request->ip());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* * Register CSP report URI
|
||||
*
|
||||
* Put this function inside RouteServiceProvider,
|
||||
* and call it in RouteServiceProvider::boot
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function cspReportRoute()
|
||||
{
|
||||
// * CSP REPORT URI, Please add /csp-report to VerifyCsrfToken $except middleware
|
||||
\Illuminate\Support\Facades\Route::post('/csp-report', function () {
|
||||
$isCspRequest = request()->header('Content-Type') === 'application/csp-report';
|
||||
if (!$isCspRequest or !request()->json('csp-report') or !is_array(request()->json('csp-report'))) {
|
||||
abort(401);
|
||||
}
|
||||
// * Flatten if needed
|
||||
$cspReport = collect(request()->json('csp-report'))
|
||||
->flatMap(function ($v, $k) {
|
||||
return [$k => is_array($v) ? collect($v)->flatten()->implode(', ') : $v];
|
||||
})->all();
|
||||
// * Filter array keys
|
||||
$cspReport = collect(array_intersect_key($cspReport, [
|
||||
'blocked-uri' => '', 'document-uri' => '', 'original-policy' => '',
|
||||
'referrer' => '', 'violated-directive' => ''
|
||||
]));
|
||||
\Illuminate\Support\Facades\Log::warning(sprintf(
|
||||
"CSP REPORT for %s : \n %s",
|
||||
$cspReport->get('document-uri'),
|
||||
$cspReport->implode("\n")
|
||||
));
|
||||
})->middleware('throttle:5,2')
|
||||
->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class)
|
||||
->name('cspReportUri');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ use Illuminate\Support\Collection;
|
|||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Symfony\Component\Finder\SplFileInfo;
|
||||
|
||||
class TranslationServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
|
@ -36,8 +35,8 @@ class TranslationServiceProvider extends ServiceProvider
|
|||
*/
|
||||
private function collectLocalesStrings(): Collection
|
||||
{
|
||||
return collect(File::allFiles(resource_path('lang/')))->flatMap(function (SplFileInfo $file) {
|
||||
if ($file->getRelativePath() and \strpos($file->getRelativePath(), 'vendor') !== 0) {
|
||||
return collect(File::allFiles(resource_path('lang/')))->flatMap(function ($file) {
|
||||
if ($file->getRelativePath()) {
|
||||
return [$file->getRelativePath() => ''];
|
||||
}
|
||||
})->keys();
|
||||
|
|
|
|||
|
|
@ -1,12 +1,7 @@
|
|||
{
|
||||
"name": "laravel/laravel",
|
||||
"version": "1.0.0",
|
||||
"type": "project",
|
||||
"time": "2023-03-02",
|
||||
"description": "The Laravel Framework.",
|
||||
"support": {
|
||||
"email": "equipe.web@ldc.fr"
|
||||
},
|
||||
"keywords": [
|
||||
"framework",
|
||||
"laravel"
|
||||
|
|
@ -14,34 +9,28 @@
|
|||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^7.3|^8.0",
|
||||
"diglactic/laravel-breadcrumbs": "^6.1",
|
||||
"diglactic/laravel-breadcrumbs": "^8.0",
|
||||
"fruitcake/laravel-cors": "^2.0",
|
||||
"guzzlehttp/guzzle": "^7.0.1",
|
||||
"kwaadpepper/enum": "^2.1",
|
||||
"kwaadpepper/image-resizer": "^0.1.2",
|
||||
"kwaadpepper/exception-handler": "^0.0.9",
|
||||
"kwaadpepper/refresh-sitemap": "^1.0",
|
||||
"laravel/framework": "^8.75",
|
||||
"laravel/tinker": "^2.5",
|
||||
"laravel/ui": "^3.4",
|
||||
"ldc/web-admin-secure-laravel": "^1.0",
|
||||
"ldc/web-status-checker-laravel": "^1.0",
|
||||
"ldc/web-admin-secure-laravel": "1.0.1",
|
||||
"ldc/web-status-checker-laravel": "1.0.3",
|
||||
"mews/purifier": "^3.3",
|
||||
"sentry/sentry-laravel": "^3.2",
|
||||
"spatie/laravel-mix-preload": "^1.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"facade/ignition": "^2.5",
|
||||
"fakerphp/faker": "^1.9.1",
|
||||
"laravel-lang/lang": "10.0",
|
||||
"laravel/sail": "^1.0.1",
|
||||
"mockery/mockery": "^1.4.4",
|
||||
"nunomaduro/collision": "^5.10",
|
||||
"phpunit/phpunit": "^9.5.10",
|
||||
"slevomat/coding-standard": "^8.8"
|
||||
},
|
||||
"repositories": {
|
||||
"lab.s-web.fr/23": {
|
||||
"type": "composer",
|
||||
"url": "https://lab.s-web.fr/api/v4/group/23/-/packages/composer/packages.json"
|
||||
}
|
||||
"slevomat/coding-standard": "^8.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
|
@ -81,11 +70,14 @@
|
|||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true
|
||||
},
|
||||
"gitlab-domains": [
|
||||
"lab.s-web.fr"
|
||||
]
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
"prefer-stable": true,
|
||||
"repositories": {
|
||||
"gitlab.com/16669037": {
|
||||
"type": "composer",
|
||||
"url": "https://gitlab.com/api/v4/group/16669037/-/packages/composer/packages.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3175
composer.lock
generated
|
|
@ -67,7 +67,7 @@ return [
|
|||
|
|
||||
*/
|
||||
|
||||
'timezone' => 'Europe/Paris',
|
||||
'timezone' => 'UTC',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
@ -80,7 +80,7 @@ return [
|
|||
|
|
||||
*/
|
||||
|
||||
'locale' => 'fr',
|
||||
'locale' => 'en',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
@ -106,7 +106,7 @@ return [
|
|||
|
|
||||
*/
|
||||
|
||||
'faker_locale' => 'fr_FR',
|
||||
'faker_locale' => 'en_US',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
@ -173,9 +173,10 @@ return [
|
|||
|
||||
App\Providers\AppServiceProvider::class,
|
||||
App\Providers\AuthServiceProvider::class,
|
||||
// phpcs:ignore
|
||||
// App\Providers\BroadcastServiceProvider::class,
|
||||
App\Providers\EventServiceProvider::class,
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
App\Providers\TranslationServiceProvider::class
|
||||
|
||||
],
|
||||
|
||||
|
|
@ -220,6 +221,8 @@ return [
|
|||
'Queue' => Illuminate\Support\Facades\Queue::class,
|
||||
'RateLimiter' => Illuminate\Support\Facades\RateLimiter::class,
|
||||
'Redirect' => Illuminate\Support\Facades\Redirect::class,
|
||||
// phpcs:ignore
|
||||
// 'Redis' => Illuminate\Support\Facades\Redis::class,
|
||||
'Request' => Illuminate\Support\Facades\Request::class,
|
||||
'Response' => Illuminate\Support\Facades\Response::class,
|
||||
'Route' => Illuminate\Support\Facades\Route::class,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ return [
|
|||
*/
|
||||
|
||||
'defaults' => [
|
||||
'guard' => 'frontend',
|
||||
'guard' => 'backend',
|
||||
'passwords' => 'users',
|
||||
],
|
||||
|
||||
|
|
@ -40,12 +40,6 @@ return [
|
|||
'driver' => 'session',
|
||||
'provider' => 'users',
|
||||
],
|
||||
|
||||
'frontend' => [
|
||||
'driver' => 'session',
|
||||
// ! Remplacer "users" par le model authenticable voulu
|
||||
'provider' => 'users',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -62,7 +62,8 @@ return [
|
|||
env('MEMCACHED_PASSWORD'),
|
||||
],
|
||||
'options' => [
|
||||
// Memcached::OPT_CONNECT_TIMEOUT => 2000, .
|
||||
// phpcs:ignore
|
||||
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
|
||||
],
|
||||
'servers' => [
|
||||
[
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ return [
|
|||
|
|
||||
*/
|
||||
|
||||
'paths' => ['api/*'],
|
||||
'paths' => ['api/*', 'sanctum/csrf-cookie'],
|
||||
|
||||
'allowed_methods' => ['*'],
|
||||
|
||||
|
|
|
|||
15
config/exception-handler.php
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
/**
|
||||
* Send mail when app debug is turn off
|
||||
* so you can be warned if an unhandled exception occurs
|
||||
*/
|
||||
'enableMaiLog' => !config('app.debug'),
|
||||
|
||||
/**
|
||||
* Put a developper secured mailbox in here
|
||||
* so you can receive email if things go mad
|
||||
*/
|
||||
'contactsList' => explode(",", env('MAIL_LIST_SERVICEWEB'))
|
||||
];
|
||||
|
|
@ -50,7 +50,7 @@ return [
|
|||
'channels' => [
|
||||
'stack' => [
|
||||
'driver' => 'stack',
|
||||
'channels' => ['single', 'sentry'],
|
||||
'channels' => ['single'],
|
||||
'ignore_exceptions' => false,
|
||||
],
|
||||
|
||||
|
|
@ -60,11 +60,6 @@ return [
|
|||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
],
|
||||
|
||||
'sentry' => [
|
||||
'driver' => 'sentry',
|
||||
'level' => env('LOG_LEVEL', 'error'),
|
||||
],
|
||||
|
||||
'daily' => [
|
||||
'driver' => 'daily',
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
|
|
|
|||
|
|
@ -29,15 +29,8 @@ return [
|
|||
// * For iframes
|
||||
'URI.SafeIframeRegexp' => "%^(http://|https://|//)(www.youtube.com/embed/|player.vimeo.com/video/|vimeo.com/)%",
|
||||
'HTML.Doctype' => 'HTML 4.01 Transitional',
|
||||
// TODO: Rules en FULL CKEDITOR.
|
||||
// * Legacy filter en full avec safe legacy.
|
||||
// 'HTML.Allowed' => 'h1[style],h2[style],h3[style],h4[style],h5[style],h6[style],figure[class|style],oembed[url],div[style],b,strong,i,em,u,a[href|title|target|rel],ul[style],ol[style],dl[style],li,p[style],br,span[style|class],img[width|height|alt|src|style|class|title],iframe[src|width|height|class|frameborder|style],table[style|align|border|cellspacing|cellpadding|width],thead,tbody,th[colspan|align|valign|style],tr[align|valign|style],td[style|colspan|rowspan|scope|align|valign],hr,dl,dt',
|
||||
// * Restricted basics.
|
||||
'HTML.Allowed' => 'br,p[style],b,u,i,a[href|title|target|rel],strong,span[class],ul,ol,li,h2,h3,h4,hr',
|
||||
// * Legacy filter en full avec safe legacy.
|
||||
// 'CSS.AllowedProperties' => 'font,font-size,font-weight,font-style,font-family,text-decoration,padding-left,color,background-color,text-align,list-style-type,float,width,height,vertical-align,border-color',
|
||||
// * Restricted basics.
|
||||
'CSS.AllowedProperties' => 'text-align',
|
||||
'HTML.Allowed' => 'h1[style],h2[style],h3[style],h4[style],h5[style],h6[style],figure[class|style],oembed[url],div[style],b,strong,i,em,u,a[href|title|target|rel],ul[style],ol[style],dl[style],li,p[style],br,span[style|class],img[width|height|alt|src|style|class|title],iframe[src|width|height|class|frameborder|style],table[style|align|border|cellspacing|cellpadding|width],thead,tbody,th[colspan|align|valign|style],tr[align|valign|style],td[style|colspan|rowspan|scope|align|valign],hr,dl,dt',
|
||||
'CSS.AllowedProperties' => 'font,font-size,font-weight,font-style,font-family,text-decoration,padding-left,color,background-color,text-align,list-style-type,float,width,height,vertical-align,border-color',
|
||||
'AutoFormat.AutoParagraph' => false,
|
||||
'AutoFormat.RemoveEmpty' => false,
|
||||
'AutoFormat.RemoveEmpty.RemoveNbsp' => false,
|
||||
|
|
|
|||
|
|
@ -1,61 +0,0 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'dsn' => env('SENTRY_LARAVEL_DSN', env('SENTRY_DSN')),
|
||||
|
||||
// The release version of your application.
|
||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')).
|
||||
'release' => env('SENTRY_RELEASE'),
|
||||
|
||||
// When left empty or `null` the Laravel environment will be used.
|
||||
'environment' => env('SENTRY_ENVIRONMENT'),
|
||||
|
||||
'breadcrumbs' => [
|
||||
// Capture Laravel logs in breadcrumbs.
|
||||
'logs' => true,
|
||||
|
||||
// Capture SQL queries in breadcrumbs.
|
||||
'sql_queries' => true,
|
||||
|
||||
// Capture bindings on SQL queries logged in breadcrumbs.
|
||||
'sql_bindings' => false,
|
||||
|
||||
// Capture queue job information in breadcrumbs.
|
||||
'queue_info' => true,
|
||||
|
||||
// Capture command information in breadcrumbs.
|
||||
'command_info' => true,
|
||||
],
|
||||
|
||||
'tracing' => [
|
||||
// Trace queue jobs as their own transactions.
|
||||
'queue_job_transactions' => env('SENTRY_TRACE_QUEUE_ENABLED', false),
|
||||
|
||||
// Capture queue jobs as spans when executed on the sync driver.
|
||||
'queue_jobs' => true,
|
||||
|
||||
// Capture SQL queries as spans.
|
||||
'sql_queries' => true,
|
||||
|
||||
// Try to find out where the SQL query originated from and add it to the query spans.
|
||||
'sql_origin' => true,
|
||||
|
||||
// Capture views as spans.
|
||||
'views' => true,
|
||||
|
||||
// Indicates if the tracing integrations supplied by Sentry should be loaded.
|
||||
'default_integrations' => true,
|
||||
|
||||
// Indicates that requests without a matching route should be traced.
|
||||
'missing_routes' => false,
|
||||
],
|
||||
|
||||
// @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#send-default-pii.
|
||||
'send_default_pii' => env('SENTRY_SEND_DEFAULT_PII', false),
|
||||
|
||||
// @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#traces-sample-rate.
|
||||
'traces_sample_rate' => env('SENTRY_TRACES_SAMPLE_RATE') === null ? null : (float)env('SENTRY_TRACES_SAMPLE_RATE'),
|
||||
|
||||
'before_send' => [\App\Lib\Helpers\ExceptionsHelper::class, 'sentryEventHandler'],
|
||||
];
|
||||
|
|
@ -14,20 +14,14 @@ return [
|
|||
|
|
||||
*/
|
||||
|
||||
'mailgun' => [
|
||||
'domain' => env('MAILGUN_DOMAIN'),
|
||||
'secret' => env('MAILGUN_SECRET'),
|
||||
'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
|
||||
'google' => [
|
||||
'analytics' => [
|
||||
'id' => env('GOOGLE_ANALYTICS_ID')
|
||||
],
|
||||
'recaptcha' => [
|
||||
'public' => env('GOOGLE_RECAPTCHA_PUBLIC'),
|
||||
'private' => env('GOOGLE_RECAPTCHA_PRIVATE'),
|
||||
'version' => env('GOOGLE_RECAPTCHA_VERSION', "3") === "3" ? true : false
|
||||
]
|
||||
],
|
||||
|
||||
'postmark' => [
|
||||
'token' => env('POSTMARK_TOKEN'),
|
||||
],
|
||||
|
||||
'ses' => [
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
],
|
||||
|
||||
];
|
||||
|
|
|
|||
|
|
@ -15,11 +15,9 @@ class UserFactory extends Factory
|
|||
public function definition()
|
||||
{
|
||||
return [
|
||||
'firstname' => $this->faker->firstname(),
|
||||
'lastname' => $this->faker->lastname(),
|
||||
'name' => $this->faker->name(),
|
||||
'email' => $this->faker->unique()->safeEmail(),
|
||||
'email_verified_at' => now(),
|
||||
// * password
|
||||
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
|
||||
'remember_token' => Str::random(10),
|
||||
];
|
||||
|
|
@ -32,7 +30,8 @@ class UserFactory extends Factory
|
|||
*/
|
||||
public function unverified()
|
||||
{
|
||||
return $this->state(function () {
|
||||
// phpcs:ignore
|
||||
return $this->state(function (array $attributes) {
|
||||
return [
|
||||
'email_verified_at' => null,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -15,12 +15,12 @@ class CreateUsersTable extends Migration
|
|||
{
|
||||
Schema::create('users', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('firstname');
|
||||
$table->string('lastname');
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
$table->timestamp('email_verified_at')->nullable();
|
||||
$table->string('password');
|
||||
$table->rememberToken();
|
||||
$table->date('last_connection')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreatePersonalAccessTokensTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('personal_access_tokens', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->morphs('tokenable');
|
||||
$table->string('name');
|
||||
$table->string('token', 64)->unique();
|
||||
$table->text('abilities')->nullable();
|
||||
$table->timestamp('last_used_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('personal_access_tokens');
|
||||
}
|
||||
}
|
||||
|
|
@ -13,5 +13,6 @@ class DatabaseSeeder extends Seeder
|
|||
*/
|
||||
public function run()
|
||||
{
|
||||
// \App\Models\User::factory(10)->create();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
5
jsconfig.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"include": [
|
||||
"./resources/js/**/*"
|
||||
]
|
||||
}
|
||||
7607
package-lock.json
generated
46
package.json
|
|
@ -8,56 +8,32 @@
|
|||
"hot": "mix watch --hot",
|
||||
"prod": "npm run production",
|
||||
"production": "mix --production",
|
||||
"eslint": "node ./node_modules/.bin/eslint -c .eslintrc.json ./resources",
|
||||
"eslint-ci": "node ./node_modules/.bin/eslint -c .eslintrc.json ./resources -f junit",
|
||||
"stylelint": "npx stylelint '**/*.scss'",
|
||||
"stylelint-ci": "npx stylelint '**/*.scss' -f json"
|
||||
"eslint": "node ./node_modules/.bin/eslint --max-warnings 0 -c .eslintrc.json ./resources",
|
||||
"eslint-ci": "node ./node_modules/.bin/eslint --max-warnings 0 -c .eslintrc.json ./resources -f junit",
|
||||
"stylelint": "node ./node_modules/.bin/stylelint --max-warnings 0 '**/*.scss'",
|
||||
"stylelint-ci": "node ./node_modules/.bin/stylelint --max-warnings 0 '**/*.scss' -f json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.1.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.1.2",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.1.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.1.2",
|
||||
"@fortawesome/vue-fontawesome": "^3.0.1",
|
||||
"@rushstack/eslint-patch": "^1.1.4",
|
||||
"@sipec/vue3-tags-input": "^3.0.4",
|
||||
"@types/bootstrap": "^5.2.6",
|
||||
"@types/cropperjs": "^1.3.0",
|
||||
"@types/toastify-js": "^1.11.1",
|
||||
"@types/webpack-env": "^1.18.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.36.2",
|
||||
"@typescript-eslint/parser": "^5.36.2",
|
||||
"@vue/eslint-config-typescript": "^11.0.0",
|
||||
"@types/bootstrap": "^5.2.1",
|
||||
"@types/webpack-env": "^1.17.0",
|
||||
"axios": "^0.21",
|
||||
"bootstrap": "^5.2.3",
|
||||
"bootstrap": "^5.2.0",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"cropperjs": "^1.5.12",
|
||||
"eslint": "^8.23.0",
|
||||
"eslint-plugin-vue": "^9.4.0",
|
||||
"eslint-webpack-plugin": "^3.2.0",
|
||||
"laravel-mix": "^6.0.6",
|
||||
"laravel-mix-clean": "^0.1.0",
|
||||
"laravel-mix-eslint": "^0.2.0",
|
||||
"laravel-mix-polyfill": "^3.0.1",
|
||||
"laravel-mix-stylelint": "^1.0.0",
|
||||
"postcss": "^8.1.14",
|
||||
"prettier": "^2.7.1",
|
||||
"resolve-url-loader": "^5.0.0",
|
||||
"sass": "^1.54.8",
|
||||
"sass": "^1.54.0",
|
||||
"sass-loader": "^12.6.0",
|
||||
"slugify": "^1.6.5",
|
||||
"stylelint": "^14.11.0",
|
||||
"stylelint-config-idiomatic-order": "^8.1.0",
|
||||
"stylelint-config-sass-guidelines": "^9.0.1",
|
||||
"stylelint-config-standard-scss": "^6.1.0",
|
||||
"stylelint-webpack-plugin": "^3.3.0",
|
||||
"sweetalert2": "^11.4.8",
|
||||
"toastify-js": "^1.12.0",
|
||||
"sweetalert2": "^11.4.24",
|
||||
"ts-loader": "^9.3.1",
|
||||
"typescript": "^4.8.2",
|
||||
"typescript-eslint": "^0.0.1-alpha.0",
|
||||
"vue": "^3.2.38",
|
||||
"vue-eslint-parser": "^9.1.0",
|
||||
"typescript": "^4.7.4",
|
||||
"vue": "^3.2.37",
|
||||
"vue-loader": "^16.8.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
<rule ref="Generic.Functions.FunctionCallArgumentSpacing"/>
|
||||
<rule ref="Generic.Metrics.CyclomaticComplexity"/>
|
||||
<rule ref="Generic.Metrics.NestingLevel"/>
|
||||
<rule ref="Generic.NamingConventions.ConstructorName"/>
|
||||
<rule ref="Generic.NamingConventions.UpperCaseConstantName"/>
|
||||
<rule ref="Generic.NamingConventions.CamelCapsFunctionName"/>
|
||||
<rule ref="Generic.PHP.DeprecatedFunctions"/>
|
||||
|
|
|
|||
31
phpunit.xml
|
|
@ -1,31 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
|
||||
bootstrap="vendor/autoload.php"
|
||||
colors="true"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Unit">
|
||||
<directory suffix="Test.php">./tests/Unit</directory>
|
||||
</testsuite>
|
||||
<testsuite name="Feature">
|
||||
<directory suffix="Test.php">./tests/Feature</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<coverage processUncoveredFiles="true">
|
||||
<include>
|
||||
<directory suffix=".php">./app</directory>
|
||||
</include>
|
||||
</coverage>
|
||||
<php>
|
||||
<server name="APP_ENV" value="testing"/>
|
||||
<server name="BCRYPT_ROUNDS" value="4"/>
|
||||
<server name="CACHE_DRIVER" value="array"/>
|
||||
<!-- <server name="DB_CONNECTION" value="sqlite"/> -->
|
||||
<!-- <server name="DB_DATABASE" value=":memory:"/> -->
|
||||
<server name="MAIL_MAILER" value="array"/>
|
||||
<server name="QUEUE_CONNECTION" value="sync"/>
|
||||
<server name="SESSION_DRIVER" value="array"/>
|
||||
<server name="TELESCOPE_ENABLED" value="false"/>
|
||||
</php>
|
||||
</phpunit>
|
||||
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 235 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
|
@ -2,8 +2,8 @@
|
|||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/mstile-150x150.png"/>
|
||||
<TileColor>#da532c</TileColor>
|
||||
<square150x150logo src="/assets/images/mstile-150x150.png"/>
|
||||
<TileColor>#091c58</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 669 B |
|
Before Width: | Height: | Size: 886 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 8 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 0 B |
|
|
@ -7,17 +7,17 @@ define('LARAVEL_START', microtime(true));
|
|||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Check If Application Is Under Maintenance
|
||||
| Check If The Application Is Under Maintenance
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If the application is maintenance / demo mode via the "down" command we
|
||||
| will require this file so that any prerendered template can be shown
|
||||
| If the application is in maintenance / demo mode via the "down" command
|
||||
| we will load this file so that any pre-rendered content can be shown
|
||||
| instead of starting the framework, which could cause an exception.
|
||||
|
|
||||
*/
|
||||
|
||||
if (file_exists(__DIR__.'/../storage/framework/maintenance.php')) {
|
||||
require __DIR__.'/../storage/framework/maintenance.php';
|
||||
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
|
||||
require $maintenance;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -48,8 +48,8 @@ $app = require_once __DIR__.'/../bootstrap/app.php';
|
|||
|
||||
$kernel = $app->make(Kernel::class);
|
||||
|
||||
$response = tap($kernel->handle(
|
||||
$response = $kernel->handle(
|
||||
$request = Request::capture()
|
||||
))->send();
|
||||
)->send();
|
||||
|
||||
$kernel->terminate($request, $response);
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 21 KiB |
|
|
@ -1,187 +0,0 @@
|
|||
<?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="621.000000pt" height="621.000000pt" viewBox="0 0 621.000000 621.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.14, written by Peter Selinger 2001-2017
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,621.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M375 6180 c-126 -3 -238 -9 -247 -14 -10 -4 -33 -29 -52 -55 l-35
|
||||
-47 -3 -2149 c-2 -1183 -1 -2517 2 -2966 5 -799 5 -817 25 -839 11 -13 40 -36
|
||||
64 -52 l45 -28 2940 0 2940 0 43 21 c29 14 52 35 68 63 l25 43 0 2919 c-1
|
||||
1605 -5 2939 -9 2963 -11 55 -37 89 -87 115 -21 11 -40 24 -42 28 -4 9 -5301
|
||||
7 -5677 -2z m5447 -366 c11 -7 9 -5386 -2 -5404 -8 -13 -318 -15 -2672 -18
|
||||
-1465 -2 -2681 1 -2703 5 l-40 9 0 2704 0 2705 2435 5 c2310 5 2966 4 2982 -6z"/>
|
||||
<path d="M4185 5150 c3 -5 13 -10 21 -10 8 0 13 -4 10 -8 -3 -5 3 -8 12 -8 9
|
||||
1 18 -3 20 -9 4 -13 -54 -25 -65 -14 -13 13 -61 -13 -67 -36 -3 -10 0 -23 7
|
||||
-28 7 -5 2 -6 -12 -2 -16 5 -21 4 -17 -4 4 -6 2 -11 -3 -11 -6 0 -11 -7 -11
|
||||
-15 0 -21 16 -19 41 6 20 20 39 22 42 4 1 -12 -84 -78 -91 -70 -13 12 -34 -41
|
||||
-62 -158 -16 -68 -30 -127 -30 -130 0 -2 9 -7 20 -10 23 -6 37 -34 23 -48 -15
|
||||
-15 -43 1 -44 25 0 20 -1 20 -9 1 -11 -26 -12 -48 -2 -41 4 2 6 -24 4 -59 -2
|
||||
-36 -1 -72 2 -81 5 -14 10 -13 31 10 13 14 28 33 33 42 12 22 35 8 29 -17 -3
|
||||
-12 -1 -17 5 -13 6 4 9 1 6 -7 -3 -8 -17 -14 -31 -14 -14 1 -25 -2 -24 -5 4
|
||||
-20 -17 -50 -36 -50 -21 0 -22 -3 -18 -81 2 -62 0 -80 -9 -74 -6 4 -10 18 -7
|
||||
31 2 13 1 24 -3 24 -3 0 -6 9 -6 21 1 15 -9 24 -34 34 -19 7 -69 27 -110 45
|
||||
-269 113 -518 162 -830 163 -338 1 -598 -52 -840 -173 -498 -249 -802 -698
|
||||
-891 -1315 -31 -217 -31 -594 0 -780 12 -71 23 -143 24 -160 1 -16 6 -36 10
|
||||
-43 5 -7 10 -22 13 -35 7 -40 13 -61 38 -137 14 -41 30 -83 36 -92 5 -10 10
|
||||
-23 10 -30 0 -6 23 -57 51 -112 249 -492 658 -780 1224 -862 150 -22 457 -30
|
||||
545 -15 36 6 115 20 175 30 121 21 233 47 249 60 6 4 31 13 56 20 82 24 225
|
||||
89 347 159 73 42 82 50 87 82 8 48 6 431 -2 452 -6 15 -12 12 -42 -19 -85 -88
|
||||
-335 -229 -512 -288 -205 -68 -285 -79 -558 -80 -199 0 -261 3 -330 18 -248
|
||||
53 -451 162 -602 326 -80 87 -124 151 -179 262 -22 46 -45 92 -50 102 -5 10
|
||||
-7 22 -4 27 3 4 2 10 -2 12 -16 7 -55 148 -80 288 -21 117 -27 187 -30 365 -3
|
||||
125 0 259 6 310 16 136 51 319 62 326 5 3 7 13 4 21 -4 9 -2 19 4 22 6 4 8 13
|
||||
5 20 -3 8 -1 17 5 21 6 3 11 12 11 20 0 8 5 26 12 42 6 15 12 30 13 33 8 31
|
||||
83 173 122 231 59 86 216 242 297 294 133 85 274 136 457 166 116 18 160 21
|
||||
329 16 293 -7 461 -50 737 -186 57 -28 107 -51 113 -51 5 0 10 -8 10 -17 0
|
||||
-15 2 -16 10 -3 6 10 10 11 10 3 0 -7 18 -22 40 -33 22 -11 40 -25 40 -31 0
|
||||
-6 5 -7 10 -4 6 4 10 -9 10 -33 0 -22 3 -54 6 -71 l7 -31 -47 0 c-34 0 -51 -5
|
||||
-63 -20 -19 -22 -43 -26 -60 -9 -6 6 -37 16 -69 24 -33 7 -87 20 -121 28 -35
|
||||
8 -63 11 -63 7 0 -29 -76 -13 -82 16 -2 12 -20 21 -58 30 -50 12 -56 11 -70
|
||||
-5 -21 -26 -60 -33 -67 -14 -3 9 -1 24 5 34 9 14 8 18 -6 18 -9 1 -34 4 -54 7
|
||||
-31 5 -38 3 -38 -10 0 -23 -35 -20 -59 5 -17 19 -29 20 -123 18 -114 -3 -138
|
||||
-7 -116 -20 10 -7 10 -9 1 -9 -7 0 -14 -10 -14 -22 -1 -19 -3 -17 -9 7 -6 25
|
||||
-8 26 -9 7 0 -13 -5 -21 -10 -18 -5 4 -12 0 -15 -6 -3 -7 -2 1 1 17 6 27 5 29
|
||||
-11 16 -14 -12 -16 -12 -10 3 3 9 1 16 -5 16 -6 0 -11 -5 -11 -11 0 -5 -4 -8
|
||||
-9 -5 -5 3 -12 -1 -15 -10 -8 -21 -8 -26 2 -21 4 3 7 -8 7 -24 0 -39 -11 -37
|
||||
-19 4 -6 29 -8 30 -18 13 -8 -15 -8 -21 0 -24 7 -2 12 -22 12 -43 0 -24 4 -38
|
||||
10 -34 6 3 10 -1 10 -9 0 -9 4 -16 9 -16 5 0 17 -7 25 -16 15 -14 16 -13 18
|
||||
13 0 15 1 33 1 40 0 6 6 9 14 6 10 -4 11 -9 4 -16 -16 -16 -14 -62 3 -76 18
|
||||
-15 56 -22 56 -11 0 5 -4 11 -9 14 -10 7 -3 44 12 64 17 22 38 -5 27 -34 -14
|
||||
-37 -22 -92 -11 -75 6 9 11 10 15 2 10 -15 22 -14 40 5 9 8 16 11 16 5 0 -5
|
||||
-5 -13 -10 -16 -8 -5 -7 -11 1 -21 6 -7 15 -11 20 -9 4 3 16 1 26 -4 38 -20
|
||||
105 -41 114 -36 5 4 9 14 9 24 0 9 6 26 14 37 14 17 16 18 31 3 15 -15 15 -18
|
||||
-9 -36 -18 -14 -23 -26 -19 -42 7 -26 48 -29 62 -3 10 16 10 16 11 0 0 -10 5
|
||||
-18 11 -18 8 0 8 -4 0 -14 -10 -12 3 -22 71 -56 46 -23 90 -48 98 -55 8 -6 41
|
||||
-26 73 -44 31 -18 57 -35 57 -37 0 -3 -6 -2 -12 1 -7 4 -4 -1 6 -9 14 -11 22
|
||||
-13 27 -5 3 6 9 0 13 -15 6 -26 19 -31 64 -27 12 1 22 -3 22 -9 0 -5 7 -10 15
|
||||
-10 8 0 15 5 15 10 0 16 40 11 47 -6 3 -9 -5 -26 -20 -42 -31 -32 -24 -62 9
|
||||
-37 25 19 31 19 44 -4 11 -22 2 -84 -11 -75 -5 3 -9 0 -9 -6 0 -7 -6 -10 -14
|
||||
-7 -9 4 -16 -4 -21 -21 -3 -15 -8 -34 -11 -43 -5 -19 63 -95 83 -91 8 2 12 -1
|
||||
9 -7 -11 -17 12 -51 29 -45 8 4 15 1 15 -6 0 -6 5 -8 10 -5 6 4 8 10 5 15 -3
|
||||
4 4 10 15 13 11 3 20 14 20 24 0 25 8 33 26 26 8 -3 14 -16 14 -28 0 -13 5
|
||||
-27 10 -30 6 -3 10 1 10 10 0 30 29 -10 32 -44 3 -35 23 -48 37 -24 8 15 11
|
||||
15 26 3 14 -12 18 -12 25 0 5 8 6 18 4 22 -9 15 7 48 23 48 10 0 14 -6 10 -20
|
||||
-3 -11 -3 -20 -1 -20 7 0 49 25 54 32 3 4 12 3 20 -2 10 -7 11 -10 3 -10 -8 0
|
||||
-13 -13 -13 -34 0 -29 3 -33 23 -29 13 3 24 11 24 18 0 8 -4 12 -8 10 -4 -3
|
||||
-9 1 -12 9 -15 37 70 58 108 26 14 -11 19 -12 22 -2 3 8 30 12 88 12 101 0
|
||||
115 10 115 80 0 46 -13 99 -28 110 -8 6 -13 14 -100 170 -35 63 -77 131 -92
|
||||
151 -35 44 -28 57 14 26 17 -13 39 -26 49 -30 9 -3 17 -11 17 -17 0 -5 6 -10
|
||||
13 -10 7 0 31 -13 53 -29 70 -52 127 -45 105 12 -7 18 -6 30 5 45 9 13 11 23
|
||||
4 27 -5 3 -10 -1 -10 -9 0 -10 -6 -13 -17 -9 -30 10 -53 35 -53 56 0 20 1 19
|
||||
26 -4 l27 -24 -7 29 -7 29 53 -7 c169 -20 267 -19 254 2 -2 4 10 8 26 9 40 3
|
||||
129 46 106 51 -10 2 -18 8 -18 13 0 6 -7 6 -19 -1 -24 -13 -41 -13 -41 -1 0 6
|
||||
8 9 18 9 34 -2 106 31 102 46 -1 5 4 13 12 17 9 6 6 9 -10 9 -15 0 -22 5 -19
|
||||
13 2 6 16 11 31 9 20 -2 29 3 38 24 11 23 10 28 -7 40 -15 11 -16 14 -3 14 24
|
||||
0 31 51 8 57 -21 6 -16 33 6 33 9 0 14 11 14 29 0 24 -5 29 -30 33 -16 3 -30
|
||||
1 -30 -3 0 -3 -11 -9 -25 -11 -19 -4 -25 -1 -25 12 0 12 10 19 30 22 17 3 30
|
||||
1 30 -4 0 -4 7 -8 16 -8 8 0 12 5 9 10 -3 6 -2 10 4 10 5 0 9 9 8 20 -1 11 -4
|
||||
20 -8 20 -4 0 -5 9 -2 19 4 17 -2 21 -41 26 -25 4 -46 12 -46 17 0 6 -7 8 -15
|
||||
4 -8 -3 -15 0 -15 5 0 6 8 9 18 6 32 -9 37 -9 68 -8 28 1 31 4 28 27 -3 30
|
||||
-21 46 -44 39 -43 -12 -85 -9 -85 5 0 11 11 14 46 13 44 -2 45 -2 45 27 0 16
|
||||
-3 27 -8 24 -4 -3 -8 0 -8 6 0 8 -10 9 -29 5 -34 -7 -101 17 -101 37 0 10 2
|
||||
10 9 -1 5 -7 14 -10 20 -7 6 4 11 2 11 -4 0 -7 7 -10 15 -6 8 3 15 1 15 -4 0
|
||||
-13 11 -12 30 0 13 9 13 11 0 20 -10 6 -11 10 -3 10 7 0 -12 25 -43 56 -30 31
|
||||
-58 53 -62 50 -3 -4 0 -11 8 -16 11 -7 12 -10 2 -10 -7 0 0 -11 15 -25 28 -24
|
||||
28 -24 5 -25 -13 0 -21 4 -17 10 6 10 -51 44 -63 36 -4 -3 -14 -1 -21 4 -18
|
||||
10 -5 31 14 23 11 -4 13 -1 9 11 -5 13 -2 15 12 10 14 -5 16 -4 8 5 -6 6 -34
|
||||
14 -62 18 -45 5 -52 3 -52 -11 0 -9 11 -28 24 -41 19 -19 21 -26 11 -35 -9 -8
|
||||
-19 -1 -41 27 -18 24 -24 38 -16 41 23 7 12 22 -18 22 -23 0 -28 4 -23 16 4
|
||||
11 8 12 13 4 8 -13 20 46 20 98 0 42 -36 118 -90 190 -21 29 -25 31 -32 15 -4
|
||||
-11 -6 -4 -4 17 3 22 -2 43 -11 55 -15 19 -15 19 -9 1 5 -16 4 -17 -9 -6 -8 7
|
||||
-15 8 -15 2 0 -12 -34 -22 -48 -13 -6 4 3 13 20 20 20 9 26 16 18 21 -9 6 -9
|
||||
9 0 12 7 2 2 14 -14 31 -14 15 -23 27 -20 27 3 0 -2 7 -11 16 -9 9 -14 23 -11
|
||||
30 3 8 1 14 -4 14 -7 0 -19 23 -25 50 -1 3 -6 16 -12 30 -6 14 -16 44 -22 68
|
||||
-8 27 -16 40 -23 36 -7 -4 -8 -3 -4 5 4 6 1 18 -8 26 -11 11 -16 12 -19 2 -3
|
||||
-6 -4 2 -2 19 2 17 -1 37 -7 45 -7 11 -8 9 -3 -6 5 -16 2 -16 -18 5 -13 14
|
||||
-21 29 -19 34 3 5 0 6 -6 2 -6 -3 -25 2 -44 13 -35 20 -134 29 -123 11z m105
|
||||
-45 c0 -5 -5 -3 -10 5 -5 8 -10 20 -10 25 0 6 5 3 10 -5 5 -8 10 -19 10 -25z
|
||||
m-15 -65 c-4 -13 -12 -19 -21 -16 -7 3 -16 1 -19 -4 -7 -11 -55 -14 -55 -4 0
|
||||
3 5 14 10 25 11 20 50 27 50 9 0 -5 7 -10 15 -10 8 0 15 8 15 17 0 9 3 14 6
|
||||
10 3 -3 3 -15 -1 -27z m95 -10 c0 -19 -48 -60 -70 -60 -28 0 -26 33 3 53 25
|
||||
18 67 22 67 7z m87 -94 c0 -8 -6 -12 -12 -10 -6 3 -12 11 -12 18 0 8 6 12 12
|
||||
10 6 -3 12 -11 12 -18z m-48 6 c9 7 5 0 -8 -15 -26 -29 -51 -36 -51 -14 0 20
|
||||
22 39 34 27 6 -6 15 -5 25 2z m-14 -52 c-3 -5 0 -10 8 -10 8 0 20 -7 27 -15
|
||||
10 -12 10 -15 0 -15 -17 0 -61 36 -53 44 10 10 25 7 18 -4z m79 -56 c17 -7 10
|
||||
-34 -9 -34 -17 0 -28 17 -20 30 7 12 8 12 29 4z m-155 -43 c12 -24 -6 -79 -29
|
||||
-86 -10 -3 -18 -11 -16 -18 1 -7 1 -9 -1 -4 -3 4 -10 7 -16 7 -16 0 -47 31
|
||||
-47 47 0 7 16 29 35 48 35 36 58 38 74 6z m75 0 c4 -5 0 -12 -6 -14 -7 -3 -3
|
||||
-6 10 -6 12 -1 22 4 22 11 0 9 3 9 11 1 17 -17 1 -52 -28 -59 -29 -8 -44 9
|
||||
-38 41 2 11 4 23 4 28 1 10 19 9 25 -2z m116 -58 c0 -23 -17 -49 -26 -41 -9
|
||||
10 4 58 16 58 6 0 10 -8 10 -17z m-360 -13 c23 -23 24 -29 10 -64 -7 -19 -17
|
||||
-26 -35 -26 -14 0 -25 5 -25 11 0 5 -5 7 -12 3 -7 -5 -9 -2 -5 8 4 11 -2 18
|
||||
-18 22 -22 6 -41 33 -30 44 8 9 35 -20 29 -32 -3 -6 6 4 21 22 31 37 38 39 65
|
||||
12z m293 -46 c8 -9 -14 -54 -24 -48 -5 3 -9 0 -9 -7 0 -10 -3 -10 -11 -2 -8 8
|
||||
-6 18 6 37 17 26 27 31 38 20z m80 -40 c11 -11 -5 -34 -24 -34 -18 0 -26 20
|
||||
-12 33 8 9 28 9 36 1z m94 -66 c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13 3 -3 4 -12
|
||||
1 -19z m-427 -16 c0 -10 -14 -31 -30 -47 -26 -25 -32 -27 -40 -15 -13 20 -12
|
||||
33 3 52 23 30 67 36 67 10z m106 -34 c4 13 14 22 26 22 17 0 19 -4 13 -27 -13
|
||||
-57 -115 -188 -128 -166 -2 4 -8 32 -12 61 -6 43 -3 61 11 88 10 19 22 34 26
|
||||
34 4 0 -1 -15 -12 -32 -32 -54 -22 -57 12 -5 31 48 32 48 44 25 l14 -23 6 23z
|
||||
m96 -163 c3 3 3 -6 0 -20 -2 -14 -13 -30 -23 -36 -11 -5 -19 -15 -19 -22 0 -7
|
||||
-20 -33 -45 -57 -53 -52 -65 -46 -65 31 0 37 5 52 26 73 29 29 49 34 40 11 -8
|
||||
-23 8 -18 34 10 16 17 27 22 35 15 7 -5 14 -7 17 -5z m-357 -191 c-18 -43 -35
|
||||
-36 -36 14 l-1 37 -8 -30 c-7 -26 -8 -24 -7 16 0 33 4 46 13 42 6 -2 16 1 20
|
||||
8 5 9 12 3 21 -19 11 -27 11 -38 -2 -68z m701 10 c13 -3 24 -10 24 -16 0 -5
|
||||
-5 -6 -10 -3 -6 4 -18 1 -26 -6 -12 -10 -18 -9 -32 4 -13 14 -14 17 -2 17 13
|
||||
0 13 2 0 11 -12 9 -12 10 3 5 11 -3 30 -9 43 -12z m164 -51 c0 -19 -20 -26
|
||||
-35 -13 -21 18 -18 25 10 25 14 0 25 -5 25 -12z m-657 -18 c-3 -9 -8 -14 -10
|
||||
-11 -3 3 -2 9 2 15 9 16 15 13 8 -4z m25 -42 c-8 -10 -6 -13 5 -13 10 0 17
|
||||
-11 19 -27 2 -16 6 -26 9 -23 4 3 16 -4 28 -14 21 -19 21 -19 42 0 22 19 22
|
||||
19 10 -1 -7 -11 -27 -45 -45 -75 -18 -30 -38 -54 -45 -52 -16 3 -41 -23 -41
|
||||
-43 0 -9 -4 -14 -9 -10 -5 3 -12 1 -16 -5 -3 -5 -12 -10 -20 -10 -22 0 -8 198
|
||||
16 229 11 13 19 31 19 40 0 25 22 44 31 29 4 -7 3 -18 -3 -25z m-188 -38 c0
|
||||
-19 -4 -36 -9 -40 -15 -8 -19 6 -14 47 6 46 23 40 23 -7z m69 -102 c-3 -96
|
||||
-13 -144 -33 -155 -9 -5 -16 -15 -16 -22 0 -8 -7 -19 -15 -26 -13 -11 -15 0
|
||||
-15 93 0 133 7 169 30 162 9 -3 23 1 30 10 7 8 14 15 17 15 2 0 3 -35 2 -77z
|
||||
m-142 -35 c-2 -13 -4 -5 -4 17 -1 22 1 32 4 23 2 -10 2 -28 0 -40z m-777 -166
|
||||
c27 -21 20 -42 -14 -42 -26 0 -51 18 -59 43 -9 24 43 23 73 -1z m1827 12 c-3
|
||||
-3 -12 -4 -19 -1 -8 3 -5 6 6 6 11 1 17 -2 13 -5z m-2127 -27 c0 -24 -19 -42
|
||||
-31 -30 -8 8 -8 16 1 32 11 22 30 20 30 -2z m1757 8 c0 -9 -9 -15 -21 -15 -21
|
||||
1 -21 1 -2 15 24 18 23 18 23 0z m-1317 -44 c8 -16 6 -21 -11 -26 -24 -8 -55
|
||||
16 -45 34 11 18 44 13 56 -8z m-151 -25 c5 -3 14 -19 19 -36 9 -25 8 -31 -8
|
||||
-40 -30 -16 -47 -12 -69 16 -25 32 -26 38 -6 58 14 14 43 15 64 2z m929 -49
|
||||
c33 -18 42 -44 18 -53 -19 -7 -105 56 -106 78 0 19 15 14 88 -25z m941 22 c-7
|
||||
-4 -21 -3 -31 2 -25 14 -23 16 13 11 21 -3 27 -7 18 -13z m-1284 1 c3 -6 -1
|
||||
-7 -9 -4 -18 7 -21 14 -7 14 6 0 13 -4 16 -10z m-354 -25 c19 -1 43 -6 53 -12
|
||||
15 -9 15 -11 -4 -28 -30 -28 -75 -28 -102 0 -30 32 -33 58 -5 49 12 -4 38 -8
|
||||
58 -9z m508 -1 c2 -2 -8 -4 -24 -4 -21 0 -25 -3 -17 -13 9 -10 -1 -12 -54 -6
|
||||
-79 8 -95 19 -28 19 35 0 45 3 34 10 -10 7 1 8 35 4 28 -4 52 -8 54 -10z
|
||||
m1056 -23 c0 -23 -23 -24 -35 -1 -10 19 -9 21 12 18 13 -2 23 -9 23 -17z
|
||||
m-1945 -32 c-12 -41 -24 -53 -14 -14 7 32 13 45 19 45 2 0 0 -14 -5 -31z m566
|
||||
13 c28 -19 24 -48 -14 -111 -1 -2 -13 2 -27 9 -20 11 -27 11 -40 0 -21 -18
|
||||
-31 -5 -11 15 9 9 16 24 16 33 0 42 46 75 76 54z m322 -39 c12 -3 22 -11 22
|
||||
-18 0 -17 -53 -72 -57 -59 -2 6 -17 9 -33 6 -24 -3 -32 1 -41 22 -9 19 -20 26
|
||||
-43 28 -17 0 -37 2 -45 2 -22 2 -25 24 -4 30 20 5 165 -2 201 -11z m-818 -13
|
||||
c0 -5 -2 -10 -4 -10 -3 0 -8 5 -11 10 -3 6 -1 10 4 10 6 0 11 -4 11 -10z
|
||||
m1832 -14 c7 4 8 2 4 -5 -4 -6 -12 -9 -17 -6 -5 4 -9 -4 -9 -16 0 -12 -3 -20
|
||||
-6 -16 -4 3 -3 19 1 34 4 15 10 22 12 15 3 -6 10 -9 15 -6z m-1615 -30 c-8
|
||||
-19 -37 -21 -37 -3 0 7 5 18 12 25 15 15 33 -1 25 -22z m76 -77 c-5 -19 -33
|
||||
-27 -33 -9 0 6 5 10 10 10 6 0 10 10 10 23 0 16 3 18 9 8 5 -7 6 -22 4 -32z
|
||||
m102 -39 c-3 -5 -14 -10 -23 -10 -15 0 -15 2 -2 10 20 13 33 13 25 0z m312
|
||||
-85 c0 -8 -4 -12 -9 -9 -5 3 -6 10 -3 15 9 13 12 11 12 -6z m503 -166 c0 -6
|
||||
-4 -7 -10 -4 -5 3 -10 11 -10 16 0 6 5 7 10 4 6 -3 10 -11 10 -16z m-433 -61
|
||||
c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13 3 -3 4 -12 1 -19z m3 -28 c0 -5 -2 -10 -4
|
||||
-10 -3 0 -8 5 -11 10 -3 6 -1 10 4 10 6 0 11 -4 11 -10z m-42 -18 c2 -7 -2
|
||||
-10 -12 -6 -9 3 -16 11 -16 16 0 13 23 5 28 -10z m139 -14 c-3 -8 -6 -5 -6 6
|
||||
-1 11 2 17 5 13 3 -3 4 -12 1 -19z m-47 -91 c0 -5 -4 -5 -10 -2 -5 3 -10 14
|
||||
-10 23 0 15 2 15 10 2 5 -8 10 -19 10 -23z m281 6 c13 -16 12 -17 -3 -4 -10 7
|
||||
-18 15 -18 17 0 8 8 3 21 -13z m196 -15 c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13 3
|
||||
-3 4 -12 1 -19z m50 -14 c-3 -3 -12 -4 -19 -1 -8 3 -5 6 6 6 11 1 17 -2 13 -5z
|
||||
m-122 -24 c3 -5 1 -10 -4 -10 -6 0 -11 5 -11 10 0 6 2 10 4 10 3 0 8 -4 11
|
||||
-10z m-245 -36 c0 -8 -4 -12 -10 -9 -5 3 -10 13 -10 21 0 8 5 12 10 9 6 -3 10
|
||||
-13 10 -21z"/>
|
||||
<path d="M2911 3704 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z"/>
|
||||
<path d="M3630 3139 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
|
||||
-5 -10 -11z"/>
|
||||
<path d="M3580 3129 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
|
||||
-5 -10 -11z"/>
|
||||
<path d="M4023 2723 c-1 -33 1 -35 9 -17 6 14 7 28 1 38 -7 12 -9 7 -10 -21z"/>
|
||||
<path d="M4280 2693 c0 -5 5 -15 10 -23 8 -12 10 -11 10 8 0 12 -4 22 -10 22
|
||||
-5 0 -10 -3 -10 -7z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 13 KiB |
|
|
@ -1 +1,14 @@
|
|||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
||||
{
|
||||
"name": "",
|
||||
"short_name": "",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/assets/images/android-chrome-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#091c58",
|
||||
"background_color": "#091c58",
|
||||
"display": "standalone"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
<!--
|
||||
Rewrites requires Microsoft URL Rewrite Module for IIS
|
||||
Download: https://www.microsoft.com/en-us/download/details.aspx?id=47337
|
||||
Debug Help: https://docs.microsoft.com/en-us/iis/extensions/url-rewrite-module/using-failed-request-tracing-to-trace-rewrite-rules
|
||||
-->
|
||||
<configuration>
|
||||
<system.webServer>
|
||||
<rewrite>
|
||||
<rules>
|
||||
<rule name="Imported Rule 1" stopProcessing="true">
|
||||
<match url="^(.*)/$" ignoreCase="false" />
|
||||
<conditions>
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
|
||||
</conditions>
|
||||
<action type="Redirect" redirectType="Permanent" url="/{R:1}" />
|
||||
</rule>
|
||||
<rule name="Imported Rule 2" stopProcessing="true">
|
||||
<match url="^" ignoreCase="false" />
|
||||
<conditions>
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
|
||||
</conditions>
|
||||
<action type="Rewrite" url="index.php" />
|
||||
</rule>
|
||||
</rules>
|
||||
</rewrite>
|
||||
</system.webServer>
|
||||
</configuration>
|
||||
BIN
resources/assets/images/android-chrome-144x144.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
resources/assets/images/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
resources/assets/images/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 458 B |
BIN
resources/assets/images/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 896 B |
BIN
resources/assets/images/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
resources/assets/images/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
39
resources/assets/images/safari-pinned-tab.svg
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<?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="180.000000pt" height="180.000000pt" viewBox="0 0 180.000000 180.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.14, written by Peter Selinger 2001-2017
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,180.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M1585 1356 l30 -25 -40 -41 c-55 -58 -135 -91 -135 -56 0 8 9 33 20
|
||||
57 10 24 15 48 11 52 -5 5 -300 7 -657 5 -594 -4 -656 -6 -731 -23 l-83 -19 0
|
||||
-123 c0 -68 4 -123 9 -123 23 0 33 -64 29 -195 -2 -112 -6 -139 -19 -149 -14
|
||||
-9 -17 -34 -18 -135 l-2 -124 173 8 c95 4 247 11 338 15 161 8 875 4 935 -5
|
||||
17 -2 103 -7 193 -11 l162 -7 0 143 c0 79 -3 140 -7 137 -3 -4 -12 -2 -20 4
|
||||
-17 15 -19 314 -2 325 6 3 9 15 6 25 -3 11 1 26 9 33 10 10 14 39 14 94 l0 79
|
||||
-52 16 c-29 9 -69 19 -88 23 -19 3 -46 15 -59 25 -13 11 -29 19 -35 19 -6 0 3
|
||||
-11 19 -24z m-1306 -105 c9 -6 11 -20 6 -50 -4 -22 -7 -149 -7 -281 -1 -254 2
|
||||
-270 46 -270 23 0 55 29 75 67 6 12 11 68 11 125 0 178 36 276 122 338 71 52
|
||||
141 35 209 -50 71 -88 79 -350 16 -464 -50 -90 -147 -129 -215 -87 -50 32 -59
|
||||
62 -33 106 13 22 16 35 9 33 -7 -2 -27 -32 -44 -67 -17 -35 -39 -70 -49 -77
|
||||
-14 -10 -57 -14 -163 -14 l-145 0 7 48 c11 77 9 564 -2 594 -12 30 -3 43 38
|
||||
51 44 8 105 7 119 -2z m717 -52 c14 -12 16 -42 13 -252 l-4 -239 28 -24 c33
|
||||
-28 37 -29 63 -10 18 14 19 29 19 273 l0 258 51 3 c39 2 54 -1 68 -17 16 -17
|
||||
16 -28 7 -115 -14 -125 -14 -328 0 -360 6 -13 27 -32 45 -42 19 -10 34 -24 34
|
||||
-30 0 -6 -21 -28 -47 -48 -55 -43 -76 -42 -137 6 -34 27 -40 29 -48 14 -13
|
||||
-23 -62 -46 -99 -46 -22 0 -41 10 -65 34 l-34 34 0 218 c-1 144 -5 240 -14
|
||||
282 -12 56 -12 63 3 72 21 14 96 6 117 -11z m599 -33 c36 -28 105 -153 105
|
||||
-191 0 -39 -37 -72 -117 -105 -87 -36 -123 -63 -123 -94 0 -27 27 -68 63 -96
|
||||
l28 -22 56 42 c65 50 87 55 91 22 8 -57 -62 -140 -136 -163 -65 -19 -105 -5
|
||||
-176 61 -35 33 -69 60 -75 60 -8 0 -7 6 3 16 9 8 16 23 16 32 8 332 9 343 62
|
||||
406 58 71 136 83 203 32z"/>
|
||||
<path d="M573 1093 c-14 -22 -19 -58 -24 -192 -3 -91 -2 -176 2 -188 13 -35
|
||||
53 -57 79 -43 34 18 40 52 40 214 0 159 -9 209 -40 226 -28 15 -38 12 -57 -17z"/>
|
||||
<path d="M1480 1087 c-38 -62 -36 -138 5 -153 21 -8 61 20 80 56 14 26 14 33
|
||||
0 68 -9 22 -27 44 -41 51 -22 10 -26 8 -44 -22z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 235 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 886 B |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 8 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
|
@ -1 +0,0 @@
|
|||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
||||
1
resources/js/vendor/ckeditor/.gitignore
vendored
|
|
@ -1 +0,0 @@
|
|||
node_modules
|
||||
49
resources/js/vendor/ckeditor/LICENSE.md
vendored
|
|
@ -1,49 +0,0 @@
|
|||
Software License Agreement
|
||||
==========================
|
||||
|
||||
Copyright (c) 2014-2021, CKSource - Frederico Knabben. All rights reserved.
|
||||
|
||||
Online builder code samples are licensed under the terms of the MIT License (see Appendix A):
|
||||
|
||||
http://en.wikipedia.org/wiki/MIT_License
|
||||
|
||||
CKEditor 5 collaboration features are only available under a commercial license. [Contact us](https://ckeditor.com/contact/) for more details.
|
||||
|
||||
Free 30-days trials of CKEditor 5 collaboration features are available:
|
||||
* https://ckeditor.com/collaboration/ - Real-time collaboration (with all features).
|
||||
* https://ckeditor.com/collaboration/comments/ - Inline comments feature (without real-time collaborative editing).
|
||||
* https://ckeditor.com/collaboration/track-changes/ - Track changes feature (without real-time collaborative editing).
|
||||
|
||||
Trademarks
|
||||
----------
|
||||
|
||||
CKEditor is a trademark of CKSource - Frederico Knabben. All other brand
|
||||
and product names are trademarks, registered trademarks or service
|
||||
marks of their respective holders.
|
||||
|
||||
---
|
||||
|
||||
Appendix A: The MIT License
|
||||
---------------------------
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014-2021, CKSource - Frederico Knabben
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
68
resources/js/vendor/ckeditor/README.md
vendored
|
|
@ -1,68 +0,0 @@
|
|||
# CKEditor 5 editor generated with the online builder
|
||||
|
||||
This repository presents a CKEditor 5 editor build generated by the [Online builder tool](https://ckeditor.com/ckeditor-5/online-builder)
|
||||
|
||||
## Quick start
|
||||
|
||||
1. Open the `sample/index.html` page in the browser.
|
||||
|
||||
If you picked the real-time collaboration plugins:
|
||||
|
||||
2. Fill the dialog with correct token, websocket and upload URL endpoints. If you do not have these yet or do not know their meaning, [contact us](https://ckeditor.com/contact/).
|
||||
|
||||
3. Copy the URL and share it or paste in another tab to enjoy real-time collaborative editing.
|
||||
|
||||
If you picked the non-real-time collaboration plugins:
|
||||
|
||||
2. Fill the prompt with the license key. If you do not have the license key yet [contact us](https://ckeditor.com/contact/).
|
||||
|
||||
## Configuring build
|
||||
|
||||
Changes like changing toolbar items, changing order of icons or customizing plugin configurations should be relatively easy to make. Open the `sample/index.html` file and edit the script that initialized the CKEditor 5. Save the file and refresh the browser. That's all.
|
||||
|
||||
*Note:* If you have any problems with browser caching use the `Ctrl + R` or `Cmd + R` shortcut depending on your system.
|
||||
|
||||
However if you want to remove or add a plugin to the build you need to follow the next step of this guide.
|
||||
|
||||
Note that it is also possible to go back to the [Online builder tool](https://ckeditor.com/ckeditor-5/online-builder) and pick other set of plugins. But we encourage you to try the harder way and to learn the principles of Node.js and CKEditor 5 ecosystems that will allow you to do more cool things in the future!
|
||||
|
||||
### Installation
|
||||
|
||||
In order to rebuild the application you need to install all dependencies first. To do it, open the terminal in the project directory and type:
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
Make sure that you have the `node` and `npm` installed first. If not, then follow the instructions on the [Node.js documentation page](https://nodejs.org/en/).
|
||||
|
||||
### Adding or removing plugins
|
||||
|
||||
Now you can install additional plugin in the build. Just follow the [Adding a plugin to an editor tutorial](https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/installing-plugins.html#adding-a-plugin-to-an-editor)
|
||||
|
||||
### Rebuilding editor
|
||||
|
||||
If you have already done the [Installation](#installation) and [Adding or removing plugins](#adding-or-removing-plugins) steps, you're ready to rebuild the editor by running the following command:
|
||||
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
This will build the CKEditor 5 to the `build` directory. You can open your browser and you should be able to see the changes you've made in the code. If not, then try to refresh also the browser cache by typing `Ctrl + R` or `Cmd + R` depending on your system.
|
||||
|
||||
## What's next?
|
||||
|
||||
Follow the guides available on https://ckeditor.com/docs/ckeditor5/latest/framework/index.html and enjoy the document editing.
|
||||
|
||||
## FAQ
|
||||
| Where is the place to report bugs and feature requests?
|
||||
|
||||
You can create an issue on https://github.com/ckeditor/ckeditor5/issues including the build id - `unyb7c61pdjq-xt0bz4ltrghq`. Make sure that the question / problem is unique, please look for a possibly asked questions in the search box. Duplicates will be closed.
|
||||
|
||||
| Where can I learn more about the CKEditor 5 framework?
|
||||
|
||||
Here: https://ckeditor.com/docs/ckeditor5/latest/framework/
|
||||
|
||||
| Is it possible to use online builder with common frameworks like React, Vue or Angular?
|
||||
|
||||
Not yet, but it these integrations will be available at some point in the future.
|
||||