Passed
Pull Request — master (#6711)
by
unknown
09:17
created

Bbb::__construct()   F

Complexity

Conditions 27
Paths > 20000

Size

Total Lines 122
Code Lines 72

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 27
eloc 72
c 1
b 0
f 0
nc 737568
nop 4
dl 0
loc 122
rs 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\ConferenceActivity;
6
use Chamilo\CoreBundle\Entity\ConferenceMeeting;
7
use Chamilo\CoreBundle\Entity\ConferenceRecording;
8
use Chamilo\CoreBundle\Enums\ActionIcon;
9
use Chamilo\CoreBundle\Enums\ObjectIcon;
10
use Chamilo\CoreBundle\Enums\StateIcon;
11
use Chamilo\CoreBundle\Repository\ConferenceActivityRepository;
12
use Chamilo\CoreBundle\Repository\ConferenceMeetingRepository;
13
use Chamilo\CoreBundle\Repository\ConferenceRecordingRepository;
14
use Chamilo\UserBundle\Entity\User;
15
16
/**
17
 * Class Bbb
18
 * This script initiates a video conference session, calling the BigBlueButton
19
 * API BigBlueButton-Chamilo connector class
20
 */
21
class Bbb
22
{
23
    public $url;
24
    public $salt;
25
    public $api;
26
    public $userCompleteName = '';
27
    public $protocol = 'http://';
28
    public $debug = false;
29
    public $logoutUrl = '';
30
    public $pluginEnabled = false;
31
    public $enableGlobalConference = false;
32
    public $enableGlobalConferencePerUser = false;
33
    public $isGlobalConference = false;
34
    public $groupSupport = false;
35
    public $userSupport = false;
36
    public $accessUrl = 1;
37
    public $userId = 0;
38
    public $plugin;
39
    private $courseCode;
40
    private $courseId;
41
    private $sessionId;
42
    private $groupId;
43
    private $maxUsersLimit;
44
    private $urlWithProtocol = '';
45
46
    /**
47
     * Constructor (generates a connection to the API and the Chamilo settings
48
     * required for the connection to the video conference server)
49
     *
50
     * @param string $host
51
     * @param string $salt
52
     * @param bool   $isGlobalConference
53
     * @param int    $isGlobalPerUser
54
     */
55
    public function __construct(
56
        $host = '',
57
        $salt = '',
58
        $isGlobalConference = false,
59
        $isGlobalPerUser = 0
60
    ) {
61
        $this->courseCode = api_get_course_id();
62
        $this->courseId = api_get_course_int_id();
63
        $this->sessionId = api_get_session_id();
64
        $this->groupId = api_get_group_id();
65
66
        // Initialize video server settings from global settings
67
        $this->plugin = BbbPlugin::create();
68
        $bbbPluginEnabled = $this->plugin->get('tool_enable');
69
70
        $bbb_host = !empty($host) ? $host : $this->plugin->get('host');
71
        $bbb_salt = !empty($salt) ? $salt : $this->plugin->get('salt');
72
73
        $this->table = Database::get_main_table('conference_meeting');
74
        $this->enableGlobalConference = $this->plugin->get('enable_global_conference') === 'true';
75
        $this->isGlobalConference = (bool) $isGlobalConference;
76
77
        $columns = Database::listTableColumns($this->table);
78
        $this->groupSupport = isset($columns['group_id']) ? true : false;
79
        $this->userSupport = isset($columns['user_id']) ? true : false;
80
        $this->accessUrl = api_get_current_access_url_id();
81
82
        $this->enableGlobalConferencePerUser = false;
83
        if ($this->userSupport && !empty($isGlobalPerUser)) {
84
            $this->enableGlobalConferencePerUser = $this->plugin->get('enable_global_conference_per_user') === 'true';
85
            $this->userId = $isGlobalPerUser;
86
        }
87
88
        if ($this->groupSupport) {
89
            // Plugin check
90
            $this->groupSupport = $this->plugin->get('enable_conference_in_course_groups') === 'true' ? true : false;
91
            if ($this->groupSupport) {
92
                // Platform check
93
                $bbbSetting = api_get_plugin_setting('bbb', 'enable_conference_in_course_groups') === 'true';
94
                if ($bbbSetting) {
95
                    // Course check
96
                    $courseInfo = api_get_course_info();
97
                    if ($courseInfo) {
98
                        $this->groupSupport = api_get_course_plugin_setting(
99
                                'bbb',
100
                                'bbb_enable_conference_in_groups',
101
                                $courseInfo
102
                            ) === '1';
103
                    }
104
                }
105
            }
106
        }
107
        $this->maxUsersLimit = $this->plugin->get('max_users_limit');
108
109
        if ($bbbPluginEnabled === 'true') {
110
            $userInfo = api_get_user_info();
111
            if (empty($userInfo) && !empty($isGlobalPerUser)) {
112
                // If we are following a link to a global "per user" conference
113
                // then generate a random guest name to join the conference
114
                // because there is no part of the process where we give a name
115
                //$this->userCompleteName = 'Guest'.rand(1000, 9999);
116
            } else {
117
                $this->userCompleteName = $userInfo['complete_name'];
118
            }
119
120
            if (api_is_anonymous()) {
121
                $this->userCompleteName = get_lang('Guest').'_'.rand(1000, 9999);
122
            }
123
124
            $this->salt = $bbb_salt;
125
            if (!empty($bbb_host)) {
126
                if (substr($bbb_host, -1, 1) !== '/') {
127
                    $bbb_host .= '/';
128
                }
129
                $this->url = $bbb_host;
130
                if (!preg_match('#/bigbluebutton/$#', $bbb_host)) {
131
                    $this->url = $bbb_host.'bigbluebutton/';
132
                }
133
            }
134
            $info = parse_url($bbb_host);
135
136
            if (isset($info['scheme'])) {
137
                $this->protocol = $info['scheme'].'://';
138
                $this->url = str_replace($this->protocol, '', $this->url);
139
                $urlWithProtocol = $bbb_host;
140
            } else {
141
                // We assume it's an http, if user wants to use https, the host *must* include the protocol.
142
                $this->protocol = 'http://';
143
                $urlWithProtocol = $this->protocol.$bbb_host;
144
            }
145
146
            // Normalize final URL with protocol to the /bigbluebutton/ endpoint
147
            if (!str_ends_with($urlWithProtocol, '/bigbluebutton/')) {
148
                $urlWithProtocol = rtrim($urlWithProtocol, '/').'/bigbluebutton/';
149
            }
150
            $this->urlWithProtocol = $urlWithProtocol;
151
152
            // Define constants safely (optional legacy compatibility)
153
            if (!defined('CONFIG_SERVER_BASE_URL')) {
154
                define('CONFIG_SERVER_BASE_URL', $this->url);
155
            }
156
            if (!defined('CONFIG_SERVER_PROTOCOL')) {
157
                define('CONFIG_SERVER_PROTOCOL', $this->protocol);
158
            }
159
            if (!defined('CONFIG_SERVER_URL_WITH_PROTOCOL')) {
160
                define('CONFIG_SERVER_URL_WITH_PROTOCOL', $this->urlWithProtocol);
161
            }
162
            if (!defined('CONFIG_SECURITY_SALT')) {
163
                // IMPORTANT: use $this->salt, not $this->securitySalt
164
                define('CONFIG_SECURITY_SALT', (string) $this->salt);
165
            }
166
167
            // Initialize API object only if we have both URL and salt
168
            if (!empty($this->urlWithProtocol) && !empty($this->salt)) {
169
                $this->api = new BigBlueButtonBN($this->urlWithProtocol, $this->salt);
170
                $this->pluginEnabled = true;
171
            } else {
172
                $this->api = null;
173
                $this->pluginEnabled = false;
174
            }
175
176
            $this->logoutUrl = $this->getListingUrl();
177
        }
178
    }
179
180
    private function ensureApi(): bool
181
    {
182
        if ($this->api instanceof BigBlueButtonBN) {
183
            return true;
184
        }
185
        $url = $this->urlWithProtocol ?: (defined('CONFIG_SERVER_URL_WITH_PROTOCOL') ? constant('CONFIG_SERVER_URL_WITH_PROTOCOL') : '');
186
        $salt = $this->salt ?: (defined('CONFIG_SECURITY_SALT') ? constant('CONFIG_SECURITY_SALT') : '');
187
188
        if ($url && $salt) {
189
            $this->api = new BigBlueButtonBN($url, $salt);
190
            return $this->api instanceof BigBlueButtonBN;
191
        }
192
193
        return false;
194
    }
195
196
    /**
197
     * @param int $courseId  Optional. Course ID.
198
     * @param int $sessionId Optional. Session ID.
199
     * @param int $groupId   Optional. Group ID.
200
     *
201
     * @return string
202
     */
203
    public function getListingUrl($courseId = 0, $sessionId = 0, $groupId = 0)
204
    {
205
        return api_get_path(WEB_PLUGIN_PATH).'Bbb/listing.php?'
206
            .$this->getUrlParams($courseId, $sessionId, $groupId);
207
    }
208
209
    /**
210
     * @param int $courseId  Optional. Course ID.
211
     * @param int $sessionId Optional. Session ID.
212
     * @param int $groupId   Optional. Group ID.
213
     *
214
     * @return string
215
     */
216
    public function getUrlParams($courseId = 0, $sessionId = 0, $groupId = 0)
217
    {
218
        if (empty($this->courseId) && !$courseId) {
219
            if ($this->isGlobalConferencePerUserEnabled()) {
220
                return 'global=1&user_id='.$this->userId;
221
            }
222
223
            if ($this->isGlobalConference()) {
224
                return 'global=1';
225
            }
226
227
            return '';
228
        }
229
230
        $defaultCourseId = (int) $this->courseId;
231
        if (!empty($courseId)) {
232
            $defaultCourseId = (int) $courseId;
233
        }
234
235
        return http_build_query(
236
            [
237
                'cid' => $defaultCourseId,
238
                'sid' => (int) $sessionId ?: $this->sessionId,
239
                'gid' => (int) $groupId ?: $this->groupId,
240
            ]
241
        );
242
    }
243
244
    /**
245
     * @return bool
246
     */
247
    public function isGlobalConferencePerUserEnabled()
248
    {
249
        return $this->enableGlobalConferencePerUser;
250
    }
251
252
    /**
253
     * @return bool
254
     */
255
    public function isGlobalConference()
256
    {
257
        if ($this->isGlobalConferenceEnabled() === false) {
258
            return false;
259
        }
260
261
        return (bool) $this->isGlobalConference;
262
    }
263
264
    /**
265
     * @return bool
266
     */
267
    public function isGlobalConferenceEnabled()
268
    {
269
        return $this->enableGlobalConference;
270
    }
271
272
    /**
273
     * @param array $userInfo
274
     *
275
     * @return bool
276
     */
277
    private static function normalizeSettingToArray($value): array
278
    {
279
        if (is_array($value)) {
280
            return $value;
281
        }
282
        if (is_string($value)) {
283
            $value = trim($value);
284
            if ($value === '') {
285
                return [];
286
            }
287
            $decoded = json_decode($value, true);
288
            if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
289
                return $decoded;
290
            }
291
            return array_values(
292
                array_filter(
293
                    array_map('trim', explode(',', $value)),
294
                    'strlen'
295
                )
296
            );
297
        }
298
        return [];
299
    }
300
301
    /**
302
     * Gets the global limit of users in a video-conference room.
303
     * This value can be overridden by course-specific values
304
     * @return  int Maximum number of users set globally
305
     */
306
    public function getMaxUsersLimit()
307
    {
308
        $limit = $this->maxUsersLimit;
309
        if ($limit <= 0) {
310
            $limit = 0;
311
        }
312
        $courseLimit = 0;
313
        $sessionLimit = 0;
314
315
        // Check course extra field
316
        if (!empty($this->courseId)) {
317
            $extraField = new ExtraField('course');
318
            $fieldIdList = $extraField->get_all(
319
                array('variable = ?' => 'plugin_bbb_course_users_limit')
320
            );
321
322
            if (!empty($fieldIdList)) {
323
                $fieldId = $fieldIdList[0]['id'] ?? null;
324
                if ($fieldId) {
325
                    $extraValue = new ExtraFieldValue('course');
326
                    $value = $extraValue->get_values_by_handler_and_field_id($this->courseId, $fieldId);
327
                    if (!empty($value['value'])) {
328
                        $courseLimit = (int) $value['value'];
329
                    }
330
                }
331
            }
332
        }
333
334
        // Check session extra field
335
        if (!empty($this->sessionId)) {
336
            $extraField = new ExtraField('session');
337
            $fieldIdList = $extraField->get_all(
338
                array('variable = ?' => 'plugin_bbb_session_users_limit')
339
            );
340
341
            if (!empty($fieldIdList)) {
342
                $fieldId = $fieldIdList[0]['id'] ?? null;
343
                if ($fieldId) {
344
                    $extraValue = new ExtraFieldValue('session');
345
                    $value = $extraValue->get_values_by_handler_and_field_id($this->sessionId, $fieldId);
346
                    if (!empty($value['value'])) {
347
                        $sessionLimit = (int) $value['value'];
348
                    }
349
                }
350
            }
351
        }
352
353
        if (!empty($sessionLimit)) {
354
            return $sessionLimit;
355
        } elseif (!empty($courseLimit)) {
356
            return $courseLimit;
357
        }
358
359
        return (int) $limit;
360
    }
361
362
    /**
363
     * Sets the global limit of users in a video-conference room.
364
     *
365
     * @param int Maximum number of users (globally)
366
     */
367
    public function setMaxUsersLimit($max)
368
    {
369
        if ($max < 0) {
370
            $max = 0;
371
        }
372
        $this->maxUsersLimit = (int) $max;
373
    }
374
375
    /**
376
     * See this file in you BBB to set up default values
377
     *
378
     * @param array $params Array of parameters that will be completed if not containing all expected variables
379
     *
380
     * /var/lib/tomcat6/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties
381
     *
382
     * More record information:
383
     * http://code.google.com/p/bigbluebutton/wiki/RecordPlaybackSpecification
384
     *
385
     * Default maximum number of users a meeting can have.
386
     * Doesn't get enforced yet but is the default value when the create
387
     * API doesn't pass a value.
388
     * defaultMaxUsers=20
389
     *
390
     * Default duration of the meeting in minutes.
391
     * Current default is 0 (meeting doesn't end).
392
     * defaultMeetingDuration=0
393
     *
394
     * Remove the meeting from memory when the end API is called.
395
     * This allows 3rd-party apps to recycle the meeting right-away
396
     * instead of waiting for the meeting to expire (see below).
397
     * removeMeetingWhenEnded=false
398
     *
399
     * The number of minutes before the system removes the meeting from memory.
400
     * defaultMeetingExpireDuration=1
401
     *
402
     * The number of minutes the system waits when a meeting is created and when
403
     * a user joins. If after this period, a user hasn't joined, the meeting is
404
     * removed from memory.
405
     * defaultMeetingCreateJoinDuration=5
406
     *
407
     * @return mixed
408
     */
409
    public function createMeeting($params)
410
    {
411
        // Ensure API is available
412
        if (!$this->ensureApi()) {
413
            if ($this->debug) {
414
                error_log('BBB API not initialized in createMeeting().');
415
            }
416
            return false;
417
        }
418
419
        // Normalize input
420
        $params = is_array($params) ? $params : [];
421
422
        // Context defaults
423
        $params['c_id']       = $params['c_id']       ?? api_get_course_int_id();
424
        $params['session_id'] = $params['session_id'] ?? api_get_session_id();
425
426
        if ($this->hasGroupSupport()) {
427
            $params['group_id'] = $params['group_id'] ?? api_get_group_id();
428
        }
429
430
        if ($this->isGlobalConferencePerUserEnabled() && !empty($this->userId)) {
431
            $params['user_id'] = (int) $this->userId;
432
        }
433
434
        // Passwords
435
        $params['attendee_pw']  = $params['attendee_pw']  ?? $this->getUserMeetingPassword();
436
        $params['moderator_pw'] = $params['moderator_pw'] ?? $this->getModMeetingPassword();
437
        $attendeePassword  = (string) $params['attendee_pw'];
438
        $moderatorPassword = (string) $params['moderator_pw'];
439
440
        // Recording and limits
441
        $params['record'] = api_get_course_plugin_setting('bbb', 'big_blue_button_record_and_store') == 1 ? 1 : 0;
442
        $max = api_get_course_plugin_setting('bbb', 'big_blue_button_max_students_allowed');
443
        $max = isset($max) ? (int) $max : -1;
444
445
        // Meeting identifiers
446
        $params['status']     = 1;
447
        $params['remote_id']  = $params['remote_id']  ?? uniqid(true, true);
448
        $params['voice_bridge']= $params['voice_bridge'] ?? rand(10000, 99999);
449
        $params['created_at'] = $params['created_at'] ?? api_get_utc_datetime();
450
        $params['access_url'] = $params['access_url'] ?? $this->accessUrl;
451
452
        // Persist meeting entity
453
        $em = Database::getManager();
454
        $meeting = new ConferenceMeeting();
455
456
        $meeting
457
            ->setCourse(api_get_course_entity($params['c_id']))
458
            ->setSession(api_get_session_entity($params['session_id']))
459
            ->setAccessUrl(api_get_url_entity($params['access_url']))
460
            ->setGroup($this->hasGroupSupport() ? api_get_group_entity($params['group_id']) : null)
461
            ->setUser($this->isGlobalConferencePerUserEnabled() ? api_get_user_entity($params['user_id'] ?? 0) : null)
462
            ->setRemoteId($params['remote_id'])
463
            ->setTitle($params['meeting_name'] ?? $this->getCurrentVideoConferenceName())
464
            ->setAttendeePw($attendeePassword)
465
            ->setModeratorPw($moderatorPassword)
466
            ->setRecord((bool) $params['record'])
467
            ->setStatus($params['status'])
468
            ->setVoiceBridge($params['voice_bridge'])
469
            ->setWelcomeMsg($params['welcome_msg'] ?? null)
470
            ->setVisibility(1)
471
            ->setHasVideoM4v(false)
472
            ->setServiceProvider('bbb');
473
474
        $em->persist($meeting);
475
        $em->flush();
476
477
        $id = $meeting->getId();
478
479
        Event::addEvent(
0 ignored issues
show
Bug introduced by
The method addEvent() does not exist on Event. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

479
        Event::/** @scrutinizer ignore-call */ 
480
               addEvent(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
480
            'bbb_create_meeting',
481
            'meeting_id',
482
            $id,
483
            null,
484
            api_get_user_id(),
485
            api_get_course_int_id(),
486
            api_get_session_id()
487
        );
488
489
        // Compute BBB params
490
        $meetingName = $meeting->getTitle();
491
        $record      = $meeting->isRecord() ? 'true' : 'false';
492
        $duration    = 300;
493
        $meetingDuration = (int) $this->plugin->get('meeting_duration');
494
        if (!empty($meetingDuration)) {
495
            $duration = $meetingDuration;
496
        }
497
498
        // Normalize optional "documents" to avoid undefined index warnings
499
        $documents = [];
500
        if (!empty($params['documents']) && is_array($params['documents'])) {
501
            foreach ($params['documents'] as $doc) {
502
                if (!is_array($doc)) {
503
                    continue;
504
                }
505
                $url = $doc['url'] ?? null;
506
                if (!$url) {
507
                    continue;
508
                }
509
                $documents[] = [
510
                    'url'          => (string) $url,
511
                    'filename'     => $doc['filename'] ?? (basename(parse_url($url, PHP_URL_PATH) ?: 'document')),
512
                    'downloadable' => array_key_exists('downloadable', $doc) ? (bool) $doc['downloadable'] : true,
513
                    'removable'    => array_key_exists('removable', $doc) ? (bool) $doc['removable'] : true,
514
                ];
515
            }
516
        }
517
518
        $bbbParams = [
519
            'meetingId'       => $meeting->getRemoteId(),
520
            'meetingName'     => $meetingName,
521
            'attendeePw'      => $attendeePassword,
522
            'moderatorPw'     => $moderatorPassword,
523
            'welcomeMsg'      => $meeting->getWelcomeMsg(),
524
            'dialNumber'      => '',
525
            'voiceBridge'     => $meeting->getVoiceBridge(),
526
            'webVoice'        => '',
527
            'logoutUrl'       => $this->logoutUrl . '&action=logout&remote_id=' . $meeting->getRemoteId(),
528
            'maxParticipants' => $max,
529
            'record'          => $record,
530
            'duration'        => $duration,
531
            'documents'       => $documents, // safe default (empty array) if none provided
532
        ];
533
534
        // Try to create meeting until success (kept as in original logic)
535
        $status = false;
536
        while ($status === false) {
537
            $result = $this->api->createMeetingWithXmlResponseArray($bbbParams);
538
539
            if ((string)($result['returncode'] ?? '') === 'SUCCESS') {
540
                if ($this->plugin->get('allow_regenerate_recording') === 'true' && !empty($result['internalMeetingID'])) {
541
                    $meeting->setInternalMeetingId($result['internalMeetingID']);
542
                    $em->flush();
543
                }
544
                return $this->joinMeeting($meetingName, true);
545
            }
546
547
            if ((string)($result['returncode'] ?? '') === 'FAILED') {
548
                if ((int)($result['httpCode'] ?? 0) === 413) {
549
                    if ($this->debug) {
550
                        error_log('BBB createMeeting failed (413): payload too large');
551
                    }
552
                } else if ($this->debug) {
553
                    error_log('BBB createMeeting failed: '.json_encode($result));
554
                }
555
                break;
556
            }
557
558
            if ($this->debug) {
559
                error_log('BBB createMeeting unexpected response: '.print_r($result, true));
0 ignored issues
show
Bug introduced by
Are you sure print_r($result, true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

559
                error_log('BBB createMeeting unexpected response: './** @scrutinizer ignore-type */ print_r($result, true));
Loading history...
560
            }
561
            break;
562
        }
563
564
        return false;
565
    }
566
567
    /**
568
     * @return bool
569
     */
570
    public function hasGroupSupport()
571
    {
572
        return $this->groupSupport;
573
    }
574
575
    /**
576
     * Gets the password for a specific meeting for the current user
577
     *
578
     * @param string $courseCode
579
     *
580
     * @return string A moderator password if user is teacher, or the course code otherwise
581
     *
582
     */
583
    public function getUserMeetingPassword($courseCode = null)
584
    {
585
        if ($this->isGlobalConferencePerUserEnabled()) {
586
            return 'url_'.$this->userId.'_'.api_get_current_access_url_id();
587
        }
588
589
        if ($this->isGlobalConference()) {
590
            return 'url_'.api_get_current_access_url_id();
591
        }
592
593
        return empty($courseCode) ? api_get_course_id() : $courseCode;
594
    }
595
596
    /**
597
     * Generated a moderator password for the meeting.
598
     *
599
     * @param string $courseCode
600
     *
601
     * @return string A password for the moderation of the videoconference
602
     */
603
    public function getModMeetingPassword($courseCode = null)
604
    {
605
        if ($this->isGlobalConferencePerUserEnabled()) {
606
            return 'url_'.$this->userId.'_'.api_get_current_access_url_id().'_mod';
607
        }
608
609
        if ($this->isGlobalConference()) {
610
            return 'url_'.api_get_current_access_url_id().'_mod';
611
        }
612
613
        $courseCode = empty($courseCode) ? api_get_course_id() : $courseCode;
614
615
        return $courseCode.'mod';
616
    }
617
618
    /**
619
     * @return string
620
     */
621
    public function getCurrentVideoConferenceName()
622
    {
623
        if ($this->isGlobalConferencePerUserEnabled()) {
624
            return 'url_'.$this->userId.'_'.api_get_current_access_url_id();
625
        }
626
627
        if ($this->isGlobalConference()) {
628
            return 'url_'.api_get_current_access_url_id();
629
        }
630
631
        if ($this->hasGroupSupport()) {
632
            return api_get_course_id().'-'.api_get_session_id().'-'.api_get_group_id();
633
        }
634
635
        return api_get_course_id().'-'.api_get_session_id();
636
    }
637
638
    /**
639
     * Returns a meeting "join" URL
640
     *
641
     * @param string The name of the meeting (usually the course code)
642
     *
643
     * @return mixed The URL to join the meeting, or false on error
644
     * @todo implement moderator pass
645
     * @assert ('') === false
646
     * @assert ('abcdefghijklmnopqrstuvwxyzabcdefghijklmno') === false
647
     */
648
    public function joinMeeting($meetingName)
649
    {
650
        if (!$this->ensureApi()) {
651
            if ($this->debug) {
652
                error_log('BBB API not initialized in joinMeeting().');
653
            }
654
            return false;
655
        }
656
        if ($this->debug) {
657
            error_log("joinMeeting: $meetingName");
658
        }
659
660
        if (empty($meetingName)) {
661
            return false;
662
        }
663
664
        $manager = $this->isConferenceManager();
665
        $pass = $manager ? $this->getModMeetingPassword() : $this->getUserMeetingPassword();
666
667
        $meetingData = Database::getManager()
668
            ->getRepository(ConferenceMeeting::class)
669
            ->findOneBy([
670
                'title' => $meetingName,
671
                'status' => 1,
672
                'accessUrl' => api_get_url_entity($this->accessUrl),
673
            ]);
674
675
        if (empty($meetingData)) {
676
            if ($this->debug) {
677
                error_log("meeting does not exist: $meetingName");
678
            }
679
680
            return false;
681
        }
682
683
        $params = [
684
            'meetingId' => $meetingData->getRemoteId(),
685
            'password' => $this->getModMeetingPassword(),
686
        ];
687
688
        $meetingInfoExists = false;
689
        $meetingIsRunningInfo = $this->getMeetingInfo($params);
690
        if ($this->debug) {
691
            error_log('Searching meeting with params:');
692
            error_log(print_r($params, 1));
693
            error_log('Result:');
694
            error_log(print_r($meetingIsRunningInfo, 1));
695
        }
696
697
        if ($meetingIsRunningInfo === false) {
698
            $params['meetingId'] = $meetingData->getId();
699
            $meetingIsRunningInfo = $this->getMeetingInfo($params);
700
            if ($this->debug) {
701
                error_log('Searching meetingId with params:');
702
                error_log(print_r($params, 1));
703
                error_log('Result:');
704
                error_log(print_r($meetingIsRunningInfo, 1));
705
            }
706
        }
707
708
        if (
709
            strval($meetingIsRunningInfo['returncode']) === 'SUCCESS' &&
710
            isset($meetingIsRunningInfo['meetingName']) &&
711
            !empty($meetingIsRunningInfo['meetingName'])
712
        ) {
713
            $meetingInfoExists = true;
714
        }
715
716
        if ($this->debug) {
717
            error_log("meeting is running: " . intval($meetingInfoExists));
718
        }
719
720
        if ($meetingInfoExists) {
721
            $joinParams = [
722
                'meetingId' => $meetingData->getRemoteId(),
723
                'username' => $this->userCompleteName,
724
                'password' => $pass,
725
                'userID' => api_get_user_id(),
726
                'moderatorPw' => $this->getModMeetingPassword(),
727
                'userID'      => api_get_user_id(),
728
                'webVoiceConf' => '',
729
            ];
730
            $url = $this->api->getJoinMeetingURL($joinParams);
731
            if (preg_match('#^https?://#i', $url)) {
732
                return $url;
733
            }
734
735
            return $this->protocol . $url;
736
        }
737
738
        return false;
739
    }
740
741
742
    /**
743
     * Checks whether a user is teacher in the current course
744
     * @return bool True if the user can be considered a teacher in this course, false otherwise
745
     */
746
    public function isConferenceManager()
747
    {
748
        if (api_is_coach() || api_is_platform_admin(false, true)) {
749
            return true;
750
        }
751
752
        if ($this->isGlobalConferencePerUserEnabled()) {
753
            $currentUserId = api_get_user_id();
754
            if ($this->userId === $currentUserId) {
755
                return true;
756
            } else {
757
                return false;
758
            }
759
        }
760
761
        $courseInfo = api_get_course_info();
762
        $groupId = api_get_group_id();
763
        if (!empty($groupId) && !empty($courseInfo)) {
764
            $groupEnabled = api_get_course_plugin_setting('bbb', 'bbb_enable_conference_in_groups') === '1';
765
            if ($groupEnabled) {
766
                $studentCanStartConference = api_get_course_plugin_setting(
767
                        'bbb',
768
                        'big_blue_button_students_start_conference_in_groups'
769
                    ) === '1';
770
771
                if ($studentCanStartConference) {
772
                    $isSubscribed = GroupManager::is_user_in_group(
0 ignored issues
show
Bug introduced by
The method is_user_in_group() does not exist on GroupManager. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

772
                    /** @scrutinizer ignore-call */ 
773
                    $isSubscribed = GroupManager::is_user_in_group(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
773
                        api_get_user_id(),
774
                        GroupManager::get_group_properties($groupId)
775
                    );
776
                    if ($isSubscribed) {
777
                        return true;
778
                    }
779
                }
780
            }
781
        }
782
783
        if (!empty($courseInfo)) {
784
            return api_is_course_admin();
785
        }
786
787
        return false;
788
    }
789
790
    /**
791
     * Get information about the given meeting
792
     *
793
     * @param array ...?
794
     *
795
     * @return mixed Array of information on success, false on error
796
     * @assert (array()) === false
797
     */
798
    public function getMeetingInfo($params)
799
    {
800
        try {
801
            // Guard against null API
802
            if (!$this->ensureApi()) {
803
                if ($this->debug) {
804
                    error_log('BBB API not initialized (missing URL or salt).');
805
                }
806
                return false;
807
            }
808
809
            $result = $this->api->getMeetingInfoWithXmlResponseArray($params);
810
            if ($result == null) {
811
                if ($this->debug) {
812
                    error_log("Failed to get any response. Maybe we can't contact the BBB server.");
813
                }
814
                return false;
815
            }
816
817
            return $result;
818
        } catch (\Throwable $e) {
819
            if ($this->debug) {
820
                error_log('BBB getMeetingInfo error: '.$e->getMessage());
821
            }
822
            return false;
823
        }
824
    }
825
826
827
    /**
828
     * @param int $meetingId
829
     * @param int $userId
830
     *
831
     * @return array
832
     */
833
    public function getMeetingParticipantInfo($meetingId, $userId): array
834
    {
835
        $em = Database::getManager();
836
        /** @var ConferenceActivityRepository $repo */
837
        $repo = $em->getRepository(ConferenceActivity::class);
838
839
        $activity = $repo->createQueryBuilder('a')
840
            ->join('a.meeting', 'm')
841
            ->join('a.participant', 'u')
842
            ->where('m.id = :meetingId')
843
            ->andWhere('u.id = :userId')
844
            ->setParameter('meetingId', $meetingId)
845
            ->setParameter('userId', $userId)
846
            ->setMaxResults(1)
847
            ->getQuery()
848
            ->getOneOrNullResult();
849
850
        if (!$activity) {
851
            return [];
852
        }
853
854
        return [
855
            'id' => $activity->getId(),
856
            'meeting_id' => $activity->getMeeting()?->getId(),
857
            'participant_id' => $activity->getParticipant()?->getId(),
858
            'in_at' => $activity->getInAt()?->format('Y-m-d H:i:s'),
859
            'out_at' => $activity->getOutAt()?->format('Y-m-d H:i:s'),
860
            'close' => $activity->isClose(),
861
            'type' => $activity->getType(),
862
            'event' => $activity->getEvent(),
863
            'activity_data' => $activity->getActivityData(),
864
            'signature_file' => $activity->getSignatureFile(),
865
            'signed_at' => $activity->getSignedAt()?->format('Y-m-d H:i:s'),
866
        ];
867
    }
868
869
    /**
870
     * Save a participant in a meeting room
871
     *
872
     * @param int $meetingId
873
     * @param int $participantId
874
     *
875
     * @return false|int The last inserted ID. Otherwise return false
876
     */
877
    public function saveParticipant(int $meetingId, int $participantId): false|int
878
    {
879
        $em = Database::getManager();
880
881
        /** @var ConferenceActivityRepository $repo */
882
        $repo = $em->getRepository(ConferenceActivity::class);
883
884
        $meeting = $em->getRepository(ConferenceMeeting::class)->find($meetingId);
885
        $user = api_get_user_entity($participantId);
886
887
        if (!$meeting || !$user) {
888
            return false;
889
        }
890
891
        $existing = $repo->createQueryBuilder('a')
892
            ->where('a.meeting = :meeting')
893
            ->andWhere('a.participant = :participant')
894
            ->andWhere('a.close = :open')
895
            ->setParameter('meeting', $meeting)
896
            ->setParameter('participant', $user)
897
            ->setParameter('open', \BbbPlugin::ROOM_OPEN)
898
            ->getQuery()
899
            ->getResult();
900
901
        foreach ($existing as $activity) {
902
            if ($activity->getInAt() != $activity->getOutAt()) {
903
                $activity->setClose(\BbbPlugin::ROOM_CLOSE);
904
            } else {
905
                $activity->setOutAt(new \DateTime());
906
                $activity->setClose(\BbbPlugin::ROOM_CLOSE);
907
            }
908
            $em->persist($activity);
909
        }
910
911
        $newActivity = new ConferenceActivity();
912
        $newActivity->setMeeting($meeting);
913
        $newActivity->setParticipant($user);
914
        $newActivity->setInAt(new \DateTime());
915
        $newActivity->setOutAt(new \DateTime());
916
        $newActivity->setClose(\BbbPlugin::ROOM_OPEN);
917
918
        $em->persist($newActivity);
919
        $em->flush();
920
921
        return $newActivity->getId();
922
    }
923
924
    /**
925
     * Tells whether the given meeting exists and is running
926
     * (using course code as name)
927
     *
928
     * @param string $meetingName Meeting name (usually the course code)
929
     *
930
     * @return bool True if meeting exists, false otherwise
931
     * @assert ('') === false
932
     * @assert ('abcdefghijklmnopqrstuvwxyzabcdefghijklmno') === false
933
     */
934
    public function meetingExists($meetingName)
935
    {
936
        $meetingData = $this->getMeetingByName($meetingName);
937
938
        return !empty($meetingData);
939
    }
940
941
    /**
942
     * @param string $meetingName
943
     *
944
     * @return array
945
     */
946
    public function getMeetingByName($meetingName)
947
    {
948
        if (empty($meetingName)) {
949
            return [];
950
        }
951
952
        $courseEntity = api_get_course_entity();
953
        $sessionEntity = api_get_session_entity();
954
        $accessUrlEntity = api_get_url_entity($this->accessUrl);
955
956
        $criteria = [
957
            'course' => $courseEntity,
958
            'session' => $sessionEntity,
959
            'title' => $meetingName,
960
            'status' => 1,
961
            'accessUrl' => $accessUrlEntity,
962
        ];
963
964
        if ($this->hasGroupSupport()) {
965
            $groupEntity = api_get_group_entity(api_get_group_id());
966
            $criteria['group'] = $groupEntity;
967
        }
968
969
        $meeting = Database::getManager()
970
            ->getRepository(ConferenceMeeting::class)
971
            ->findOneBy($criteria);
972
973
        if ($this->debug) {
974
            error_log('meeting_exists '.print_r($meeting ? ['id' => $meeting->getId()] : [], 1));
0 ignored issues
show
Bug introduced by
Are you sure print_r($meeting ? array...>getId()) : array(), 1) of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

974
            error_log('meeting_exists './** @scrutinizer ignore-type */ print_r($meeting ? ['id' => $meeting->getId()] : [], 1));
Loading history...
975
        }
976
977
        if (!$meeting) {
978
            return [];
979
        }
980
981
        return [
982
            'id' => $meeting->getId(),
983
            'c_id' => $meeting->getCourse()?->getId(),
984
            'session_id' => $meeting->getSession()?->getId(),
985
            'meeting_name' => $meeting->getTitle(),
986
            'status' => $meeting->getStatus(),
987
            'access_url' => $meeting->getAccessUrl()?->getId(),
988
            'group_id' => $meeting->getGroup()?->getIid(),
989
            'remote_id' => $meeting->getRemoteId(),
990
            'moderator_pw' => $meeting->getModeratorPw(),
991
            'attendee_pw' => $meeting->getAttendeePw(),
992
            'created_at' => $meeting->getCreatedAt()->format('Y-m-d H:i:s'),
993
            'closed_at' => $meeting->getClosedAt()?->format('Y-m-d H:i:s'),
994
            'visibility' => $meeting->getVisibility(),
995
            'video_url' => $meeting->getVideoUrl(),
996
            'has_video_m4v' => $meeting->isHasVideoM4v(),
997
            'record' => $meeting->isRecord(),
998
            'internal_meeting_id' => $meeting->getInternalMeetingId(),
999
        ];
1000
    }
1001
1002
    /**
1003
     * Gets a list from the database of all meetings attached to a course with the given status
1004
     * @param int $courseId
1005
     * @param int $sessionId
1006
     * @param int $status 0 for closed meetings, 1 for open meetings
1007
     *
1008
     * @return array
1009
     */
1010
    public function getAllMeetingsInCourse($courseId, $sessionId, $status)
1011
    {
1012
        $em = Database::getManager();
1013
        $courseEntity = api_get_course_entity($courseId);
1014
        $sessionEntity = api_get_session_entity($sessionId);
1015
1016
        $meetings = $em->getRepository(ConferenceMeeting::class)->findBy([
1017
            'course' => $courseEntity,
1018
            'session' => $sessionEntity,
1019
            'status' => $status,
1020
        ]);
1021
1022
        $results = [];
1023
        foreach ($meetings as $meeting) {
1024
            $results[] = [
1025
                'id' => $meeting->getId(),
1026
                'c_id' => $meeting->getCourse()?->getId(),
1027
                'session_id' => $meeting->getSession()?->getId(),
1028
                'meeting_name' => $meeting->getTitle(),
1029
                'status' => $meeting->getStatus(),
1030
                'access_url' => $meeting->getAccessUrl()?->getId(),
1031
                'group_id' => $meeting->getGroup()?->getIid(),
1032
                'remote_id' => $meeting->getRemoteId(),
1033
                'moderator_pw' => $meeting->getModeratorPw(),
1034
                'attendee_pw' => $meeting->getAttendeePw(),
1035
                'created_at' => $meeting->getCreatedAt()->format('Y-m-d H:i:s'),
1036
                'closed_at' => $meeting->getClosedAt()?->format('Y-m-d H:i:s'),
1037
                'visibility' => $meeting->getVisibility(),
1038
                'video_url' => $meeting->getVideoUrl(),
1039
                'has_video_m4v' => $meeting->isHasVideoM4v(),
1040
                'record' => $meeting->isRecord(),
1041
                'internal_meeting_id' => $meeting->getInternalMeetingId(),
1042
            ];
1043
        }
1044
1045
        return $results;
1046
    }
1047
1048
    /**
1049
     * Gets all the course meetings saved in the plugin_bbb_meeting table and
1050
     * generate actionable links (join/close/delete/etc)
1051
     *
1052
     * @param int   $courseId
1053
     * @param int   $sessionId
1054
     * @param int   $groupId
1055
     * @param bool  $isAdminReport Optional. Set to true then the report is for admins
1056
     * @param array $dateRange     Optional
1057
     *
1058
     * @return array Array of current open meeting rooms
1059
     * @throws Exception
1060
     */
1061
    public function getMeetings(
1062
        $courseId = 0,
1063
        $sessionId = 0,
1064
        $groupId = 0,
1065
        $isAdminReport = false,
1066
        $dateRange = []
1067
    ) {
1068
        if (!$this->ensureApi()) {
1069
            if ($this->debug) {
1070
                error_log('BBB API not initialized in getMeetings().');
1071
            }
1072
            return [];
1073
        }
1074
1075
        $em = Database::getManager();
1076
        $repo = $em->getRepository(ConferenceMeeting::class);
1077
        $manager = $this->isConferenceManager();
1078
        $isGlobal = $this->isGlobalConference();
1079
        $meetings = [];
1080
1081
        if (!empty($dateRange)) {
1082
            $dateStart = (new \DateTime($dateRange['search_meeting_start']))->setTime(0, 0, 0);
1083
            $dateEnd = (new \DateTime($dateRange['search_meeting_end']))->setTime(23, 59, 59);
1084
            $meetings = $repo->findByDateRange($dateStart, $dateEnd);
1085
        } elseif ($this->isGlobalConference()) {
1086
            $meetings = $repo->findBy([
1087
                'course' => null,
1088
                'user' => api_get_user_entity($this->userId),
1089
                'accessUrl' => api_get_url_entity($this->accessUrl),
1090
            ]);
1091
        } elseif ($this->isGlobalConferencePerUserEnabled()) {
1092
            $meetings = $repo->findBy([
1093
                'course' => api_get_course_entity($courseId),
1094
                'session' => api_get_session_entity($sessionId),
1095
                'user' => api_get_user_entity($this->userId),
1096
                'accessUrl' => api_get_url_entity($this->accessUrl),
1097
            ]);
1098
        } else {
1099
            $criteria = [
1100
                'course' => api_get_course_entity($courseId),
1101
                'session' => api_get_session_entity($sessionId),
1102
                'accessUrl' => api_get_url_entity($this->accessUrl),
1103
            ];
1104
            if ($this->hasGroupSupport() && $groupId) {
1105
                $criteria['group'] = api_get_group_entity($groupId);
1106
            }
1107
            $meetings = $repo->findBy($criteria, ['createdAt' => 'ASC']);
1108
        }
1109
1110
        $result = [];
1111
        foreach ($meetings as $meeting) {
1112
            $meetingArray = $this->convertMeetingToArray($meeting);
1113
            $recordLink = $this->plugin->get_lang('NoRecording');
1114
            $meetingBBB = $this->getMeetingInfo([
1115
                'meetingId' => $meeting->getRemoteId(),
1116
                'password' => $manager ? $meeting->getModeratorPw() : $meeting->getAttendeePw(),
1117
            ]);
1118
1119
            if (!$meetingBBB && $meeting->getId()) {
1120
                $meetingBBB = $this->getMeetingInfo([
1121
                    'meetingId' => $meeting->getId(),
1122
                    'password' => $manager ? $meeting->getModeratorPw() : $meeting->getAttendeePw(),
1123
                ]);
1124
            }
1125
1126
            if (!$meeting->isVisible() && !$manager) {
1127
                continue;
1128
            }
1129
1130
            $meetingBBB['end_url'] = $this->endUrl(['id' => $meeting->getId()]);
1131
            if (isset($meetingBBB['returncode']) && (string) $meetingBBB['returncode'] === 'FAILED') {
1132
                if ($meeting->getStatus() === 1 && $manager) {
1133
                    $this->endMeeting($meeting->getId(), $meeting->getCourse()?->getCode());
1134
                }
1135
            } else {
1136
                $meetingBBB['add_to_calendar_url'] = $this->addToCalendarUrl($meetingArray);
1137
            }
1138
1139
            if ($meeting->isRecord()) {
1140
                $recordings = $this->api->getRecordingsWithXmlResponseArray(['meetingId' => $meeting->getRemoteId()]);
1141
                if (!empty($recordings) && (!isset($recordings['messageKey']) || $recordings['messageKey'] !== 'noRecordings')) {
1142
                    $record = end($recordings);
1143
                    if (isset($record['playbackFormatUrl'])) {
1144
                        $recordLink = Display::url(
1145
                            $this->plugin->get_lang('ViewRecord'),
1146
                            $record['playbackFormatUrl'],
1147
                            ['target' => '_blank', 'class' => 'btn btn--plain']
1148
                        );
1149
                        $this->updateMeetingVideoUrl($meeting->getId(), $record['playbackFormatUrl']);
1150
                    }
1151
                }
1152
            }
1153
1154
            $actionLinks = $this->getActionLinks($meetingArray, $record ?? [], $isGlobal, $isAdminReport);
1155
1156
            $item = array_merge($meetingArray, [
1157
                'go_url' => '',
1158
                'show_links' => $recordLink,
1159
                'action_links' => implode(PHP_EOL, $actionLinks),
1160
                'publish_url' => $this->publishUrl(['id' => $meeting->getId()]),
1161
                'unpublish_url' => $this->unPublishUrl(['id' => $meeting->getId()]),
1162
            ]);
1163
1164
            if ($meeting->getStatus() === 1) {
1165
                $joinParams = [
1166
                    'meetingId' => $meeting->getRemoteId(),
1167
                    'username' => $this->userCompleteName,
1168
                    'password' => $manager ? $meeting->getModeratorPw() : $meeting->getAttendeePw(),
1169
                    'createTime' => '',
1170
                    'userID' => '',
1171
                    'webVoiceConf' => '',
1172
                ];
1173
                $item['go_url'] = $this->protocol.$this->api->getJoinMeetingURL($joinParams);
1174
            }
1175
1176
            $result[] = array_merge($item, $meetingBBB);
1177
        }
1178
1179
        return $result;
1180
    }
1181
1182
    private function convertMeetingToArray(ConferenceMeeting $meeting): array
1183
    {
1184
        return [
1185
            'id' => $meeting->getId(),
1186
            'remote_id' => $meeting->getRemoteId(),
1187
            'internal_meeting_id' => $meeting->getInternalMeetingId(),
1188
            'meeting_name' => $meeting->getTitle(),
1189
            'status' => $meeting->getStatus(),
1190
            'visibility' => $meeting->getVisibility(),
1191
            'created_at' => $meeting->getCreatedAt() instanceof \DateTime ? $meeting->getCreatedAt()->format('Y-m-d H:i:s') : '',
0 ignored issues
show
introduced by
$meeting->getCreatedAt() is always a sub-type of DateTime.
Loading history...
1192
            'closed_at' => $meeting->getClosedAt() instanceof \DateTime ? $meeting->getClosedAt()->format('Y-m-d H:i:s') : '',
1193
            'record' => $meeting->isRecord() ? 1 : 0,
1194
            'c_id' => $meeting->getCourse()?->getId() ?? 0,
1195
            'session_id' => $meeting->getSession()?->getId() ?? 0,
1196
            'group_id' => $meeting->getGroup()?->getIid() ?? 0,
1197
            'course' => $meeting->getCourse(),
1198
            'session' => $meeting->getSession(),
1199
            'title' => $meeting->getTitle(),
1200
        ];
1201
    }
1202
1203
    public function getMeetingsLight(
1204
        $courseId = 0,
1205
        $sessionId = 0,
1206
        $groupId = 0,
1207
        $dateRange = []
1208
    ): array {
1209
        $em = Database::getManager();
1210
        $repo = $em->getRepository(ConferenceMeeting::class);
1211
        $meetings = [];
1212
1213
        if (!empty($dateRange)) {
1214
            $dateStart = (new \DateTime($dateRange['search_meeting_start']))->setTime(0, 0, 0);
1215
            $dateEnd = (new \DateTime($dateRange['search_meeting_end']))->setTime(23, 59, 59);
1216
            $meetings = $repo->findByDateRange($dateStart, $dateEnd);
1217
        } else {
1218
            $criteria = [
1219
                'course' => api_get_course_entity($courseId),
1220
                'session' => api_get_session_entity($sessionId),
1221
                'accessUrl' => api_get_url_entity($this->accessUrl),
1222
            ];
1223
            if ($this->hasGroupSupport() && $groupId) {
1224
                $criteria['group'] = api_get_group_entity($groupId);
1225
            }
1226
            $meetings = $repo->findBy($criteria, ['createdAt' => 'DESC']);
1227
        }
1228
1229
        $result = [];
1230
        foreach ($meetings as $meeting) {
1231
            $meetingArray = $this->convertMeetingToArray($meeting);
1232
1233
            $item = array_merge($meetingArray, [
1234
                'go_url' => '',
1235
                'show_links' => $this->plugin->get_lang('NoRecording'),
1236
                'action_links' => '',
1237
                'publish_url' => $this->publishUrl(['id' => $meeting->getId()]),
1238
                'unpublish_url' => $this->unPublishUrl(['id' => $meeting->getId()]),
1239
            ]);
1240
1241
            $result[] = $item;
1242
        }
1243
1244
        return $result;
1245
    }
1246
1247
    /**
1248
     * @param array $meeting
1249
     *
1250
     * @return string
1251
     */
1252
    public function endUrl($meeting)
1253
    {
1254
        if (!isset($meeting['id'])) {
1255
            return '';
1256
        }
1257
1258
        return api_get_path(WEB_PLUGIN_PATH).'Bbb/listing.php?'.$this->getUrlParams().'&action=end&id='.$meeting['id'];
1259
    }
1260
1261
    /**
1262
     * Closes a meeting (usually when the user click on the close button from
1263
     * the conferences listing.
1264
     *
1265
     * @param string The internal ID of the meeting (id field for this meeting)
1266
     * @param string $courseCode
1267
     *
1268
     * @return void
1269
     * @assert (0) === false
1270
     */
1271
    public function endMeeting($id, $courseCode = null)
1272
    {
1273
        if (empty($id)) {
1274
            return false;
1275
        }
1276
1277
        $em = Database::getManager();
1278
1279
        /** @var ConferenceMeetingRepository $repo */
1280
        $repo = $em->getRepository(ConferenceMeeting::class);
1281
1282
        $meetingData = $repo->findOneAsArrayById((int) $id);
1283
        if (!$meetingData) {
1284
            return false;
1285
        }
1286
1287
        $manager = $this->isConferenceManager();
1288
        $pass = $manager ? $meetingData['moderatorPw'] : $meetingData['attendeePw'];
1289
1290
        Event::addEvent(
1291
            'bbb_end_meeting',
1292
            'meeting_id',
1293
            (int) $id,
1294
            null,
1295
            api_get_user_id(),
1296
            api_get_course_int_id(),
1297
            api_get_session_id()
1298
        );
1299
1300
        $endParams = [
1301
            'meetingId' => $meetingData['remoteId'],
1302
            'password' => $pass,
1303
        ];
1304
        $this->api->endMeetingWithXmlResponseArray($endParams);
1305
1306
        $repo->closeMeeting((int) $id, new \DateTime());
1307
1308
        /** @var ConferenceActivityRepository $activityRepo */
1309
        $activityRepo = $em->getRepository(ConferenceActivity::class);
1310
1311
        $activities = $activityRepo->findOpenWithSameInAndOutTime((int) $id);
1312
1313
        foreach ($activities as $activity) {
1314
            $activity->setOutAt(new \DateTime());
1315
            $activity->setClose(BbbPlugin::ROOM_CLOSE);
1316
            $em->persist($activity);
1317
        }
1318
1319
        $activityRepo->closeAllByMeetingId((int) $id);
1320
1321
        $em->flush();
1322
1323
        return true;
1324
    }
1325
1326
    /**
1327
     * @param array $meeting
1328
     * @param array $record
1329
     *
1330
     * @return string
1331
     */
1332
    public function addToCalendarUrl($meeting, $record = []): string
1333
    {
1334
        $url = isset($record['playbackFormatUrl']) ? $record['playbackFormatUrl'] : '';
1335
1336
        return api_get_path(WEB_PLUGIN_PATH).'Bbb/listing.php?'.$this->getUrlParams(
1337
            ).'&action=add_to_calendar&id='.$meeting['id'].'&start='.api_strtotime($meeting['created_at']).'&url='.$url;
1338
    }
1339
1340
    /**
1341
     * @param int    $meetingId
1342
     * @param string $videoUrl
1343
     *
1344
     * @return bool|int
1345
     */
1346
    public function updateMeetingVideoUrl(int $meetingId, string $videoUrl): void
1347
    {
1348
        $em = Database::getManager();
1349
        /** @var ConferenceMeetingRepository $repo */
1350
        $repo = $em->getRepository(ConferenceMeeting::class);
1351
        $repo->updateVideoUrl($meetingId, $videoUrl);
1352
    }
1353
1354
    /**
1355
     * Force the course, session and/or group IDs
1356
     *
1357
     * @param string $courseCode
1358
     * @param int    $sessionId
1359
     * @param int    $groupId
1360
     */
1361
    public function forceCIdReq($courseCode, $sessionId = 0, $groupId = 0)
1362
    {
1363
        $this->courseCode = $courseCode;
1364
        $this->sessionId = (int) $sessionId;
1365
        $this->groupId = (int) $groupId;
1366
    }
1367
1368
    /**
1369
     * @param array $meetingInfo
1370
     * @param array $recordInfo
1371
     * @param bool  $isGlobal
1372
     * @param bool  $isAdminReport
1373
     *
1374
     * @return array
1375
     */
1376
    private function getActionLinks(
1377
        $meetingInfo,
1378
        $recordInfo,
1379
        $isGlobal = false,
1380
        $isAdminReport = false
1381
    ) {
1382
        $isVisible = $meetingInfo['visibility'] != 0;
1383
        $linkVisibility = $isVisible
1384
            ? Display::url(
1385
                Display::getMdiIcon(StateIcon::ACTIVE, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Make invisible')),
1386
                $this->unPublishUrl($meetingInfo)
1387
            )
1388
            : Display::url(
1389
                Display::getMdiIcon(StateIcon::INACTIVE, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Make visible')),
1390
                $this->publishUrl($meetingInfo)
1391
            );
1392
1393
        $links = [];
1394
        if ($this->plugin->get('allow_regenerate_recording') === 'true' && $meetingInfo['record'] == 1) {
1395
            if (!empty($recordInfo)) {
1396
                $links[] = Display::url(
1397
                    Display::getMdiIcon(ActionIcon::REFRESH, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('RegenerateRecord')),
1398
                    $this->regenerateRecordUrl($meetingInfo, $recordInfo)
1399
                );
1400
            } else {
1401
                $links[] = Display::url(
1402
                    Display::getMdiIcon(ActionIcon::REFRESH, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('RegenerateRecord')),
1403
                    $this->regenerateRecordUrlFromMeeting($meetingInfo)
1404
                );
1405
            }
1406
        }
1407
1408
        if (empty($recordInfo)) {
1409
            if (!$isAdminReport) {
1410
                if ($meetingInfo['status'] == 0) {
1411
                    $links[] = Display::url(
1412
                        Display::getMdiIcon(ActionIcon::DELETE, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Delete')),
1413
                        $this->deleteRecordUrl($meetingInfo)
1414
                    );
1415
                    $links[] = $linkVisibility;
1416
                }
1417
1418
                return $links;
1419
            } else {
1420
                $links[] = Display::url(
1421
                    Display::getMdiIcon(ObjectIcon::HOME, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Go to the course')),
1422
                    $this->getListingUrl($meetingInfo['c_id'], $meetingInfo['session_id'], $meetingInfo['group_id'])
1423
                );
1424
1425
                return $links;
1426
            }
1427
        }
1428
1429
        if (!$isGlobal) {
1430
            $links[] = Display::url(
1431
                Display::getMdiIcon(ObjectIcon::LINK, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('UrlMeetingToShare')),
1432
                $this->copyToRecordToLinkTool($meetingInfo)
1433
            );
1434
            $links[] = Display::url(
1435
                Display::getMdiIcon(ObjectIcon::AGENDA, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Add to calendar')),
1436
                $this->addToCalendarUrl($meetingInfo, $recordInfo)
1437
            );
1438
        }
1439
1440
        $hide = $this->plugin->get('disable_download_conference_link') === 'true' ? true : false;
1441
1442
        if ($hide == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1443
            if ($meetingInfo['has_video_m4v']) {
1444
                $links[] = Display::url(
1445
                    Display::getMdiIcon(ActionIcon::SAVE_FORM, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Download file')),
1446
                    $recordInfo['playbackFormatUrl'].'/capture.m4v',
1447
                    ['target' => '_blank']
1448
                );
1449
            } else {
1450
                $links[] = Display::url(
1451
                    Display::getMdiIcon(ActionIcon::SAVE_FORM, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Download file')),
1452
                    '#',
1453
                    [
1454
                        'id' => "btn-check-meeting-video-{$meetingInfo['id']}",
1455
                        'class' => 'check-meeting-video',
1456
                        'data-id' => $meetingInfo['id'],
1457
                    ]
1458
                );
1459
            }
1460
        }
1461
1462
1463
        if (!$isAdminReport) {
1464
            $links[] = Display::url(
1465
                Display::getMdiIcon(ActionIcon::DELETE, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Delete')),
1466
                $this->deleteRecordUrl($meetingInfo)
1467
            );
1468
            $links[] = $linkVisibility;
1469
        } else {
1470
            $links[] = Display::url(
1471
                Display::getMdiIcon(ObjectIcon::HOME, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Go to the course')),
1472
                $this->getListingUrl($meetingInfo['c_id'], $meetingInfo['session_id'], $meetingInfo['group_id'])
1473
            );
1474
        }
1475
1476
1477
        return $links;
1478
    }
1479
1480
    /**
1481
     * @param array $meeting
1482
     *
1483
     * @return string
1484
     */
1485
    public function unPublishUrl($meeting)
1486
    {
1487
        if (!isset($meeting['id'])) {
1488
            return null;
1489
        }
1490
1491
        return api_get_path(WEB_PLUGIN_PATH).'Bbb/listing.php?'.$this->getUrlParams(
1492
            ).'&action=unpublish&id='.$meeting['id'];
1493
    }
1494
1495
    /**
1496
     * @param array $meeting
1497
     *
1498
     * @return string
1499
     */
1500
    public function publishUrl($meeting)
1501
    {
1502
        if (!isset($meeting['id'])) {
1503
            return '';
1504
        }
1505
1506
        return api_get_path(WEB_PLUGIN_PATH).'Bbb/listing.php?'.$this->getUrlParams(
1507
            ).'&action=publish&id='.$meeting['id'];
1508
    }
1509
1510
    /**
1511
     * @param array $meeting
1512
     * @param array $recordInfo
1513
     *
1514
     * @return string
1515
     */
1516
    public function regenerateRecordUrl($meeting, $recordInfo)
1517
    {
1518
        if ($this->plugin->get('allow_regenerate_recording') !== 'true') {
1519
            return '';
1520
        }
1521
1522
        if (!isset($meeting['id'])) {
1523
            return '';
1524
        }
1525
1526
        if (empty($recordInfo) || (!empty($recordInfo['recordId']) && !isset($recordInfo['recordId']))) {
1527
            return '';
1528
        }
1529
1530
        return api_get_path(WEB_PLUGIN_PATH).'Bbb/listing.php?'.$this->getUrlParams().
1531
            '&action=regenerate_record&id='.$meeting['id'].'&record_id='.$recordInfo['recordId'];
1532
    }
1533
1534
    /**
1535
     * @param array $meeting
1536
     *
1537
     * @return string
1538
     */
1539
    public function regenerateRecordUrlFromMeeting($meeting)
1540
    {
1541
        if ($this->plugin->get('allow_regenerate_recording') !== 'true') {
1542
            return '';
1543
        }
1544
1545
        if (!isset($meeting['id'])) {
1546
            return '';
1547
        }
1548
1549
        return api_get_path(WEB_PLUGIN_PATH).'Bbb/listing.php?'.$this->getUrlParams().
1550
            '&action=regenerate_record&id='.$meeting['id'];
1551
    }
1552
1553
    /**
1554
     * @param array $meeting
1555
     *
1556
     * @return string
1557
     */
1558
    public function deleteRecordUrl($meeting)
1559
    {
1560
        if (!isset($meeting['id'])) {
1561
            return '';
1562
        }
1563
1564
        return api_get_path(WEB_PLUGIN_PATH).'Bbb/listing.php?'.$this->getUrlParams(
1565
            ).'&action=delete_record&id='.$meeting['id'];
1566
    }
1567
1568
    /**
1569
     * @param array $meeting
1570
     *
1571
     * @return string
1572
     */
1573
    public function copyToRecordToLinkTool($meeting)
1574
    {
1575
        if (!isset($meeting['id'])) {
1576
            return '';
1577
        }
1578
1579
        return api_get_path(WEB_PLUGIN_PATH).
1580
            'Bbb/listing.php?'.$this->getUrlParams().'&action=copy_record_to_link_tool&id='.$meeting['id'];
1581
    }
1582
1583
    /**
1584
     * Function disabled
1585
     */
1586
    public function publishMeeting($id)
1587
    {
1588
        if (empty($id)) {
1589
            return false;
1590
        }
1591
1592
        $em = Database::getManager();
1593
        /** @var ConferenceMeetingRepository $repo */
1594
        $repo = $em->getRepository(ConferenceMeeting::class);
1595
1596
        $meeting = $repo->find($id);
1597
        if (!$meeting) {
1598
            return false;
1599
        }
1600
1601
        $meeting->setVisibility(1);
1602
        $em->flush();
1603
1604
        return true;
1605
    }
1606
1607
    /**
1608
     * Function disabled
1609
     */
1610
    public function unpublishMeeting($id)
1611
    {
1612
        if (empty($id)) {
1613
            return false;
1614
        }
1615
1616
        $em = Database::getManager();
1617
        /** @var ConferenceMeetingRepository $repo */
1618
        $repo = $em->getRepository(ConferenceMeeting::class);
1619
1620
        $meeting = $repo->find($id);
1621
        if (!$meeting) {
1622
            return false;
1623
        }
1624
1625
        $meeting->setVisibility(0);
1626
        $em->flush();
1627
1628
        return true;
1629
    }
1630
1631
    /**
1632
     * Get users online in the current course room.
1633
     *
1634
     * @return int The number of users currently connected to the videoconference
1635
     * @assert () > -1
1636
     */
1637
    public function getUsersOnlineInCurrentRoom()
1638
    {
1639
        $courseId = api_get_course_int_id();
1640
        $sessionId = api_get_session_id();
1641
1642
        $em = Database::getManager();
1643
        $repo = $em->getRepository(ConferenceMeeting::class);
1644
1645
        $qb = $repo->createQueryBuilder('m')
1646
            ->where('m.status = 1')
1647
            ->andWhere('m.accessUrl = :accessUrl')
1648
            ->setParameter('accessUrl', $this->accessUrl)
1649
            ->setMaxResults(1);
1650
1651
        if ($this->hasGroupSupport()) {
1652
            $groupId = api_get_group_id();
1653
            $qb->andWhere('m.course = :courseId')
1654
                ->andWhere('m.session = :sessionId')
1655
                ->andWhere('m.group = :groupId')
1656
                ->setParameter('courseId', $courseId)
1657
                ->setParameter('sessionId', $sessionId)
1658
                ->setParameter('groupId', $groupId);
1659
        } elseif ($this->isGlobalConferencePerUserEnabled()) {
1660
            $qb->andWhere('m.user = :userId')
1661
                ->setParameter('userId', $this->userId);
1662
        } else {
1663
            $qb->andWhere('m.course = :courseId')
1664
                ->andWhere('m.session = :sessionId')
1665
                ->setParameter('courseId', $courseId)
1666
                ->setParameter('sessionId', $sessionId);
1667
        }
1668
1669
        $meetingData = $qb->getQuery()->getOneOrNullResult();
1670
1671
        if (!$meetingData) {
1672
            return 0;
1673
        }
1674
        $pass = $meetingData->getModeratorPw();
1675
        $info = $this->getMeetingInfo([
1676
            'meetingId' => $meetingData->getRemoteId(),
1677
            'password' => $pass,
1678
        ]);
1679
        if ($info === false) {
1680
            $info = $this->getMeetingInfo([
1681
                'meetingId' => $meetingData->getId(),
1682
                'password' => $pass,
1683
            ]);
1684
        }
1685
1686
        if (!empty($info) && isset($info['participantCount'])) {
1687
            return (int) $info['participantCount'];
1688
        }
1689
1690
        return 0;
1691
    }
1692
1693
    /**
1694
     * @param int    $id
1695
     * @param string $recordId
1696
     *
1697
     * @return bool
1698
     */
1699
    public function regenerateRecording($id, $recordId = '')
1700
    {
1701
        if ($this->plugin->get('allow_regenerate_recording') !== 'true') {
1702
            return false;
1703
        }
1704
1705
        if (empty($id)) {
1706
            return false;
1707
        }
1708
1709
        $em = Database::getManager();
1710
        /** @var ConferenceMeetingRepository $repo */
1711
        $repo = $em->getRepository(ConferenceMeeting::class);
1712
1713
        $meetingData = $repo->findOneAsArrayById((int) $id);
1714
        if (!$meetingData) {
1715
            return false;
1716
        }
1717
1718
        Event::addEvent(
1719
            'bbb_regenerate_record',
1720
            'record_id',
1721
            (int) $recordId,
1722
            null,
1723
            api_get_user_id(),
1724
            api_get_course_int_id(),
1725
            api_get_session_id()
1726
        );
1727
1728
        /** @var ConferenceRecordingRepository $recordingRepo */
1729
        $recordingRepo = $em->getRepository(ConferenceRecordingRepository::class);
1730
        $recordings = $recordingRepo->findByMeetingRemoteId($meetingData['remoteId']);
1731
1732
        if (!empty($recordings) && isset($recordings['messageKey']) && $recordings['messageKey'] === 'noRecordings') {
1733
            if (!empty($meetingData['internalMeetingId'])) {
1734
                return $this->api->generateRecording(['recordId' => $meetingData['internalMeetingId']]);
1735
            }
1736
1737
            return false;
1738
        }
1739
1740
        if (!empty($recordings['records'])) {
1741
            foreach ($recordings['records'] as $record) {
1742
                if ($recordId == $record['recordId']) {
1743
                    return $this->api->generateRecording(['recordId' => $recordId]);
1744
                }
1745
            }
1746
        }
1747
1748
        return false;
1749
    }
1750
1751
    /**
1752
     * Deletes a recording of a meeting
1753
     *
1754
     * @param int $id ID of the recording
1755
     *
1756
     * @return bool
1757
     *
1758
     * @assert () === false
1759
     * @todo Also delete links and agenda items created from this recording
1760
     */
1761
    public function deleteRecording($id)
1762
    {
1763
        if (empty($id)) {
1764
            return false;
1765
        }
1766
1767
        $em = Database::getManager();
1768
1769
        /** @var ConferenceMeetingRepository $meetingRepo */
1770
        $meetingRepo = $em->getRepository(ConferenceMeeting::class);
1771
        $meetingData = $meetingRepo->findOneAsArrayById((int) $id);
1772
        if (!$meetingData) {
1773
            return false;
1774
        }
1775
1776
        Event::addEvent(
1777
            'bbb_delete_record',
1778
            'meeting_id',
1779
            $id,
1780
            null,
1781
            api_get_user_id(),
1782
            api_get_course_int_id(),
1783
            api_get_session_id()
1784
        );
1785
1786
        $delete = false;
1787
        $recordings = [];
1788
1789
        if (!empty($meetingData['remoteId'])) {
1790
            Event::addEvent(
1791
                'bbb_delete_record',
1792
                'remote_id',
1793
                $meetingData['remoteId'],
1794
                null,
1795
                api_get_user_id(),
1796
                api_get_course_int_id(),
1797
                api_get_session_id()
1798
            );
1799
1800
            /** @var ConferenceRecordingRepository $recordingRepo */
1801
            $recordingRepo = $em->getRepository(ConferenceRecording::class);
1802
            $recordings = $recordingRepo->findByMeetingRemoteId($meetingData['remoteId']);
1803
        }
1804
1805
        if (!empty($recordings) && isset($recordings['messageKey']) && $recordings['messageKey'] === 'noRecordings') {
1806
            $delete = true;
1807
        } elseif (!empty($recordings['records'])) {
1808
            $recordsToDelete = [];
1809
            foreach ($recordings['records'] as $record) {
1810
                $recordsToDelete[] = $record['recordId'];
1811
            }
1812
1813
            if (!empty($recordsToDelete)) {
1814
                $recordingParams = ['recordId' => implode(',', $recordsToDelete)];
1815
                Event::addEvent(
1816
                    'bbb_delete_record',
1817
                    'record_id_list',
1818
                    implode(',', $recordsToDelete),
1819
                    null,
1820
                    api_get_user_id(),
1821
                    api_get_course_int_id(),
1822
                    api_get_session_id()
1823
                );
1824
1825
                $result = $this->api->deleteRecordingsWithXmlResponseArray($recordingParams);
1826
1827
                if (!empty($result) && isset($result['deleted']) && $result['deleted'] === 'true') {
1828
                    $delete = true;
1829
                }
1830
            }
1831
        }
1832
1833
        if (!$delete) {
1834
            $delete = true;
1835
        }
1836
1837
        if ($delete) {
1838
            /** @var ConferenceActivityRepository $activityRepo */
1839
            $activityRepo = $em->getRepository(ConferenceActivity::class);
1840
            $activityRepo->closeAllByMeetingId((int) $id);
1841
1842
            $meeting = $meetingRepo->find((int) $id);
1843
            if ($meeting) {
1844
                $em->remove($meeting);
1845
            }
1846
1847
            $em->flush();
1848
        }
1849
1850
        return $delete;
1851
    }
1852
1853
    /**
1854
     * Creates a link in the links tool from the given videoconference recording
1855
     *
1856
     * @param int $id ID of the item in the plugin_bbb_meeting table
1857
     * @param string Hash identifying the recording, as provided by the API
1858
     *
1859
     * @return mixed ID of the newly created link, or false on error
1860
     * @assert (null, null) === false
1861
     * @assert (1, null) === false
1862
     * @assert (null, 'abcdefabcdefabcdefabcdef') === false
1863
     */
1864
    public function copyRecordingToLinkTool($id)
1865
    {
1866
        if (empty($id)) {
1867
            return false;
1868
        }
1869
1870
        $em = Database::getManager();
1871
        /** @var ConferenceMeetingRepository $repo */
1872
        $repo = $em->getRepository(ConferenceMeeting::class);
1873
1874
        $meetingData = $repo->findOneAsArrayById((int) $id);
1875
        if (!$meetingData || empty($meetingData['remoteId'])) {
1876
            return false;
1877
        }
1878
1879
        $records = $this->api->getRecordingsWithXmlResponseArray([
1880
            'meetingId' => $meetingData['remoteId']
1881
        ]);
1882
1883
        if (!empty($records)) {
1884
            if (isset($records['message']) && !empty($records['message'])) {
1885
                if ($records['messageKey'] == 'noRecordings') {
1886
                    return false;
1887
                }
1888
            } else {
1889
                $record = $records[0];
1890
                if (is_array($record) && isset($record['recordId'])) {
1891
                    $url = $record['playbackFormatUrl'];
1892
                    $link = new \Link();
1893
                    $params = [
1894
                        'url' => $url,
1895
                        'title' => $meetingData['title'],
1896
                    ];
1897
                    $id = $link->save($params);
1898
1899
                    return $id;
1900
                }
1901
            }
1902
        }
1903
1904
        return false;
1905
    }
1906
1907
    /**
1908
     * Checks if the video conference server is running.
1909
     * Function currently disabled (always returns 1)
1910
     * @return bool True if server is running, false otherwise
1911
     * @assert () === false
1912
     */
1913
    public function isServerRunning()
1914
    {
1915
        return true;
1916
        //return BigBlueButtonBN::isServerRunning($this->protocol.$this->url);
1917
    }
1918
1919
    /**
1920
     * Checks if the video conference plugin is properly configured
1921
     * @return bool True if plugin has a host and a salt, false otherwise
1922
     * @assert () === false
1923
     */
1924
    public function isServerConfigured()
1925
    {
1926
        $host = $this->plugin->get('host');
1927
1928
        if (empty($host)) {
1929
            return false;
1930
        }
1931
1932
        $salt = $this->plugin->get('salt');
1933
1934
        if (empty($salt)) {
1935
            return false;
1936
        }
1937
1938
        return true;
1939
        //return BigBlueButtonBN::isServerRunning($this->protocol.$this->url);
1940
    }
1941
1942
    /**
1943
     * Get active session in the all platform
1944
     */
1945
    public function getActiveSessionsCount(): int
1946
    {
1947
        $em = Database::getManager();
1948
        $qb = $em->createQueryBuilder();
1949
1950
        $qb->select('COUNT(m.id)')
1951
            ->from(ConferenceMeeting::class, 'm')
1952
            ->where('m.status = :status')
1953
            ->andWhere('m.accessUrl = :accessUrl')
1954
            ->setParameter('status', 1)
1955
            ->setParameter('accessUrl', $this->accessUrl);
1956
1957
        return (int) $qb->getQuery()->getSingleScalarResult();
1958
    }
1959
1960
    /**
1961
     * Get active session in the all platform
1962
     */
1963
    public function getActiveSessions(): array
1964
    {
1965
        $em = Database::getManager();
1966
        $repo = $em->getRepository(ConferenceMeeting::class);
1967
1968
        $qb = $repo->createQueryBuilder('m')
1969
            ->where('m.status = :status')
1970
            ->andWhere('m.accessUrl = :accessUrl')
1971
            ->setParameter('status', 1)
1972
            ->setParameter('accessUrl', $this->accessUrl);
1973
1974
        return $qb->getQuery()->getArrayResult();
1975
    }
1976
1977
    /**
1978
     * @param string $url
1979
     */
1980
    public function redirectToBBB($url)
1981
    {
1982
        if (file_exists(__DIR__.'/../config.vm.php')) {
1983
            // Using VM
1984
            echo Display::url($this->plugin->get_lang('ClickToContinue'), $url);
1985
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1986
        } else {
1987
            // Classic
1988
            header("Location: $url");
1989
            exit;
1990
        }
1991
    }
1992
1993
    /**
1994
     * @return string
1995
     */
1996
    public function getConferenceUrl()
1997
    {
1998
        return api_get_path(WEB_PLUGIN_PATH).'Bbb/start.php?launch=1&'.$this->getUrlParams();
1999
    }
2000
2001
    /**
2002
     * Get the meeting info from DB by its name
2003
     *
2004
     * @param string $name
2005
     *
2006
     * @return array
2007
     */
2008
    public function findMeetingByName(string $name): ?array
2009
    {
2010
        $em = Database::getManager();
2011
        /** @var ConferenceMeetingRepository $repo */
2012
        $repo = $em->getRepository(ConferenceMeeting::class);
2013
2014
        $qb = $repo->createQueryBuilder('m')
2015
            ->where('m.title = :name')
2016
            ->setParameter('name', $name)
2017
            ->setMaxResults(1);
2018
2019
        $result = $qb->getQuery()->getArrayResult();
2020
2021
        return $result[0] ?? null;
2022
    }
2023
2024
    /**
2025
     * Get the meeting info from DB by its name
2026
     *
2027
     * @param int $id
2028
     *
2029
     * @return array
2030
     */
2031
    public function getMeeting(int $id): ?array
2032
    {
2033
        $em = Database::getManager();
2034
        /** @var ConferenceMeetingRepository $repo */
2035
        $repo = $em->getRepository(ConferenceMeeting::class);
2036
2037
        return $repo->findOneAsArrayById($id);
2038
    }
2039
2040
    /**
2041
     * Get the meeting info.
2042
     *
2043
     * @param int $id
2044
     *
2045
     * @return array
2046
     */
2047
    public function getMeetingByRemoteId(string $id): ?array
2048
    {
2049
        $em = Database::getManager();
2050
        /** @var ConferenceMeetingRepository $repo */
2051
        $repo = $em->getRepository(ConferenceMeeting::class);
2052
2053
        return $repo->findOneByRemoteIdAndAccessUrl($id, $this->accessUrl);
2054
    }
2055
2056
    /**
2057
     * @param int $meetingId
2058
     *
2059
     * @return array
2060
     */
2061
    public function findConnectedMeetingParticipants(int $meetingId): array
2062
    {
2063
        $em = Database::getManager();
2064
        /** @var ConferenceActivityRepository $repo */
2065
        $repo = $em->getRepository(ConferenceActivity::class);
2066
2067
        $activities = $repo->createQueryBuilder('a')
2068
            ->where('a.meeting = :meetingId')
2069
            ->andWhere('a.inAt IS NOT NULL')
2070
            ->setParameter('meetingId', $meetingId)
2071
            ->getQuery()
2072
            ->getResult();
2073
2074
        $participantIds = [];
2075
        $return = [];
2076
2077
        foreach ($activities as $activity) {
2078
            $participant = $activity->getParticipant();
2079
            $participantId = $participant?->getId();
2080
2081
            if (!$participantId || in_array($participantId, $participantIds)) {
2082
                continue;
2083
            }
2084
2085
            $participantIds[] = $participantId;
2086
2087
            $return[] = [
2088
                'id' => $activity->getId(),
2089
                'meeting_id' => $meetingId,
2090
                'participant' => api_get_user_entity($participantId),
2091
                'in_at' => $activity->getInAt()?->format('Y-m-d H:i:s'),
2092
                'out_at' => $activity->getOutAt()?->format('Y-m-d H:i:s'),
2093
            ];
2094
        }
2095
2096
        return $return;
2097
    }
2098
2099
    /**
2100
     * Check if the meeting has a capture.m4v video file. If exists then the has_video_m4v field is updated
2101
     *
2102
     * @param int $meetingId
2103
     *
2104
     * @return bool
2105
     */
2106
    public function checkDirectMeetingVideoUrl(int $meetingId): bool
2107
    {
2108
        $em = Database::getManager();
2109
        /** @var ConferenceMeetingRepository $repo */
2110
        $repo = $em->getRepository(ConferenceMeeting::class);
2111
2112
        $meetingInfo = $repo->findOneAsArrayById($meetingId);
2113
2114
        if (empty($meetingInfo) || !isset($meetingInfo['videoUrl'])) {
2115
            return false;
2116
        }
2117
2118
        $hasCapture = SocialManager::verifyUrl($meetingInfo['videoUrl'].'/capture.m4v');
2119
2120
        if ($hasCapture) {
2121
            $qb = $em->createQueryBuilder();
2122
            $qb->update(ConferenceMeeting::class, 'm')
2123
                ->set('m.hasVideoM4v', ':value')
2124
                ->where('m.id = :id')
2125
                ->setParameter('value', true)
2126
                ->setParameter('id', $meetingId)
2127
                ->getQuery()
2128
                ->execute();
2129
2130
            return true;
2131
        }
2132
2133
        return false;
2134
    }
2135
2136
    public static function showGlobalConferenceLink($userInfo)
2137
    {
2138
        if (empty($userInfo)) {
2139
            return false;
2140
        }
2141
2142
        $setting = api_get_plugin_setting('bbb', 'enable_global_conference');
2143
        $settingLink = api_get_plugin_setting('bbb', 'enable_global_conference_link');
2144
2145
        if ($setting === 'true' && $settingLink === 'true') {
2146
            $allowedRoles = api_get_plugin_setting('bbb', 'global_conference_allow_roles');
2147
            $allowedRoles = self::normalizeSettingToArray($allowedRoles);
2148
2149
            if (api_is_platform_admin()) {
2150
                $userInfo['status'] = PLATFORM_ADMIN;
2151
            }
2152
2153
            if (!empty($allowedRoles)) {
2154
                $needle = (string) $userInfo['status'];
2155
                $haystack = array_map('strval', $allowedRoles);
2156
2157
                if (!in_array($needle, $haystack, true)) {
2158
                    return false;
2159
                }
2160
            }
2161
2162
            return true;
2163
        }
2164
2165
        return false;
2166
    }
2167
}
2168