# fluent-boards/trunk/app/Hooks/Handlers/FileHandler.php

FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration, version trunk. 138 lines.

- Page: https://pluginprobe.com/plugins/fluent-boards/trunk/code/app/Hooks/Handlers/FileHandler.php
- Raw: https://pluginprobe.com/plugins/fluent-boards/trunk/raw/app/Hooks/Handlers/FileHandler.php
- Modified: 2026-09-09T13:50:06+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/fluent-boards/trunk/code/app/Hooks/Handlers/FileHandler.php#L10-L20`.

```php
<?php

namespace FluentBoards\App\Hooks\Handlers;

use DateTimeImmutable;
use Exception;
use FluentBoards\App\Services\Libs\FileSystem;
use FluentBoards\App\Models\Attachment;
use function Sodium\add;

class FileHandler
{
//    private function validateFile($file)
//    {
//        if (!$file) {
//            throw new Exception('File is empty.');
//        }
//        if (!$this->isFileTypeSupported($file)) {
//            throw new Exception('File type not supported');
//        }
//        if ($file['size'] > $this->getFileUploadLimit()) {
//            throw new Exception('File size is too large');
//        }
//    }

    private function getFileUploadLimit() {
        // Logic for calculating file upload limit as in your original code
        return min(
            wp_convert_hr_to_bytes(ini_get('upload_max_filesize')),
            wp_convert_hr_to_bytes(ini_get('post_max_size')),
            wp_max_upload_size()
        );
    }

    /**
     * Delete a local attachment only when its stored path belongs to Fluent Boards.
     *
     * @param mixed $attachment
     * @param int|null $boardId
     * @return bool
     */
    public function deleteAttachmentFile($attachment, $boardId = null)
    {
        if (
            !$attachment ||
            $attachment->attachment_type === 'url' ||
            (!empty($attachment->driver) && $attachment->driver !== 'local') ||
            empty($attachment->file_path)
        ) {
            return false;
        }

        $storedFilename = rawurldecode((string) $attachment->file_path);
        $isBareFilename = $storedFilename !== ''
            && strpos($storedFilename, '/') === false
            && strpos($storedFilename, '\\') === false;
        $filePath = FileSystem::resolveLocalAttachmentPath($attachment->file_path, $boardId);

        if (!$filePath) {
            return false;
        }

        // Legacy filenames need the board-qualified URL to distinguish same-named files across boards.
        if ($isBareFilename) {
            if (
                empty($attachment->full_url) ||
                Attachment::where('full_url', $attachment->full_url)->exists()
            ) {
                return false;
            }
        } elseif (Attachment::where('file_path', $attachment->file_path)->exists()) {
            return false;
        }

        return (bool) wp_delete_file($filePath);
    }


    /**
     * Summary of isFileTypeSupported checking file type it will allow only file which is readable by browser
     * @param mixed $file
     * TODO: Refactorable: This can be in a Helper class. and we may pass it to frontend via wp_localize_script appvars
     * so that we can check similarly for better experience.
     * @return bool
     */
    public function isFileTypeSupported($file)
    {
        // Define supported file types that are generally allowed by user
        $allowedMimeTypes = get_allowed_mime_types();

        // Check if the file type is supported
        return in_array(strtolower($file['type']), $allowedMimeTypes);
    }
    
    /**
     * @throws Exception
     */
    public function handleMediaFileUpload($data)
    {
        // Check if file was uploaded
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification handled by REST API/controller layer
        if (!isset($_FILES['file']['tmp_name']) || !isset($_FILES['file']['name'])) {
            throw new Exception(esc_html__('No file was uploaded. Please try again.', 'fluent-boards'));
        }

        // Sanitize filename from request for validation
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification handled by REST API/controller layer
        $filename = sanitize_file_name(wp_unslash($_FILES['file']['name']));

        // Validate and sanitize tmp_name before use
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification handled by REST API/controller layer
        $tmp_name = sanitize_text_field(wp_unslash($_FILES['file']['tmp_name']));

        if (empty($tmp_name) || !file_exists($tmp_name) || !is_uploaded_file($tmp_name)) {
            throw new Exception(esc_html__('Invalid upload. Please try again.', 'fluent-boards'));
        }

        // Check if the uploaded file is an image
        $wp_filetype = wp_check_filetype_and_ext($tmp_name, $filename);

        if ( ! wp_match_mime_types( 'image', $wp_filetype['type'] ) ) {
            throw new Exception(esc_html__('The uploaded file is not a valid image. Please try again.', 'fluent-boards'));
        }
        require_once(ABSPATH . 'wp-admin/includes/image.php');
        require_once( ABSPATH . 'wp-admin/includes/file.php' );
        require_once( ABSPATH . 'wp-admin/includes/media.php' );
        $attachment_id = media_handle_upload( 'file', 0, [] );

        $attachment = wp_prepare_attachment_for_js( $attachment_id);
        if(!$attachment) {
            throw new Exception(esc_html__('The uploaded file is not a valid image. Please try again.', 'fluent-boards'));
        } else {
            return $attachment;
        }
    }

}

```
