diff --git a/CHANGELOG.md b/CHANGELOG.md index 606f62e591..6d7968e7c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Environment variable `VERSION` to change the displayed version in the footer ([#3300], [#3302]) - System-wide default welcome message ([#3301]) +- Popover to show dial-in number and pin during a running meeting ([#1143], [#1810]) ### Fixed @@ -661,6 +662,7 @@ You can find the changelog for older versions there [here](https://github.com/TH [#1120]: https://github.com/THM-Health/PILOS/pull/1120 [#1126]: https://github.com/THM-Health/PILOS/pull/1126 [#1133]: https://github.com/THM-Health/PILOS/pull/1133 +[#1143]: https://github.com/THM-Health/PILOS/issues/1143 [#1150]: https://github.com/THM-Health/PILOS/issues/1150 [#1159]: https://github.com/THM-Health/PILOS/pull/1159 [#1166]: https://github.com/THM-Health/PILOS/pull/1166 @@ -731,6 +733,7 @@ You can find the changelog for older versions there [here](https://github.com/TH [#1795]: https://github.com/THM-Health/PILOS/pull/1795 [#1801]: https://github.com/THM-Health/PILOS/pull/1801 [#1802]: https://github.com/THM-Health/PILOS/pull/1802 +[#1810]: https://github.com/THM-Health/PILOS/pull/1810 [#1811]: https://github.com/THM-Health/PILOS/pull/1811 [#1824]: https://github.com/THM-Health/PILOS/issues/1824 [#1825]: https://github.com/THM-Health/PILOS/pull/1825 diff --git a/app/Http/Resources/LastMeetingResource.php b/app/Http/Resources/LastMeetingResource.php index 916f2139c7..1f600f9b68 100644 --- a/app/Http/Resources/LastMeetingResource.php +++ b/app/Http/Resources/LastMeetingResource.php @@ -24,6 +24,10 @@ public function toArray($request) 'usage' => $this->when($this->end == null, [ 'participant_count' => $this->room->participant_count, ]), + 'dial_in' => $this->when($this->end == null, [ + 'number' => $this->dial_number, + 'pin' => $this->voice_bridge, + ]), 'server_connection_issues' => $this->end == null && $this->server->error_count > 0, ]; } diff --git a/app/Services/RoomService.php b/app/Services/RoomService.php index ac0b095fd4..a61497410e 100644 --- a/app/Services/RoomService.php +++ b/app/Services/RoomService.php @@ -86,7 +86,10 @@ public function start(): MeetingService $meetingService = new MeetingService($meeting); Log::info('Starting new meeting for room {room} on server {server}', ['room' => $this->room->getLogLabel(), 'server' => $server->getLogLabel()]); - if (! $meetingService->start()) { + + $createMeetingResponse = $meetingService->start(); + + if (! $createMeetingResponse) { // Creating Meeting failed, remove meeting $meeting->forceDelete(); @@ -103,6 +106,13 @@ public function start(): MeetingService // but the api call has not been completed yet therefore the meeting will not be found on the server // and the server poller will mark the meeting as ended immediately $meeting->start = date('Y-m-d H:i:s'); + + // Store dial-in number and voice-bridge (pin) if valid + if (! in_array($createMeetingResponse->getDialNumber(), config('bigbluebutton.invalid_dial_numbers'))) { + $meeting->dial_number = $createMeetingResponse->getDialNumber(); + $meeting->voice_bridge = $createMeetingResponse->getVoiceBridge(); + } + $meeting->save(); // Change latest meeting or the room to newly created meeting diff --git a/composer.json b/composer.json index b46c6c3c5f..64baabb667 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,6 @@ "laravel/sanctum": "^v4.0", "laravel/telescope": "^5.0", "laravel/tinker": "^3.0", - "littleredbutton/bigbluebutton-api-php": "^6.0", "maennchen/zipstream-php": "^3.1", "nunomaduro/collision": "^8.1", "phpoffice/phpspreadsheet": "^5.7", @@ -37,6 +36,7 @@ "spatie/laravel-ignition": "^2.0", "spatie/laravel-settings": "^3.3", "symfony/var-exporter": "8.0.14", + "thm-health/bigbluebutton-api-php": "^7.0", "web-token/jwt-framework": "^4.0" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 6f7d112875..09a79c8892 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ecc5d9539071da996f342f1503989263", + "content-hash": "526224e1bf2dff2a18e983daeb9384e5", "packages": [ { "name": "bacon/bacon-qr-code", @@ -3015,137 +3015,6 @@ ], "time": "2026-03-08T20:05:35+00:00" }, - { - "name": "littleredbutton/bigbluebutton-api-php", - "version": "6.2.2", - "source": { - "type": "git", - "url": "https://github.com/littleredbutton/bigbluebutton-api-php.git", - "reference": "0178d70c05c2bcf35efbfe57a423e068c6edb15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/littleredbutton/bigbluebutton-api-php/zipball/0178d70c05c2bcf35efbfe57a423e068c6edb15e", - "reference": "0178d70c05c2bcf35efbfe57a423e068c6edb15e", - "shasum": "" - }, - "require": { - "ext-curl": "*", - "ext-dom": "*", - "ext-json": "*", - "ext-mbstring": "*", - "ext-simplexml": "*", - "php": ">=8.1" - }, - "require-dev": { - "nyholm/psr7": "^1.4", - "psr/http-client": "^1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0 || ^2.0", - "symfony/dotenv": "^5.4|^6.4|^7.0", - "symfony/http-client": "^5.4|^6.4|^7.0", - "symfony/http-client-contracts": "^1.1|^2.0|^3.0", - "symfony/process": "^5.4|^6.4|^7.0" - }, - "suggest": { - "psr/http-client-implementation": "To use the PsrHttpClientTransport.", - "psr/http-factory-implementation": "To use the PsrHttpClientTransport.", - "psr/http-message": "To use the PsrClientHttpTransport.", - "symfony/http-client": "To use the SymfonyHttpClientTransport.", - "symfony/http-client-contracts": "To use the SymfonyHttpClientTransport." - }, - "type": "library", - "extra": { - "cotor": { - "extensions": { - "phpunit/phpunit": { - "fakerphp/faker": "1.20.*" - } - }, - "vimeo/psalm": "^5.23", - "rector/rector": "^1.0", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9", - "friendsofphp/php-cs-fixer": "^3.3", - "brainmaestro/composer-git-hooks": "^2.8" - }, - "hooks": { - "pre-push": [ - "tools/phpunit --testsuite=\"BigBlueButton unit test suite,BigBlueButton integration test suite\"", - "tools/psalm --threads=1", - "tools/phpstan analyse", - "tools/rector process --dry-run src/ tests/" - ], - "post-merge": "composer install", - "pre-commit": [ - "tools/php-cs-fixer fix --dry-run --allow-risky=yes" - ], - "post-checkout": "composer install" - } - }, - "autoload": { - "psr-4": { - "BigBlueButton\\": [ - "src", - "tests/integration" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL-3.0-or-later" - ], - "authors": [ - { - "name": "Ghazi Triki", - "email": "ghazi.triki@riadvice.tn", - "role": "Developer" - }, - { - "name": "Klaus Herberth", - "email": "klaus@jsxc.org", - "role": "Developer" - }, - { - "name": "Samuel Weirich", - "email": "samuel@coding-space.de", - "role": "Developer" - }, - { - "name": "Jignesh Joisar", - "email": "jigneshjoisar@gmail.com", - "role": "Developer" - }, - { - "name": "Pablo Ogando", - "email": "pablo.ogando@teltek.es", - "role": "Developer" - }, - { - "name": "Alfonso RodrÃguez", - "email": "arodriguez@teltek.es", - "role": "Developer" - }, - { - "name": "Felix Jacobi", - "email": "felix@jacobi-hamburg.net", - "role": "Developer" - } - ], - "description": "BigBlueButton PHP API Library for PHP", - "homepage": "http://bigbluebutton.org/", - "keywords": [ - "api", - "bbb", - "bigbluebutton" - ], - "support": { - "docs": "https://github.com/littleredbutton/bigbluebutton-api-php/blob/master/README.md", - "issues": "https://github.com/littleredbutton/bigbluebutton-api-php/issues", - "source": "https://github.com/littleredbutton/bigbluebutton-api-php/" - }, - "time": "2025-11-17T12:30:55+00:00" - }, { "name": "livewire/livewire", "version": "v4.3.3", @@ -9653,6 +9522,137 @@ ], "time": "2026-06-27T08:56:37+00:00" }, + { + "name": "thm-health/bigbluebutton-api-php", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/THM-Health/bigbluebutton-api-php.git", + "reference": "016e2097a8b60e60af4ca47188f6e32212a9718a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/THM-Health/bigbluebutton-api-php/zipball/016e2097a8b60e60af4ca47188f6e32212a9718a", + "reference": "016e2097a8b60e60af4ca47188f6e32212a9718a", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-dom": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "php": ">=8.1" + }, + "require-dev": { + "nyholm/psr7": "^1.4", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0 || ^2.0", + "symfony/dotenv": "^5.4|^6.4|^7.0", + "symfony/http-client": "^5.4|^6.4|^7.0", + "symfony/http-client-contracts": "^1.1|^2.0|^3.0", + "symfony/process": "^5.4|^6.4|^7.0" + }, + "suggest": { + "psr/http-client-implementation": "To use the PsrHttpClientTransport.", + "psr/http-factory-implementation": "To use the PsrHttpClientTransport.", + "psr/http-message": "To use the PsrClientHttpTransport.", + "symfony/http-client": "To use the SymfonyHttpClientTransport.", + "symfony/http-client-contracts": "To use the SymfonyHttpClientTransport." + }, + "type": "library", + "extra": { + "cotor": { + "extensions": { + "phpunit/phpunit": { + "fakerphp/faker": "1.24.*" + } + }, + "vimeo/psalm": "^5.23", + "rector/rector": "^1.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10", + "friendsofphp/php-cs-fixer": "^3.3", + "brainmaestro/composer-git-hooks": "^2.8" + }, + "hooks": { + "pre-push": [ + "tools/phpunit --testsuite=\"BigBlueButton unit test suite,BigBlueButton integration test suite\"", + "tools/psalm --threads=1", + "tools/phpstan analyse", + "tools/rector process --dry-run src/ tests/" + ], + "post-merge": "composer install", + "pre-commit": [ + "tools/php-cs-fixer fix --dry-run --allow-risky=yes" + ], + "post-checkout": "composer install" + } + }, + "autoload": { + "psr-4": { + "BigBlueButton\\": [ + "src", + "tests/integration" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Ghazi Triki", + "email": "ghazi.triki@riadvice.tn", + "role": "Developer" + }, + { + "name": "Klaus Herberth", + "email": "klaus@jsxc.org", + "role": "Developer" + }, + { + "name": "Samuel Weirich", + "email": "samuel@coding-space.de", + "role": "Developer" + }, + { + "name": "Jignesh Joisar", + "email": "jigneshjoisar@gmail.com", + "role": "Developer" + }, + { + "name": "Pablo Ogando", + "email": "pablo.ogando@teltek.es", + "role": "Developer" + }, + { + "name": "Alfonso RodrÃguez", + "email": "arodriguez@teltek.es", + "role": "Developer" + }, + { + "name": "Felix Jacobi", + "email": "felix@jacobi-hamburg.net", + "role": "Developer" + } + ], + "description": "BigBlueButton PHP API Library for PHP", + "homepage": "http://bigbluebutton.org/", + "keywords": [ + "api", + "bbb", + "bigbluebutton" + ], + "support": { + "docs": "https://github.com/littleredbutton/bigbluebutton-api-php/blob/master/README.md", + "issues": "https://github.com/littleredbutton/bigbluebutton-api-php/issues", + "source": "https://github.com/littleredbutton/bigbluebutton-api-php/" + }, + "time": "2026-07-20T15:30:51+00:00" + }, { "name": "tijsverkoyen/css-to-inline-styles", "version": "v2.4.0", diff --git a/config/bigbluebutton.php b/config/bigbluebutton.php index e51b67dd83..eb149adc85 100644 --- a/config/bigbluebutton.php +++ b/config/bigbluebutton.php @@ -20,7 +20,7 @@ 'room_refresh_rate' => (int) env('ROOM_REFRESH_RATE', 30), 'server_online_threshold' => (int) env('BBB_SERVER_ONLINE_THRESHOLD', 3), 'server_offline_threshold' => (int) env('BBB_SERVER_OFFLINE_THRESHOLD', 3), - + 'invalid_dial_numbers' => explode(',', env('BBB_INVALID_DIAL_NUMBERS', '0,613-555-1212,613-555-1234,0000')), 'load_new_meeting_min_user_count' => (int) env('BBB_LOAD_MIN_USER_COUNT', 15), 'load_new_meeting_min_user_interval' => (int) env('BBB_LOAD_MIN_USER_INTERVAL', 15), diff --git a/database/migrations/2026_07_20_171150_add_dial_in_to_meetings_table.php b/database/migrations/2026_07_20_171150_add_dial_in_to_meetings_table.php new file mode 100644 index 0000000000..bca65c8220 --- /dev/null +++ b/database/migrations/2026_07_20_171150_add_dial_in_to_meetings_table.php @@ -0,0 +1,32 @@ +string('dial_number')->nullable(); + $table->string('voice_bridge')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('meetings', function (Blueprint $table) { + $table->dropColumn('dial_number'); + $table->dropColumn('voice_bridge'); + }); + } +}; diff --git a/lang/en/rooms.php b/lang/en/rooms.php index 238a8a87f9..bf32beca9b 100644 --- a/lang/en/rooms.php +++ b/lang/en/rooms.php @@ -306,6 +306,16 @@ 'room_link_copied' => 'The personalized room link for :firstname :lastname was copied to your clipboard.', 'title' => 'Personalized room links', ], + 'phone' => [ + 'call' => 'Call', + 'join_by_phone' => 'Join by phone', + 'note' => 'This phone number and pin is only valid for the duration of the meeting', + 'number' => 'Phone number', + 'pin' => 'Pin', + 'qrcode' => 'QR-Code', + 'scan_qr_code' => 'Scan this QR code with your smartphone to join the meeting by phone', + 'title' => 'Phone number and Pin', + ], 'placeholder_name' => 'John Doe', 'recording_accept' => 'I consent to the recording.', 'recording_attendance_accept' => 'I consent to the attendance logging.', diff --git a/package-lock.json b/package-lock.json index d543a80c71..cbc854a453 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "primeicons": "^7.0.0", "primelocale": "^2.4.0", "primevue": "^4.5.5", + "qrcode": "^1.5.4", "tailwindcss": "^4.1.14", "tailwindcss-primeui": "^0.6.1", "ua-parser-js": "^1.0.41", @@ -5047,7 +5048,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5057,7 +5057,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -5553,7 +5552,6 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5776,7 +5774,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -5788,7 +5785,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -5803,7 +5799,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -5816,7 +5811,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/colorette": { @@ -6123,7 +6117,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6211,6 +6204,12 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" + }, "node_modules/dompurify": { "version": "3.4.11", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.11.tgz", @@ -6274,7 +6273,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/empathic": { @@ -7319,7 +7317,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -7850,7 +7847,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9738,7 +9734,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -9797,7 +9792,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -10042,6 +10036,15 @@ "pathe": "^2.0.3" } }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/postcss": { "version": "8.5.15", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", @@ -10435,6 +10438,23 @@ "node": ">=6" } }, + "node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "license": "MIT", + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/qs": { "version": "6.15.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", @@ -10602,7 +10622,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -10623,7 +10642,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true, "license": "ISC" }, "node_modules/resolve": { @@ -10994,7 +11012,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true, "license": "ISC" }, "node_modules/shebang-command": { @@ -11286,7 +11303,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -11317,7 +11333,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -12810,7 +12825,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "dev": true, "license": "ISC" }, "node_modules/word-wrap": { @@ -12976,7 +12990,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true, "license": "ISC" }, "node_modules/yallist": { @@ -13036,7 +13049,6 @@ "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, "license": "MIT", "dependencies": { "cliui": "^6.0.0", @@ -13059,7 +13071,6 @@ "version": "18.1.3", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, "license": "ISC", "dependencies": { "camelcase": "^5.0.0", @@ -13073,7 +13084,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, "license": "MIT", "dependencies": { "locate-path": "^5.0.0", @@ -13087,7 +13097,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, "license": "MIT", "dependencies": { "p-locate": "^4.1.0" @@ -13100,7 +13109,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, "license": "MIT", "dependencies": { "p-try": "^2.0.0" @@ -13116,7 +13124,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, "license": "MIT", "dependencies": { "p-limit": "^2.2.0" diff --git a/package.json b/package.json index 23962b81b1..fb1d36f1ac 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "primeicons": "^7.0.0", "primelocale": "^2.4.0", "primevue": "^4.5.5", + "qrcode": "^1.5.4", "tailwindcss": "^4.1.14", "tailwindcss-primeui": "^0.6.1", "ua-parser-js": "^1.0.41", diff --git a/resources/js/components/RoomJoinByPhoneButton.vue b/resources/js/components/RoomJoinByPhoneButton.vue new file mode 100644 index 0000000000..6ec77b7644 --- /dev/null +++ b/resources/js/components/RoomJoinByPhoneButton.vue @@ -0,0 +1,172 @@ + + + + + + + {{ $t("rooms.phone.title") }} + + + + + + + + + + + + + + + + + + + + + + + {{ $t("rooms.phone.note") }} + + + + + + + {{ $t("rooms.phone.scan_qr_code") }} + + + + + + + + diff --git a/resources/js/views/RoomsView.vue b/resources/js/views/RoomsView.vue index be3211e2d4..716dcdf590 100644 --- a/resources/js/views/RoomsView.vue +++ b/resources/js/views/RoomsView.vue @@ -212,6 +212,11 @@ @guests-not-allowed="handleGuestsNotAllowed" @changed="reload(true)" /> + create(['room_id' => $room->id]); + $meeting = Meeting::factory()->create(['room_id' => $room->id, 'dial_number' => '613-555-1234', 'voice_bridge' => '01234']); $room->latestMeeting()->associate($meeting); $room->save(); @@ -1608,9 +1608,10 @@ public function test_room_view() ], ], ]) + ->assertJsonMissingPath('data.last_meeting.dial_in') ->assertJsonMissingPath('data.last_meeting.usage'); - // Test with running meeting and usage statistics + // Test with running meeting, usage statistics and dial-in $meeting->end = null; $meeting->save(); @@ -1626,11 +1627,56 @@ public function test_room_view() 'usage' => [ 'participant_count' => 10, ], + 'dial_in' => [ + 'number' => '613-555-1234', + 'pin' => '01234', + ], ], ], ]) ->assertJsonCount(1, 'data.last_meeting.usage'); + // Test without dial-in + $meeting->dial_number = null; + $meeting->voice_bridge = null; + $meeting->save(); + + $this->actingAs($this->user)->getJson(route('api.v1.rooms.show', ['room' => $room])) + ->assertStatus(200) + ->assertJson([ + 'data' => [ + 'last_meeting' => [ + 'start' => $meeting->start->toJson(), + 'end' => null, + 'detached' => null, + 'server_connection_issues' => false, + 'usage' => [ + 'participant_count' => 10, + ], + 'dial_in' => [ + 'number' => null, + 'pin' => null, + ], + ], + ], + ]); + + $this->actingAs($this->user)->getJson(route('api.v1.rooms.show', ['room' => $room])) + ->assertStatus(200) + ->assertJson([ + 'data' => [ + 'last_meeting' => [ + 'start' => $meeting->start->toJson(), + 'end' => null, + 'detached' => null, + 'server_connection_issues' => false, + 'usage' => [ + 'participant_count' => 10, + ], + ], + ], + ]); + // Test with server with connection issues $meeting->server->error_count = 1; $meeting->server->save(); @@ -3849,7 +3895,7 @@ public function test_server_usage_update_during_room_start() 5400b2af9176c1be733b9a4f1adbc7fb41a72123-1624606850899 1624606850899 Fri Jun 25 09:40:50 CEST 2021 - 70663 + 00663 613-555-1234 true 0 @@ -3930,7 +3976,7 @@ public function test_room_start_during_server_usage_update() 5400b2af9176c1be733b9a4f1adbc7fb41a72123-1624606850899 1624606850899 Fri Jun 25 09:40:50 CEST 2021 - 70663 + 00663 613-555-1234 true 0 @@ -4142,7 +4188,7 @@ public function test_join() 5400b2af9176c1be733b9a4f1adbc7fb41a72123-1624606850899 1624606850899 Fri Jun 25 09:40:50 CEST 2021 - 70663 + 00663 613-555-1234 true 0 @@ -4551,7 +4597,7 @@ public function test_join_url() 5400b2af9176c1be733b9a4f1adbc7fb41a72123-1624606850899 1624606850899 Fri Jun 25 09:40:50 CEST 2021 - 70663 + 00663 613-555-1234 true 0 @@ -4697,7 +4743,7 @@ public function test_join_url_dark_mode() 5400b2af9176c1be733b9a4f1adbc7fb41a72123-1624606850899 1624606850899 Fri Jun 25 09:40:50 CEST 2021 - 70663 + 00663 613-555-1234 true 0 @@ -4800,7 +4846,7 @@ public function test_join_record() 5400b2af9176c1be733b9a4f1adbc7fb41a72123-1624606850899 1624606850899 Fri Jun 25 09:40:50 CEST 2021 - 70663 + 00663 613-555-1234 true 0 @@ -4933,7 +4979,7 @@ public function test_join_attendance_recording() 5400b2af9176c1be733b9a4f1adbc7fb41a72123-1624606850899 1624606850899 Fri Jun 25 09:40:50 CEST 2021 - 70663 + 00663 613-555-1234 true 0 @@ -5055,7 +5101,7 @@ public function test_join_streaming() 5400b2af9176c1be733b9a4f1adbc7fb41a72123-1624606850899 1624606850899 Fri Jun 25 09:40:50 CEST 2021 - 70663 + 00663 613-555-1234 true 0 diff --git a/tests/Backend/Unit/RoomServiceTest.php b/tests/Backend/Unit/RoomServiceTest.php new file mode 100644 index 0000000000..606e9cb0ba --- /dev/null +++ b/tests/Backend/Unit/RoomServiceTest.php @@ -0,0 +1,73 @@ +room = Room::factory()->create(['access_code' => '123456789']); + } + + public function test_start_dial_in() + { + config(['bigbluebutton.invalid_dial_numbers' => ['0', '0000']]); + + $server = Server::factory()->create(); + $bbbFaker = new BigBlueButtonServerFaker($server->base_url, $server->secret); + $bbbFaker->addCreateMeetingRequest(); + $bbbFaker->addCreateMeetingRequest(); + + $this->room->roomType->serverPool->servers()->attach($server); + + // Start new meeting, result has a valid dial-in number + $roomService = new RoomService($this->room); + $roomService->start(); + + // Check meeting was created + $this->room->refresh(); + $this->assertCount(1, $this->room->meetings); + + // Check dial-in number and voice-bridge (pin) are set + $meeting = $this->room->latestMeeting; + $this->assertEquals('613-555-1234', $meeting->dial_number); + $this->assertEquals('02443', $meeting->voice_bridge); + + // Set meeting as ended + $meetingService = new MeetingService($meeting); + $meetingService->setEnd(); + + // Change list of invalid dial-in numbers + config(['bigbluebutton.invalid_dial_numbers' => ['0', '0000', '613-555-1234']]); + + // Start new meeting, result has an invalid dial-in number + $roomService->start(); + + // Check another meeting was created + $this->room->refresh(); + $this->assertCount(2, $this->room->meetings); + + // Check dial-in number and voice-bridge (pin) are not set + $meeting = $this->room->latestMeeting; + $this->assertNull($meeting->dial_number); + $this->assertNull($meeting->voice_bridge); + } +} diff --git a/tests/Backend/Utils/BigBlueButtonServerFaker.php b/tests/Backend/Utils/BigBlueButtonServerFaker.php index 77ab65df79..1eece7d322 100644 --- a/tests/Backend/Utils/BigBlueButtonServerFaker.php +++ b/tests/Backend/Utils/BigBlueButtonServerFaker.php @@ -128,7 +128,7 @@ public static function createCreateMeetingResponse(Request $request): PromiseInt 5756487f8952a40879db59f8fe4085798cb79ccc-1695892370102 bbb-none 1695892370102 - 92443 + 02443 613-555-1234 Thu Sep 28 09:12:50 UTC 2023 false diff --git a/tests/Frontend/e2e/RoomsViewMeetings.cy.js b/tests/Frontend/e2e/RoomsViewMeetings.cy.js index bfe785dc41..897f1d79b2 100644 --- a/tests/Frontend/e2e/RoomsViewMeetings.cy.js +++ b/tests/Frontend/e2e/RoomsViewMeetings.cy.js @@ -1709,6 +1709,79 @@ describe("Rooms view meetings", function () { ); }); + it("join running meeting by phone", function () { + cy.fixture("room.json").then((room) => { + room.data.last_meeting = { + start: "2023-08-21T08:18:28.000000Z", + end: null, + dial_in: { + number: null, + pin: null, + }, + }; + + cy.intercept("GET", "api/v1/rooms/abc-def-123", { + statusCode: 200, + body: room, + }).as("roomRequest"); + }); + + cy.visit("/rooms/abc-def-123"); + + cy.wait("@roomRequest"); + + // Check if button is not shown, if no dial-number is set + cy.get('[data-test="room-join-by-phone-button"]').should("not.exist"); + + // Reload with dial-in + cy.fixture("room.json").then((room) => { + room.data.last_meeting = { + start: "2023-08-21T08:18:28.000000Z", + end: null, + dial_in: { + number: "+49 123 456789", + pin: "01234", + }, + }; + + cy.intercept("GET", "api/v1/rooms/abc-def-123", { + statusCode: 200, + body: room, + }).as("roomRequest"); + }); + + cy.reload(); + + cy.wait("@roomRequest"); + + cy.get('[data-test="room-join-by-phone-button"]') + .should("exist") + .should("have.text", "rooms.phone.join_by_phone") + .click(); + + cy.get('[data-test="room-join-by-phone-overlay"]') + .should("be.visible") + .within(() => { + cy.get("#phone-number").should("have.value", "+49 123 456789"); + cy.get("#phone-pin").should("have.value", "01234"); + + cy.get('[data-test="join-by-phone-call-button"]') + .should("have.text", "rooms.phone.call") + .should("have.attr", "href", "tel:+49123456789,01234#"); + + cy.fixture("files/dial-in-qr-code.png", "base64").then( + (qrCodeImage) => { + cy.get('[data-test="join-by-phone-qr-code"]') + .should("be.visible") + .should("have.attr", "src") + .then((src) => { + expect(src).to.equal("data:image/png;base64," + qrCodeImage); + }); + }, + ); + }); + }); + it("start meeting", function () { const startRequest = interceptIndefinitely( "POST", diff --git a/tests/Frontend/fixtures/files/dial-in-qr-code.png b/tests/Frontend/fixtures/files/dial-in-qr-code.png new file mode 100644 index 0000000000..7f871d3519 Binary files /dev/null and b/tests/Frontend/fixtures/files/dial-in-qr-code.png differ
+ {{ $t("rooms.phone.scan_qr_code") }} +