getClientOriginalName(), $folderName); $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::class === SplFileInfo::class) { /** @var SplFileInfo */ $image = $image; $newPath = self::prepareMoveFile($image->getBaseName(), $folderName); if (!File::copy($image->getRealPath(), $newPath)) { throw new \Exception(sprintf( 'Failed to copy file %s to %s', $image->getRealPath(), $newPath )); } return str_replace(\storage_path('app/public'), '/storage', $newPath); } if (\is_string($image)) { return $image; } throw new Exception(sprintf('Cannot handle image %s', \gettype($image))); } /** * Destroy the file associate with the attribute. * * @param string $path * @return void */ public static function removeFile(string $path): void { $path = \ltrim(\urldecode($path), '/'); if (!str_starts_with($path, 'storage')) { return; } $path = ToolboxHelper::mbReplace('storage/', '', $path); if (Storage::disk('public')->exists($path)) { Storage::disk('public')->delete($path); } } /** * Store an image for a model * if a string is passed then the store action is ignored and the strieng is returned * * @param string $filename * @param string $folderName * @return string The storage path */ private static function prepareMoveFile(string $filename, string $folderName): string { $pathFolder = \storage_path("app/public/images/$folderName"); 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, mixed $value, ?int $id = null, ?callable $query = null ): void { 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(); } /** * Mutltibyte string replace * @param mixed $search * @param mixed $replace * @param mixed $subject * @param integer $count * @return mixed */ public static function mbReplace(mixed $search, mixed $replace, mixed $subject, int &$count = 0): mixed { 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; } /** * 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('/(?<=)/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 => trans( 'crud.libs.toolbox.assertNoInternalBoUrls', ['attribute' => $field] ) ]); } return $match; } catch (NotFoundHttpException) { return $match; } }, $text ); return $text; } /** * Validate recaptcha. * * @param \Illuminate\Http\Request $request * @param boolean $isV3 * @return void */ public static function validateRecaptcha(Request $request, bool $isV3 = true): void { 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): void { try { $response = Http::asForm()->post('https://www.google.com/recaptcha/api/siteverify', [ 'secret' => config('services.google.recaptcha.private'), 'response' => \strval($value), 'remoteip' => resolve('clientIp') ]); throw_if($e = $response->toException(), $e); if ($isV3) { throw_if(!$response->json('success') or !$response->json('challenge_ts') or \Illuminate\Support\Facades\Date::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'), \RuntimeException::class); if (\floatval($response->json('score')) < 0.8) { $fail(trans('recaptcha.not_enough_score')); } return; } throw_if(!$response->json('success') or !$response->json('challenge_ts') or \Illuminate\Support\Facades\Date::parse($response->json('challenge_ts'))->lessThan(\now('utc')->subMinutes(5)) or $response->json('hostname') !== \parse_url(config('app.url'), \PHP_URL_HOST), \RuntimeException::class); } catch (\Exception) { $fail(trans('recaptcha.error')); } //end try } ] ], [], ['g-recaptcha-response' => 'reCaptcha']); } }