|
| 1 | +# File Uploads |
| 2 | + |
| 3 | +Uploading files is handled through the unified upload pipeline introduced in WoltLab Suite 6.1. |
| 4 | +The API covers the upload process, storage, thumbnail generation and serving of files to the browser. |
| 5 | +Attachments are implemented as an extra layer on top of the upload pipeline. |
| 6 | + |
| 7 | +The most common use cases are the attachment system that relies on the WYSIWYG editor as well as the `FileProcessorFormField`. |
| 8 | + |
| 9 | +# Provide the `IFileProcessor` |
| 10 | + |
| 11 | +At the very core of each uploadable type is an implementation of `IFileProcessor` that handles the validation and handling of new files. |
| 12 | + |
| 13 | +It is strongly recommended to derive from `AbstractFileProcessor` that makes it easy to opt out of some extra features and will provide backwards compatibility with new features. |
| 14 | + |
| 15 | +## Important Methods and Features |
| 16 | + |
| 17 | +Please refer to the documentation in `IFileProcessor` for an explanation of methods that are not explained here. |
| 18 | + |
| 19 | +### Thumbnails |
| 20 | + |
| 21 | +It is possible to automatically generate thumbnails for any uploaded file. |
| 22 | +The number of thumbnail variants is not limited but each thumbnail must have an identifier that is unique for your file processor. |
| 23 | + |
| 24 | +```php |
| 25 | +#[\Override] |
| 26 | +public function getThumbnailFormats(): array |
| 27 | +{ |
| 28 | + return [ |
| 29 | + new ThumbnailFormat( |
| 30 | + '', // An empty string is a valid identifier. |
| 31 | + 800, |
| 32 | + 600, |
| 33 | + true, // Retaining the dimensions can cause _one_ of the sides to underflow the configured width or height. |
| 34 | + ), |
| 35 | + new ThumbnailFormat( |
| 36 | + 'square', |
| 37 | + 100, |
| 38 | + 100, |
| 39 | + false, // This will generate a 100x100 thumbnail where the longest size will be cropped from the center. |
| 40 | + ), |
| 41 | + ]; |
| 42 | +} |
| 43 | +``` |
| 44 | + |
| 45 | +The abstract implementation returns an empty array which will disable thumbnails entirely. |
| 46 | +Changes to the thumbnail configuration, for example, updating the dimensions or adding new thumbnail formats are not applied to existing files automatically. |
| 47 | + |
| 48 | +The system tracks the configuration used to generate a thumbnail. |
| 49 | +The existing rebuild data worker for files will check the existing thumbnails against the formats provided by your processor and regenerate any thumbnail when possible. |
| 50 | + |
| 51 | +The identifier must be stable because it is used to verify if a specific thumbnail still matches the configured settings. |
| 52 | + |
| 53 | +### Resizing Images Before Uploading |
| 54 | + |
| 55 | +You can opt-in to the resize feature by returning a custom `ResizeConfiguration` from `getResizeConfiguration`, otherwise images of arbitrary size will be accepted. |
| 56 | + |
| 57 | +```php |
| 58 | +#[\Override] |
| 59 | +public function getResizeConfiguration(): ResizeConfiguration |
| 60 | +{ |
| 61 | + if (!\ATTACHMENT_IMAGE_AUTOSCALE) { |
| 62 | + // The resizing has been disabled through the options. |
| 63 | + return ResizeConfiguration::unbounded(); |
| 64 | + } |
| 65 | + |
| 66 | + return new ResizeConfiguration( |
| 67 | + \ATTACHMENT_IMAGE_AUTOSCALE_MAX_WIDTH, |
| 68 | + \ATTACHMENT_IMAGE_AUTOSCALE_MAX_HEIGHT, |
| 69 | + ResizeFileType::fromString(\ATTACHMENT_IMAGE_AUTOSCALE_FILE_TYPE), |
| 70 | + \ATTACHMENT_IMAGE_AUTOSCALE_QUALITY |
| 71 | + ); |
| 72 | +} |
| 73 | +``` |
| 74 | + |
| 75 | +The `ResizeFileType` controls the output format of the resized image. |
| 76 | +The available options are `jpeg` and `webp` but it is also possible to keep the existing image format. |
| 77 | + |
| 78 | +### Adopting Files and Thumbnails |
| 79 | + |
| 80 | +Files are associated with your object type after being uploaded but you possibly want to store the file id in your database table. |
| 81 | +This is where `adopt(File $file, array $context): void` comes into play which notifies you of the successful upload of a file while providing the context that is used to upload the file in the first place. |
| 82 | + |
| 83 | +Thumbnails are generated in a separate request for performance reasons and you are being notified through `adoptThumbnail(FileThumbnail $thumbnail): void`. |
| 84 | +This is meant to allow you to track the thumbnail id in the database. |
| 85 | + |
| 86 | +### Tracking Downloads |
| 87 | + |
| 88 | +File downloads are handled through the `FileDownloadAction` which validates the requested file and permissions to download it. |
| 89 | +Every time a file is being downloaded, `trackDownload(File $file): void` is invoked to allow you to update any counters. |
| 90 | + |
| 91 | +Static images are served directly by the web server for performance reasons and it is not possible to track those accesses. |
| 92 | + |
| 93 | +## Registering the File Processor |
| 94 | + |
| 95 | +The file processor is registered as an object type for `com.woltlab.wcf.file` through the `objectType.xml`: |
| 96 | + |
| 97 | +```xml |
| 98 | +<type> |
| 99 | + <name>com.woltlab.wcf.attachment</name> |
| 100 | + <definitionname>com.woltlab.wcf.file</definitionname> |
| 101 | + <classname>wcf\system\file\processor\AttachmentFileProcessor</classname> |
| 102 | +</type> |
| 103 | +``` |
| 104 | + |
| 105 | +# `FileProcessorFormField` |
| 106 | + |
| 107 | +It is highly recommended that you take advantage of the existing form builder field `FileProcessorFormField`. |
| 108 | +The integration with the form builder enables you to focus on the file processing and does not require you to manually handle the integration of the upload field. |
| 109 | + |
| 110 | +Please see documentation for [FileProcessorFormField](form_fields.md#fileprocessorformfield) to learn more. |
| 111 | + |
| 112 | +# Implementing an Unmanaged File Upload |
| 113 | + |
| 114 | +If you cannot use or want to use the existing form builder implementation you can still implement the UI yourself following this guide. |
| 115 | + |
| 116 | +## Creating the Context for New Files |
| 117 | + |
| 118 | +The HTML element for the file upload is generated through the helper method `FileProcessor::getHtmlElement()` that expects a reference to your `IFileProcessor` as well as a context for new files. |
| 119 | + |
| 120 | +The context is an array with arbitrary values that will be provided to your `IFileProcessor` when processing uploaded files. |
| 121 | +You can provide anything you need in order to recognize what the file belongs to, for example, an identifier, object ids, et cetera. |
| 122 | + |
| 123 | +```php |
| 124 | +final class ExampleFileProcessor extends AbstractFileProcessor { |
| 125 | + public function toHtmlElement(string $someIdentifier, int $someObjectID): string { |
| 126 | + return FileProcessor::getInstance()->getHtmlElement( |
| 127 | + $this, |
| 128 | + [ |
| 129 | + 'identifier' => $someIdentifier, |
| 130 | + 'objectID' => $someObjectID, |
| 131 | + ], |
| 132 | + ); |
| 133 | + } |
| 134 | +} |
| 135 | +``` |
| 136 | + |
| 137 | +This code will generate a `<woltlab-core-file-upload>` HTML element that can be inserted anywhere on the page. |
| 138 | +You do not need to initialize any extra JavaScript to make the element work, it will be initialized dynamically. |
| 139 | + |
| 140 | +## Lifecycle of Uploaded Files |
| 141 | + |
| 142 | +Any file that passes the pre-upload validation will be uploaded to the server. |
| 143 | +This will trigger the `uploadStart` event on the `<woltlab-core-file-upload>` element, exposing a `<woltlab-core-file>` element as the only detail. |
| 144 | + |
| 145 | +It is your responsibility to insert this element at a location of your choice on the page, it represents the upload progress, reports any errors when uploading the file and shows the uploaded file on completion. |
| 146 | + |
| 147 | +The `<woltlab-core-file>` element exposes the `.ready` property that is a Promise representing the state of the upload. |
| 148 | +A successful upload will resolve the promise without any value, you can then access the file id through the `.fileId` property. |
| 149 | + |
| 150 | +A failed upload will reject the `.ready` promise without any value, instead you can retrieve the error through the `.apiError` property if you want to further process it. |
| 151 | +The UI is automatically updated with the error message, you only need to handle the `.apiError` property if you need to inspect the root cause. |
| 152 | + |
| 153 | +## Deleting a File |
| 154 | + |
| 155 | +You can delete a file from the UI by invoking `deleteFile()` from `WoltLabSuite/Core/Api/Files` which takes the value of `.fileId`. |
| 156 | + |
| 157 | +## Render a Previously Uploaded File |
| 158 | + |
| 159 | +You can render the `<woltlab-core-file>` element through `File::toHtmlElement()`. |
| 160 | +This method accepts an optional list of meta data that is serialized to JSON and exposed on the `data-meta-data` property. |
| 161 | + |
| 162 | +The `.ready` promise exists for these files too and will resolve immediately. |
0 commit comments