Passed
Push — master ( 932820...be283a )
by Yannick
09:00
created

Bbb::ensureApi()   B

Complexity

Conditions 8
Paths 9

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 8
nc 9
nop 0
dl 0
loc 14
rs 8.4444
c 0
b 0
f 0
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');
94
                $bbbSetting = isset($bbbSetting['bbb']) ? $bbbSetting['bbb'] === 'true' : false;
95
96
                if ($bbbSetting) {
97
                    // Course check
98
                    $courseInfo = api_get_course_info();
99
                    if ($courseInfo) {
100
                        $this->groupSupport = api_get_course_plugin_setting(
101
                                'bbb',
102
                                'bbb_enable_conference_in_groups',
103
                                $courseInfo
104
                            ) === '1';
105
                    }
106
                }
107
            }
108
        }
109
        $this->maxUsersLimit = $this->plugin->get('max_users_limit');
110
111
        if ($bbbPluginEnabled === 'true') {
112
            $userInfo = api_get_user_info();
113
            if (empty($userInfo) && !empty($isGlobalPerUser)) {
114
                // If we are following a link to a global "per user" conference
115
                // then generate a random guest name to join the conference
116
                // because there is no part of the process where we give a name
117
                //$this->userCompleteName = 'Guest'.rand(1000, 9999);
118
            } else {
119
                $this->userCompleteName = $userInfo['complete_name'];
120
            }
121
122
            if (api_is_anonymous()) {
123
                $this->userCompleteName = get_lang('Guest').'_'.rand(1000, 9999);
124
            }
125
126
            $this->salt = $bbb_salt;
127
            if (!empty($bbb_host)) {
128
                if (substr($bbb_host, -1, 1) !== '/') {
129
                    $bbb_host .= '/';
130
                }
131
                $this->url = $bbb_host;
132
                if (!preg_match('#/bigbluebutton/$#', $bbb_host)) {
133
                    $this->url = $bbb_host.'bigbluebutton/';
134
                }
135
            }
136
            $info = parse_url($bbb_host);
137
138
            if (isset($info['scheme'])) {
139
                $this->protocol = $info['scheme'].'://';
140
                $this->url = str_replace($this->protocol, '', $this->url);
141
                $urlWithProtocol = $bbb_host;
142
            } else {
143
                // We assume it's an http, if user wants to use https, the host *must* include the protocol.
144
                $this->protocol = 'http://';
145
                $urlWithProtocol = $this->protocol.$bbb_host;
146
            }
147
148
            // Normalize final URL with protocol to the /bigbluebutton/ endpoint
149
            if (!str_ends_with($urlWithProtocol, '/bigbluebutton/')) {
150
                $urlWithProtocol = rtrim($urlWithProtocol, '/').'/bigbluebutton/';
151
            }
152
            $this->urlWithProtocol = $urlWithProtocol;
153
154
            // Define constants safely (optional legacy compatibility)
155
            if (!defined('CONFIG_SERVER_BASE_URL')) {
156
                define('CONFIG_SERVER_BASE_URL', $this->url);
157
            }
158
            if (!defined('CONFIG_SERVER_PROTOCOL')) {
159
                define('CONFIG_SERVER_PROTOCOL', $this->protocol);
160
            }
161
            if (!defined('CONFIG_SERVER_URL_WITH_PROTOCOL')) {
162
                define('CONFIG_SERVER_URL_WITH_PROTOCOL', $this->urlWithProtocol);
163
            }
164
            if (!defined('CONFIG_SECURITY_SALT')) {
165
                // IMPORTANT: use $this->salt, not $this->securitySalt
166
                define('CONFIG_SECURITY_SALT', (string) $this->salt);
167
            }
168
169
            // Initialize API object only if we have both URL and salt
170
            if (!empty($this->urlWithProtocol) && !empty($this->salt)) {
171
                $this->api = new BigBlueButtonBN($this->urlWithProtocol, $this->salt);
172
                $this->pluginEnabled = true;
173
            } else {
174
                $this->api = null;
175
                $this->pluginEnabled = false;
176
            }
177
178
            $this->logoutUrl = $this->getListingUrl();
179
        }
180
    }
181
182
    private function ensureApi(): bool
183
    {
184
        if ($this->api instanceof BigBlueButtonBN) {
185
            return true;
186
        }
187
        $url = $this->urlWithProtocol ?: (defined('CONFIG_SERVER_URL_WITH_PROTOCOL') ? constant('CONFIG_SERVER_URL_WITH_PROTOCOL') : '');
188
        $salt = $this->salt ?: (defined('CONFIG_SECURITY_SALT') ? constant('CONFIG_SECURITY_SALT') : '');
189
190
        if ($url && $salt) {
191
            $this->api = new BigBlueButtonBN($url, $salt);
192
            return $this->api instanceof BigBlueButtonBN;
193
        }
194
195
        return false;
196
    }
197
198
    /**
199
     * @param int $courseId  Optional. Course ID.
200
     * @param int $sessionId Optional. Session ID.
201
     * @param int $groupId   Optional. Group ID.
202
     *
203
     * @return string
204
     */
205
    public function getListingUrl($courseId = 0, $sessionId = 0, $groupId = 0)
206
    {
207
        return api_get_path(WEB_PLUGIN_PATH).'Bbb/listing.php?'
208
            .$this->getUrlParams($courseId, $sessionId, $groupId);
209
    }
210
211
    /**
212
     * @param int $courseId  Optional. Course ID.
213
     * @param int $sessionId Optional. Session ID.
214
     * @param int $groupId   Optional. Group ID.
215
     *
216
     * @return string
217
     */
218
    public function getUrlParams($courseId = 0, $sessionId = 0, $groupId = 0)
219
    {
220
        if (empty($this->courseId) && !$courseId) {
221
            if ($this->isGlobalConferencePerUserEnabled()) {
222
                return 'global=1&user_id='.$this->userId;
223
            }
224
225
            if ($this->isGlobalConference()) {
226
                return 'global=1';
227
            }
228
229
            return '';
230
        }
231
232
        $defaultCourseId = (int) $this->courseId;
233
        if (!empty($courseId)) {
234
            $defaultCourseId = (int) $courseId;
235
        }
236
237
        return http_build_query(
238
            [
239
                'cid' => $defaultCourseId,
240
                'sid' => (int) $sessionId ?: $this->sessionId,
241
                'gid' => (int) $groupId ?: $this->groupId,
242
            ]
243
        );
244
    }
245
246
    /**
247
     * @return bool
248
     */
249
    public function isGlobalConferencePerUserEnabled()
250
    {
251
        return $this->enableGlobalConferencePerUser;
252
    }
253
254
    /**
255
     * @return bool
256
     */
257
    public function isGlobalConference()
258
    {
259
        if ($this->isGlobalConferenceEnabled() === false) {
260
            return false;
261
        }
262
263
        return (bool) $this->isGlobalConference;
264
    }
265
266
    /**
267
     * @return bool
268
     */
269
    public function isGlobalConferenceEnabled()
270
    {
271
        return $this->enableGlobalConference;
272
    }
273
274
    /**
275
     * @param array $userInfo
276
     *
277
     * @return bool
278
     */
279
    public static function showGlobalConferenceLink($userInfo)
280
    {
281
        if (empty($userInfo)) {
282
            return false;
283
        }
284
        $setting = api_get_plugin_setting('bbb', 'enable_global_conference');
285
        $settingLink = api_get_plugin_setting('bbb', 'enable_global_conference_link');
286
        if ($setting === 'true' && $settingLink === 'true') {
287
            //$content = Display::url(get_lang('LaunchVideoConferenceRoom'), $url);
288
            $allowedRoles = api_get_plugin_setting(
289
                'bbb',
290
                'global_conference_allow_roles'
291
            );
292
293
            if (api_is_platform_admin()) {
294
                $userInfo['status'] = PLATFORM_ADMIN;
295
            }
296
297
            $showGlobalLink = true;
298
            if (!empty($allowedRoles)) {
299
                if (!in_array($userInfo['status'], $allowedRoles)) {
300
                    $showGlobalLink = false;
301
                }
302
            }
303
304
            return $showGlobalLink;
305
        }
306
    }
307
308
    /**
309
     * Gets the global limit of users in a video-conference room.
310
     * This value can be overridden by course-specific values
311
     * @return  int Maximum number of users set globally
312
     */
313
    public function getMaxUsersLimit()
314
    {
315
        $limit = $this->maxUsersLimit;
316
        if ($limit <= 0) {
317
            $limit = 0;
318
        }
319
        $courseLimit = 0;
320
        $sessionLimit = 0;
321
322
        // Check course extra field
323
        if (!empty($this->courseId)) {
324
            $extraField = new ExtraField('course');
325
            $fieldIdList = $extraField->get_all(
326
                array('variable = ?' => 'plugin_bbb_course_users_limit')
327
            );
328
329
            if (!empty($fieldIdList)) {
330
                $fieldId = $fieldIdList[0]['id'] ?? null;
331
                if ($fieldId) {
332
                    $extraValue = new ExtraFieldValue('course');
333
                    $value = $extraValue->get_values_by_handler_and_field_id($this->courseId, $fieldId);
334
                    if (!empty($value['value'])) {
335
                        $courseLimit = (int) $value['value'];
336
                    }
337
                }
338
            }
339
        }
340
341
        // Check session extra field
342
        if (!empty($this->sessionId)) {
343
            $extraField = new ExtraField('session');
344
            $fieldIdList = $extraField->get_all(
345
                array('variable = ?' => 'plugin_bbb_session_users_limit')
346
            );
347
348
            if (!empty($fieldIdList)) {
349
                $fieldId = $fieldIdList[0]['id'] ?? null;
350
                if ($fieldId) {
351
                    $extraValue = new ExtraFieldValue('session');
352
                    $value = $extraValue->get_values_by_handler_and_field_id($this->sessionId, $fieldId);
353
                    if (!empty($value['value'])) {
354
                        $sessionLimit = (int) $value['value'];
355
                    }
356
                }
357
            }
358
        }
359
360
        if (!empty($sessionLimit)) {
361
            return $sessionLimit;
362
        } elseif (!empty($courseLimit)) {
363
            return $courseLimit;
364
        }
365
366
        return (int) $limit;
367
    }
368
369
    /**
370
     * Sets the global limit of users in a video-conference room.
371
     *
372
     * @param int Maximum number of users (globally)
373
     */
374
    public function setMaxUsersLimit($max)
375
    {
376
        if ($max < 0) {
377
            $max = 0;
378
        }
379
        $this->maxUsersLimit = (int) $max;
380
    }
381
382
    /**
383
     * See this file in you BBB to set up default values
384
     *
385
     * @param array $params Array of parameters that will be completed if not containing all expected variables
386
     *
387
     * /var/lib/tomcat6/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties
388
     *
389
     * More record information:
390
     * http://code.google.com/p/bigbluebutton/wiki/RecordPlaybackSpecification
391
     *
392
     * Default maximum number of users a meeting can have.
393
     * Doesn't get enforced yet but is the default value when the create
394
     * API doesn't pass a value.
395
     * defaultMaxUsers=20
396
     *
397
     * Default duration of the meeting in minutes.
398
     * Current default is 0 (meeting doesn't end).
399
     * defaultMeetingDuration=0
400
     *
401
     * Remove the meeting from memory when the end API is called.
402
     * This allows 3rd-party apps to recycle the meeting right-away
403
     * instead of waiting for the meeting to expire (see below).
404
     * removeMeetingWhenEnded=false
405
     *
406
     * The number of minutes before the system removes the meeting from memory.
407
     * defaultMeetingExpireDuration=1
408
     *
409
     * The number of minutes the system waits when a meeting is created and when
410
     * a user joins. If after this period, a user hasn't joined, the meeting is
411
     * removed from memory.
412
     * defaultMeetingCreateJoinDuration=5
413
     *
414
     * @return mixed
415
     */
416
    public function createMeeting($params)
417
    {
418
        // Ensure API is available
419
        if (!$this->ensureApi()) {
420
            if ($this->debug) {
421
                error_log('BBB API not initialized in createMeeting().');
422
            }
423
            return false;
424
        }
425
426
        // Normalize input
427
        $params = is_array($params) ? $params : [];
428
429
        // Context defaults
430
        $params['c_id']       = $params['c_id']       ?? api_get_course_int_id();
431
        $params['session_id'] = $params['session_id'] ?? api_get_session_id();
432
433
        if ($this->hasGroupSupport()) {
434
            $params['group_id'] = $params['group_id'] ?? api_get_group_id();
435
        }
436
437
        if ($this->isGlobalConferencePerUserEnabled() && !empty($this->userId)) {
438
            $params['user_id'] = (int) $this->userId;
439
        }
440
441
        // Passwords
442
        $params['attendee_pw']  = $params['attendee_pw']  ?? $this->getUserMeetingPassword();
443
        $params['moderator_pw'] = $params['moderator_pw'] ?? $this->getModMeetingPassword();
444
        $attendeePassword  = (string) $params['attendee_pw'];
445
        $moderatorPassword = (string) $params['moderator_pw'];
446
447
        // Recording and limits
448
        $params['record'] = api_get_course_plugin_setting('bbb', 'big_blue_button_record_and_store') == 1 ? 1 : 0;
449
        $max = api_get_course_plugin_setting('bbb', 'big_blue_button_max_students_allowed');
450
        $max = isset($max) ? (int) $max : -1;
451
452
        // Meeting identifiers
453
        $params['status']     = 1;
454
        $params['remote_id']  = $params['remote_id']  ?? uniqid(true, true);
455
        $params['voice_bridge']= $params['voice_bridge'] ?? rand(10000, 99999);
456
        $params['created_at'] = $params['created_at'] ?? api_get_utc_datetime();
457
        $params['access_url'] = $params['access_url'] ?? $this->accessUrl;
458
459
        // Persist meeting entity
460
        $em = Database::getManager();
461
        $meeting = new ConferenceMeeting();
462
463
        $meeting
464
            ->setCourse(api_get_course_entity($params['c_id']))
465
            ->setSession(api_get_session_entity($params['session_id']))
466
            ->setAccessUrl(api_get_url_entity($params['access_url']))
467
            ->setGroup($this->hasGroupSupport() ? api_get_group_entity($params['group_id']) : null)
468
            ->setUser($this->isGlobalConferencePerUserEnabled() ? api_get_user_entity($params['user_id'] ?? 0) : null)
469
            ->setRemoteId($params['remote_id'])
470
            ->setTitle($params['meeting_name'] ?? $this->getCurrentVideoConferenceName())
471
            ->setAttendeePw($attendeePassword)
472
            ->setModeratorPw($moderatorPassword)
473
            ->setRecord((bool) $params['record'])
474
            ->setStatus($params['status'])
475
            ->setVoiceBridge($params['voice_bridge'])
476
            ->setWelcomeMsg($params['welcome_msg'] ?? null)
477
            ->setVisibility(1)
478
            ->setHasVideoM4v(false)
479
            ->setServiceProvider('bbb');
480
481
        $em->persist($meeting);
482
        $em->flush();
483
484
        $id = $meeting->getId();
485
486
        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

486
        Event::/** @scrutinizer ignore-call */ 
487
               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...
487
            'bbb_create_meeting',
488
            'meeting_id',
489
            $id,
490
            null,
491
            api_get_user_id(),
492
            api_get_course_int_id(),
493
            api_get_session_id()
494
        );
495
496
        // Compute BBB params
497
        $meetingName = $meeting->getTitle();
498
        $record      = $meeting->isRecord() ? 'true' : 'false';
499
        $duration    = 300;
500
        $meetingDuration = (int) $this->plugin->get('meeting_duration');
501
        if (!empty($meetingDuration)) {
502
            $duration = $meetingDuration;
503
        }
504
505
        // Normalize optional "documents" to avoid undefined index warnings
506
        $documents = [];
507
        if (!empty($params['documents']) && is_array($params['documents'])) {
508
            foreach ($params['documents'] as $doc) {
509
                if (!is_array($doc)) {
510
                    continue;
511
                }
512
                $url = $doc['url'] ?? null;
513
                if (!$url) {
514
                    continue;
515
                }
516
                $documents[] = [
517
                    'url'          => (string) $url,
518
                    'filename'     => $doc['filename'] ?? (basename(parse_url($url, PHP_URL_PATH) ?: 'document')),
519
                    'downloadable' => array_key_exists('downloadable', $doc) ? (bool) $doc['downloadable'] : true,
520
                    'removable'    => array_key_exists('removable', $doc) ? (bool) $doc['removable'] : true,
521
                ];
522
            }
523
        }
524
525
        $bbbParams = [
526
            'meetingId'       => $meeting->getRemoteId(),
527
            'meetingName'     => $meetingName,
528
            'attendeePw'      => $attendeePassword,
529
            'moderatorPw'     => $moderatorPassword,
530
            'welcomeMsg'      => $meeting->getWelcomeMsg(),
531
            'dialNumber'      => '',
532
            'voiceBridge'     => $meeting->getVoiceBridge(),
533
            'webVoice'        => '',
534
            'logoutUrl'       => $this->logoutUrl . '&action=logout&remote_id=' . $meeting->getRemoteId(),
535
            'maxParticipants' => $max,
536
            'record'          => $record,
537
            'duration'        => $duration,
538
            'documents'       => $documents, // safe default (empty array) if none provided
539
        ];
540
541
        // Try to create meeting until success (kept as in original logic)
542
        $status = false;
543
        while ($status === false) {
544
            $result = $this->api->createMeetingWithXmlResponseArray($bbbParams);
545
546
            if (is_array($result) && (string) ($result['returncode'] ?? '') === 'SUCCESS') {
547
                if ($this->plugin->get('allow_regenerate_recording') === 'true' && !empty($result['internalMeetingID'])) {
548
                    $meeting->setInternalMeetingId($result['internalMeetingID']);
549
                    $em->flush();
550
                }
551
552
                return $this->joinMeeting($meetingName, true);
553
            }
554
555
            // Break condition to avoid infinite loop if API keeps failing
556
            if ($this->debug) {
557
                error_log('BBB createMeeting failed, 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

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

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

972
            error_log('meeting_exists './** @scrutinizer ignore-type */ print_r($meeting ? ['id' => $meeting->getId()] : [], 1));
Loading history...
973
        }
974
975
        if (!$meeting) {
976
            return [];
977
        }
978
979
        return [
980
            'id' => $meeting->getId(),
981
            'c_id' => $meeting->getCourse()?->getId(),
982
            'session_id' => $meeting->getSession()?->getId(),
983
            'meeting_name' => $meeting->getTitle(),
984
            'status' => $meeting->getStatus(),
985
            'access_url' => $meeting->getAccessUrl()?->getId(),
986
            'group_id' => $meeting->getGroup()?->getIid(),
987
            'remote_id' => $meeting->getRemoteId(),
988
            'moderator_pw' => $meeting->getModeratorPw(),
989
            'attendee_pw' => $meeting->getAttendeePw(),
990
            'created_at' => $meeting->getCreatedAt()->format('Y-m-d H:i:s'),
991
            'closed_at' => $meeting->getClosedAt()?->format('Y-m-d H:i:s'),
992
            'visibility' => $meeting->getVisibility(),
993
            'video_url' => $meeting->getVideoUrl(),
994
            'has_video_m4v' => $meeting->isHasVideoM4v(),
995
            'record' => $meeting->isRecord(),
996
            'internal_meeting_id' => $meeting->getInternalMeetingId(),
997
        ];
998
    }
999
1000
    /**
1001
     * Gets a list from the database of all meetings attached to a course with the given status
1002
     * @param int $courseId
1003
     * @param int $sessionId
1004
     * @param int $status 0 for closed meetings, 1 for open meetings
1005
     *
1006
     * @return array
1007
     */
1008
    public function getAllMeetingsInCourse($courseId, $sessionId, $status)
1009
    {
1010
        $em = Database::getManager();
1011
        $courseEntity = api_get_course_entity($courseId);
1012
        $sessionEntity = api_get_session_entity($sessionId);
1013
1014
        $meetings = $em->getRepository(ConferenceMeeting::class)->findBy([
1015
            'course' => $courseEntity,
1016
            'session' => $sessionEntity,
1017
            'status' => $status,
1018
        ]);
1019
1020
        $results = [];
1021
        foreach ($meetings as $meeting) {
1022
            $results[] = [
1023
                'id' => $meeting->getId(),
1024
                'c_id' => $meeting->getCourse()?->getId(),
1025
                'session_id' => $meeting->getSession()?->getId(),
1026
                'meeting_name' => $meeting->getTitle(),
1027
                'status' => $meeting->getStatus(),
1028
                'access_url' => $meeting->getAccessUrl()?->getId(),
1029
                'group_id' => $meeting->getGroup()?->getIid(),
1030
                'remote_id' => $meeting->getRemoteId(),
1031
                'moderator_pw' => $meeting->getModeratorPw(),
1032
                'attendee_pw' => $meeting->getAttendeePw(),
1033
                'created_at' => $meeting->getCreatedAt()->format('Y-m-d H:i:s'),
1034
                'closed_at' => $meeting->getClosedAt()?->format('Y-m-d H:i:s'),
1035
                'visibility' => $meeting->getVisibility(),
1036
                'video_url' => $meeting->getVideoUrl(),
1037
                'has_video_m4v' => $meeting->isHasVideoM4v(),
1038
                'record' => $meeting->isRecord(),
1039
                'internal_meeting_id' => $meeting->getInternalMeetingId(),
1040
            ];
1041
        }
1042
1043
        return $results;
1044
    }
1045
1046
    /**
1047
     * Gets all the course meetings saved in the plugin_bbb_meeting table and
1048
     * generate actionable links (join/close/delete/etc)
1049
     *
1050
     * @param int   $courseId
1051
     * @param int   $sessionId
1052
     * @param int   $groupId
1053
     * @param bool  $isAdminReport Optional. Set to true then the report is for admins
1054
     * @param array $dateRange     Optional
1055
     *
1056
     * @return array Array of current open meeting rooms
1057
     * @throws Exception
1058
     */
1059
    public function getMeetings(
1060
        $courseId = 0,
1061
        $sessionId = 0,
1062
        $groupId = 0,
1063
        $isAdminReport = false,
1064
        $dateRange = []
1065
    ) {
1066
        if (!$this->ensureApi()) {
1067
            if ($this->debug) {
1068
                error_log('BBB API not initialized in getMeetings().');
1069
            }
1070
            return [];
1071
        }
1072
1073
        $em = Database::getManager();
1074
        $repo = $em->getRepository(ConferenceMeeting::class);
1075
        $manager = $this->isConferenceManager();
1076
        $isGlobal = $this->isGlobalConference();
1077
        $meetings = [];
1078
1079
        if (!empty($dateRange)) {
1080
            $dateStart = (new \DateTime($dateRange['search_meeting_start']))->setTime(0, 0, 0);
1081
            $dateEnd = (new \DateTime($dateRange['search_meeting_end']))->setTime(23, 59, 59);
1082
            $meetings = $repo->findByDateRange($dateStart, $dateEnd);
1083
        } elseif ($this->isGlobalConference()) {
1084
            $meetings = $repo->findBy([
1085
                'course' => null,
1086
                'user' => api_get_user_entity($this->userId),
1087
                'accessUrl' => api_get_url_entity($this->accessUrl),
1088
            ]);
1089
        } elseif ($this->isGlobalConferencePerUserEnabled()) {
1090
            $meetings = $repo->findBy([
1091
                'course' => api_get_course_entity($courseId),
1092
                'session' => api_get_session_entity($sessionId),
1093
                'user' => api_get_user_entity($this->userId),
1094
                'accessUrl' => api_get_url_entity($this->accessUrl),
1095
            ]);
1096
        } else {
1097
            $criteria = [
1098
                'course' => api_get_course_entity($courseId),
1099
                'session' => api_get_session_entity($sessionId),
1100
                'accessUrl' => api_get_url_entity($this->accessUrl),
1101
            ];
1102
            if ($this->hasGroupSupport() && $groupId) {
1103
                $criteria['group'] = api_get_group_entity($groupId);
1104
            }
1105
            $meetings = $repo->findBy($criteria, ['createdAt' => 'ASC']);
1106
        }
1107
1108
        $result = [];
1109
        foreach ($meetings as $meeting) {
1110
            $meetingArray = $this->convertMeetingToArray($meeting);
1111
            $recordLink = $this->plugin->get_lang('NoRecording');
1112
            $meetingBBB = $this->getMeetingInfo([
1113
                'meetingId' => $meeting->getRemoteId(),
1114
                'password' => $manager ? $meeting->getModeratorPw() : $meeting->getAttendeePw(),
1115
            ]);
1116
1117
            if (!$meetingBBB && $meeting->getId()) {
1118
                $meetingBBB = $this->getMeetingInfo([
1119
                    'meetingId' => $meeting->getId(),
1120
                    'password' => $manager ? $meeting->getModeratorPw() : $meeting->getAttendeePw(),
1121
                ]);
1122
            }
1123
1124
            if (!$meeting->isVisible() && !$manager) {
1125
                continue;
1126
            }
1127
1128
            $meetingBBB['end_url'] = $this->endUrl(['id' => $meeting->getId()]);
1129
            if (isset($meetingBBB['returncode']) && (string) $meetingBBB['returncode'] === 'FAILED') {
1130
                if ($meeting->getStatus() === 1 && $manager) {
1131
                    $this->endMeeting($meeting->getId(), $meeting->getCourse()?->getCode());
1132
                }
1133
            } else {
1134
                $meetingBBB['add_to_calendar_url'] = $this->addToCalendarUrl($meetingArray);
1135
            }
1136
1137
            if ($meeting->isRecord()) {
1138
                $recordings = $this->api->getRecordingsWithXmlResponseArray(['meetingId' => $meeting->getRemoteId()]);
1139
                if (!empty($recordings) && (!isset($recordings['messageKey']) || $recordings['messageKey'] !== 'noRecordings')) {
1140
                    $record = end($recordings);
1141
                    if (isset($record['playbackFormatUrl'])) {
1142
                        $recordLink = Display::url(
1143
                            $this->plugin->get_lang('ViewRecord'),
1144
                            $record['playbackFormatUrl'],
1145
                            ['target' => '_blank', 'class' => 'btn btn--plain']
1146
                        );
1147
                        $this->updateMeetingVideoUrl($meeting->getId(), $record['playbackFormatUrl']);
1148
                    }
1149
                }
1150
            }
1151
1152
            $actionLinks = $this->getActionLinks($meetingArray, $record ?? [], $isGlobal, $isAdminReport);
1153
1154
            $item = array_merge($meetingArray, [
1155
                'go_url' => '',
1156
                'show_links' => $recordLink,
1157
                'action_links' => implode(PHP_EOL, $actionLinks),
1158
                'publish_url' => $this->publishUrl(['id' => $meeting->getId()]),
1159
                'unpublish_url' => $this->unPublishUrl(['id' => $meeting->getId()]),
1160
            ]);
1161
1162
            if ($meeting->getStatus() === 1) {
1163
                $joinParams = [
1164
                    'meetingId' => $meeting->getRemoteId(),
1165
                    'username' => $this->userCompleteName,
1166
                    'password' => $manager ? $meeting->getModeratorPw() : $meeting->getAttendeePw(),
1167
                    'createTime' => '',
1168
                    'userID' => '',
1169
                    'webVoiceConf' => '',
1170
                ];
1171
                $item['go_url'] = $this->protocol.$this->api->getJoinMeetingURL($joinParams);
1172
            }
1173
1174
            $result[] = array_merge($item, $meetingBBB);
1175
        }
1176
1177
        return $result;
1178
    }
1179
1180
    private function convertMeetingToArray(ConferenceMeeting $meeting): array
1181
    {
1182
        return [
1183
            'id' => $meeting->getId(),
1184
            'remote_id' => $meeting->getRemoteId(),
1185
            'internal_meeting_id' => $meeting->getInternalMeetingId(),
1186
            'meeting_name' => $meeting->getTitle(),
1187
            'status' => $meeting->getStatus(),
1188
            'visibility' => $meeting->getVisibility(),
1189
            '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...
1190
            'closed_at' => $meeting->getClosedAt() instanceof \DateTime ? $meeting->getClosedAt()->format('Y-m-d H:i:s') : '',
1191
            'record' => $meeting->isRecord() ? 1 : 0,
1192
            'c_id' => $meeting->getCourse()?->getId() ?? 0,
1193
            'session_id' => $meeting->getSession()?->getId() ?? 0,
1194
            'group_id' => $meeting->getGroup()?->getIid() ?? 0,
1195
            'course' => $meeting->getCourse(),
1196
            'session' => $meeting->getSession(),
1197
            'title' => $meeting->getTitle(),
1198
        ];
1199
    }
1200
1201
    public function getMeetingsLight(
1202
        $courseId = 0,
1203
        $sessionId = 0,
1204
        $groupId = 0,
1205
        $dateRange = []
1206
    ): array {
1207
        $em = Database::getManager();
1208
        $repo = $em->getRepository(ConferenceMeeting::class);
1209
        $meetings = [];
1210
1211
        if (!empty($dateRange)) {
1212
            $dateStart = (new \DateTime($dateRange['search_meeting_start']))->setTime(0, 0, 0);
1213
            $dateEnd = (new \DateTime($dateRange['search_meeting_end']))->setTime(23, 59, 59);
1214
            $meetings = $repo->findByDateRange($dateStart, $dateEnd);
1215
        } else {
1216
            $criteria = [
1217
                'course' => api_get_course_entity($courseId),
1218
                'session' => api_get_session_entity($sessionId),
1219
                'accessUrl' => api_get_url_entity($this->accessUrl),
1220
            ];
1221
            if ($this->hasGroupSupport() && $groupId) {
1222
                $criteria['group'] = api_get_group_entity($groupId);
1223
            }
1224
            $meetings = $repo->findBy($criteria, ['createdAt' => 'DESC']);
1225
        }
1226
1227
        $result = [];
1228
        foreach ($meetings as $meeting) {
1229
            $meetingArray = $this->convertMeetingToArray($meeting);
1230
1231
            $item = array_merge($meetingArray, [
1232
                'go_url' => '',
1233
                'show_links' => $this->plugin->get_lang('NoRecording'),
1234
                'action_links' => '',
1235
                'publish_url' => $this->publishUrl(['id' => $meeting->getId()]),
1236
                'unpublish_url' => $this->unPublishUrl(['id' => $meeting->getId()]),
1237
            ]);
1238
1239
            $result[] = $item;
1240
        }
1241
1242
        return $result;
1243
    }
1244
1245
    /**
1246
     * @param array $meeting
1247
     *
1248
     * @return string
1249
     */
1250
    public function endUrl($meeting)
1251
    {
1252
        if (!isset($meeting['id'])) {
1253
            return '';
1254
        }
1255
1256
        return api_get_path(WEB_PLUGIN_PATH).'Bbb/listing.php?'.$this->getUrlParams().'&action=end&id='.$meeting['id'];
1257
    }
1258
1259
    /**
1260
     * Closes a meeting (usually when the user click on the close button from
1261
     * the conferences listing.
1262
     *
1263
     * @param string The internal ID of the meeting (id field for this meeting)
1264
     * @param string $courseCode
1265
     *
1266
     * @return void
1267
     * @assert (0) === false
1268
     */
1269
    public function endMeeting($id, $courseCode = null)
1270
    {
1271
        if (empty($id)) {
1272
            return false;
1273
        }
1274
1275
        $em = Database::getManager();
1276
1277
        /** @var ConferenceMeetingRepository $repo */
1278
        $repo = $em->getRepository(ConferenceMeeting::class);
1279
1280
        $meetingData = $repo->findOneAsArrayById((int) $id);
1281
        if (!$meetingData) {
1282
            return false;
1283
        }
1284
1285
        $manager = $this->isConferenceManager();
1286
        $pass = $manager ? $meetingData['moderatorPw'] : $meetingData['attendeePw'];
1287
1288
        Event::addEvent(
1289
            'bbb_end_meeting',
1290
            'meeting_id',
1291
            (int) $id,
1292
            null,
1293
            api_get_user_id(),
1294
            api_get_course_int_id(),
1295
            api_get_session_id()
1296
        );
1297
1298
        $endParams = [
1299
            'meetingId' => $meetingData['remoteId'],
1300
            'password' => $pass,
1301
        ];
1302
        $this->api->endMeetingWithXmlResponseArray($endParams);
1303
1304
        $repo->closeMeeting((int) $id, new \DateTime());
1305
1306
        /** @var ConferenceActivityRepository $activityRepo */
1307
        $activityRepo = $em->getRepository(ConferenceActivity::class);
1308
1309
        $activities = $activityRepo->findOpenWithSameInAndOutTime((int) $id);
1310
1311
        foreach ($activities as $activity) {
1312
            $activity->setOutAt(new \DateTime());
1313
            $activity->setClose(BbbPlugin::ROOM_CLOSE);
1314
            $em->persist($activity);
1315
        }
1316
1317
        $activityRepo->closeAllByMeetingId((int) $id);
1318
1319
        $em->flush();
1320
1321
        return true;
1322
    }
1323
1324
    /**
1325
     * @param array $meeting
1326
     * @param array $record
1327
     *
1328
     * @return string
1329
     */
1330
    public function addToCalendarUrl($meeting, $record = []): string
1331
    {
1332
        $url = isset($record['playbackFormatUrl']) ? $record['playbackFormatUrl'] : '';
1333
1334
        return api_get_path(WEB_PLUGIN_PATH).'Bbb/listing.php?'.$this->getUrlParams(
1335
            ).'&action=add_to_calendar&id='.$meeting['id'].'&start='.api_strtotime($meeting['created_at']).'&url='.$url;
1336
    }
1337
1338
    /**
1339
     * @param int    $meetingId
1340
     * @param string $videoUrl
1341
     *
1342
     * @return bool|int
1343
     */
1344
    public function updateMeetingVideoUrl(int $meetingId, string $videoUrl): void
1345
    {
1346
        $em = Database::getManager();
1347
        /** @var ConferenceMeetingRepository $repo */
1348
        $repo = $em->getRepository(ConferenceMeeting::class);
1349
        $repo->updateVideoUrl($meetingId, $videoUrl);
1350
    }
1351
1352
    /**
1353
     * Force the course, session and/or group IDs
1354
     *
1355
     * @param string $courseCode
1356
     * @param int    $sessionId
1357
     * @param int    $groupId
1358
     */
1359
    public function forceCIdReq($courseCode, $sessionId = 0, $groupId = 0)
1360
    {
1361
        $this->courseCode = $courseCode;
1362
        $this->sessionId = (int) $sessionId;
1363
        $this->groupId = (int) $groupId;
1364
    }
1365
1366
    /**
1367
     * @param array $meetingInfo
1368
     * @param array $recordInfo
1369
     * @param bool  $isGlobal
1370
     * @param bool  $isAdminReport
1371
     *
1372
     * @return array
1373
     */
1374
    private function getActionLinks(
1375
        $meetingInfo,
1376
        $recordInfo,
1377
        $isGlobal = false,
1378
        $isAdminReport = false
1379
    ) {
1380
        $isVisible = $meetingInfo['visibility'] != 0;
1381
        $linkVisibility = $isVisible
1382
            ? Display::url(
1383
                Display::getMdiIcon(StateIcon::ACTIVE, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Make invisible')),
1384
                $this->unPublishUrl($meetingInfo)
1385
            )
1386
            : Display::url(
1387
                Display::getMdiIcon(StateIcon::INACTIVE, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Make visible')),
1388
                $this->publishUrl($meetingInfo)
1389
            );
1390
1391
        $links = [];
1392
        if ($this->plugin->get('allow_regenerate_recording') === 'true' && $meetingInfo['record'] == 1) {
1393
            if (!empty($recordInfo)) {
1394
                $links[] = Display::url(
1395
                    Display::getMdiIcon(ActionIcon::REFRESH, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('RegenerateRecord')),
1396
                    $this->regenerateRecordUrl($meetingInfo, $recordInfo)
1397
                );
1398
            } else {
1399
                $links[] = Display::url(
1400
                    Display::getMdiIcon(ActionIcon::REFRESH, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('RegenerateRecord')),
1401
                    $this->regenerateRecordUrlFromMeeting($meetingInfo)
1402
                );
1403
            }
1404
        }
1405
1406
        if (empty($recordInfo)) {
1407
            if (!$isAdminReport) {
1408
                if ($meetingInfo['status'] == 0) {
1409
                    $links[] = Display::url(
1410
                        Display::getMdiIcon(ActionIcon::DELETE, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Delete')),
1411
                        $this->deleteRecordUrl($meetingInfo)
1412
                    );
1413
                    $links[] = $linkVisibility;
1414
                }
1415
1416
                return $links;
1417
            } else {
1418
                $links[] = Display::url(
1419
                    Display::getMdiIcon(ObjectIcon::HOME, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Go to the course')),
1420
                    $this->getListingUrl($meetingInfo['c_id'], $meetingInfo['session_id'], $meetingInfo['group_id'])
1421
                );
1422
1423
                return $links;
1424
            }
1425
        }
1426
1427
        if (!$isGlobal) {
1428
            $links[] = Display::url(
1429
                Display::getMdiIcon(ObjectIcon::LINK, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('UrlMeetingToShare')),
1430
                $this->copyToRecordToLinkTool($meetingInfo)
1431
            );
1432
            $links[] = Display::url(
1433
                Display::getMdiIcon(ObjectIcon::AGENDA, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Add to calendar')),
1434
                $this->addToCalendarUrl($meetingInfo, $recordInfo)
1435
            );
1436
        }
1437
1438
        $hide = $this->plugin->get('disable_download_conference_link') === 'true' ? true : false;
1439
1440
        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...
1441
            if ($meetingInfo['has_video_m4v']) {
1442
                $links[] = Display::url(
1443
                    Display::getMdiIcon(ActionIcon::SAVE_FORM, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Download file')),
1444
                    $recordInfo['playbackFormatUrl'].'/capture.m4v',
1445
                    ['target' => '_blank']
1446
                );
1447
            } else {
1448
                $links[] = Display::url(
1449
                    Display::getMdiIcon(ActionIcon::SAVE_FORM, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Download file')),
1450
                    '#',
1451
                    [
1452
                        'id' => "btn-check-meeting-video-{$meetingInfo['id']}",
1453
                        'class' => 'check-meeting-video',
1454
                        'data-id' => $meetingInfo['id'],
1455
                    ]
1456
                );
1457
            }
1458
        }
1459
1460
1461
        if (!$isAdminReport) {
1462
            $links[] = Display::url(
1463
                Display::getMdiIcon(ActionIcon::DELETE, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Delete')),
1464
                $this->deleteRecordUrl($meetingInfo)
1465
            );
1466
            $links[] = $linkVisibility;
1467
        } else {
1468
            $links[] = Display::url(
1469
                Display::getMdiIcon(ObjectIcon::HOME, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Go to the course')),
1470
                $this->getListingUrl($meetingInfo['c_id'], $meetingInfo['session_id'], $meetingInfo['group_id'])
1471
            );
1472
        }
1473
1474
1475
        return $links;
1476
    }
1477
1478
    /**
1479
     * @param array $meeting
1480
     *
1481
     * @return string
1482
     */
1483
    public function unPublishUrl($meeting)
1484
    {
1485
        if (!isset($meeting['id'])) {
1486
            return null;
1487
        }
1488
1489
        return api_get_path(WEB_PLUGIN_PATH).'Bbb/listing.php?'.$this->getUrlParams(
1490
            ).'&action=unpublish&id='.$meeting['id'];
1491
    }
1492
1493
    /**
1494
     * @param array $meeting
1495
     *
1496
     * @return string
1497
     */
1498
    public function publishUrl($meeting)
1499
    {
1500
        if (!isset($meeting['id'])) {
1501
            return '';
1502
        }
1503
1504
        return api_get_path(WEB_PLUGIN_PATH).'Bbb/listing.php?'.$this->getUrlParams(
1505
            ).'&action=publish&id='.$meeting['id'];
1506
    }
1507
1508
    /**
1509
     * @param array $meeting
1510
     * @param array $recordInfo
1511
     *
1512
     * @return string
1513
     */
1514
    public function regenerateRecordUrl($meeting, $recordInfo)
1515
    {
1516
        if ($this->plugin->get('allow_regenerate_recording') !== 'true') {
1517
            return '';
1518
        }
1519
1520
        if (!isset($meeting['id'])) {
1521
            return '';
1522
        }
1523
1524
        if (empty($recordInfo) || (!empty($recordInfo['recordId']) && !isset($recordInfo['recordId']))) {
1525
            return '';
1526
        }
1527
1528
        return api_get_path(WEB_PLUGIN_PATH).'Bbb/listing.php?'.$this->getUrlParams().
1529
            '&action=regenerate_record&id='.$meeting['id'].'&record_id='.$recordInfo['recordId'];
1530
    }
1531
1532
    /**
1533
     * @param array $meeting
1534
     *
1535
     * @return string
1536
     */
1537
    public function regenerateRecordUrlFromMeeting($meeting)
1538
    {
1539
        if ($this->plugin->get('allow_regenerate_recording') !== 'true') {
1540
            return '';
1541
        }
1542
1543
        if (!isset($meeting['id'])) {
1544
            return '';
1545
        }
1546
1547
        return api_get_path(WEB_PLUGIN_PATH).'Bbb/listing.php?'.$this->getUrlParams().
1548
            '&action=regenerate_record&id='.$meeting['id'];
1549
    }
1550
1551
    /**
1552
     * @param array $meeting
1553
     *
1554
     * @return string
1555
     */
1556
    public function deleteRecordUrl($meeting)
1557
    {
1558
        if (!isset($meeting['id'])) {
1559
            return '';
1560
        }
1561
1562
        return api_get_path(WEB_PLUGIN_PATH).'Bbb/listing.php?'.$this->getUrlParams(
1563
            ).'&action=delete_record&id='.$meeting['id'];
1564
    }
1565
1566
    /**
1567
     * @param array $meeting
1568
     *
1569
     * @return string
1570
     */
1571
    public function copyToRecordToLinkTool($meeting)
1572
    {
1573
        if (!isset($meeting['id'])) {
1574
            return '';
1575
        }
1576
1577
        return api_get_path(WEB_PLUGIN_PATH).
1578
            'Bbb/listing.php?'.$this->getUrlParams().'&action=copy_record_to_link_tool&id='.$meeting['id'];
1579
    }
1580
1581
    /**
1582
     * Function disabled
1583
     */
1584
    public function publishMeeting($id)
1585
    {
1586
        if (empty($id)) {
1587
            return false;
1588
        }
1589
1590
        $em = Database::getManager();
1591
        /** @var ConferenceMeetingRepository $repo */
1592
        $repo = $em->getRepository(ConferenceMeeting::class);
1593
1594
        $meeting = $repo->find($id);
1595
        if (!$meeting) {
1596
            return false;
1597
        }
1598
1599
        $meeting->setVisibility(1);
1600
        $em->flush();
1601
1602
        return true;
1603
    }
1604
1605
    /**
1606
     * Function disabled
1607
     */
1608
    public function unpublishMeeting($id)
1609
    {
1610
        if (empty($id)) {
1611
            return false;
1612
        }
1613
1614
        $em = Database::getManager();
1615
        /** @var ConferenceMeetingRepository $repo */
1616
        $repo = $em->getRepository(ConferenceMeeting::class);
1617
1618
        $meeting = $repo->find($id);
1619
        if (!$meeting) {
1620
            return false;
1621
        }
1622
1623
        $meeting->setVisibility(0);
1624
        $em->flush();
1625
1626
        return true;
1627
    }
1628
1629
    /**
1630
     * Get users online in the current course room.
1631
     *
1632
     * @return int The number of users currently connected to the videoconference
1633
     * @assert () > -1
1634
     */
1635
    public function getUsersOnlineInCurrentRoom()
1636
    {
1637
        $courseId = api_get_course_int_id();
1638
        $sessionId = api_get_session_id();
1639
1640
        $em = Database::getManager();
1641
        $repo = $em->getRepository(ConferenceMeeting::class);
1642
1643
        $qb = $repo->createQueryBuilder('m')
1644
            ->where('m.status = 1')
1645
            ->andWhere('m.accessUrl = :accessUrl')
1646
            ->setParameter('accessUrl', $this->accessUrl)
1647
            ->setMaxResults(1);
1648
1649
        if ($this->hasGroupSupport()) {
1650
            $groupId = api_get_group_id();
1651
            $qb->andWhere('m.course = :courseId')
1652
                ->andWhere('m.session = :sessionId')
1653
                ->andWhere('m.group = :groupId')
1654
                ->setParameter('courseId', $courseId)
1655
                ->setParameter('sessionId', $sessionId)
1656
                ->setParameter('groupId', $groupId);
1657
        } elseif ($this->isGlobalConferencePerUserEnabled()) {
1658
            $qb->andWhere('m.user = :userId')
1659
                ->setParameter('userId', $this->userId);
1660
        } else {
1661
            $qb->andWhere('m.course = :courseId')
1662
                ->andWhere('m.session = :sessionId')
1663
                ->setParameter('courseId', $courseId)
1664
                ->setParameter('sessionId', $sessionId);
1665
        }
1666
1667
        $meetingData = $qb->getQuery()->getOneOrNullResult();
1668
1669
        if (!$meetingData) {
1670
            return 0;
1671
        }
1672
        $pass = $meetingData->getModeratorPw();
1673
        $info = $this->getMeetingInfo([
1674
            'meetingId' => $meetingData->getRemoteId(),
1675
            'password' => $pass,
1676
        ]);
1677
        if ($info === false) {
1678
            $info = $this->getMeetingInfo([
1679
                'meetingId' => $meetingData->getId(),
1680
                'password' => $pass,
1681
            ]);
1682
        }
1683
1684
        if (!empty($info) && isset($info['participantCount'])) {
1685
            return (int) $info['participantCount'];
1686
        }
1687
1688
        return 0;
1689
    }
1690
1691
    /**
1692
     * @param int    $id
1693
     * @param string $recordId
1694
     *
1695
     * @return bool
1696
     */
1697
    public function regenerateRecording($id, $recordId = '')
1698
    {
1699
        if ($this->plugin->get('allow_regenerate_recording') !== 'true') {
1700
            return false;
1701
        }
1702
1703
        if (empty($id)) {
1704
            return false;
1705
        }
1706
1707
        $em = Database::getManager();
1708
        /** @var ConferenceMeetingRepository $repo */
1709
        $repo = $em->getRepository(ConferenceMeeting::class);
1710
1711
        $meetingData = $repo->findOneAsArrayById((int) $id);
1712
        if (!$meetingData) {
1713
            return false;
1714
        }
1715
1716
        Event::addEvent(
1717
            'bbb_regenerate_record',
1718
            'record_id',
1719
            (int) $recordId,
1720
            null,
1721
            api_get_user_id(),
1722
            api_get_course_int_id(),
1723
            api_get_session_id()
1724
        );
1725
1726
        /** @var ConferenceRecordingRepository $recordingRepo */
1727
        $recordingRepo = $em->getRepository(ConferenceRecordingRepository::class);
1728
        $recordings = $recordingRepo->findByMeetingRemoteId($meetingData['remoteId']);
1729
1730
        if (!empty($recordings) && isset($recordings['messageKey']) && $recordings['messageKey'] === 'noRecordings') {
1731
            if (!empty($meetingData['internalMeetingId'])) {
1732
                return $this->api->generateRecording(['recordId' => $meetingData['internalMeetingId']]);
1733
            }
1734
1735
            return false;
1736
        }
1737
1738
        if (!empty($recordings['records'])) {
1739
            foreach ($recordings['records'] as $record) {
1740
                if ($recordId == $record['recordId']) {
1741
                    return $this->api->generateRecording(['recordId' => $recordId]);
1742
                }
1743
            }
1744
        }
1745
1746
        return false;
1747
    }
1748
1749
    /**
1750
     * Deletes a recording of a meeting
1751
     *
1752
     * @param int $id ID of the recording
1753
     *
1754
     * @return bool
1755
     *
1756
     * @assert () === false
1757
     * @todo Also delete links and agenda items created from this recording
1758
     */
1759
    public function deleteRecording($id)
1760
    {
1761
        if (empty($id)) {
1762
            return false;
1763
        }
1764
1765
        $em = Database::getManager();
1766
1767
        /** @var ConferenceMeetingRepository $meetingRepo */
1768
        $meetingRepo = $em->getRepository(ConferenceMeeting::class);
1769
        $meetingData = $meetingRepo->findOneAsArrayById((int) $id);
1770
        if (!$meetingData) {
1771
            return false;
1772
        }
1773
1774
        Event::addEvent(
1775
            'bbb_delete_record',
1776
            'meeting_id',
1777
            $id,
1778
            null,
1779
            api_get_user_id(),
1780
            api_get_course_int_id(),
1781
            api_get_session_id()
1782
        );
1783
1784
        $delete = false;
1785
        $recordings = [];
1786
1787
        if (!empty($meetingData['remoteId'])) {
1788
            Event::addEvent(
1789
                'bbb_delete_record',
1790
                'remote_id',
1791
                $meetingData['remoteId'],
1792
                null,
1793
                api_get_user_id(),
1794
                api_get_course_int_id(),
1795
                api_get_session_id()
1796
            );
1797
1798
            /** @var ConferenceRecordingRepository $recordingRepo */
1799
            $recordingRepo = $em->getRepository(ConferenceRecording::class);
1800
            $recordings = $recordingRepo->findByMeetingRemoteId($meetingData['remoteId']);
1801
        }
1802
1803
        if (!empty($recordings) && isset($recordings['messageKey']) && $recordings['messageKey'] === 'noRecordings') {
1804
            $delete = true;
1805
        } elseif (!empty($recordings['records'])) {
1806
            $recordsToDelete = [];
1807
            foreach ($recordings['records'] as $record) {
1808
                $recordsToDelete[] = $record['recordId'];
1809
            }
1810
1811
            if (!empty($recordsToDelete)) {
1812
                $recordingParams = ['recordId' => implode(',', $recordsToDelete)];
1813
                Event::addEvent(
1814
                    'bbb_delete_record',
1815
                    'record_id_list',
1816
                    implode(',', $recordsToDelete),
1817
                    null,
1818
                    api_get_user_id(),
1819
                    api_get_course_int_id(),
1820
                    api_get_session_id()
1821
                );
1822
1823
                $result = $this->api->deleteRecordingsWithXmlResponseArray($recordingParams);
1824
1825
                if (!empty($result) && isset($result['deleted']) && $result['deleted'] === 'true') {
1826
                    $delete = true;
1827
                }
1828
            }
1829
        }
1830
1831
        if (!$delete) {
1832
            $delete = true;
1833
        }
1834
1835
        if ($delete) {
1836
            /** @var ConferenceActivityRepository $activityRepo */
1837
            $activityRepo = $em->getRepository(ConferenceActivity::class);
1838
            $activityRepo->closeAllByMeetingId((int) $id);
1839
1840
            $meeting = $meetingRepo->find((int) $id);
1841
            if ($meeting) {
1842
                $em->remove($meeting);
1843
            }
1844
1845
            $em->flush();
1846
        }
1847
1848
        return $delete;
1849
    }
1850
1851
    /**
1852
     * Creates a link in the links tool from the given videoconference recording
1853
     *
1854
     * @param int $id ID of the item in the plugin_bbb_meeting table
1855
     * @param string Hash identifying the recording, as provided by the API
1856
     *
1857
     * @return mixed ID of the newly created link, or false on error
1858
     * @assert (null, null) === false
1859
     * @assert (1, null) === false
1860
     * @assert (null, 'abcdefabcdefabcdefabcdef') === false
1861
     */
1862
    public function copyRecordingToLinkTool($id)
1863
    {
1864
        if (empty($id)) {
1865
            return false;
1866
        }
1867
1868
        $em = Database::getManager();
1869
        /** @var ConferenceMeetingRepository $repo */
1870
        $repo = $em->getRepository(ConferenceMeeting::class);
1871
1872
        $meetingData = $repo->findOneAsArrayById((int) $id);
1873
        if (!$meetingData || empty($meetingData['remoteId'])) {
1874
            return false;
1875
        }
1876
1877
        $records = $this->api->getRecordingsWithXmlResponseArray([
1878
            'meetingId' => $meetingData['remoteId']
1879
        ]);
1880
1881
        if (!empty($records)) {
1882
            if (isset($records['message']) && !empty($records['message'])) {
1883
                if ($records['messageKey'] == 'noRecordings') {
1884
                    return false;
1885
                }
1886
            } else {
1887
                $record = $records[0];
1888
                if (is_array($record) && isset($record['recordId'])) {
1889
                    $url = $record['playbackFormatUrl'];
1890
                    $link = new \Link();
1891
                    $params = [
1892
                        'url' => $url,
1893
                        'title' => $meetingData['title'],
1894
                    ];
1895
                    $id = $link->save($params);
1896
1897
                    return $id;
1898
                }
1899
            }
1900
        }
1901
1902
        return false;
1903
    }
1904
1905
    /**
1906
     * Checks if the video conference server is running.
1907
     * Function currently disabled (always returns 1)
1908
     * @return bool True if server is running, false otherwise
1909
     * @assert () === false
1910
     */
1911
    public function isServerRunning()
1912
    {
1913
        return true;
1914
        //return BigBlueButtonBN::isServerRunning($this->protocol.$this->url);
1915
    }
1916
1917
    /**
1918
     * Checks if the video conference plugin is properly configured
1919
     * @return bool True if plugin has a host and a salt, false otherwise
1920
     * @assert () === false
1921
     */
1922
    public function isServerConfigured()
1923
    {
1924
        $host = $this->plugin->get('host');
1925
1926
        if (empty($host)) {
1927
            return false;
1928
        }
1929
1930
        $salt = $this->plugin->get('salt');
1931
1932
        if (empty($salt)) {
1933
            return false;
1934
        }
1935
1936
        return true;
1937
        //return BigBlueButtonBN::isServerRunning($this->protocol.$this->url);
1938
    }
1939
1940
    /**
1941
     * Get active session in the all platform
1942
     */
1943
    public function getActiveSessionsCount(): int
1944
    {
1945
        $em = Database::getManager();
1946
        $qb = $em->createQueryBuilder();
1947
1948
        $qb->select('COUNT(m.id)')
1949
            ->from(ConferenceMeeting::class, 'm')
1950
            ->where('m.status = :status')
1951
            ->andWhere('m.accessUrl = :accessUrl')
1952
            ->setParameter('status', 1)
1953
            ->setParameter('accessUrl', $this->accessUrl);
1954
1955
        return (int) $qb->getQuery()->getSingleScalarResult();
1956
    }
1957
1958
    /**
1959
     * Get active session in the all platform
1960
     */
1961
    public function getActiveSessions(): array
1962
    {
1963
        $em = Database::getManager();
1964
        $repo = $em->getRepository(ConferenceMeeting::class);
1965
1966
        $qb = $repo->createQueryBuilder('m')
1967
            ->where('m.status = :status')
1968
            ->andWhere('m.accessUrl = :accessUrl')
1969
            ->setParameter('status', 1)
1970
            ->setParameter('accessUrl', $this->accessUrl);
1971
1972
        return $qb->getQuery()->getArrayResult();
1973
    }
1974
1975
    /**
1976
     * @param string $url
1977
     */
1978
    public function redirectToBBB($url)
1979
    {
1980
        if (file_exists(__DIR__.'/../config.vm.php')) {
1981
            // Using VM
1982
            echo Display::url($this->plugin->get_lang('ClickToContinue'), $url);
1983
            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...
1984
        } else {
1985
            // Classic
1986
            header("Location: $url");
1987
            exit;
1988
        }
1989
    }
1990
1991
    /**
1992
     * @return string
1993
     */
1994
    public function getConferenceUrl()
1995
    {
1996
        return api_get_path(WEB_PLUGIN_PATH).'Bbb/start.php?launch=1&'.$this->getUrlParams();
1997
    }
1998
1999
    /**
2000
     * Get the meeting info from DB by its name
2001
     *
2002
     * @param string $name
2003
     *
2004
     * @return array
2005
     */
2006
    public function findMeetingByName(string $name): ?array
2007
    {
2008
        $em = Database::getManager();
2009
        /** @var ConferenceMeetingRepository $repo */
2010
        $repo = $em->getRepository(ConferenceMeeting::class);
2011
2012
        $qb = $repo->createQueryBuilder('m')
2013
            ->where('m.title = :name')
2014
            ->setParameter('name', $name)
2015
            ->setMaxResults(1);
2016
2017
        $result = $qb->getQuery()->getArrayResult();
2018
2019
        return $result[0] ?? null;
2020
    }
2021
2022
    /**
2023
     * Get the meeting info from DB by its name
2024
     *
2025
     * @param int $id
2026
     *
2027
     * @return array
2028
     */
2029
    public function getMeeting(int $id): ?array
2030
    {
2031
        $em = Database::getManager();
2032
        /** @var ConferenceMeetingRepository $repo */
2033
        $repo = $em->getRepository(ConferenceMeeting::class);
2034
2035
        return $repo->findOneAsArrayById($id);
2036
    }
2037
2038
    /**
2039
     * Get the meeting info.
2040
     *
2041
     * @param int $id
2042
     *
2043
     * @return array
2044
     */
2045
    public function getMeetingByRemoteId(string $id): ?array
2046
    {
2047
        $em = Database::getManager();
2048
        /** @var ConferenceMeetingRepository $repo */
2049
        $repo = $em->getRepository(ConferenceMeeting::class);
2050
2051
        return $repo->findOneByRemoteIdAndAccessUrl($id, $this->accessUrl);
2052
    }
2053
2054
    /**
2055
     * @param int $meetingId
2056
     *
2057
     * @return array
2058
     */
2059
    public function findConnectedMeetingParticipants(int $meetingId): array
2060
    {
2061
        $em = Database::getManager();
2062
        /** @var ConferenceActivityRepository $repo */
2063
        $repo = $em->getRepository(ConferenceActivity::class);
2064
2065
        $activities = $repo->createQueryBuilder('a')
2066
            ->where('a.meeting = :meetingId')
2067
            ->andWhere('a.inAt IS NOT NULL')
2068
            ->setParameter('meetingId', $meetingId)
2069
            ->getQuery()
2070
            ->getResult();
2071
2072
        $participantIds = [];
2073
        $return = [];
2074
2075
        foreach ($activities as $activity) {
2076
            $participant = $activity->getParticipant();
2077
            $participantId = $participant?->getId();
2078
2079
            if (!$participantId || in_array($participantId, $participantIds)) {
2080
                continue;
2081
            }
2082
2083
            $participantIds[] = $participantId;
2084
2085
            $return[] = [
2086
                'id' => $activity->getId(),
2087
                'meeting_id' => $meetingId,
2088
                'participant' => api_get_user_entity($participantId),
2089
                'in_at' => $activity->getInAt()?->format('Y-m-d H:i:s'),
2090
                'out_at' => $activity->getOutAt()?->format('Y-m-d H:i:s'),
2091
            ];
2092
        }
2093
2094
        return $return;
2095
    }
2096
2097
    /**
2098
     * Check if the meeting has a capture.m4v video file. If exists then the has_video_m4v field is updated
2099
     *
2100
     * @param int $meetingId
2101
     *
2102
     * @return bool
2103
     */
2104
    public function checkDirectMeetingVideoUrl(int $meetingId): bool
2105
    {
2106
        $em = Database::getManager();
2107
        /** @var ConferenceMeetingRepository $repo */
2108
        $repo = $em->getRepository(ConferenceMeeting::class);
2109
2110
        $meetingInfo = $repo->findOneAsArrayById($meetingId);
2111
2112
        if (empty($meetingInfo) || !isset($meetingInfo['videoUrl'])) {
2113
            return false;
2114
        }
2115
2116
        $hasCapture = SocialManager::verifyUrl($meetingInfo['videoUrl'].'/capture.m4v');
2117
2118
        if ($hasCapture) {
2119
            $qb = $em->createQueryBuilder();
2120
            $qb->update(ConferenceMeeting::class, 'm')
2121
                ->set('m.hasVideoM4v', ':value')
2122
                ->where('m.id = :id')
2123
                ->setParameter('value', true)
2124
                ->setParameter('id', $meetingId)
2125
                ->getQuery()
2126
                ->execute();
2127
2128
            return true;
2129
        }
2130
2131
        return false;
2132
    }
2133
}
2134