Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Show allowed file sizes and file types next to file upload inputs ([#3060])
- Keyboard and focus support for the user avatar cropper to improve accessibility ([#3060])

### Changed

- Improved error handling when a room requires an access code but none was provided ([#3035])
- User avatar cropper and preview now displayed in circular shape ([#3060])

## [v4.14.2] - 2026-04-10

Expand Down Expand Up @@ -776,6 +782,7 @@ You can find the changelog for older versions there [here](https://github.com/TH
[#3035]: https://github.com/THM-Health/PILOS/pull/3035
[#3039]: https://github.com/THM-Health/PILOS/issues/3039
[#3040]: https://github.com/THM-Health/PILOS/pull/3040
[#3060]: https://github.com/THM-Health/PILOS/pull/3060
[unreleased]: https://github.com/THM-Health/PILOS/compare/v4.14.2...develop
[v3.0.0]: https://github.com/THM-Health/PILOS/releases/tag/v3.0.0
[v3.0.1]: https://github.com/THM-Health/PILOS/releases/tag/v3.0.1
Expand Down
3 changes: 2 additions & 1 deletion app/Http/Requests/StoreRoomFileRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@

use App\Rules\Antivirus;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\File;

class StoreRoomFileRequest extends FormRequest
{
public function rules()
{
return [
'file' => ['bail', 'required', 'file', 'max:'.(config('bigbluebutton.max_filesize') * 1000), 'mimes:'.config('bigbluebutton.allowed_file_mimes'), new Antivirus], // https://github.com/bigbluebutton/bigbluebutton/blob/v2.2.x-release/bigbluebutton-html5/private/config/settings.yml
'file' => ['bail', 'required', File::types(config('bigbluebutton.allowed_file_mimes'))->extensions(config('bigbluebutton.allowed_file_mimes'))->max(config('bigbluebutton.max_filesize').'mb'), new Antivirus], // https://github.com/bigbluebutton/bigbluebutton/blob/v2.2.x-release/bigbluebutton-html5/private/config/settings.yml
];
}
}
4 changes: 3 additions & 1 deletion app/Http/Requests/UpdateRoomStreamingConfigRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use App\Rules\Antivirus;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\File;

class UpdateRoomStreamingConfigRequest extends FormRequest
{
Expand All @@ -19,7 +21,7 @@ public function rules()
return [
'enabled' => ['required', 'boolean'],
'url' => ['nullable', 'required_if_accepted:enabled', 'string', 'url:rtmp,rtmps', 'max:255'],
'pause_image' => ['bail', 'nullable', 'image', 'mimes:jpg,bmp,png,gif', 'max:5000', 'dimensions:width=1920,height=1080', new Antivirus], // 5 MB
'pause_image' => ['bail', 'nullable', File::types(['jpg', 'bmp', 'png', 'gif'])->extensions(['jpg', 'jpeg', 'bmp', 'png', 'gif'])->max('5mb'), Rule::dimensions()->width(1920)->height(1080), new Antivirus],
];
}

Expand Down
4 changes: 3 additions & 1 deletion app/Http/Requests/UpdateRoomTypeStreamingSettingsRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use App\Rules\Antivirus;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\File;

class UpdateRoomTypeStreamingSettingsRequest extends FormRequest
{
Expand All @@ -18,7 +20,7 @@ public function rules()
{
return [
'enabled' => ['required', 'boolean'],
'default_pause_image' => ['bail', 'nullable', 'image', 'mimes:jpg,bmp,png,gif', 'max:5000', 'dimensions:width=1920,height=1080', new Antivirus], // 5 MB
'default_pause_image' => ['bail', 'nullable', File::types(['jpg', 'bmp', 'png', 'gif'])->extensions(['jpg', 'jpeg', 'bmp', 'png', 'gif'])->max('5mb'), Rule::dimensions()->width(1920)->height(1080), new Antivirus],
];
}
}
19 changes: 10 additions & 9 deletions app/Http/Requests/UpdateSettingsRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use App\Rules\Antivirus;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\File;

class UpdateSettingsRequest extends FormRequest
{
Expand Down Expand Up @@ -44,16 +45,16 @@ public function rules()
'general_no_welcome_page' => ['required', 'boolean'],

'theme_logo' => ['required_without:theme_logo_file', 'string', 'max:255'],
'theme_logo_file' => ['bail', 'required_without:theme_logo', 'image:allow_svg', 'max:500', new Antivirus], // 500 KB, larger files are bad for loading times
'theme_logo_file' => ['bail', 'required_without:theme_logo', File::types(['jpg', 'png', 'gif', 'svg'])->extensions(['jpg', 'jpeg', 'png', 'gif', 'svg'])->max('500kb'), new Antivirus], // 500 KB, larger files are bad for loading times
'theme_logo_dark' => ['required_without:theme_logo_dark_file', 'string', 'max:255'],
'theme_logo_dark_file' => ['bail', 'required_without:theme_logo_dark', 'image:allow_svg', 'max:500', new Antivirus], // 500 KB, larger files are bad for loading times
'theme_logo_dark_file' => ['bail', 'required_without:theme_logo_dark', File::types(['jpg', 'png', 'gif', 'svg'])->extensions(['jpg', 'jpeg', 'png', 'gif', 'svg'])->max('500kb'), new Antivirus], // 500 KB, larger files are bad for loading times
'theme_favicon' => ['required_without:theme_favicon_file', 'string', 'max:255'],
'theme_favicon_file' => ['bail', 'required_without:theme_favicon', 'mimes:ico', 'max:500', new Antivirus], // 500 KB, larger files are bad for loading times
'theme_favicon_file' => ['bail', 'required_without:theme_favicon', File::types('ico')->extensions('ico')->max('500kb'), new Antivirus], // 500 KB, larger files are bad for loading times
'theme_favicon_dark' => ['required_without:theme_favicon_dark_file', 'string', 'max:255'],
'theme_favicon_dark_file' => ['bail', 'required_without:theme_favicon_dark', 'mimes:ico', 'max:500', new Antivirus], // 500 KB, larger files are bad for loading times
'theme_favicon_dark_file' => ['bail', 'required_without:theme_favicon_dark', File::types('ico')->extensions('ico')->max('500kb'), new Antivirus], // 500 KB, larger files are bad for loading times
'theme_primary_color' => ['required', 'string', 'hex_color'],
'theme_rounded' => ['required', 'boolean'],
'theme_custom_css' => ['bail', 'nullable', 'file', 'max:500', 'extensions:css', new Antivirus],
'theme_custom_css' => ['bail', 'nullable', File::types(['css', 'txt'])->extensions('css')->max('500kb'), new Antivirus],

'banner_enabled' => ['required', 'boolean'],
'banner_title' => ['nullable', 'string', 'max:255'],
Expand Down Expand Up @@ -84,12 +85,12 @@ public function rules()
'recording_recording_retention_period' => ['required', 'numeric', Rule::enum(TimePeriod::class)->except($disabledRecordingRetentionPeriods)],

'bbb_logo' => ['nullable', 'string', 'max:255'],
'bbb_logo_file' => ['bail', 'image:allow_svg', 'max:500', new Antivirus],
'bbb_logo_file' => ['bail', File::types(['jpg', 'png', 'gif', 'svg'])->extensions(['jpg', 'jpeg', 'png', 'gif', 'svg'])->max('500kb'), new Antivirus], // 500 KB, larger files are bad for loading times
'bbb_logo_dark' => ['nullable', 'string', 'max:255'],
'bbb_logo_dark_file' => ['bail', 'image:allow_svg', 'max:500', new Antivirus],
'bbb_logo_dark_file' => ['bail', File::types(['jpg', 'png', 'gif', 'svg'])->extensions(['jpg', 'jpeg', 'png', 'gif', 'svg'])->max('500kb'), new Antivirus], // 500 KB, larger files are bad for loading times

'bbb_style' => ['bail', 'nullable', 'file', 'max:500', 'extensions:css', new Antivirus],
'bbb_default_presentation' => ['bail', 'nullable', 'file', 'max:'.(config('bigbluebutton.max_filesize') * 1000), 'mimes:'.config('bigbluebutton.allowed_file_mimes'), new Antivirus],
'bbb_style' => ['bail', 'nullable', File::types(['css', 'txt'])->extensions('css')->max('500kb'), new Antivirus], // 500 KB, larger files are bad for loading times
'bbb_default_presentation' => ['bail', 'nullable', File::types(config('bigbluebutton.allowed_file_mimes'))->extensions(config('bigbluebutton.allowed_file_mimes'))->max(config('bigbluebutton.max_filesize').'mb'), new Antivirus],
];
}
}
6 changes: 4 additions & 2 deletions app/Http/Requests/UpdateStreamingSettingsRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use App\Rules\Antivirus;
use App\Rules\CustomJoinMeetingParameters;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\File;

class UpdateStreamingSettingsRequest extends FormRequest
{
Expand All @@ -18,8 +20,8 @@ class UpdateStreamingSettingsRequest extends FormRequest
public function rules()
{
return [
'default_pause_image' => ['bail', 'nullable', 'image', 'mimes:jpg,bmp,png,gif', 'max:5000', 'dimensions:width=1920,height=1080', new Antivirus], // 5 MB
'css_file' => ['bail', 'nullable', 'file', 'max:500', 'extensions:css', new Antivirus],
'default_pause_image' => ['bail', 'nullable', File::types(['jpg', 'bmp', 'png', 'gif'])->extensions(['jpg', 'jpeg', 'bmp', 'png', 'gif'])->max('5mb'), Rule::dimensions()->width(1920)->height(1080), new Antivirus],
'css_file' => ['bail', 'nullable', File::types(['css', 'txt'])->extensions('css')->max('500kb'), new Antivirus],
'join_parameters' => ['nullable', 'string', 'max:65000', new CustomJoinMeetingParameters],
];
}
Expand Down
3 changes: 2 additions & 1 deletion app/Http/Requests/UserRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\File;

class UserRequest extends FormRequest
{
Expand All @@ -27,7 +28,7 @@ public function rules()
'timezone' => ['sometimes', 'required', Rule::in(timezone_identifiers_list())],
'roles' => ['sometimes', 'required', 'array'],
'roles.*' => ['sometimes', 'distinct', 'integer', 'exists:App\Models\Role,id', Rule::notIn($prohibitedRoles)],
'image' => ['bail', 'sometimes', 'nullable', 'mimes:jpg', 'dimensions:width=100,height=100', Rule::prohibitedIf($this->user?->has_external_image), new Antivirus],
'image' => ['bail', 'sometimes', 'nullable', File::types('jpg')->extensions('jpg')->max('50kb'), Rule::dimensions()->width(100)->height(100), Rule::prohibitedIf($this->user?->has_external_image), new Antivirus],
];

if (! $this->user || $this->user->authenticator === 'local') {
Expand Down
2 changes: 1 addition & 1 deletion config/bigbluebutton.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
'secret' => env('BBB_TEST_SERVER_SECRET'),
],
'max_filesize' => (int) env('BBB_MAX_FILESIZE', 30),
'allowed_file_mimes' => env('BBB_ALLOWED_FILE_MIMES', 'pdf,doc,docx,xls,xlsx,ppt,pptx,txt,rtf,odt,ods,odp,odg,odc,odi,jpg,jpeg,png'),
'allowed_file_mimes' => explode(',', env('BBB_ALLOWED_FILE_MIMES', 'pdf,doc,docx,xls,xlsx,ppt,pptx,txt,rtf,odt,ods,odp,odg,odc,odi,jpg,jpeg,png')),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Edge case: empty BBB_ALLOWED_FILE_MIMES produces [''] instead of [].

If an operator sets BBB_ALLOWED_FILE_MIMES= (empty), explode(',', '') returns [''], which will propagate an empty entry into File::types()/File::extensions() on the backend and into the frontend accept="." / "Allowed formats: " display. Consider normalizing and trimming entries, and dropping empties.

🛡️ Suggested hardening
-    'allowed_file_mimes' => explode(',', env('BBB_ALLOWED_FILE_MIMES', 'pdf,doc,docx,xls,xlsx,ppt,pptx,txt,rtf,odt,ods,odp,odg,odc,odi,jpg,jpeg,png')),
+    'allowed_file_mimes' => array_values(array_filter(array_map(
+        'trim',
+        explode(',', (string) env('BBB_ALLOWED_FILE_MIMES', 'pdf,doc,docx,xls,xlsx,ppt,pptx,txt,rtf,odt,ods,odp,odg,odc,odi,jpg,jpeg,png'))
+    ), 'strlen')),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
'allowed_file_mimes' => explode(',', env('BBB_ALLOWED_FILE_MIMES', 'pdf,doc,docx,xls,xlsx,ppt,pptx,txt,rtf,odt,ods,odp,odg,odc,odi,jpg,jpeg,png')),
'allowed_file_mimes' => array_values(array_filter(array_map(
'trim',
explode(',', (string) env('BBB_ALLOWED_FILE_MIMES', 'pdf,doc,docx,xls,xlsx,ppt,pptx,txt,rtf,odt,ods,odp,odg,odc,odi,jpg,jpeg,png'))
), 'strlen')),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@config/bigbluebutton.php` at line 13, The config value for
'allowed_file_mimes' should be normalized so an empty BBB_ALLOWED_FILE_MIMES
does not produce ['']; instead, take env('BBB_ALLOWED_FILE_MIMES', '...'),
explode on ',', trim each entry, filter out empty strings (and optionally
normalize case), and assign the resulting array to 'allowed_file_mimes' so
downstream consumers like File::types()/File::extensions() and the frontend
accept/label do not receive an empty entry.

'welcome_message_limit' => (int) env('WELCOME_MESSAGE_LIMIT', 500), // max 5000
'room_name_limit' => (int) env('ROOM_NAME_LIMIT', 50),
'room_id_max_tries' => (int) env('BBB_ROOM_ID_MAX_TRIES', 1000),
Expand Down
7 changes: 7 additions & 0 deletions lang/en/admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,13 @@
'title' => 'Profile picture',
'title_short' => 'Picture',
'upload' => 'Upload new picture',
'aria_crop_selection' => [
'move' => 'Use the arrow keys to move the crop selection area',
'top_left' => 'Use the arrow keys to move the top left drag handle to change the crop selection area',
'top_right' => 'Use the arrow keys to move the top right drag handle to change the crop selection area',
'bottom_left' => 'Use the arrow keys to move the bottom left drag handle to change the crop selection area',
'bottom_right' => 'Use the arrow keys to move the bottom right drag handle to change the crop selection area',
],
],
'last_login' => [
'title' => 'Last login',
Expand Down
4 changes: 3 additions & 1 deletion lang/en/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,9 @@
'user' => 'User',
'user_name' => 'Name',
'users' => 'Users',
'validation' => [
'file' => [
'allowed_formats' => 'Allowed file formats: :formats',
'max_size' => 'Max. file size: :size',
'invalid_type' => 'The file type is not allowed.',
'too_large' => 'The selected file is too large.',
],
Comment thread
samuelwei marked this conversation as resolved.
Expand Down
4 changes: 1 addition & 3 deletions lang/en/rooms.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,8 @@
'downloadable' => 'Downloadable files',
'use_in_meeting' => 'Files available in video conference',
],
'formats' => 'Allowed file formats: :formats',
'nodata' => 'No files available',
'select_or_drag' => 'Select a file or drag and drop it here...',
'size' => 'Max. file size: :size MB',
'sort' => [
'filename' => 'Filename',
'uploaded_at' => 'Added',
Expand Down Expand Up @@ -465,7 +463,7 @@
'enabled' => 'Enabled',
'pause_image' => 'Pause image',
'pause_image_alt' => 'Pause image',
'pause_image_format' => 'Format: PNG, JPEG, GIF, BMP; Resolution: 1920x1080px',
'pause_image_resolution' => 'Resolution: 1920x1080px',
'title' => 'Streaming configuration',
'url' => 'RTMP(S) URL',
],
Expand Down
36 changes: 23 additions & 13 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,15 @@
"axios": "^1.15.1",
"chart.js": "^4.5.1",
"chartjs-adapter-date-fns": "^3.0.0",
"cropperjs": "^1.6.2",
"date-fns": "^4.1.0",
"dompurify": "^3.4.0",
"dotenv": "^17.4.2",
"laravel-vite-plugin": "^2.1.0",
"lodash-es": "^4.18.1",
"mitt": "^3.0.1",
"pinia": "^3.0.4",
"pixelmatch": "^7.1.0",
"primeicons": "^7.0.0",
"primelocale": "^2.3.1",
"primevue": "^4.5.5",
Expand All @@ -76,7 +78,6 @@
"vue": "^3.5",
"vue-axe": "^3.1.2",
"vue-chartjs": "^5.3.3",
"vue-cropperjs": "^5.0.0",
"vue-i18n": "^11.3",
"vue-multiselect": "^3.5.0",
"vue-router": "^5.0.4"
Expand Down
48 changes: 48 additions & 0 deletions resources/css/app/_cropperjs.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
@import "cropperjs/dist/cropper.css";

.cropper-container {
overflow: hidden;

img {
display: block;

/* This rule is very important, please don't ignore this */
max-width: 100%;
}

.cropper-view-box,
.cropper-face {
border-radius: 50%;
}

.cropper-view-box {
outline: 0;
}

.cropper-face {
background-color: transparent;
opacity: 1;
border: 1px dashed #fff;
}

.cropper-line {
display: none;
}

.cropper-point {
background-color: #fff;
border-radius: 50%;
border: 1px solid #767676;
opacity: 1;
width: 10px;
height: 10px;
}

.cropper-point.point-n,
.cropper-point.point-w,
.cropper-point.point-s,
.cropper-point.point-e,
.cropper-point.point-se::before {
display: none;
}
}
1 change: 1 addition & 0 deletions resources/css/app/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
@import "./_profile-image.css";
@import "./_color-select.css";
@import "./_stretched-link.css";
@import "./_cropperjs.css";
6 changes: 4 additions & 2 deletions resources/js/components/AdminStreamingRoomTypeEditButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
v-model:file="defaultPauseImage"
v-model:file-deleted="defaultPauseImageDeleted"
:disabled="disabled || isLoadingAction"
:max-file-size="5000000"
:max-file-size="5_000_000"
:hide-url="true"
show-delete
:preview-alt="$t('rooms.streaming.config.pause_image_alt')"
Expand All @@ -102,7 +102,9 @@
:url-error="formErrors.fieldError('default_pause_image')"
:file-error="formErrors.fieldError('default_pause_image')"
/>
<small>{{ $t("rooms.streaming.config.pause_image_format") }}</small>
<small>{{
$t("rooms.streaming.config.pause_image_resolution")
}}</small>
</div>
</fieldset>
</form>
Expand Down
Loading
Loading