)/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(?: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(?: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; } /** * Generate url using laravel route passing our group query if present * This is used to keep ou group tracking * Curd-policies is only used for root users as it cannot keep track of our group * and validate things * * @param array|string $name * @param mixed $parameters * @param boolean $absolute * @return string * @throws \ErrorException If an object given as parameter is not a Model. */ public static function routeWithGroup($name, $parameters = [], bool $absolute = true): string { // * Handle if parameter if is an object. if (\is_object($parameters)) { $parents = \class_parents($parameters); if ($parents === false or !\in_array(Model::class, $parents)) { throw new \ErrorException(\sprintf( '%s only support object of type %s', __FUNCTION__, Model::class )); } /** @var \Illuminate\Database\Eloquent\Model $model */ $model = $parameters; $parameters = [Str::singular($model->getTable()) => $model]; } // * Handle if parameter isnt and array like slug or else. if (!is_array($parameters)) { // * Best effort, result is not garantee. $parameters = [$parameters]; } // * Pass our group. if ($group = Session::get('activeGroup', request()->group)) { $parameters = array_merge($parameters, ['group' => $group]); } // * Call laravel route helper. return route($name, $parameters, $absolute); } /** * 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], [ $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(); } /** * Check that two arrays have the same content (1 level only) * * @param array $array1 * @param array $array2 * @return boolean */ public static function arrayHaveSameContent(array $array1, array $array2): bool { return (array_diff($array1, $array2) == [] && array_diff($array2, $array1) == []); } /** * * 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'); } /** * Validate recaptcha * * @param Request $request * @param boolean $isV3 * @return void */ public static function validateRecaptcha(Request $request, bool $isV3 = true) { Validator::validate($request->all(), [ 'g-recaptcha-response' => [ 'required', 'string', // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed, Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed function ($attribute, $value, $fail) use ($isV3) { try { $response = Http::asForm()->post('https://www.google.com/recaptcha/api/siteverify', [ 'secret' => config('services.google.recaptcha.private'), 'response' => \strval($value), 'remoteip' => app('clientIp') ]); if ($e = $response->toException()) { throw $e; } if ($isV3) { 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 \RuntimeException(); } if (\floatval($response->json('score')) < 0.8) { $fail("Vous n'avez pas obtenu un score assez élevé lors de la validation du recaptcha"); } return; } 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) ) { throw new \RuntimeException(); } } catch (\Exception $e) { $fail('Erreur lors de la validation du formulaire veuillez réssayer'); } //end try } ] ], [], ['g-recaptcha-response' => 'reCaptcha']); } }