Passed
Pull Request — master (#221)
by
unknown
01:52
created

BigBlueButton::getRecordings()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 5
rs 10
cc 1
nc 1
nop 1
1
<?php
2
3
/*
4
 * BigBlueButton open source conferencing system - https://www.bigbluebutton.org/.
5
 *
6
 * Copyright (c) 2016-2024 BigBlueButton Inc. and by respective authors (see below).
7
 *
8
 * This program is free software; you can redistribute it and/or modify it under the
9
 * terms of the GNU Lesser General Public License as published by the Free Software
10
 * Foundation; either version 3.0 of the License, or (at your option) any later
11
 * version.
12
 *
13
 * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
14
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
15
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public License along
18
 * with BigBlueButton; if not, see <https://www.gnu.org/licenses/>.
19
 */
20
21
namespace BigBlueButton;
22
23
use BigBlueButton\Core\ApiMethod;
24
use BigBlueButton\Enum\HashingAlgorithm;
25
use BigBlueButton\Exceptions\BadResponseException;
26
use BigBlueButton\Parameters\CreateMeetingParameters;
27
use BigBlueButton\Parameters\DeleteRecordingsParameters;
28
use BigBlueButton\Parameters\EndMeetingParameters;
29
use BigBlueButton\Parameters\GetMeetingInfoParameters;
30
use BigBlueButton\Parameters\GetRecordingsParameters;
31
use BigBlueButton\Parameters\GetRecordingTextTracksParameters;
32
use BigBlueButton\Parameters\HooksCreateParameters;
33
use BigBlueButton\Parameters\HooksDestroyParameters;
34
use BigBlueButton\Parameters\InsertDocumentParameters;
35
use BigBlueButton\Parameters\IsMeetingRunningParameters;
36
use BigBlueButton\Parameters\JoinMeetingParameters;
37
use BigBlueButton\Parameters\PublishRecordingsParameters;
38
use BigBlueButton\Parameters\PutRecordingTextTrackParameters;
39
use BigBlueButton\Parameters\UpdateRecordingsParameters;
40
use BigBlueButton\Responses\ApiVersionResponse;
41
use BigBlueButton\Responses\CreateMeetingResponse;
42
use BigBlueButton\Responses\DeleteRecordingsResponse;
43
use BigBlueButton\Responses\EndMeetingResponse;
44
use BigBlueButton\Responses\GetMeetingInfoResponse;
45
use BigBlueButton\Responses\GetMeetingsResponse;
46
use BigBlueButton\Responses\GetRecordingsResponse;
47
use BigBlueButton\Responses\GetRecordingTextTracksResponse;
48
use BigBlueButton\Responses\HooksCreateResponse;
49
use BigBlueButton\Responses\HooksDestroyResponse;
50
use BigBlueButton\Responses\HooksListResponse;
51
use BigBlueButton\Responses\IsMeetingRunningResponse;
52
use BigBlueButton\Responses\JoinMeetingResponse;
53
use BigBlueButton\Responses\PublishRecordingsResponse;
54
use BigBlueButton\Responses\PutRecordingTextTrackResponse;
55
use BigBlueButton\Responses\UpdateRecordingsResponse;
56
use BigBlueButton\Util\UrlBuilder;
57
58
/**
59
 * Class BigBlueButton.
60
 */
61
class BigBlueButton
62
{
63
    protected string $bbbSecret;
64
    protected string $bbbBaseUrl;
65
    protected UrlBuilder $urlBuilder;
66
    protected string $jSessionId;
67
68
    protected string $hashingAlgorithm;
69
70
    /**
71
     * @var array<int, mixed>
72
     */
73
    protected array $curlOpts = [];
74
    protected int $timeOut    = 10;
75
76
    /**
77
     * @param null|array<string, mixed> $opts
78
     */
79
    public function __construct(?string $baseUrl = null, ?string $secret = null, ?array $opts = [])
80
    {
81
        // Provide an early error message if configuration is wrong
82
        if (is_null($secret) && false === getenv('BBB_SERVER_BASE_URL')) {
83
            throw new \RuntimeException('No BBB-Server-Url found! Please provide it either in constructor ' .
84
                "(1st argument) or by environment variable 'BBB_SERVER_BASE_URL'!");
85
        }
86
87
        if (is_null($secret) && false === getenv('BBB_SECRET') && false === getenv('BBB_SECURITY_SALT')) {
88
            throw new \RuntimeException('No BBB-Secret (or BBB-Salt) found! Please provide it either in constructor ' .
89
                "(2nd argument) or by environment variable 'BBB_SECRET' (or 'BBB_SECURITY_SALT')!");
90
        }
91
92
        // Keeping backward compatibility with older deployed versions
93
        // BBB_SECRET is the new variable name and have higher priority against the old named BBB_SECURITY_SALT
94
        // Reminder: getenv() will return FALSE if not set. But bool is not accepted by $this->bbbSecret
95
        //           nor $this->bbbBaseUrl (only strings), thus FALSE will be converted automatically to an empty
96
        //           string (''). Having a bool should be not possible due to the checks above and the automated
97
        //           conversion, but PHPStan is still unhappy, so it's covered explicit by adding `?: ''`.
98
        $this->bbbBaseUrl       = $baseUrl ?: getenv('BBB_SERVER_BASE_URL') ?: '';
99
        $this->bbbSecret        = $secret ?: getenv('BBB_SECRET') ?: getenv('BBB_SECURITY_SALT') ?: '';
100
        $this->hashingAlgorithm = HashingAlgorithm::SHA_256;
101
        $this->urlBuilder       = new UrlBuilder($this->bbbSecret, $this->bbbBaseUrl, $this->hashingAlgorithm);
102
        $this->curlOpts         = $opts['curl'] ?? [];
103
    }
104
105
    public function setHashingAlgorithm(string $hashingAlgorithm): void
106
    {
107
        $this->hashingAlgorithm = $hashingAlgorithm;
108
        $this->urlBuilder->setHashingAlgorithm($hashingAlgorithm);
109
    }
110
111
    /**
112
     * @throws BadResponseException|\RuntimeException
113
     */
114
    public function getApiVersion(): ApiVersionResponse
115
    {
116
        $xml = $this->processXmlResponse($this->urlBuilder->buildUrl());
117
118
        return new ApiVersionResponse($xml);
119
    }
120
121
    // __________________ BBB ADMINISTRATION METHODS _________________
122
    /* The methods in the following section support the following categories of the BBB API:
123
    -- create
124
    -- join
125
    -- end
126
    -- insertDocument
127
    */
128
129
    public function getCreateMeetingUrl(CreateMeetingParameters $createMeetingParams): string
130
    {
131
        return $this->urlBuilder->buildUrl(ApiMethod::CREATE, $createMeetingParams->getHTTPQuery());
132
    }
133
134
    /**
135
     * @throws BadResponseException|\RuntimeException
136
     */
137
    public function createMeeting(CreateMeetingParameters $createMeetingParams): CreateMeetingResponse
138
    {
139
        $xml = $this->processXmlResponse($this->getCreateMeetingUrl($createMeetingParams), $createMeetingParams->getPresentationsAsXML());
140
141
        return new CreateMeetingResponse($xml);
142
    }
143
144
    public function getJoinMeetingURL(JoinMeetingParameters $joinMeetingParams): string
145
    {
146
        return $this->urlBuilder->buildUrl(ApiMethod::JOIN, $joinMeetingParams->getHTTPQuery());
147
    }
148
149
    /**
150
     * @throws BadResponseException|\RuntimeException
151
     */
152
    public function joinMeeting(JoinMeetingParameters $joinMeetingParams): JoinMeetingResponse
153
    {
154
        $xml = $this->processXmlResponse($this->getJoinMeetingURL($joinMeetingParams));
155
156
        return new JoinMeetingResponse($xml);
157
    }
158
159
    public function getEndMeetingURL(EndMeetingParameters $endParams): string
160
    {
161
        return $this->urlBuilder->buildUrl(ApiMethod::END, $endParams->getHTTPQuery());
162
    }
163
164
    /**
165
     * @throws BadResponseException|\RuntimeException
166
     */
167
    public function endMeeting(EndMeetingParameters $endParams): EndMeetingResponse
168
    {
169
        $xml = $this->processXmlResponse($this->getEndMeetingURL($endParams));
170
171
        return new EndMeetingResponse($xml);
172
    }
173
174
    public function getInsertDocumentUrl(InsertDocumentParameters $insertDocumentParameters): string
175
    {
176
        return $this->urlBuilder->buildUrl(ApiMethod::INSERT_DOCUMENT, $insertDocumentParameters->getHTTPQuery());
177
    }
178
179
    /**
180
     * @throws BadResponseException|\RuntimeException
181
     */
182
    public function insertDocument(InsertDocumentParameters $insertDocumentParams): CreateMeetingResponse
183
    {
184
        $xml = $this->processXmlResponse($this->getInsertDocumentUrl($insertDocumentParams), $insertDocumentParams->getPresentationsAsXML());
185
186
        return new CreateMeetingResponse($xml);
187
    }
188
189
    // __________________ BBB MONITORING METHODS _________________
190
    /* The methods in the following section support the following categories of the BBB API:
191
    -- isMeetingRunning
192
    -- getMeetings
193
    -- getMeetingInfo
194
    */
195
196
    public function getIsMeetingRunningUrl(IsMeetingRunningParameters $meetingParams): string
197
    {
198
        return $this->urlBuilder->buildUrl(ApiMethod::IS_MEETING_RUNNING, $meetingParams->getHTTPQuery());
199
    }
200
201
    /**
202
     * @throws BadResponseException|\RuntimeException
203
     */
204
    public function isMeetingRunning(IsMeetingRunningParameters $meetingParams): IsMeetingRunningResponse
205
    {
206
        $xml = $this->processXmlResponse($this->getIsMeetingRunningUrl($meetingParams));
207
208
        return new IsMeetingRunningResponse($xml);
209
    }
210
211
    /**
212
     * Checks weather a meeting is existing.
213
     *
214
     * @see https://github.com/bigbluebutton/bigbluebutton/issues/8246
215
     *
216
     * @throws BadResponseException
217
     */
218
    public function isMeetingExisting(string $meetingId): bool
219
    {
220
        $meetings = $this->getMeetings()->getMeetings();
221
222
        foreach ($meetings as $meeting) {
223
            if ($meetingId === $meeting->getMeetingId()) {
224
                return true;
225
            }
226
        }
227
228
        return false;
229
    }
230
231
    public function getMeetingsUrl(): string
232
    {
233
        return $this->urlBuilder->buildUrl(ApiMethod::GET_MEETINGS);
234
    }
235
236
    /**
237
     * @throws BadResponseException|\RuntimeException
238
     */
239
    public function getMeetings(): GetMeetingsResponse
240
    {
241
        $xml = $this->processXmlResponse($this->getMeetingsUrl());
242
243
        return new GetMeetingsResponse($xml);
244
    }
245
246
    public function getMeetingInfoUrl(GetMeetingInfoParameters $meetingParams): string
247
    {
248
        return $this->urlBuilder->buildUrl(ApiMethod::GET_MEETING_INFO, $meetingParams->getHTTPQuery());
249
    }
250
251
    /**
252
     * @throws BadResponseException|\RuntimeException
253
     */
254
    public function getMeetingInfo(GetMeetingInfoParameters $meetingParams): GetMeetingInfoResponse
255
    {
256
        $xml = $this->processXmlResponse($this->getMeetingInfoUrl($meetingParams));
257
258
        return new GetMeetingInfoResponse($xml);
259
    }
260
261
    // __________________ BBB RECORDING METHODS _________________
262
    /* The methods in the following section support the following categories of the BBB API:
263
    -- getRecordings
264
    -- publishRecordings
265
    -- deleteRecordings
266
    */
267
268
    public function getRecordingsUrl(GetRecordingsParameters $recordingsParams): string
269
    {
270
        return $this->urlBuilder->buildUrl(ApiMethod::GET_RECORDINGS, $recordingsParams->getHTTPQuery());
271
    }
272
273
    /**
274
     * @param mixed $recordingParams
275
     *
276
     * @throws BadResponseException|\RuntimeException
277
     */
278
    public function getRecordings($recordingParams): GetRecordingsResponse
279
    {
280
        $xml = $this->processXmlResponse($this->getRecordingsUrl($recordingParams));
281
282
        return new GetRecordingsResponse($xml);
283
    }
284
285
    public function getPublishRecordingsUrl(PublishRecordingsParameters $recordingParams): string
286
    {
287
        return $this->urlBuilder->buildUrl(ApiMethod::PUBLISH_RECORDINGS, $recordingParams->getHTTPQuery());
288
    }
289
290
    public function publishRecordings(PublishRecordingsParameters $recordingParams): PublishRecordingsResponse
291
    {
292
        $xml = $this->processXmlResponse($this->getPublishRecordingsUrl($recordingParams));
293
294
        return new PublishRecordingsResponse($xml);
295
    }
296
297
    public function getDeleteRecordingsUrl(DeleteRecordingsParameters $recordingParams): string
298
    {
299
        return $this->urlBuilder->buildUrl(ApiMethod::DELETE_RECORDINGS, $recordingParams->getHTTPQuery());
300
    }
301
302
    /**
303
     * @throws BadResponseException|\RuntimeException
304
     */
305
    public function deleteRecordings(DeleteRecordingsParameters $recordingParams): DeleteRecordingsResponse
306
    {
307
        $xml = $this->processXmlResponse($this->getDeleteRecordingsUrl($recordingParams));
308
309
        return new DeleteRecordingsResponse($xml);
310
    }
311
312
    public function getUpdateRecordingsUrl(UpdateRecordingsParameters $recordingParams): string
313
    {
314
        return $this->urlBuilder->buildUrl(ApiMethod::UPDATE_RECORDINGS, $recordingParams->getHTTPQuery());
315
    }
316
317
    /**
318
     * @throws BadResponseException|\RuntimeException
319
     */
320
    public function updateRecordings(UpdateRecordingsParameters $recordingParams): UpdateRecordingsResponse
321
    {
322
        $xml = $this->processXmlResponse($this->getUpdateRecordingsUrl($recordingParams));
323
324
        return new UpdateRecordingsResponse($xml);
325
    }
326
327
    public function getRecordingTextTracksUrl(GetRecordingTextTracksParameters $getRecordingTextTracksParameters): string
328
    {
329
        return $this->urlBuilder->buildUrl(ApiMethod::GET_RECORDING_TEXT_TRACKS, $getRecordingTextTracksParameters->getHTTPQuery());
330
    }
331
332
    public function getRecordingTextTracks(GetRecordingTextTracksParameters $getRecordingTextTracksParams): GetRecordingTextTracksResponse
333
    {
334
        $json = $this->processJsonResponse($this->getRecordingTextTracksUrl($getRecordingTextTracksParams));
335
336
        return new GetRecordingTextTracksResponse($json);
337
    }
338
339
    public function getPutRecordingTextTrackUrl(PutRecordingTextTrackParameters $putRecordingTextTrackParams): string
340
    {
341
        return $this->urlBuilder->buildUrl(ApiMethod::PUT_RECORDING_TEXT_TRACK, $putRecordingTextTrackParams->getHTTPQuery());
342
    }
343
344
    public function putRecordingTextTrack(PutRecordingTextTrackParameters $putRecordingTextTrackParams): PutRecordingTextTrackResponse
345
    {
346
        $json = $this->processJsonResponse($this->getPutRecordingTextTrackUrl($putRecordingTextTrackParams));
347
348
        return new PutRecordingTextTrackResponse($json);
349
    }
350
351
    // ____________________ WEB HOOKS METHODS ___________________
352
353
    public function getHooksCreateUrl(HooksCreateParameters $hookCreateParams): string
354
    {
355
        return $this->urlBuilder->buildUrl(ApiMethod::HOOKS_CREATE, $hookCreateParams->getHTTPQuery());
356
    }
357
358
    /**
359
     * @param mixed $hookCreateParams
360
     *
361
     * @throws BadResponseException
362
     */
363
    public function hooksCreate($hookCreateParams): HooksCreateResponse
364
    {
365
        $xml = $this->processXmlResponse($this->getHooksCreateUrl($hookCreateParams));
366
367
        return new HooksCreateResponse($xml);
368
    }
369
370
    public function getHooksListUrl(): string
371
    {
372
        return $this->urlBuilder->buildUrl(ApiMethod::HOOKS_LIST);
373
    }
374
375
    public function hooksList(): HooksListResponse
376
    {
377
        $xml = $this->processXmlResponse($this->getHooksListUrl());
378
379
        return new HooksListResponse($xml);
380
    }
381
382
    public function getHooksDestroyUrl(HooksDestroyParameters $hooksDestroyParams): string
383
    {
384
        return $this->urlBuilder->buildUrl(ApiMethod::HOOKS_DESTROY, $hooksDestroyParams->getHTTPQuery());
385
    }
386
387
    /**
388
     * @param mixed $hooksDestroyParams
389
     *
390
     * @throws BadResponseException
391
     */
392
    public function hooksDestroy($hooksDestroyParams): HooksDestroyResponse
393
    {
394
        $xml = $this->processXmlResponse($this->getHooksDestroyUrl($hooksDestroyParams));
395
396
        return new HooksDestroyResponse($xml);
397
    }
398
399
    // ____________________ SPECIAL METHODS ___________________
400
401
    public function getJSessionId(): string
402
    {
403
        return $this->jSessionId;
404
    }
405
406
    public function setJSessionId(string $jSessionId): void
407
    {
408
        $this->jSessionId = $jSessionId;
409
    }
410
411
    /**
412
     * @param array<int, mixed> $curlOpts
413
     */
414
    public function setCurlOpts(array $curlOpts): void
415
    {
416
        $this->curlOpts = $curlOpts;
417
    }
418
419
    /**
420
     * Set Curl Timeout (Optional), Default 10 Seconds.
421
     */
422
    public function setTimeOut(int $TimeOutInSeconds): self
423
    {
424
        $this->timeOut = $TimeOutInSeconds;
425
426
        return $this;
427
    }
428
429
    /**
430
     * Public accessor for buildUrl.
431
     */
432
    public function buildUrl(string $method = '', string $params = '', bool $append = true): string
433
    {
434
        return $this->urlBuilder->buildUrl($method, $params, $append);
435
    }
436
437
    // ____________________ INTERNAL CLASS METHODS ___________________
438
439
    /**
440
     * A private utility method used by other public methods to request HTTP responses.
441
     *
442
     * @throws BadResponseException|\RuntimeException
443
     */
444
    private function sendRequest(string $url, string $payload = '', string $contentType = 'application/xml'): string
445
    {
446
        if (!extension_loaded('curl')) {
447
            throw new \RuntimeException('Post XML data set but curl PHP module is not installed or not enabled.');
448
        }
449
450
        $ch         = curl_init();
451
        $cookieFile = tmpfile();
452
453
        if (!$ch) {  // @phpstan-ignore-line
454
            throw new \RuntimeException('Unhandled curl error: ' . curl_error($ch));
455
        }
456
457
        // JSESSIONID
458
        if ($cookieFile) {
0 ignored issues
show
introduced by
$cookieFile is of type resource, thus it always evaluated to false.
Loading history...
459
            $cookieFilePath = stream_get_meta_data($cookieFile)['uri'];
460
            $cookies        = file_get_contents($cookieFilePath);
461
462
            curl_setopt($ch, CURLOPT_COOKIEFILE, $cookieFilePath);
463
            curl_setopt($ch, CURLOPT_COOKIEJAR, $cookieFilePath);
464
465
            if ($cookies) {
466
                if (false !== mb_strpos($cookies, 'JSESSIONID')) {
467
                    preg_match('/(?:JSESSIONID\s*)(?<JSESSIONID>.*)/', $cookies, $output_array);
468
                    $this->setJSessionId($output_array['JSESSIONID']);
469
                }
470
            }
471
        }
472
473
        // PAYLOAD
474
        if (!empty($payload)) {
475
            curl_setopt($ch, CURLOPT_HEADER, 0);
476
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
477
            curl_setopt($ch, CURLOPT_POST, 1);
478
            curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
479
            curl_setopt($ch, CURLOPT_HTTPHEADER, [
480
                'Content-type: ' . $contentType,
481
                'Content-length: ' . mb_strlen($payload),
482
            ]);
483
        }
484
485
        // OTHERS
486
        foreach ($this->curlOpts as $opt => $value) {
487
            curl_setopt($ch, $opt, $value);
488
        }
489
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
490
        curl_setopt($ch, CURLOPT_ENCODING, 'UTF-8');
491
        curl_setopt($ch, CURLOPT_URL, $url);
492
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
493
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
494
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->timeOut);
495
496
        // EXECUTE and RESULT
497
        $data     = curl_exec($ch);
498
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
499
500
        // ANALYSE
501
        if (false === $data) {
502
            throw new \RuntimeException('Unhandled curl error: ' . curl_error($ch));
503
        }
504
505
        if (is_bool($data)) {
506
            throw new \RuntimeException('Curl error: BOOL received, but STRING expected.');
507
        }
508
509
        if ($httpCode < 200 || $httpCode >= 300) {
510
            throw new BadResponseException('Bad response, HTTP code: ' . $httpCode);
511
        }
512
513
        // CLOSE AND UNSET
514
        curl_close($ch);
515
        unset($ch);
516
517
        // RETURN
518
        return $data;
519
    }
520
521
    /**
522
     * A private utility method used by other public methods to process XML responses.
523
     *
524
     * @throws BadResponseException|\Exception
525
     */
526
    private function processXmlResponse(string $url, string $payload = '', string $contentType = 'application/xml'): \SimpleXMLElement
527
    {
528
        $response = $this->sendRequest($url, $payload, $contentType);
529
530
        return new \SimpleXMLElement($response);
531
    }
532
533
    /**
534
     * A private utility method used by other public methods to process json responses.
535
     *
536
     * @throws BadResponseException
537
     */
538
    private function processJsonResponse(string $url, string $payload = '', string $contentType = 'application/json'): string
539
    {
540
        return $this->sendRequest($url, $payload, $contentType);
541
    }
542
}
543