Completed
Push — master ( 736635...5b97c8 )
by Ghazi
25s queued 23s
created

BigBlueButton   F

Complexity

Total Complexity 64

Size/Duplication

Total Lines 458
Duplicated Lines 0 %

Importance

Changes 24
Bugs 3 Features 2
Metric Value
wmc 64
eloc 121
c 24
b 3
f 2
dl 0
loc 458
rs 3.28

43 Methods

Rating   Name   Duplication   Size   Complexity  
A endMeeting() 0 5 1
A getUpdateRecordingsUrl() 0 3 1
A getPutRecordingTextTrackUrl() 0 3 1
A deleteRecordings() 0 5 1
A getJoinMeetingURL() 0 3 1
A publishRecordings() 0 5 1
A getIsMeetingRunningUrl() 0 3 1
A getPublishRecordingsUrl() 0 3 1
A setHashingAlgorithm() 0 4 1
A getDeleteRecordingsUrl() 0 3 1
A buildUrl() 0 3 1
A hooksDestroy() 0 5 1
A getMeetingInfo() 0 5 1
A processJsonResponse() 0 3 1
A createMeeting() 0 5 1
A setCurlOpts() 0 3 1
A getMeetings() 0 5 1
A insertDocument() 0 5 1
A getHooksCreateUrl() 0 3 1
A getApiVersion() 0 5 1
A getRecordingTextTracks() 0 5 1
A setJSessionId() 0 3 1
A getInsertDocumentUrl() 0 3 1
A hooksList() 0 5 1
A getMeetingsUrl() 0 3 1
A getRecordingTextTracksUrl() 0 3 1
A getRecordingsUrl() 0 3 1
A getEndMeetingURL() 0 3 1
A getMeetingInfoUrl() 0 3 1
A getRecordings() 0 5 1
A hooksCreate() 0 5 1
A putRecordingTextTrack() 0 5 1
A setTimeOut() 0 5 1
A isMeetingRunning() 0 5 1
A getJSessionId() 0 3 1
A processXmlResponse() 0 3 1
A updateRecordings() 0 5 1
A joinMeeting() 0 5 1
A getHooksListUrl() 0 3 1
A getHooksDestroyUrl() 0 3 1
C sendRequest() 0 75 12
B __construct() 0 24 11
A getCreateMeetingUrl() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like BigBlueButton often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use BigBlueButton, and based on these observations, apply Extract Interface, too.

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
    public function getMeetingsUrl(): string
212
    {
213
        return $this->urlBuilder->buildUrl(ApiMethod::GET_MEETINGS);
214
    }
215
216
    /**
217
     * @throws BadResponseException|\RuntimeException
218
     */
219
    public function getMeetings(): GetMeetingsResponse
220
    {
221
        $xml = $this->processXmlResponse($this->getMeetingsUrl());
222
223
        return new GetMeetingsResponse($xml);
224
    }
225
226
    public function getMeetingInfoUrl(GetMeetingInfoParameters $meetingParams): string
227
    {
228
        return $this->urlBuilder->buildUrl(ApiMethod::GET_MEETING_INFO, $meetingParams->getHTTPQuery());
229
    }
230
231
    /**
232
     * @throws BadResponseException|\RuntimeException
233
     */
234
    public function getMeetingInfo(GetMeetingInfoParameters $meetingParams): GetMeetingInfoResponse
235
    {
236
        $xml = $this->processXmlResponse($this->getMeetingInfoUrl($meetingParams));
237
238
        return new GetMeetingInfoResponse($xml);
239
    }
240
241
    // __________________ BBB RECORDING METHODS _________________
242
    /* The methods in the following section support the following categories of the BBB API:
243
    -- getRecordings
244
    -- publishRecordings
245
    -- deleteRecordings
246
    */
247
248
    public function getRecordingsUrl(GetRecordingsParameters $recordingsParams): string
249
    {
250
        return $this->urlBuilder->buildUrl(ApiMethod::GET_RECORDINGS, $recordingsParams->getHTTPQuery());
251
    }
252
253
    /**
254
     * @param mixed $recordingParams
255
     *
256
     * @throws BadResponseException|\RuntimeException
257
     */
258
    public function getRecordings($recordingParams): GetRecordingsResponse
259
    {
260
        $xml = $this->processXmlResponse($this->getRecordingsUrl($recordingParams));
261
262
        return new GetRecordingsResponse($xml);
263
    }
264
265
    public function getPublishRecordingsUrl(PublishRecordingsParameters $recordingParams): string
266
    {
267
        return $this->urlBuilder->buildUrl(ApiMethod::PUBLISH_RECORDINGS, $recordingParams->getHTTPQuery());
268
    }
269
270
    public function publishRecordings(PublishRecordingsParameters $recordingParams): PublishRecordingsResponse
271
    {
272
        $xml = $this->processXmlResponse($this->getPublishRecordingsUrl($recordingParams));
273
274
        return new PublishRecordingsResponse($xml);
275
    }
276
277
    public function getDeleteRecordingsUrl(DeleteRecordingsParameters $recordingParams): string
278
    {
279
        return $this->urlBuilder->buildUrl(ApiMethod::DELETE_RECORDINGS, $recordingParams->getHTTPQuery());
280
    }
281
282
    /**
283
     * @throws BadResponseException|\RuntimeException
284
     */
285
    public function deleteRecordings(DeleteRecordingsParameters $recordingParams): DeleteRecordingsResponse
286
    {
287
        $xml = $this->processXmlResponse($this->getDeleteRecordingsUrl($recordingParams));
288
289
        return new DeleteRecordingsResponse($xml);
290
    }
291
292
    public function getUpdateRecordingsUrl(UpdateRecordingsParameters $recordingParams): string
293
    {
294
        return $this->urlBuilder->buildUrl(ApiMethod::UPDATE_RECORDINGS, $recordingParams->getHTTPQuery());
295
    }
296
297
    /**
298
     * @throws BadResponseException|\RuntimeException
299
     */
300
    public function updateRecordings(UpdateRecordingsParameters $recordingParams): UpdateRecordingsResponse
301
    {
302
        $xml = $this->processXmlResponse($this->getUpdateRecordingsUrl($recordingParams));
303
304
        return new UpdateRecordingsResponse($xml);
305
    }
306
307
    public function getRecordingTextTracksUrl(GetRecordingTextTracksParameters $getRecordingTextTracksParameters): string
308
    {
309
        return $this->urlBuilder->buildUrl(ApiMethod::GET_RECORDING_TEXT_TRACKS, $getRecordingTextTracksParameters->getHTTPQuery());
310
    }
311
312
    public function getRecordingTextTracks(GetRecordingTextTracksParameters $getRecordingTextTracksParams): GetRecordingTextTracksResponse
313
    {
314
        $json = $this->processJsonResponse($this->getRecordingTextTracksUrl($getRecordingTextTracksParams));
315
316
        return new GetRecordingTextTracksResponse($json);
317
    }
318
319
    public function getPutRecordingTextTrackUrl(PutRecordingTextTrackParameters $putRecordingTextTrackParams): string
320
    {
321
        return $this->urlBuilder->buildUrl(ApiMethod::PUT_RECORDING_TEXT_TRACK, $putRecordingTextTrackParams->getHTTPQuery());
322
    }
323
324
    public function putRecordingTextTrack(PutRecordingTextTrackParameters $putRecordingTextTrackParams): PutRecordingTextTrackResponse
325
    {
326
        $json = $this->processJsonResponse($this->getPutRecordingTextTrackUrl($putRecordingTextTrackParams));
327
328
        return new PutRecordingTextTrackResponse($json);
329
    }
330
331
    // ____________________ WEB HOOKS METHODS ___________________
332
333
    public function getHooksCreateUrl(HooksCreateParameters $hookCreateParams): string
334
    {
335
        return $this->urlBuilder->buildUrl(ApiMethod::HOOKS_CREATE, $hookCreateParams->getHTTPQuery());
336
    }
337
338
    /**
339
     * @param mixed $hookCreateParams
340
     *
341
     * @throws BadResponseException
342
     */
343
    public function hooksCreate($hookCreateParams): HooksCreateResponse
344
    {
345
        $xml = $this->processXmlResponse($this->getHooksCreateUrl($hookCreateParams));
346
347
        return new HooksCreateResponse($xml);
348
    }
349
350
    public function getHooksListUrl(): string
351
    {
352
        return $this->urlBuilder->buildUrl(ApiMethod::HOOKS_LIST);
353
    }
354
355
    public function hooksList(): HooksListResponse
356
    {
357
        $xml = $this->processXmlResponse($this->getHooksListUrl());
358
359
        return new HooksListResponse($xml);
360
    }
361
362
    public function getHooksDestroyUrl(HooksDestroyParameters $hooksDestroyParams): string
363
    {
364
        return $this->urlBuilder->buildUrl(ApiMethod::HOOKS_DESTROY, $hooksDestroyParams->getHTTPQuery());
365
    }
366
367
    /**
368
     * @param mixed $hooksDestroyParams
369
     *
370
     * @throws BadResponseException
371
     */
372
    public function hooksDestroy($hooksDestroyParams): HooksDestroyResponse
373
    {
374
        $xml = $this->processXmlResponse($this->getHooksDestroyUrl($hooksDestroyParams));
375
376
        return new HooksDestroyResponse($xml);
377
    }
378
379
    // ____________________ SPECIAL METHODS ___________________
380
381
    public function getJSessionId(): string
382
    {
383
        return $this->jSessionId;
384
    }
385
386
    public function setJSessionId(string $jSessionId): void
387
    {
388
        $this->jSessionId = $jSessionId;
389
    }
390
391
    /**
392
     * @param array<int, mixed> $curlOpts
393
     */
394
    public function setCurlOpts(array $curlOpts): void
395
    {
396
        $this->curlOpts = $curlOpts;
397
    }
398
399
    /**
400
     * Set Curl Timeout (Optional), Default 10 Seconds.
401
     */
402
    public function setTimeOut(int $TimeOutInSeconds): self
403
    {
404
        $this->timeOut = $TimeOutInSeconds;
405
406
        return $this;
407
    }
408
409
    /**
410
     * Public accessor for buildUrl.
411
     */
412
    public function buildUrl(string $method = '', string $params = '', bool $append = true): string
413
    {
414
        return $this->urlBuilder->buildUrl($method, $params, $append);
415
    }
416
417
    // ____________________ INTERNAL CLASS METHODS ___________________
418
419
    /**
420
     * A private utility method used by other public methods to request HTTP responses.
421
     *
422
     * @throws BadResponseException|\RuntimeException
423
     */
424
    private function sendRequest(string $url, string $payload = '', string $contentType = 'application/xml'): string
425
    {
426
        if (!extension_loaded('curl')) {
427
            throw new \RuntimeException('Post XML data set but curl PHP module is not installed or not enabled.');
428
        }
429
430
        $ch         = curl_init();
431
        $cookieFile = tmpfile();
432
433
        if (!$ch) {  // @phpstan-ignore-line
434
            throw new \RuntimeException('Unhandled curl error: ' . curl_error($ch));
435
        }
436
437
        // JSESSIONID
438
        if ($cookieFile) {
0 ignored issues
show
introduced by
$cookieFile is of type resource, thus it always evaluated to false.
Loading history...
439
            $cookieFilePath = stream_get_meta_data($cookieFile)['uri'];
440
            $cookies        = file_get_contents($cookieFilePath);
441
442
            curl_setopt($ch, CURLOPT_COOKIEFILE, $cookieFilePath);
443
            curl_setopt($ch, CURLOPT_COOKIEJAR, $cookieFilePath);
444
445
            if ($cookies) {
446
                if (false !== mb_strpos($cookies, 'JSESSIONID')) {
447
                    preg_match('/(?:JSESSIONID\s*)(?<JSESSIONID>.*)/', $cookies, $output_array);
448
                    $this->setJSessionId($output_array['JSESSIONID']);
449
                }
450
            }
451
        }
452
453
        // PAYLOAD
454
        if (!empty($payload)) {
455
            curl_setopt($ch, CURLOPT_HEADER, 0);
456
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
457
            curl_setopt($ch, CURLOPT_POST, 1);
458
            curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
459
            curl_setopt($ch, CURLOPT_HTTPHEADER, [
460
                'Content-type: ' . $contentType,
461
                'Content-length: ' . mb_strlen($payload),
462
            ]);
463
        }
464
465
        // OTHERS
466
        foreach ($this->curlOpts as $opt => $value) {
467
            curl_setopt($ch, $opt, $value);
468
        }
469
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
470
        curl_setopt($ch, CURLOPT_ENCODING, 'UTF-8');
471
        curl_setopt($ch, CURLOPT_URL, $url);
472
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
473
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
474
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->timeOut);
475
476
        // EXECUTE and RESULT
477
        $data     = curl_exec($ch);
478
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
479
480
        // ANALYSE
481
        if (false === $data) {
482
            throw new \RuntimeException('Unhandled curl error: ' . curl_error($ch));
483
        }
484
485
        if (is_bool($data)) {
486
            throw new \RuntimeException('Curl error: BOOL received, but STRING expected.');
487
        }
488
489
        if ($httpCode < 200 || $httpCode >= 300) {
490
            throw new BadResponseException('Bad response, HTTP code: ' . $httpCode);
491
        }
492
493
        // CLOSE AND UNSET
494
        curl_close($ch);
495
        unset($ch);
496
497
        // RETURN
498
        return $data;
499
    }
500
501
    /**
502
     * A private utility method used by other public methods to process XML responses.
503
     *
504
     * @throws BadResponseException|\Exception
505
     */
506
    private function processXmlResponse(string $url, string $payload = '', string $contentType = 'application/xml'): \SimpleXMLElement
507
    {
508
        return new \SimpleXMLElement($this->sendRequest($url, $payload, $contentType));
509
    }
510
511
    /**
512
     * A private utility method used by other public methods to process json responses.
513
     *
514
     * @throws BadResponseException
515
     */
516
    private function processJsonResponse(string $url, string $payload = '', string $contentType = 'application/json'): string
517
    {
518
        return $this->sendRequest($url, $payload, $contentType);
519
    }
520
}
521