CKFinder 3

CKFinder 3 là trình quản lý tập tin được tích hợp với CKEditor 4 và CKEditor 5. Nó giúp bạn dễ dàng đưa các tập tin và hình ảnh vào nội dung của Editor một cách an toàn.

Đây là một tính năng có phí và bạn cần có một giấy phép để sử dụng nó, ngoài giấy phép thương mại của CKEditor của bạn.

Private Folders Per User

Để tạo các thư mục riêng biệt cho người dùng, chúng ta cần tạo một cơ chế đơn giản để ánh xạ người dùng hiện tại vào một đường dẫn thư mục phù hợp.

Khi xây dựng đường dẫn thư mục, chúng ta cần phải lưu ý những điều sau đây để có thể tránh các cuộc tấn công từ Path Traversal:

● Không tiết lộ bất kỳ thông tin nhạy cảm nào.

● Không sử dụng bất kỳ dữ liệu không an toàn nào.

Thực hiện Private Folders Per User cho CKFinder

Trước khi, bắt đầu thực hiện tính năng này, bạn cần phải tích hợp CKFinder vào CKEditor trong Laravel (Phiên bản được áp dụng trong bài viết là Laravel 11).

Nếu chưa thực hiện tích hợp CKFinder vào CKEditor trong Laravel thì tham khảo lại bài viết sau:

● Integrating CKFinder Into CKEditor 5 In Laravel

Xây dựng Authentication trong Laravel

Trước khi, chúng ta thực hiện tính năng tạo thư mục riêng biệt cho người dùng, chúng ta cần phải chuẩn bị chức năng Authentication bằng Laravel.

Đầu tiên, chúng ta sẽ tạo Controller để xây dựng xử lý chức năng Authentication bằng lệnh sau:

php artisan make:controller AuthController

Sau đó, chúng ta tiếp tục chỉnh sửa nội dung của tập tin AuthController.php trong thư mục app/Http/Controllers như sau:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;

class AuthController extends Controller
{
    /**
     * Handle an authentication attempt.
     */
    public function authenticate(Request $request)
    {
        if ($request->isMethod('get')) {
            return view('signin');
        }

        $credentials = $request->validate([
            'email'    => ['required', 'email'],
            'password' => ['required'],
        ]);

        if (Auth::attempt($credentials)) {
            $request->session()->regenerate();
            return redirect()->intended('welcome');
        }

        return back()->withErrors([
            'email' => 'The provided credentials do not match our records.',
        ])->onlyInput('email');
    }

    /**
     * Log the user out of the application.
     */
    public function logout(Request $request): RedirectResponse
    {
        Auth::logout();

        $request->session()->invalidate();

        $request->session()->regenerateToken();

        return redirect('/');
    }
}

Sau khi hoàn thành các phương thức xử lý yêu cầu liên quan đến chức năng Authentication.

Chúng ta cần tạo các router để sử dụng các phương thức này. Để làm điều này, hãy chỉnh sửa tập tin routes/web.php như sau:

<?php
...
use App\Http\Controllers\AuthController;
...
Route::match(['get', 'post'], '/', [AuthController::class, 'authenticate'])->name('login');
Route::middleware(['web', 'auth'])->group(function () {
    Route::get('logout', [AuthController::class, 'logout'])->name('logout');
    Route::view('welcome', 'welcome')->name('welcome');
});
...

Cuối cùng, chúng ta sẽ tạo giao diện đăng nhập bằng cách tạo tập tin signin.blade.php nằm trong thư mục resources/views với nội dung như sau:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Private Folders Per User For ElFinder In Laravel</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
    <meta name="description" content="ManhDan Blogs">
    <meta name="author" content="ManhDan Blogs">
    <meta name="generator" content="ManhDan Blogs 0.84.0">
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
</head>
<body>
    <section class="h-100">
        <div class="container h-100">
            <div class="row justify-content-sm-center h-100">
                <div class="col-xxl-4 col-xl-5 col-lg-5 col-md-7 col-sm-9">
                    <div class="text-center my-3">
                        <img src="https://manhdandev.com/web/img/logo.webp" alt="logo" width="120">
                    </div>
                    <div class="card shadow-lg">
                        <div class="card-body p-5">
                            <h1 class="fs-4 card-title fw-bold mb-4">Login</h1>
                            <form action="{{ route('login') }}" method="post" class="needs-validation @if ($errors->any()) was-validated @endif" novalidate="" autocomplete="off">
                                @csrf
                                <div class="mb-3">
                                    <label class="mb-2 text-muted" for="email">E-Mail Address</label>
                                    <input id="email" type="email" class="form-control" name="email" value="" required autofocus>
                                    @error('email')
                                        <div class="invalid-feedback">
                                            {{ $message }}
                                        </div>
                                    @enderror
                                </div>
                                <div class="mb-3">
                                    <div class="mb-2 w-100">
                                        <label class="text-muted" for="password">Password</label>
                                    </div>
                                    <input id="password" type="password" class="form-control" name="password" required>
                                </div>
                                @error('password')
                                    <div class="invalid-feedback">
                                        {{ $message }}
                                    </div>
                                @enderror
                                <div class="d-flex align-items-center">
                                    <button type="submit" class="btn btn-primary ms-auto">
                                        Login
                                    </button>
                                </div>
                            </form>
                        </div>
                    </div>
                    <div class="text-center mt-5 text-muted">
                        Copyright &copy; 2021-2024 &mdash; ManhDanBlogs
                    </div>
                </div>
            </div>
        </div>
    </section>
</body>
</html>

Cài đặt Private Folders Per User

Để bắt đầu thực hiện tính năng tạo thư mục riêng cho từng người dùng, trước tiên chúng ta cần tạo một tập tin có tên là CKFinder.php trong thư mục app/Helpers với nội dung như sau:

<?php

namespace App\Helpers;

/**
 * Class CKFinder
 *
 * Helper class for managing CKFinder configurations and generating URLs.
 */
class CKFinder
{
    /**
     * Hashed user directory.
     *
     * @var string
     */
    protected string $hashedDirectory;

    /**
     * CKFinder constructor.
     *
     * @param string $userDirectory The user directory for CKFinder.
     */
    public function __construct(string $userDirectory)
    {
        $this->hashedDirectory = sha1($userDirectory);
    }

    /**
     * Configure the CKFinder backend using the hashed directory.
     */
    protected function configureBackend()
    {
        return [
            'name'               => 'default',
            'adapter'            => 'local',
            'baseUrl'            => $this->generateBaseUrl(),
            'root'               => $this->generateRootPath(),
            'filesystemEncoding' => 'UTF-8',
        ];
    }

    /**
     * Generate the base URL for CKFinder based on the hashed user directory.
     *
     * @return string The base URL.
     */
    protected function generateBaseUrl(): string
    {
        return config('app.url') . "/storage/{$this->hashedDirectory}";
    }

    /**
     * Generate the root storage path for CKFinder based on the hashed user directory.
     *
     * @return string The root storage path.
     */
    protected function generateRootPath(): string
    {
        return storage_path("app/public/{$this->hashedDirectory}");
    }

    /**
     * Configure the private directory for CKFinder.
     */
    protected function configurePrivateDir()
    {
        $basePrivatePath = "ckfinder/{$this->hashedDirectory}";
        return [
            'backend' => 'laravel_cache',
            'tags'    => "{$basePrivatePath}/tags",
            'cache'   => "{$basePrivatePath}/cache",
            'thumbs'  => "{$basePrivatePath}/cache/thumbs",
            'logs'    => [
                'backend' => 'laravel_logs',
                'path'    => "{$basePrivatePath}/logs",
            ],
        ];
    }

    /**
     * Get the complete CKFinder configuration.
     *
     * @return array The complete CKFinder configuration.
     */
    public function getConfiguration(): array
    {
        return [
            'backends'   => $this->configureBackend(),
            'privateDir' => $this->configurePrivateDir(),
        ];
    }
}

Sau khi thực hiện lệnh trên xong, chúng ta sẽ chỉnh sửa tập tin CKFinderMiddleware.php trong thư mục app/Http/Middleware với nội dung như sau:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Facades\Auth;
use App\Helpers\CKFinder as CKFinderHelper;

class CKFinderMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        # Set the authentication configuration for CKFinder
        config(['ckfinder.authentication' => function() {
            return Auth::check();
        }]);
        # Configure the private folders for the user
        if (Auth::check()) {
            $this->configureUserPrivateFolders(Auth::user()->id);
        }
        return $next($request);
    }

    /**
     * Configure CKFinder private folders per user.
     *
     * @param string $userDirectory
     * @return void
     */
    protected function configureUserPrivateFolders(string $userDirectory): void
    {
        $ckfinder = new CKFinderHelper($userDirectory);
        $config   = $ckfinder->getConfiguration();
        config([
            'ckfinder.privateDir'       => $config['privateDir'],
            'ckfinder.backends.default' => $config['backends'],
        ]);
    }
}

Chúng ta sẽ thay đổi tùy chọn loadRoutes trong tập tin config/ckfinder.php như sau:

$config['loadRoutes'] = false;

Sao chép các route từ vendor/ckfinder/ckfinder-laravel-package/src/routes.php vào routes/web.php để bảo vệ chúng bằng Middleware xác thực của Laravel:

<?php
...
use App\Http\Controllers\AuthController;
...
Route::match(['get', 'post'], '/', [AuthController::class, 'authenticate'])->name('login');
Route::middleware(['web', 'auth'])->group(function () {
    Route::get('logout', [AuthController::class, 'logout'])->name('logout');
    Route::view('welcome', 'welcome')->name('welcome');
    Route::any('/ckfinder/connector', '\CKSource\CKFinderBridge\Controller\CKFinderController@requestAction')->name('ckfinder_connector');
    Route::any('/ckfinder/browser', '\CKSource\CKFinderBridge\Controller\CKFinderController@browserAction')->name('ckfinder_browser');
});
...

Kết quả của công việc bạn đã làm đang chờ bạn khám phá!

Sau khi hoàn thành các bước ở phía trên, giờ là lúc để chúng ta khám phá thành quả của công sức mình.

Để kiểm tra tính năng tạo thư mục riêng cho từng người dùng trong CKFinder, chúng ta cần tạo 2 người dùng mới.

Trong trường hợp này, chúng ta sẽ dụng Laravel Tinker để thực hiện thao tác nhanh hơn:

php artisan tinker

Trong trường hợp này, chúng ta sẽ sử dụng Laravel Tinker để thực hiện thao tác nhanh chóng:

\App\Models\User::factory(2)->create();

❄ Giả sử, sau khi chúng ta thực hiện lệnh trên chúng ta sẽ có 2 người dùng mới:

● [email protected] 

● [email protected]

Đầu tiên, chúng ta hãy mở trình duyệt lên và truy cập vào địa chỉ  http://127.0.0.1 

Tiếp theo, chúng ta sẽ đăng nhập bằng người dùng [email protected] và thực hiện upload hình ảnh bằng CKFinder:

Cuối cùng, chúng ta sẽ đăng nhập bằng người dùng [email protected] và thực hiện upload hình ảnh bằng CKFinder:

CÓ THỂ BẠN QUAN TÂM

Generate PDF with Header and Footer on every page in Laravel

Generate PDF with Header and Footer on every page in Laravel

Hôm nay, trong bài viết này mình sẽ chia sẻ với các bạn cách dompdf mà header và footer sẽ được hiển thị ở tất cả các trang. Đây cũng là một vấn đề khá phổ biến, khi chúng ta phát triển các tính năng...

Laravel Socialite Login With Facebook

Laravel Socialite Login With Facebook

Ngoài xác thực dựa trên biểu mẫu điển hình, Laravel cũng cung cấp một cách đơn giản và thuận tiện để sử dụng Laravel Socialite để xác thực với các nhà cung cấp OAuth. Socialite hiện hỗ trợ xác thực qu...

How To Optimize Your Site With GZIP Compression

How To Optimize Your Site With GZIP Compression

GZIP là công nghệ nén thường được sử dụng để truyền dữ liệu một cách nhanh chóng qua Insternet. Hiện nay, GZIP là một tiêu chuẩn để nén các file trên trang web, đa số các website hiện nay hơn 99% sử d...

Laravel Validate Video Duration

Laravel Validate Video Duration

Đôi khi trong dự án, chúng ta cần xác định thời lượng video được phép upload lên server. Nhưng rất tiếc, Laravel không cung cấp validate xác định thời lượng video để chúng ta thực hiện được. Vì vậy, t...

Export CSV from SQL Server - Import into MySQL with Laravel

Export CSV from SQL Server - Import into MySQL with Laravel

Transfer Database Trong quá trình phát triển và bảo trì dự án, việc di chuyển cơ sở dữ liệu từ hệ thống này sang hệ thống khác là một nhiệm vụ khá phổ biến. Giả sử bạn cần di chuyển dữ liệu từ SQ...

Pipeline Design Pattern in Laravel

Pipeline Design Pattern in Laravel

Pipeline Design Pattern là nơi mà các dữ liệu được chuyển qua một chuỗi các nhiệm vụ hoặc giai đoạn. Pipeline hoạt động giống như một chuỗi dây chuyền lắp ráp, nơi dữ liệu được xử lý và sau đó, sẽ...

Laravel Routing

Laravel Routing

Route là gì? Đúng như tên gọi của nó, Route thực hiện chức năng định tuyến, dẫn đường cho các HTTP request gửi đến đúng nơi mà ta mong muốn. Với sự phát triển mạnh mẽ của ứng dụng web ngày nay, việc...

Laravel Custom Eloquent Casts

Laravel Custom Eloquent Casts

Trước đây, chúng ta bị giới hạn cast mặc định do Laravel cung cấp. Mặc dù, có một số gói thư viện có thể  giúp chúng ta custom được nhưng chúng có một nhược điểm lớn. Bởi vì, chúng ghi đề phương thức...

Laravel Upload File Using Trait

Laravel Upload File Using Trait

Hiện nay, đa số các dự án đều có chức năng upload file, nên tôi đã thử xây dựng một lớp Trait Upload File, để chúng ta dễ dàng sao chép qua các dự án khác để sử dụng, nhằm rút ngắn thời gian phát triể...

ManhDanBlogs