mdb = $mdb; else { global $mdb; $this -> mdb = $mdb; } $this -> upload_dir = $upload_dir ? rtrim( $upload_dir, '/\\' ) : getcwd() . DIRECTORY_SEPARATOR . 'upload' . DIRECTORY_SEPARATOR . 'task_attachments'; $this -> upload_url = rtrim( $upload_url, '/' ); } public function listByTaskId( $task_id ) { $this -> ensureStorage(); $rows = $this -> mdb -> select( 'tasks_attachments', '*', [ 'AND' => [ 'task_id' => (int)$task_id, 'deleted' => 0 ], 'ORDER' => [ 'id' => 'DESC' ] ] ); if ( !is_array( $rows ) ) return []; foreach ( $rows as &$row ) { $row['title_effective'] = self::effectiveTitle( $row['title'], $row['original_name'] ); $row['url'] = $this -> buildPublicUrl( $row['relative_path'], $row['stored_name'] ); $row['size_human'] = $this -> formatSize( (int)$row['file_size'] ); } return $rows; } public function upload( $task_id, $user_id, array $file ) { $this -> ensureStorage(); if ( !isset( $file['error'] ) or $file['error'] !== UPLOAD_ERR_OK ) return [ 'status' => 'error', 'msg' => 'Nie udało się wgrać pliku.' ]; if ( !isset( $file['tmp_name'] ) or !is_uploaded_file( $file['tmp_name'] ) ) return [ 'status' => 'error', 'msg' => 'Nieprawidłowy plik.' ]; $original_name = trim( (string)$file['name'] ); if ( $original_name === '' ) return [ 'status' => 'error', 'msg' => 'Brak nazwy pliku.' ]; $safe_original_name = self::sanitizeFileName( $original_name ); $ext = strtolower( pathinfo( $safe_original_name, PATHINFO_EXTENSION ) ); $stored_name = uniqid( 'att_', true ) . ( $ext ? '.' . $ext : '' ); $relative_path = date( 'Y' ) . '/' . date( 'm' ); $target_dir = $this -> upload_dir . DIRECTORY_SEPARATOR . str_replace( '/', DIRECTORY_SEPARATOR, $relative_path ); if ( !is_dir( $target_dir ) ) @mkdir( $target_dir, 0777, true ); $target_file = $target_dir . DIRECTORY_SEPARATOR . $stored_name; if ( !move_uploaded_file( $file['tmp_name'], $target_file ) ) return [ 'status' => 'error', 'msg' => 'Nie udało się zapisać pliku.' ]; $insert = $this -> mdb -> insert( 'tasks_attachments', [ 'task_id' => (int)$task_id, 'user_id' => (int)$user_id, 'title' => null, 'original_name' => $safe_original_name, 'stored_name' => $stored_name, 'relative_path' => $relative_path, 'file_ext' => $ext, 'file_size' => isset( $file['size'] ) ? (int)$file['size'] : 0, 'date_add' => date( 'Y-m-d H:i:s' ), 'deleted' => 0 ] ); if ( !$insert ) return [ 'status' => 'error', 'msg' => 'Nie udało się zapisać załącznika w bazie.' ]; return [ 'status' => 'success' ]; } public function rename( $attachment_id, $title ) { $this -> ensureStorage(); $title = trim( (string)$title ); return $this -> mdb -> update( 'tasks_attachments', [ 'title' => $title !== '' ? $title : null ], [ 'AND' => [ 'id' => (int)$attachment_id, 'deleted' => 0 ] ] ); } public function delete( $attachment_id, $deleted_by_user_id = null ) { $this -> ensureStorage(); $attachment = $this -> mdb -> get( 'tasks_attachments', '*', [ 'AND' => [ 'id' => (int)$attachment_id, 'deleted' => 0 ] ] ); if ( !$attachment ) return false; $file_path = $this -> upload_dir . DIRECTORY_SEPARATOR . str_replace( '/', DIRECTORY_SEPARATOR, $attachment['relative_path'] ) . DIRECTORY_SEPARATOR . $attachment['stored_name']; if ( file_exists( $file_path ) ) @unlink( $file_path ); return $this -> mdb -> update( 'tasks_attachments', [ 'deleted' => 1, 'deleted_by' => $deleted_by_user_id ? (int)$deleted_by_user_id : null, 'date_delete' => date( 'Y-m-d H:i:s' ) ], [ 'id' => (int)$attachment_id ] ); } public static function effectiveTitle( $title, $fallback ) { $title = trim( (string)$title ); return $title !== '' ? $title : (string)$fallback; } public static function sanitizeFileName( $name ) { $name = preg_replace( '/[^\p{L}\p{N}\s\.\-_]+/u', '_', (string)$name ); $name = preg_replace( '/\s+/', '_', $name ); $name = trim( $name, '._-' ); return $name !== '' ? $name : 'zalacznik'; } private function ensureStorage() { if ( !is_dir( $this -> upload_dir ) ) @mkdir( $this -> upload_dir, 0777, true ); if ( !$this -> table_ready ) { $this -> ensureTable(); $this -> table_ready = true; } } private function ensureTable() { $this -> mdb -> query( 'CREATE TABLE IF NOT EXISTS `tasks_attachments` ( `id` INT NOT NULL AUTO_INCREMENT, `task_id` INT NOT NULL, `user_id` INT NOT NULL, `title` VARCHAR(255) NULL, `original_name` VARCHAR(255) NOT NULL, `stored_name` VARCHAR(255) NOT NULL, `relative_path` VARCHAR(255) NOT NULL, `file_ext` VARCHAR(20) NULL, `file_size` INT UNSIGNED NOT NULL DEFAULT 0, `date_add` DATETIME NOT NULL, `deleted` TINYINT(1) NOT NULL DEFAULT 0, `deleted_by` INT NULL, `date_delete` DATETIME NULL, PRIMARY KEY (`id`), INDEX `idx_tasks_attachments_task` (`task_id`), INDEX `idx_tasks_attachments_deleted` (`deleted`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8' ); } private function buildPublicUrl( $relative_path, $stored_name ) { return $this -> upload_url . '/' . trim( $relative_path, '/' ) . '/' . rawurlencode( $stored_name ); } private function formatSize( $bytes ) { if ( $bytes < 1024 ) return $bytes . ' B'; $kb = $bytes / 1024; if ( $kb < 1024 ) return number_format( $kb, 1, '.', '' ) . ' KB'; return number_format( $kb / 1024, 1, '.', '' ) . ' MB'; } }