Passed
Pull Request — 1.11.x (#3998)
by Ghazi
11:49
created

bbb::insertMeetingFormat()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 3
dl 0
loc 11
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
/**
6
 * Class bbb
7
 * This script initiates a video conference session, calling the BigBlueButton
8
 * API BigBlueButton-Chamilo connector class
9
 */
10
class bbb
11
{
12
    public $url;
13
    public $salt;
14
    public $api;
15
    public $userCompleteName = '';
16
    public $protocol = 'http://';
17
    public $debug = false;
18
    public $logoutUrl = '';
19
    public $pluginEnabled = false;
20
    public $enableGlobalConference = false;
21
    public $enableGlobalConferencePerUser = false;
22
    public $isGlobalConference = false;
23
    public $groupSupport = false;
24
    public $userSupport = false;
25
    public $accessUrl = 1;
26
    public $userId = 0;
27
    public $plugin;
28
    private $courseCode;
29
    private $courseId;
30
    private $sessionId;
31
    private $groupId;
32
    private $maxUsersLimit;
33
34
    /**
35
     * Constructor (generates a connection to the API and the Chamilo settings
36
     * required for the connection to the video conference server)
37
     *
38
     * @param string $host
39
     * @param string $salt
40
     * @param bool $isGlobalConference
41
     * @param int $isGlobalPerUser
42
     */
43
    public function __construct(
44
        $host = '',
45
        $salt = '',
46
        $isGlobalConference = false,
47
        $isGlobalPerUser = 0
48
    )
49
    {
50
        $this->courseCode = api_get_course_id();
51
        $this->courseId = api_get_course_int_id();
52
        $this->sessionId = api_get_session_id();
53
        $this->groupId = api_get_group_id();
54
55
        // Initialize video server settings from global settings
56
        $this->plugin = BBBPlugin::create();
57
        $bbbPluginEnabled = $this->plugin->get('tool_enable');
0 ignored issues
show
Bug introduced by
The method get() does not exist on null. ( Ignorable by Annotation )

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

57
        /** @scrutinizer ignore-call */ 
58
        $bbbPluginEnabled = $this->plugin->get('tool_enable');

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...
58
59
        $bbb_host = !empty($host) ? $host : $this->plugin->get('host');
60
        $bbb_salt = !empty($salt) ? $salt : $this->plugin->get('salt');
61
62
        $this->table = Database::get_main_table('plugin_bbb_meeting');
63
        $this->enableGlobalConference = $this->plugin->get('enable_global_conference') === 'true';
64
        $this->isGlobalConference = (bool)$isGlobalConference;
65
66
        $columns = Database::listTableColumns($this->table);
67
        $this->groupSupport = isset($columns['group_id']) ? true : false;
68
        $this->userSupport = isset($columns['user_id']) ? true : false;
69
        $this->accessUrl = api_get_current_access_url_id();
70
71
        $this->enableGlobalConferencePerUser = false;
72
        if ($this->userSupport && !empty($isGlobalPerUser)) {
73
            $this->enableGlobalConferencePerUser = $this->plugin->get('enable_global_conference_per_user') === 'true';
74
            $this->userId = $isGlobalPerUser;
75
        }
76
77
        if ($this->groupSupport) {
78
            // Plugin check
79
            $this->groupSupport = $this->plugin->get('enable_conference_in_course_groups') === 'true' ? true : false;
80
            if ($this->groupSupport) {
81
                // Platform check
82
                $bbbSetting = api_get_setting('bbb_enable_conference_in_course_groups');
83
                $bbbSetting = isset($bbbSetting['bbb']) ? $bbbSetting['bbb'] === 'true' : false;
84
85
                if ($bbbSetting) {
86
                    // Course check
87
                    $courseInfo = api_get_course_info();
88
                    if ($courseInfo) {
89
                        $this->groupSupport = api_get_course_plugin_setting(
90
                                'bbb',
91
                                'bbb_enable_conference_in_groups',
92
                                $courseInfo
93
                            ) === '1';
94
                    }
95
                }
96
            }
97
        }
98
        $this->maxUsersLimit = $this->plugin->get('max_users_limit');
99
100
        if ($bbbPluginEnabled === 'true') {
101
            $userInfo = api_get_user_info();
102
            if (empty($userInfo) && !empty($isGlobalPerUser)) {
103
                // If we are following a link to a global "per user" conference
104
                // then generate a random guest name to join the conference
105
                // because there is no part of the process where we give a name
106
                //$this->userCompleteName = 'Guest'.rand(1000, 9999);
107
            } else {
108
                $this->userCompleteName = $userInfo['complete_name'];
109
            }
110
111
            if (api_is_anonymous()) {
112
                $this->userCompleteName = get_lang('Guest') . '_' . rand(1000, 9999);
113
            }
114
115
            $this->salt = $bbb_salt;
116
            if (!empty($bbb_host)) {
117
                if (substr($bbb_host, -1, 1) !== '/') {
118
                    $bbb_host .= '/';
119
                }
120
                $this->url = $bbb_host;
121
                if (!preg_match('#/bigbluebutton/$#', $bbb_host)) {
122
                    $this->url = $bbb_host . 'bigbluebutton/';
123
                }
124
            }
125
            $info = parse_url($bbb_host);
126
127
            if (isset($info['scheme'])) {
128
                $this->protocol = $info['scheme'] . '://';
129
                $this->url = str_replace($this->protocol, '', $this->url);
130
                $urlWithProtocol = $bbb_host;
131
            } else {
132
                // We assume it's an http, if user wants to use https, the host *must* include the protocol.
133
                $this->protocol = 'http://';
134
                $urlWithProtocol = $this->protocol . $bbb_host;
135
            }
136
137
            // Setting BBB api
138
            define('CONFIG_SECURITY_SALT', $this->salt);
139
            define('CONFIG_SERVER_URL_WITH_PROTOCOL', $urlWithProtocol);
140
            define('CONFIG_SERVER_BASE_URL', $this->url);
141
            define('CONFIG_SERVER_PROTOCOL', $this->protocol);
142
143
            $this->api = new BigBlueButtonBN();
144
            $this->pluginEnabled = true;
145
            $this->logoutUrl = $this->getListingUrl();
146
        }
147
    }
148
149
    /**
150
     * @param int $courseId Optional. Course ID.
151
     * @param int $sessionId Optional. Session ID.
152
     * @param int $groupId Optional. Group ID.
153
     *
154
     * @return string
155
     */
156
    public function getListingUrl($courseId = 0, $sessionId = 0, $groupId = 0)
157
    {
158
        return api_get_path(WEB_PLUGIN_PATH) . 'bbb/listing.php?'
159
            . $this->getUrlParams($courseId, $sessionId, $groupId);
160
    }
161
162
    /**
163
     * @param int $courseId Optional. Course ID.
164
     * @param int $sessionId Optional. Session ID.
165
     * @param int $groupId Optional. Group ID.
166
     *
167
     * @return string
168
     */
169
    public function getUrlParams($courseId = 0, $sessionId = 0, $groupId = 0)
170
    {
171
        if (empty($this->courseCode) && !$courseId) {
172
            if ($this->isGlobalConferencePerUserEnabled()) {
173
                return 'global=1&user_id=' . $this->userId;
174
            }
175
176
            if ($this->isGlobalConference()) {
177
                return 'global=1';
178
            }
179
180
            return '';
181
        }
182
183
        $courseCode = $this->courseCode;
184
        if (!empty($courseId)) {
185
            $course = api_get_course_info_by_id($courseId);
186
            if ($course) {
187
                $courseCode = $course['code'];
188
            }
189
        }
190
191
        return http_build_query(
192
            [
193
                'cidReq' => $courseCode,
194
                'id_session' => $sessionId ?: $this->sessionId,
195
                'gidReq' => $groupId ?: $this->groupId,
196
            ]
197
        );
198
    }
199
200
    /**
201
     * @return bool
202
     */
203
    public function isGlobalConferencePerUserEnabled()
204
    {
205
        return $this->enableGlobalConferencePerUser;
206
    }
207
208
    /**
209
     * @return bool
210
     */
211
    public function isGlobalConference()
212
    {
213
        if ($this->isGlobalConferenceEnabled() === false) {
214
            return false;
215
        }
216
217
        return (bool)$this->isGlobalConference;
218
    }
219
220
    /**
221
     * @return bool
222
     */
223
    public function isGlobalConferenceEnabled()
224
    {
225
        return $this->enableGlobalConference;
226
    }
227
228
    /**
229
     * @param array $userInfo
230
     *
231
     * @return bool
232
     */
233
    public static function showGlobalConferenceLink($userInfo)
234
    {
235
        if (empty($userInfo)) {
236
            return false;
237
        }
238
        $setting = api_get_plugin_setting('bbb', 'enable_global_conference');
239
        $settingLink = api_get_plugin_setting('bbb', 'enable_global_conference_link');
240
        if ($setting === 'true' && $settingLink === 'true') {
241
            //$content = Display::url(get_lang('LaunchVideoConferenceRoom'), $url);
242
            $allowedRoles = api_get_plugin_setting(
243
                'bbb',
244
                'global_conference_allow_roles'
245
            );
246
247
            if (api_is_platform_admin()) {
248
                $userInfo['status'] = PLATFORM_ADMIN;
249
            }
250
251
            $showGlobalLink = true;
252
            if (!empty($allowedRoles)) {
253
                if (!in_array($userInfo['status'], $allowedRoles)) {
254
                    $showGlobalLink = false;
255
                }
256
            }
257
258
            return $showGlobalLink;
259
        }
260
    }
261
262
    /**
263
     * Gets the global limit of users in a video-conference room.
264
     * This value can be overridden by course-specific values
265
     * @return  int Maximum number of users set globally
266
     */
267
    public function getMaxUsersLimit()
268
    {
269
        $limit = $this->maxUsersLimit;
270
        if ($limit <= 0) {
271
            $limit = 0;
272
        }
273
        $courseLimit = 0;
274
        $sessionLimit = 0;
275
        // Check the extra fields for this course and session
276
        // Session limit takes priority over course limit
277
        // Course limit takes priority over global limit
278
        if (!empty($this->courseId)) {
279
            $extraField = new ExtraField('course');
280
            $fieldId = $extraField->get_all(
281
                array('variable = ?' => 'plugin_bbb_course_users_limit')
282
            );
283
            $extraValue = new ExtraFieldValue('course');
284
            $value = $extraValue->get_values_by_handler_and_field_id($this->courseId, $fieldId[0]['id']);
285
            if (!empty($value['value'])) {
286
                $courseLimit = (int)$value['value'];
287
            }
288
        }
289
        if (!empty($this->sessionId)) {
290
            $extraField = new ExtraField('session');
291
            $fieldId = $extraField->get_all(
292
                array('variable = ?' => 'plugin_bbb_session_users_limit')
293
            );
294
            $extraValue = new ExtraFieldValue('session');
295
            $value = $extraValue->get_values_by_handler_and_field_id($this->sessionId, $fieldId[0]['id']);
296
            if (!empty($value['value'])) {
297
                $sessionLimit = (int)$value['value'];
298
            }
299
        }
300
301
        if (!empty($sessionLimit)) {
302
            return $sessionLimit;
303
        } elseif (!empty($courseLimit)) {
304
            return $courseLimit;
305
        }
306
307
        return (int)$limit;
308
    }
309
310
    /**
311
     * Sets the global limit of users in a video-conference room.
312
     *
313
     * @param int Maximum number of users (globally)
314
     */
315
    public function setMaxUsersLimit($max)
316
    {
317
        if ($max < 0) {
318
            $max = 0;
319
        }
320
        $this->maxUsersLimit = (int)$max;
321
    }
322
323
    /**
324
     * See this file in you BBB to set up default values
325
     *
326
     * @param array $params Array of parameters that will be completed if not containing all expected variables
327
     *
328
     * /var/lib/tomcat6/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties
329
     *
330
     * More record information:
331
     * http://code.google.com/p/bigbluebutton/wiki/RecordPlaybackSpecification
332
     *
333
     * Default maximum number of users a meeting can have.
334
     * Doesn't get enforced yet but is the default value when the create
335
     * API doesn't pass a value.
336
     * defaultMaxUsers=20
337
     *
338
     * Default duration of the meeting in minutes.
339
     * Current default is 0 (meeting doesn't end).
340
     * defaultMeetingDuration=0
341
     *
342
     * Remove the meeting from memory when the end API is called.
343
     * This allows 3rd-party apps to recycle the meeting right-away
344
     * instead of waiting for the meeting to expire (see below).
345
     * removeMeetingWhenEnded=false
346
     *
347
     * The number of minutes before the system removes the meeting from memory.
348
     * defaultMeetingExpireDuration=1
349
     *
350
     * The number of minutes the system waits when a meeting is created and when
351
     * a user joins. If after this period, a user hasn't joined, the meeting is
352
     * removed from memory.
353
     * defaultMeetingCreateJoinDuration=5
354
     *
355
     * @return mixed
356
     */
357
    public function createMeeting($params)
358
    {
359
        $params['c_id'] = api_get_course_int_id();
360
        $params['session_id'] = api_get_session_id();
361
362
        if ($this->hasGroupSupport()) {
363
            $params['group_id'] = api_get_group_id();
364
        }
365
366
        if ($this->isGlobalConferencePerUserEnabled() && !empty($this->userId)) {
367
            $params['user_id'] = (int)$this->userId;
368
        }
369
370
        $params['attendee_pw'] = isset($params['attendee_pw']) ? $params['attendee_pw'] : $this->getUserMeetingPassword();
371
        $attendeePassword = $params['attendee_pw'];
372
        $params['moderator_pw'] = isset($params['moderator_pw']) ? $params['moderator_pw'] : $this->getModMeetingPassword();
373
        $moderatorPassword = $params['moderator_pw'];
374
375
        $params['record'] = api_get_course_plugin_setting('bbb', 'big_blue_button_record_and_store') == 1;
376
        $max = api_get_course_plugin_setting('bbb', 'big_blue_button_max_students_allowed');
377
        $max = isset($max) ? $max : -1;
378
379
        $params['status'] = 1;
380
        // Generate a pseudo-global-unique-id to avoid clash of conferences on
381
        // the same BBB server with several Chamilo portals
382
        $params['remote_id'] = uniqid(true, true);
383
        // Each simultaneous conference room needs to have a different
384
        // voice_bridge composed of a 5 digits number, so generating a random one
385
        $params['voice_bridge'] = rand(10000, 99999);
386
        $params['created_at'] = api_get_utc_datetime();
387
        $params['access_url'] = $this->accessUrl;
388
        $id = Database::insert($this->table, $params);
389
390
        if ($id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $id of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
391
            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

391
            Event::/** @scrutinizer ignore-call */ 
392
                   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...
392
                'bbb_create_meeting',
393
                'meeting_id',
394
                (int)$id,
395
                null,
396
                api_get_user_id(),
397
                api_get_course_int_id(),
398
                api_get_session_id()
399
            );
400
401
            $meetingName = isset($params['meeting_name']) ? $params['meeting_name'] : $this->getCurrentVideoConferenceName();
402
            $welcomeMessage = isset($params['welcome_msg']) ? $params['welcome_msg'] : null;
403
            $record = isset($params['record']) && $params['record'] ? 'true' : 'false';
404
            //$duration = isset($params['duration']) ? intval($params['duration']) : 0;
405
            // This setting currently limits the maximum conference duration,
406
            // to avoid lingering sessions on the video-conference server #6261
407
            $duration = 300;
408
            $meetingDuration = (int)$this->plugin->get('meeting_duration');
409
            if (!empty($meetingDuration)) {
410
                $duration = $meetingDuration;
411
            }
412
            $bbbParams = array(
413
                'meetingId' => $params['remote_id'], // REQUIRED
414
                'meetingName' => $meetingName, // REQUIRED
415
                'attendeePw' => $attendeePassword, // Match this value in getJoinMeetingURL() to join as attendee.
416
                'moderatorPw' => $moderatorPassword, // Match this value in getJoinMeetingURL() to join as moderator.
417
                'welcomeMsg' => $welcomeMessage, // ''= use default. Change to customize.
418
                'dialNumber' => '', // The main number to call into. Optional.
419
                'voiceBridge' => $params['voice_bridge'], // PIN to join voice. Required.
420
                'webVoice' => '', // Alphanumeric to join voice. Optional.
421
                'logoutUrl' => $this->logoutUrl . '&action=logout&remote_id=' . $params['remote_id'],
422
                'maxParticipants' => $max, // Optional. -1 = unlimitted. Not supported in BBB. [number]
423
                'record' => $record, // New. 'true' will tell BBB to record the meeting.
424
                'duration' => $duration, // Default = 0 which means no set duration in minutes. [number]
425
                //'meta_category' => '',  // Use to pass additional info to BBB server. See API docs.
426
            );
427
428
            $status = false;
429
            $meeting = null;
430
            while ($status === false) {
431
                $result = $this->api->createMeetingWithXmlResponseArray($bbbParams);
432
                if (isset($result) && strval($result['returncode']) == 'SUCCESS') {
433
                    if ($this->plugin->get('allow_regenerate_recording') === 'true') {
434
                        $internalId = Database::escape_string($result['internalMeetingID']);
435
                        $sql = "UPDATE $this->table SET internal_meeting_id = '" . $internalId . "'
436
                                WHERE id = $id";
437
                        Database::query($sql);
438
                    }
439
                    $meeting = $this->joinMeeting($meetingName, true);
440
441
                    return $meeting;
442
                }
443
            }
444
        }
445
446
        return false;
447
    }
448
449
    /**
450
     * @return bool
451
     */
452
    public function hasGroupSupport()
453
    {
454
        return $this->groupSupport;
455
    }
456
457
    /**
458
     * Gets the password for a specific meeting for the current user
459
     *
460
     * @param string $courseCode
461
     *
462
     * @return string A moderator password if user is teacher, or the course code otherwise
463
     *
464
     */
465
    public function getUserMeetingPassword($courseCode = null)
466
    {
467
        if ($this->isGlobalConferencePerUserEnabled()) {
468
            return 'url_' . $this->userId . '_' . api_get_current_access_url_id();
469
        }
470
471
        if ($this->isGlobalConference()) {
472
            return 'url_' . api_get_current_access_url_id();
473
        }
474
        $courseCode = empty($courseCode) ? api_get_course_id() : $courseCode;
475
476
        return $courseCode;
477
    }
478
479
    /**
480
     * Generated a moderator password for the meeting.
481
     *
482
     * @param string $courseCode
483
     *
484
     * @return string A password for the moderation of the videoconference
485
     */
486
    public function getModMeetingPassword($courseCode = null)
487
    {
488
        if ($this->isGlobalConferencePerUserEnabled()) {
489
            return 'url_' . $this->userId . '_' . api_get_current_access_url_id() . '_mod';
490
        }
491
492
        if ($this->isGlobalConference()) {
493
            return 'url_' . api_get_current_access_url_id() . '_mod';
494
        }
495
496
        $courseCode = empty($courseCode) ? api_get_course_id() : $courseCode;
497
498
        return $courseCode . 'mod';
499
    }
500
501
    /**
502
     * @return string
503
     */
504
    public function getCurrentVideoConferenceName()
505
    {
506
        if ($this->isGlobalConferencePerUserEnabled()) {
507
            return 'url_' . $this->userId . '_' . api_get_current_access_url_id();
508
        }
509
510
        if ($this->isGlobalConference()) {
511
            return 'url_' . api_get_current_access_url_id();
512
        }
513
514
        if ($this->hasGroupSupport()) {
515
            return api_get_course_id() . '-' . api_get_session_id() . '-' . api_get_group_id();
516
        }
517
518
        return api_get_course_id() . '-' . api_get_session_id();
519
    }
520
521
    /**
522
     * Returns a meeting "join" URL
523
     *
524
     * @param string The name of the meeting (usually the course code)
525
     *
526
     * @return mixed The URL to join the meeting, or false on error
527
     * @todo implement moderator pass
528
     * @assert ('') === false
529
     * @assert ('abcdefghijklmnopqrstuvwxyzabcdefghijklmno') === false
530
     */
531
    public function joinMeeting($meetingName)
532
    {
533
        if ($this->debug) {
534
            error_log("joinMeeting: $meetingName");
535
        }
536
537
        if (empty($meetingName)) {
538
            return false;
539
        }
540
541
        $manager = $this->isConferenceManager();
542
        if ($manager) {
543
            $pass = $this->getModMeetingPassword();
544
        } else {
545
            $pass = $this->getUserMeetingPassword();
546
        }
547
548
        $meetingData = Database::select(
549
            '*',
550
            $this->table,
551
            array(
552
                'where' => array(
553
                    'meeting_name = ? AND status = 1 AND access_url = ?' => array(
554
                        $meetingName,
555
                        $this->accessUrl,
556
                    ),
557
                ),
558
            ),
559
            'first'
560
        );
561
562
        if (empty($meetingData) || !is_array($meetingData)) {
563
            if ($this->debug) {
564
                error_log("meeting does not exist: $meetingName");
565
            }
566
567
            return false;
568
        }
569
570
        $params = array(
571
            'meetingId' => $meetingData['remote_id'],
572
            //  -- REQUIRED - The unique id for the meeting
573
            'password' => $this->getModMeetingPassword()
574
            //  -- REQUIRED - The moderator password for the meeting
575
        );
576
577
        $meetingInfoExists = false;
578
        $meetingIsRunningInfo = $this->getMeetingInfo($params);
579
        if ($this->debug) {
580
            error_log('Searching meeting with params:');
581
            error_log(print_r($params, 1));
582
            error_log('Result:');
583
            error_log(print_r($meetingIsRunningInfo, 1));
584
        }
585
586
        if ($meetingIsRunningInfo === false) {
587
            // checking with the remote_id didn't work, so just in case and
588
            // to provide backwards support, check with the id
589
            $params = array(
590
                'meetingId' => $meetingData['id'],
591
                //  -- REQUIRED - The unique id for the meeting
592
                'password' => $this->getModMeetingPassword()
593
                //  -- REQUIRED - The moderator password for the meeting
594
            );
595
            $meetingIsRunningInfo = $this->getMeetingInfo($params);
596
            if ($this->debug) {
597
                error_log('Searching meetingId with params:');
598
                error_log(print_r($params, 1));
599
                error_log('Result:');
600
                error_log(print_r($meetingIsRunningInfo, 1));
601
            }
602
        }
603
604
        if (strval($meetingIsRunningInfo['returncode']) === 'SUCCESS' &&
605
            isset($meetingIsRunningInfo['meetingName']) &&
606
            !empty($meetingIsRunningInfo['meetingName'])
607
        ) {
608
            $meetingInfoExists = true;
609
        }
610
611
        if ($this->debug) {
612
            error_log(
613
                "meeting is running: " . intval($meetingInfoExists)
614
            );
615
        }
616
617
        $url = false;
618
        if ($meetingInfoExists) {
619
            $joinParams = [
620
                'meetingId' => $meetingData['remote_id'],
621
                //	-- REQUIRED - A unique id for the meeting
622
                'username' => $this->userCompleteName,
623
                //-- REQUIRED - The name that will display for the user in the meeting
624
                'password' => $pass,
625
                //-- REQUIRED - The attendee or moderator password, depending on what's passed here
626
                //'createTime' => api_get_utc_datetime(),			//-- OPTIONAL - string. Leave blank ('') unless you set this correctly.
627
                'userID' => api_get_user_id(),
628
                //-- OPTIONAL - string
629
                'webVoiceConf' => '',
630
            ];
631
            $url = $this->api->getJoinMeetingURL($joinParams);
632
            $url = $this->protocol . $url;
633
        }
634
635
        if ($this->debug) {
636
            error_log("return url :" . $url);
0 ignored issues
show
Bug introduced by
Are you sure $url of type false|string 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

636
            error_log("return url :" . /** @scrutinizer ignore-type */ $url);
Loading history...
637
        }
638
639
        return $url;
640
    }
641
642
    /**
643
     * Checks whether a user is teacher in the current course
644
     * @return bool True if the user can be considered a teacher in this course, false otherwise
645
     */
646
    public function isConferenceManager()
647
    {
648
        if (api_is_coach() || api_is_platform_admin(false, true)) {
649
            return true;
650
        }
651
652
        if ($this->isGlobalConferencePerUserEnabled()) {
653
            $currentUserId = api_get_user_id();
654
            if ($this->userId === $currentUserId) {
655
                return true;
656
            } else {
657
                return false;
658
            }
659
        }
660
661
        $courseInfo = api_get_course_info();
662
        $groupId = api_get_group_id();
663
        if (!empty($groupId) && !empty($courseInfo)) {
664
            $groupEnabled = api_get_course_plugin_setting('bbb', 'bbb_enable_conference_in_groups') === '1';
665
            if ($groupEnabled) {
666
                $studentCanStartConference = api_get_course_plugin_setting(
667
                        'bbb',
668
                        'big_blue_button_students_start_conference_in_groups'
669
                    ) === '1';
670
671
                if ($studentCanStartConference) {
672
                    $isSubscribed = GroupManager::is_user_in_group(
673
                        api_get_user_id(),
674
                        GroupManager::get_group_properties($groupId)
675
                    );
676
                    if ($isSubscribed) {
677
                        return true;
678
                    }
679
                }
680
            }
681
        }
682
683
        if (!empty($courseInfo)) {
684
            return api_is_course_admin();
685
        }
686
687
        return false;
688
    }
689
690
    /**
691
     * Get information about the given meeting
692
     *
693
     * @param array ...?
694
     *
695
     * @return mixed Array of information on success, false on error
696
     * @assert (array()) === false
697
     */
698
    public function getMeetingInfo($params)
699
    {
700
        try {
701
            $result = $this->api->getMeetingInfoWithXmlResponseArray($params);
702
            if ($result == null) {
703
                if ($this->debug) {
704
                    error_log("Failed to get any response. Maybe we can't contact the BBB server.");
705
                }
706
            }
707
708
            return $result;
709
        } catch (Exception $e) {
710
            if ($this->debug) {
711
                error_log('Caught exception: ', $e->getMessage(), "\n");
712
            }
713
        }
714
715
        return false;
716
    }
717
718
719
    /**
720
     * @param int $meetingId
721
     * @param int $userId
722
     *
723
     * @return array
724
     */
725
    public function getMeetingParticipantInfo($meetingId, $userId)
726
    {
727
        $meetingData = Database::select(
728
            '*',
729
            'plugin_bbb_room',
730
            array('where' => array('meeting_id = ? AND participant_id = ?' => [$meetingId, $userId])),
731
            'first'
732
        );
733
734
        if ($meetingData) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $meetingData of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
735
            return $meetingData;
736
        }
737
738
        return [];
739
    }
740
741
    /**
742
     * Save a participant in a meeting room
743
     *
744
     * @param int $meetingId
745
     * @param int $participantId
746
     *
747
     * @return false|int The last inserted ID. Otherwise return false
748
     */
749
    public function saveParticipant($meetingId, $participantId)
750
    {
751
        $meetingData = Database::select(
752
            '*',
753
            'plugin_bbb_room',
754
            [
755
                'where' => [
756
                    'meeting_id = ? AND participant_id = ? AND close = ?' => [
757
                        $meetingId,
758
                        $participantId,
759
                        BBBPlugin::ROOM_OPEN,
760
                    ],
761
                ],
762
            ]
763
        );
764
765
        foreach ($meetingData as $roomItem) {
766
            $inAt = $roomItem['in_at'];
767
            $outAt = $roomItem['out_at'];
768
            $roomId = $roomItem['id'];
769
            if (!empty($roomId)) {
770
                if ($inAt != $outAt) {
771
                    Database::update(
772
                        'plugin_bbb_room',
773
                        ['close' => BBBPlugin::ROOM_CLOSE],
774
                        ['id = ? ' => $roomId]
775
                    );
776
                } else {
777
                    Database::update(
778
                        'plugin_bbb_room',
779
                        ['out_at' => api_get_utc_datetime(), 'close' => BBBPlugin::ROOM_CLOSE],
780
                        ['id = ? ' => $roomId]
781
                    );
782
                }
783
            }
784
        }
785
786
        $params = [
787
            'meeting_id' => $meetingId,
788
            'participant_id' => $participantId,
789
            'in_at' => api_get_utc_datetime(),
790
            'out_at' => api_get_utc_datetime(),
791
            'close' => BBBPlugin::ROOM_OPEN,
792
        ];
793
794
        return Database::insert(
795
            'plugin_bbb_room',
796
            $params
797
        );
798
    }
799
800
    /**
801
     * Tells whether the given meeting exists and is running
802
     * (using course code as name)
803
     *
804
     * @param string $meetingName Meeting name (usually the course code)
805
     *
806
     * @return bool True if meeting exists, false otherwise
807
     * @assert ('') === false
808
     * @assert ('abcdefghijklmnopqrstuvwxyzabcdefghijklmno') === false
809
     */
810
    public function meetingExists($meetingName)
811
    {
812
        $meetingData = $this->getMeetingByName($meetingName);
813
814
        return !empty($meetingData);
815
    }
816
817
    /**
818
     * @param string $meetingName
819
     *
820
     * @return array
821
     */
822
    public function getMeetingByName($meetingName)
823
    {
824
        if (empty($meetingName)) {
825
            return [];
826
        }
827
828
        $courseId = api_get_course_int_id();
829
        $sessionId = api_get_session_id();
830
        $conditions = array(
831
            'where' => array(
832
                'c_id = ? AND session_id = ? AND meeting_name = ? AND status = 1 AND access_url = ?' =>
833
                    array($courseId, $sessionId, $meetingName, $this->accessUrl),
834
            ),
835
        );
836
837
        if ($this->hasGroupSupport()) {
838
            $groupId = api_get_group_id();
839
            $conditions = array(
840
                'where' => array(
841
                    'c_id = ? AND session_id = ? AND meeting_name = ? AND group_id = ? AND status = 1 AND access_url = ?' =>
842
                        array(
843
                            $courseId,
844
                            $sessionId,
845
                            $meetingName,
846
                            $groupId,
847
                            $this->accessUrl,
848
                        ),
849
                ),
850
            );
851
        }
852
853
        $meetingData = Database::select(
854
            '*',
855
            $this->table,
856
            $conditions,
857
            'first'
858
        );
859
860
        if ($this->debug) {
861
            error_log('meeting_exists ' . print_r($meetingData, 1));
0 ignored issues
show
Bug introduced by
Are you sure print_r($meetingData, 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

861
            error_log('meeting_exists ' . /** @scrutinizer ignore-type */ print_r($meetingData, 1));
Loading history...
862
        }
863
864
        return $meetingData;
865
    }
866
867
    /**
868
     * Gets a list from the database of all meetings attached to a course with the given status
869
     * @param int $courseId
870
     * @param int $sessionId
871
     * @param int $status 0 for closed meetings, 1 for open meetings
872
     *
873
     * @return array
874
     */
875
    public function getAllMeetingsInCourse($courseId, $sessionId, $status)
876
    {
877
        $conditions = array(
878
            'where' => array(
879
                'status = ? AND c_id = ? AND session_id = ? ' => array(
880
                    $status,
881
                    $courseId,
882
                    $sessionId,
883
                ),
884
            ),
885
        );
886
887
        return Database::select(
888
            '*',
889
            $this->table,
890
            $conditions
891
        );
892
    }
893
894
    /**
895
     * Gets all the course meetings saved in the plugin_bbb_meeting table and
896
     * generate actionable links (join/close/delete/etc)
897
     *
898
     * @param int $courseId
899
     * @param int $sessionId
900
     * @param int $groupId
901
     * @param bool $isAdminReport Optional. Set to true then the report is for admins
902
     * @param array $dateRange Optional
903
     *
904
     * @return array Array of current open meeting rooms
905
     * @throws Exception
906
     */
907
    public function getMeetings(
908
        $courseId = 0,
909
        $sessionId = 0,
910
        $groupId = 0,
911
        $isAdminReport = false,
912
        $dateRange = []
913
    )
914
    {
915
        $em = Database::getManager();
916
        $manager = $this->isConferenceManager();
917
918
        $conditions = [];
919
        if ($courseId || $sessionId || $groupId) {
920
            $conditions = array(
921
                'where' => array(
922
                    'c_id = ? AND session_id = ? ' => array($courseId, $sessionId),
923
                ),
924
            );
925
926
            if ($this->hasGroupSupport()) {
927
                $conditions = array(
928
                    'where' => array(
929
                        'c_id = ? AND session_id = ? AND group_id = ? ' => array(
930
                            $courseId,
931
                            $sessionId,
932
                            $groupId,
933
                        ),
934
                    ),
935
                );
936
            }
937
938
            if ($this->isGlobalConferencePerUserEnabled()) {
939
                $conditions = array(
940
                    'where' => array(
941
                        'c_id = ? AND session_id = ? AND user_id = ?' => array(
942
                            $courseId,
943
                            $sessionId,
944
                            $this->userId,
945
                        ),
946
                    ),
947
                );
948
            }
949
        }
950
951
        if (!empty($dateRange)) {
952
            $dateStart = date_create($dateRange['search_meeting_start']);
953
            $dateStart = date_format($dateStart, 'Y-m-d H:i:s');
954
            $dateEnd = date_create($dateRange['search_meeting_end']);
955
            $dateEnd = $dateEnd->add(new DateInterval('P1D'));
956
            $dateEnd = date_format($dateEnd, 'Y-m-d H:i:s');
957
958
            $conditions = array(
959
                'where' => array(
960
                    'created_at BETWEEN ? AND ? ' => array($dateStart, $dateEnd),
961
                ),
962
            );
963
        }
964
965
        $conditions['order'] = 'created_at ASC';
966
967
968
        $meetingList = Database::select(
969
            '*',
970
            $this->table,
971
            $conditions
972
        );
973
        $isGlobal = $this->isGlobalConference();
974
        $newMeetingList = array();
975
        foreach ($meetingList as $meetingDB) {
976
            $item = array();
977
            $courseId = $meetingDB['c_id'];
978
            $courseInfo = api_get_course_info_by_id($courseId);
979
            $courseCode = '';
980
            if (!empty($courseInfo)) {
981
                $courseCode = $courseInfo['code'];
982
            }
983
984
            if ($manager) {
985
                $pass = $meetingDB['moderator_pw'];
986
            } else {
987
                $pass = $meetingDB['attendee_pw'];
988
            }
989
990
            $meetingBBB = $this->getMeetingInfo(
991
                [
992
                    'meetingId' => $meetingDB['remote_id'],
993
                    'password' => $pass,
994
                ]
995
            );
996
997
            if ($meetingBBB === false) {
998
                // Checking with the remote_id didn't work, so just in case and
999
                // to provide backwards support, check with the id
1000
                $params = array(
1001
                    'meetingId' => $meetingDB['id'],
1002
                    //  -- REQUIRED - The unique id for the meeting
1003
                    'password' => $pass
1004
                    //  -- REQUIRED - The moderator password for the meeting
1005
                );
1006
                $meetingBBB = $this->getMeetingInfo($params);
1007
            }
1008
1009
            if ($meetingDB['visibility'] == 0 && $this->isConferenceManager() === false) {
1010
                continue;
1011
            }
1012
1013
            $meetingBBB['end_url'] = $this->endUrl($meetingDB);
1014
1015
            if (isset($meetingBBB['returncode']) && (string)$meetingBBB['returncode'] === 'FAILED') {
1016
                if ($meetingDB['status'] == 1 && $this->isConferenceManager()) {
1017
                    $this->endMeeting($meetingDB['id'], $courseCode);
1018
                }
1019
            } else {
1020
                $meetingBBB['add_to_calendar_url'] = $this->addToCalendarUrl($meetingDB);
1021
            }
1022
1023
            if ($meetingDB['record'] == 1) {
1024
                // backwards compatibility (when there was no remote ID)
1025
                $mId = $meetingDB['remote_id'];
1026
                if (empty($mId)) {
1027
                    $mId = $meetingDB['id'];
1028
                }
1029
                if (empty($mId)) {
1030
                    // if the id is still empty (should *never* occur as 'id' is
1031
                    // the table's primary key), skip this conference
1032
                    continue;
1033
                }
1034
1035
                $record = [];
1036
                $recordingParams = ['meetingId' => $mId];
1037
                $records = $this->api->getRecordingsWithXmlResponseArray($recordingParams);
1038
                if (!empty($records)) {
1039
                    if (!isset($records['messageKey']) || $records['messageKey'] !== 'noRecordings') {
1040
                        $record = end($records);
1041
                        if (!is_array($record) || !isset($record['recordId'])) {
1042
                            continue;
1043
                        }
1044
1045
                        if (!empty($record['playbackFormat'])) {
1046
                            $this->updateMeetingVideoUrl($meetingDB['id'], $record['playbackFormatUrl']);
1047
                        }
1048
                    }
1049
                }
1050
1051
                if (isset($record['playbackFormat']) && !empty($record['playbackFormat'])) {
1052
                    $recordLink = array();
1053
                    foreach ($record['playbackFormat'] as $format) {
1054
                        $this->insertMeetingFormat(intval($meetingDB['id']), $format->type->__toString(), $format->url->__toString());
1055
                        $recordLink['record'][] = 1;
1056
                        $recordLink[] = Display::url(
1057
                            $this->plugin->get_lang($format->type->__toString()),
1058
                            $format->url->__toString(),
1059
                            ['target' => '_blank', 'class' => 'btn btn-default']
1060
                        );
1061
                    }
1062
1063
1064
                } else {
1065
                    $recordLink = $this->plugin->get_lang('NoRecording');
1066
                }
1067
1068
                if ($isAdminReport) {
1069
                    $this->forceCIdReq(
1070
                        $courseInfo['code'],
1071
                        $meetingDB['session_id'],
1072
                        $meetingDB['group_id']
1073
                    );
1074
                }
1075
1076
                $actionLinks = $this->getActionLinks(
1077
                    $meetingDB,
1078
                    $record,
1079
                    $isGlobal,
1080
                    $isAdminReport
1081
                );
1082
                $item['show_links'] = $recordLink;
1083
                $item['record'] = true;
1084
1085
            } else {
1086
                $actionLinks = $this->getActionLinks(
1087
                    $meetingDB,
1088
                    [],
1089
                    $isGlobal,
1090
                    $isAdminReport
1091
                );
1092
1093
                $item['show_links'] = $this->plugin->get_lang('NoRecording');
1094
                $item['record'] = false;
1095
            }
1096
1097
            $item['action_links'] = implode(PHP_EOL, $actionLinks);
1098
            $item['created_at'] = api_convert_and_format_date($meetingDB['created_at']);
1099
            // created_at
1100
            $meetingDB['created_at'] = $item['created_at']; //avoid overwrite in array_merge() below
1101
1102
            $item['closed_at'] = '';
1103
            if (!empty($meetingDB['closed_at'])) {
1104
                $item['closed_at'] = api_convert_and_format_date($meetingDB['closed_at']);
1105
                $meetingDB['closed_at'] = $item['closed_at'];
1106
            }
1107
1108
            $item['publish_url'] = $this->publishUrl($meetingDB);
1109
            $item['unpublish_url'] = $this->unPublishUrl($meetingBBB);
1110
1111
            if ($meetingDB['status'] == 1) {
1112
                $joinParams = [
1113
                    'meetingId' => $meetingDB['remote_id'],
1114
                    //-- REQUIRED - A unique id for the meeting
1115
                    'username' => $this->userCompleteName,
1116
                    //-- REQUIRED - The name that will display for the user in the meeting
1117
                    'password' => $pass,
1118
                    //-- REQUIRED - The attendee or moderator password, depending on what's passed here
1119
                    'createTime' => '',
1120
                    //-- OPTIONAL - string. Leave blank ('') unless you set this correctly.
1121
                    'userID' => '',
1122
                    //	-- OPTIONAL - string
1123
                    'webVoiceConf' => '',
1124
                ];
1125
                $item['go_url'] = $this->protocol . $this->api->getJoinMeetingURL($joinParams);
1126
            }
1127
            $item = array_merge($item, $meetingDB, $meetingBBB);
1128
1129
            $item['course'] = $em->find('ChamiloCoreBundle:Course', $item['c_id']);
1130
            $item['session'] = $em->find('ChamiloCoreBundle:Session', $item['session_id']);
1131
            $newMeetingList[] = $item;
1132
        }
1133
1134
        return $newMeetingList;
1135
    }
1136
1137
    /**
1138
     * @param array $meeting
1139
     *
1140
     * @return string
1141
     */
1142
    public function endUrl($meeting)
1143
    {
1144
        if (!isset($meeting['id'])) {
1145
            return '';
1146
        }
1147
1148
        return api_get_path(WEB_PLUGIN_PATH) . 'bbb/listing.php?' . $this->getUrlParams() . '&action=end&id=' . $meeting['id'];
1149
    }
1150
1151
    /**
1152
     * Closes a meeting (usually when the user click on the close button from
1153
     * the conferences listing.
1154
     *
1155
     * @param string The internal ID of the meeting (id field for this meeting)
1156
     * @param string $courseCode
1157
     *
1158
     * @return void
1159
     * @assert (0) === false
1160
     */
1161
    public function endMeeting($id, $courseCode = null)
1162
    {
1163
        if (empty($id)) {
1164
            return false;
1165
        }
1166
1167
        $meetingData = Database::select(
1168
            '*',
1169
            $this->table,
1170
            array('where' => array('id = ?' => array($id))),
1171
            'first'
1172
        );
1173
1174
        $manager = $this->isConferenceManager();
1175
        if ($manager) {
1176
            $pass = $meetingData['moderator_pw'];
1177
        } else {
1178
            $pass = $meetingData['attendee_pw'];
1179
        }
1180
1181
        Event::addEvent(
1182
            'bbb_end_meeting',
1183
            'meeting_id',
1184
            (int)$id,
1185
            null,
1186
            api_get_user_id(),
1187
            api_get_course_int_id(),
1188
            api_get_session_id()
1189
        );
1190
1191
        $endParams = array(
1192
            'meetingId' => $meetingData['remote_id'], // REQUIRED - We have to know which meeting to end.
1193
            'password' => $pass, // REQUIRED - Must match moderator pass for meeting.
1194
        );
1195
        $this->api->endMeetingWithXmlResponseArray($endParams);
1196
        Database::update(
1197
            $this->table,
1198
            array('status' => 0, 'closed_at' => api_get_utc_datetime()),
1199
            array('id = ? ' => $id)
1200
        );
1201
1202
        // Update users with in_at y ou_at field equal
1203
        $roomTable = Database::get_main_table('plugin_bbb_room');
1204
        $conditions['where'] = ['meeting_id=? AND in_at=out_at AND close=?' => [$id, BBBPlugin::ROOM_OPEN]];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$conditions was never initialized. Although not strictly required by PHP, it is generally a good practice to add $conditions = array(); before regardless.
Loading history...
1205
        $roomList = Database::select(
1206
            '*',
1207
            $roomTable,
1208
            $conditions
1209
        );
1210
1211
        foreach ($roomList as $roomDB) {
1212
            $roomId = $roomDB['id'];
1213
            if (!empty($roomId)) {
1214
                Database::update(
1215
                    $roomTable,
1216
                    ['out_at' => api_get_utc_datetime(), 'close' => BBBPlugin::ROOM_CLOSE],
1217
                    ['id = ? ' => $roomId]
1218
                );
1219
            }
1220
        }
1221
1222
        // Close all meeting rooms with meeting ID
1223
        Database::update(
1224
            $roomTable,
1225
            ['close' => BBBPlugin::ROOM_CLOSE],
1226
            ['meeting_id = ? ' => $id]
1227
        );
1228
    }
1229
1230
    /**
1231
     * @param array $meeting
1232
     * @param array $record
1233
     *
1234
     * @return string
1235
     */
1236
    public function addToCalendarUrl($meeting, $record = [])
1237
    {
1238
        $url = isset($record['playbackFormatUrl']) ? $record['playbackFormatUrl'] : '';
1239
1240
        return api_get_path(WEB_PLUGIN_PATH) . 'bbb/listing.php?' . $this->getUrlParams() . '&action=add_to_calendar&id=' . $meeting['id'] . '&start=' . api_strtotime($meeting['created_at']) . '&url=' . $url;
1241
    }
1242
1243
    /**
1244
     * @param int $meetingId
1245
     * @param string $videoUrl
1246
     *
1247
     * @return bool|int
1248
     */
1249
    public function updateMeetingVideoUrl($meetingId, $videoUrl)
1250
    {
1251
        return Database::update(
1252
            'plugin_bbb_meeting',
1253
            ['video_url' => $videoUrl],
1254
            ['id = ?' => intval($meetingId)]
1255
        );
1256
    }
1257
1258
    /**
1259
     * @param int $meetingId
1260
     * @param string $formatType
1261
     * @param string $resourceUrl
1262
     *
1263
     * @return bool|int
1264
     */
1265
    public function insertMeetingFormat(int $meetingId, string $formatType, string $resourceUrl)
1266
    {
1267
        $em = Database::getManager();
1268
        $sm = $em->getConnection()->getSchemaManager();
1269
        if ($sm->tablesExist('plugin_bbb_meeting_format')) {
1270
            return Database::insert(
1271
                'plugin_bbb_meeting_format',
1272
                [
1273
                    'format_type' => $formatType,
1274
                    'resource_url' => $resourceUrl,
1275
                    'meeting_id' => $meetingId
1276
                ]
1277
            );
1278
        }
1279
1280
    }
1281
1282
    /**
1283
     * Force the course, session and/or group IDs
1284
     *
1285
     * @param string $courseCode
1286
     * @param int $sessionId
1287
     * @param int $groupId
1288
     */
1289
    public function forceCIdReq($courseCode, $sessionId = 0, $groupId = 0)
1290
    {
1291
        $this->courseCode = $courseCode;
1292
        $this->sessionId = (int)$sessionId;
1293
        $this->groupId = (int)$groupId;
1294
    }
1295
1296
    /**
1297
     * @param array $meetingInfo
1298
     * @param array $recordInfo
1299
     * @param bool $isGlobal
1300
     * @param bool $isAdminReport
1301
     *
1302
     * @return array
1303
     */
1304
    private function getActionLinks(
1305
        $meetingInfo,
1306
        $recordInfo,
1307
        $isGlobal = false,
1308
        $isAdminReport = false
1309
    )
1310
    {
1311
        $isVisible = $meetingInfo['visibility'] != 0;
1312
        $linkVisibility = $isVisible
1313
            ? Display::url(
1314
                Display::return_icon('visible.png', get_lang('MakeInvisible')),
1315
                $this->unPublishUrl($meetingInfo)
1316
            )
1317
            : Display::url(
1318
                Display::return_icon('invisible.png', get_lang('MakeVisible')),
1319
                $this->publishUrl($meetingInfo)
1320
            );
1321
1322
        $links = [];
1323
        if ($this->plugin->get('allow_regenerate_recording') === 'true' && $meetingInfo['record'] == 1) {
1324
            if (!empty($recordInfo)) {
1325
                $links[] = Display::url(
1326
                    Display::return_icon('reload.png', get_lang('RegenerateRecord')),
1327
                    $this->regenerateRecordUrl($meetingInfo, $recordInfo)
1328
                );
1329
            } else {
1330
                $links[] = Display::url(
1331
                    Display::return_icon('reload.png', get_lang('RegenerateRecord')),
1332
                    $this->regenerateRecordUrlFromMeeting($meetingInfo)
1333
                );
1334
            }
1335
        }
1336
1337
        if (empty($recordInfo)) {
1338
            if (!$isAdminReport) {
1339
                if ($meetingInfo['status'] == 0) {
1340
                    $links[] = Display::url(
1341
                        Display::return_icon('delete.png', get_lang('Delete')),
1342
                        $this->deleteRecordUrl($meetingInfo)
1343
                    );
1344
                    $links[] = $linkVisibility;
1345
                }
1346
1347
                return $links;
1348
            } else {
1349
                $links[] = Display::url(
1350
                    Display::return_icon('course_home.png', get_lang('GoToCourse')),
1351
                    $this->getListingUrl($meetingInfo['c_id'], $meetingInfo['session_id'], $meetingInfo['group_id'])
1352
                );
1353
1354
                return $links;
1355
            }
1356
        }
1357
1358
        if (!$isGlobal) {
1359
            $links[] = Display::url(
1360
                Display::return_icon('link.gif', get_lang('UrlMeetingToShare')),
1361
                $this->copyToRecordToLinkTool($meetingInfo)
1362
            );
1363
            $links[] = Display::url(
1364
                Display::return_icon('agenda.png', get_lang('AddToCalendar')),
1365
                $this->addToCalendarUrl($meetingInfo, $recordInfo)
1366
            );
1367
        }
1368
1369
        $hide = $this->plugin->get('disable_download_conference_link') === 'true' ? true : false;
1370
1371
        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...
1372
            if ($meetingInfo['has_video_m4v']) {
1373
                $links[] = Display::url(
1374
                    Display::return_icon('save.png', get_lang('DownloadFile')),
1375
                    $recordInfo['playbackFormatUrl'] . '/capture.m4v',
1376
                    ['target' => '_blank']
1377
                );
1378
            } else {
1379
                $links[] = Display::url(
1380
                    Display::return_icon('save.png', get_lang('DownloadFile')),
1381
                    '#',
1382
                    [
1383
                        'id' => "btn-check-meeting-video-{$meetingInfo['id']}",
1384
                        'class' => 'check-meeting-video',
1385
                        'data-id' => $meetingInfo['id'],
1386
                    ]
1387
                );
1388
            }
1389
        }
1390
1391
1392
        if (!$isAdminReport) {
1393
            $links[] = Display::url(
1394
                Display::return_icon('delete.png', get_lang('Delete')),
1395
                $this->deleteRecordUrl($meetingInfo)
1396
            );
1397
            $links[] = $linkVisibility;
1398
        } else {
1399
            $links[] = Display::url(
1400
                Display::return_icon('course_home.png', get_lang('GoToCourse')),
1401
                $this->getListingUrl($meetingInfo['c_id'], $meetingInfo['session_id'], $meetingInfo['group_id'])
1402
            );
1403
        }
1404
1405
1406
        return $links;
1407
    }
1408
1409
    /**
1410
     * @param array $meeting
1411
     *
1412
     * @return string
1413
     */
1414
    public function unPublishUrl($meeting)
1415
    {
1416
        if (!isset($meeting['id'])) {
1417
            return null;
1418
        }
1419
1420
        return api_get_path(WEB_PLUGIN_PATH) . 'bbb/listing.php?' . $this->getUrlParams() . '&action=unpublish&id=' . $meeting['id'];
1421
    }
1422
1423
    /**
1424
     * @param array $meeting
1425
     *
1426
     * @return string
1427
     */
1428
    public function publishUrl($meeting)
1429
    {
1430
        if (!isset($meeting['id'])) {
1431
            return '';
1432
        }
1433
1434
        return api_get_path(WEB_PLUGIN_PATH) . 'bbb/listing.php?' . $this->getUrlParams() . '&action=publish&id=' . $meeting['id'];
1435
    }
1436
1437
    /**
1438
     * @param array $meeting
1439
     * @param array $recordInfo
1440
     *
1441
     * @return string
1442
     */
1443
    public function regenerateRecordUrl($meeting, $recordInfo)
1444
    {
1445
        if ($this->plugin->get('allow_regenerate_recording') !== 'true') {
1446
            return '';
1447
        }
1448
1449
        if (!isset($meeting['id'])) {
1450
            return '';
1451
        }
1452
1453
        if (empty($recordInfo) || (!empty($recordInfo['recordId']) && !isset($recordInfo['recordId']))) {
1454
            return '';
1455
        }
1456
1457
        return api_get_path(WEB_PLUGIN_PATH) . 'bbb/listing.php?' . $this->getUrlParams() .
1458
            '&action=regenerate_record&id=' . $meeting['id'] . '&record_id=' . $recordInfo['recordId'];
1459
    }
1460
1461
    /**
1462
     * @param array $meeting
1463
     *
1464
     * @return string
1465
     */
1466
    public function regenerateRecordUrlFromMeeting($meeting)
1467
    {
1468
        if ($this->plugin->get('allow_regenerate_recording') !== 'true') {
1469
            return '';
1470
        }
1471
1472
        if (!isset($meeting['id'])) {
1473
            return '';
1474
        }
1475
1476
        return api_get_path(WEB_PLUGIN_PATH) . 'bbb/listing.php?' . $this->getUrlParams() .
1477
            '&action=regenerate_record&id=' . $meeting['id'];
1478
    }
1479
1480
    /**
1481
     * @param array $meeting
1482
     *
1483
     * @return string
1484
     */
1485
    public function deleteRecordUrl($meeting)
1486
    {
1487
        if (!isset($meeting['id'])) {
1488
            return '';
1489
        }
1490
1491
        return api_get_path(WEB_PLUGIN_PATH) . 'bbb/listing.php?' . $this->getUrlParams() . '&action=delete_record&id=' . $meeting['id'];
1492
    }
1493
1494
    /**
1495
     * @param array $meeting
1496
     *
1497
     * @return string
1498
     */
1499
    public function copyToRecordToLinkTool($meeting)
1500
    {
1501
        if (!isset($meeting['id'])) {
1502
            return '';
1503
        }
1504
1505
        return api_get_path(WEB_PLUGIN_PATH) .
1506
            'bbb/listing.php?' . $this->getUrlParams() . '&action=copy_record_to_link_tool&id=' . $meeting['id'];
1507
    }
1508
1509
    /**
1510
     * Function disabled
1511
     */
1512
    public function publishMeeting($id)
1513
    {
1514
        //return BigBlueButtonBN::setPublishRecordings($id, 'true', $this->url, $this->salt);
1515
        if (empty($id)) {
1516
            return false;
1517
        }
1518
        $id = intval($id);
1519
        Database::update($this->table, array('visibility' => 1), array('id = ? ' => $id));
1520
1521
        return true;
1522
    }
1523
1524
    /**
1525
     * Function disabled
1526
     */
1527
    public function unpublishMeeting($id)
1528
    {
1529
        //return BigBlueButtonBN::setPublishRecordings($id, 'false', $this->url, $this->salt);
1530
        if (empty($id)) {
1531
            return false;
1532
        }
1533
        $id = intval($id);
1534
        Database::update($this->table, array('visibility' => 0), array('id = ?' => $id));
1535
1536
        return true;
1537
    }
1538
1539
    /**
1540
     * Get users online in the current course room.
1541
     *
1542
     * @return int The number of users currently connected to the videoconference
1543
     * @assert () > -1
1544
     */
1545
    public function getUsersOnlineInCurrentRoom()
1546
    {
1547
        $courseId = api_get_course_int_id();
1548
        $sessionId = api_get_session_id();
1549
1550
        $conditions = array(
1551
            'where' => array(
1552
                'c_id = ? AND session_id = ? AND status = 1 AND access_url = ?' => array(
1553
                    $courseId,
1554
                    $sessionId,
1555
                    $this->accessUrl,
1556
                ),
1557
            ),
1558
        );
1559
1560
        if ($this->hasGroupSupport()) {
1561
            $groupId = api_get_group_id();
1562
            $conditions = array(
1563
                'where' => array(
1564
                    'c_id = ? AND session_id = ? AND group_id = ? AND status = 1 AND access_url = ?' => array(
1565
                        $courseId,
1566
                        $sessionId,
1567
                        $groupId,
1568
                        $this->accessUrl,
1569
                    ),
1570
                ),
1571
            );
1572
        }
1573
1574
        if ($this->isGlobalConferencePerUserEnabled()) {
1575
            $conditions = array(
1576
                'where' => array(
1577
                    'user_id = ? AND status = 1 AND access_url = ?' => array(
1578
                        $this->userId,
1579
                        $this->accessUrl,
1580
                    ),
1581
                ),
1582
            );
1583
        }
1584
1585
        $meetingData = Database::select(
1586
            '*',
1587
            $this->table,
1588
            $conditions,
1589
            'first'
1590
        );
1591
1592
        if (empty($meetingData)) {
1593
            return 0;
1594
        }
1595
        $pass = $meetingData['moderator_pw'];
1596
        $info = $this->getMeetingInfo(array('meetingId' => $meetingData['remote_id'], 'password' => $pass));
1597
        if ($info === false) {
1598
            //checking with the remote_id didn't work, so just in case and
1599
            // to provide backwards support, check with the id
1600
            $params = array(
1601
                'meetingId' => $meetingData['id'],
1602
                //  -- REQUIRED - The unique id for the meeting
1603
                'password' => $pass
1604
                //  -- REQUIRED - The moderator password for the meeting
1605
            );
1606
            $info = $this->getMeetingInfo($params);
1607
        }
1608
1609
        if (!empty($info) && isset($info['participantCount'])) {
1610
            return $info['participantCount'];
1611
        }
1612
1613
        return 0;
1614
    }
1615
1616
    /**
1617
     * @param int $id
1618
     * @param string $recordId
1619
     *
1620
     * @return bool
1621
     */
1622
    public function regenerateRecording($id, $recordId = '')
1623
    {
1624
        if ($this->plugin->get('allow_regenerate_recording') !== 'true') {
1625
            return false;
1626
        }
1627
1628
        if (empty($id)) {
1629
            return false;
1630
        }
1631
1632
        $meetingData = Database::select(
1633
            '*',
1634
            $this->table,
1635
            array('where' => array('id = ?' => array($id))),
1636
            'first'
1637
        );
1638
1639
        Event::addEvent(
1640
            'bbb_regenerate_record',
1641
            'record_id',
1642
            (int)$recordId,
1643
            null,
1644
            api_get_user_id(),
1645
            api_get_course_int_id(),
1646
            api_get_session_id()
1647
        );
1648
1649
        // Check if there are recordings for this meeting
1650
        $recordings = $this->api->getRecordings(['meetingId' => $meetingData['remote_id']]);
1651
        if (!empty($recordings) && isset($recordings['messageKey']) && $recordings['messageKey'] === 'noRecordings') {
1652
            // Regenerate the meeting id
1653
            if (!empty($meetingData['internal_meeting_id'])) {
1654
                return $this->api->generateRecording(['recordId' => $meetingData['internal_meeting_id']]);
1655
            }
1656
1657
            /*$pass = $this->getModMeetingPassword();
1658
            $info = $this->getMeetingInfo(['meetingId' => $meetingData['remote_id'], 'password' => $pass]);
1659
            if (!empty($info) && isset($info['internalMeetingID'])) {
1660
                return $this->api->generateRecording(['recordId' => $meetingData['internal_meeting_id']]);
1661
            }*/
1662
1663
            return false;
1664
        } else {
1665
            if (!empty($recordings['records'])) {
1666
                $recordExists = false;
1667
                foreach ($recordings['records'] as $record) {
1668
                    if ($recordId == $record['recordId']) {
1669
                        $recordExists = true;
1670
                        break;
1671
                    }
1672
                }
1673
1674
                if ($recordExists) {
1675
                    return $this->api->generateRecording(['recordId' => $recordId]);
1676
                }
1677
            }
1678
        }
1679
1680
        return false;
1681
    }
1682
1683
    /**
1684
     * Deletes a recording of a meeting
1685
     *
1686
     * @param int $id ID of the recording
1687
     *
1688
     * @return bool
1689
     *
1690
     * @assert () === false
1691
     * @todo Also delete links and agenda items created from this recording
1692
     */
1693
    public function deleteRecording($id)
1694
    {
1695
        if (empty($id)) {
1696
            return false;
1697
        }
1698
1699
        $meetingData = Database::select(
1700
            '*',
1701
            $this->table,
1702
            array('where' => array('id = ?' => array($id))),
1703
            'first'
1704
        );
1705
1706
        Event::addEvent(
1707
            'bbb_delete_record',
1708
            'meeting_id',
1709
            $id,
1710
            null,
1711
            api_get_user_id(),
1712
            api_get_course_int_id(),
1713
            api_get_session_id()
1714
        );
1715
1716
        $delete = false;
1717
        $recordings = [];
1718
        // Check if there are recordings for this meeting
1719
        if (!empty($meetingData['remote_id'])) {
1720
            Event::addEvent(
1721
                'bbb_delete_record',
1722
                'remote_id',
1723
                $meetingData['remote_id'],
1724
                null,
1725
                api_get_user_id(),
1726
                api_get_course_int_id(),
1727
                api_get_session_id()
1728
            );
1729
            $recordings = $this->api->getRecordings(['meetingId' => $meetingData['remote_id']]);
1730
        }
1731
        if (!empty($recordings) && isset($recordings['messageKey']) && $recordings['messageKey'] == 'noRecordings') {
1732
            $delete = true;
1733
        } else {
1734
            if (!empty($recordings['records'])) {
1735
                $recordsToDelete = [];
1736
                foreach ($recordings['records'] as $record) {
1737
                    $recordsToDelete[] = $record['recordId'];
1738
                }
1739
                $delete = true;
1740
                if (!empty($recordsToDelete)) {
1741
                    $recordingParams = ['recordId' => implode(',', $recordsToDelete)];
1742
                    Event::addEvent(
1743
                        'bbb_delete_record',
1744
                        'record_id_list',
1745
                        implode(',', $recordsToDelete),
1746
                        null,
1747
                        api_get_user_id(),
1748
                        api_get_course_int_id(),
1749
                        api_get_session_id()
1750
                    );
1751
                    $result = $this->api->deleteRecordingsWithXmlResponseArray($recordingParams);
1752
                    if (!empty($result) && isset($result['deleted']) && $result['deleted'] === 'true') {
1753
                        $delete = true;
1754
                    }
1755
                }
1756
            }
1757
        }
1758
1759
        if ($delete) {
1760
            Database::delete(
1761
                'plugin_bbb_room',
1762
                array('meeting_id = ?' => array($id))
1763
            );
1764
1765
            Database::delete(
1766
                $this->table,
1767
                array('id = ?' => array($id))
1768
            );
1769
        }
1770
1771
        return $delete;
1772
    }
1773
1774
    /**
1775
     * Creates a link in the links tool from the given videoconference recording
1776
     *
1777
     * @param int $id ID of the item in the plugin_bbb_meeting table
1778
     * @param string Hash identifying the recording, as provided by the API
1779
     *
1780
     * @return mixed ID of the newly created link, or false on error
1781
     * @assert (null, null) === false
1782
     * @assert (1, null) === false
1783
     * @assert (null, 'abcdefabcdefabcdefabcdef') === false
1784
     */
1785
    public function copyRecordingToLinkTool($id)
1786
    {
1787
        if (empty($id)) {
1788
            return false;
1789
        }
1790
        //$records =  BigBlueButtonBN::getRecordingsUrl($id);
1791
        $meetingData = Database::select(
1792
            '*',
1793
            $this->table,
1794
            array('where' => array('id = ?' => array($id))),
1795
            'first'
1796
        );
1797
1798
        $records = $this->api->getRecordingsWithXmlResponseArray(
1799
            array('meetingId' => $meetingData['remote_id'])
1800
        );
1801
1802
        if (!empty($records)) {
1803
            if (isset($records['message']) && !empty($records['message'])) {
1804
                if ($records['messageKey'] == 'noRecordings') {
1805
                    $recordArray[] = $this->plugin->get_lang('NoRecording');
0 ignored issues
show
Comprehensibility Best Practice introduced by
$recordArray was never initialized. Although not strictly required by PHP, it is generally a good practice to add $recordArray = array(); before regardless.
Loading history...
1806
                } else {
1807
                    //$recordArray[] = $records['message'];
1808
                }
1809
1810
                return false;
1811
            } else {
1812
                $record = $records[0];
1813
                if (is_array($record) && isset($record['recordId'])) {
1814
                    $url = $record['playbackFormatUrl'];
1815
                    $link = new Link();
1816
                    $params['url'] = $url;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$params was never initialized. Although not strictly required by PHP, it is generally a good practice to add $params = array(); before regardless.
Loading history...
1817
                    $params['title'] = $meetingData['meeting_name'];
1818
                    $id = $link->save($params);
1819
1820
                    return $id;
1821
                }
1822
            }
1823
        }
1824
1825
        return false;
1826
    }
1827
1828
    /**
1829
     * Checks if the video conference server is running.
1830
     * Function currently disabled (always returns 1)
1831
     * @return bool True if server is running, false otherwise
1832
     * @assert () === false
1833
     */
1834
    public function isServerRunning()
1835
    {
1836
        return true;
1837
        //return BigBlueButtonBN::isServerRunning($this->protocol.$this->url);
1838
    }
1839
1840
    /**
1841
     * Checks if the video conference plugin is properly configured
1842
     * @return bool True if plugin has a host and a salt, false otherwise
1843
     * @assert () === false
1844
     */
1845
    public function isServerConfigured()
1846
    {
1847
        $host = $this->plugin->get('host');
1848
1849
        if (empty($host)) {
1850
            return false;
1851
        }
1852
1853
        $salt = $this->plugin->get('salt');
1854
1855
        if (empty($salt)) {
1856
            return false;
1857
        }
1858
1859
        return true;
1860
        //return BigBlueButtonBN::isServerRunning($this->protocol.$this->url);
1861
    }
1862
1863
    /**
1864
     * Get active session in the all platform
1865
     */
1866
    public function getActiveSessionsCount()
1867
    {
1868
        $meetingList = Database::select(
1869
            'count(id) as count',
1870
            $this->table,
1871
            array('where' => array('status = ? AND access_url = ?' => array(1, $this->accessUrl))),
1872
            'first'
1873
        );
1874
1875
        return $meetingList['count'];
1876
    }
1877
1878
    /**
1879
     * Get active session in the all platform
1880
     */
1881
    public function getActiveSessions()
1882
    {
1883
        $meetingList = Database::select(
1884
            '*',
1885
            $this->table,
1886
            array('where' => array('status = ? AND access_url = ?' => array(1, $this->accessUrl)))
1887
        );
1888
1889
        return $meetingList;
1890
    }
1891
1892
    /**
1893
     * @param string $url
1894
     */
1895
    public function redirectToBBB($url)
1896
    {
1897
        if (file_exists(__DIR__ . '/../config.vm.php')) {
1898
            // Using VM
1899
            echo Display::url($this->plugin->get_lang('ClickToContinue'), $url);
1900
            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...
1901
        } else {
1902
            // Classic
1903
            header("Location: $url");
1904
            exit;
1905
        }
1906
    }
1907
1908
    /**
1909
     * @return string
1910
     */
1911
    public function getConferenceUrl()
1912
    {
1913
        return api_get_path(WEB_PLUGIN_PATH) . 'bbb/start.php?launch=1&' . $this->getUrlParams();
1914
    }
1915
1916
    /**
1917
     * Get the meeting info from DB by its name
1918
     *
1919
     * @param string $name
1920
     *
1921
     * @return array
1922
     */
1923
    public function findMeetingByName($name)
1924
    {
1925
        $meetingData = Database::select(
1926
            '*',
1927
            'plugin_bbb_meeting',
1928
            array('where' => array('meeting_name = ? AND status = 1 ' => $name)),
1929
            'first'
1930
        );
1931
1932
        return $meetingData;
1933
    }
1934
1935
    /**
1936
     * Get the meeting info from DB by its name
1937
     *
1938
     * @param int $id
1939
     *
1940
     * @return array
1941
     */
1942
    public function getMeeting($id)
1943
    {
1944
        $meetingData = Database::select(
1945
            '*',
1946
            'plugin_bbb_meeting',
1947
            array('where' => array('id = ?' => $id)),
1948
            'first'
1949
        );
1950
1951
        return $meetingData;
1952
    }
1953
1954
    /**
1955
     * Get the meeting info.
1956
     *
1957
     * @param int $id
1958
     *
1959
     * @return array
1960
     */
1961
    public function getMeetingByRemoteId($id)
1962
    {
1963
        $meetingData = Database::select(
1964
            '*',
1965
            'plugin_bbb_meeting',
1966
            array('where' => array('remote_id = ?' => $id)),
1967
            'first'
1968
        );
1969
1970
        return $meetingData;
1971
    }
1972
1973
    /**
1974
     * @param int $meetingId
1975
     *
1976
     * @return array
1977
     */
1978
    public function findConnectedMeetingParticipants($meetingId)
1979
    {
1980
        $meetingData = Database::select(
1981
            '*',
1982
            'plugin_bbb_room',
1983
            array('where' => array('meeting_id = ? AND in_at IS NOT NULL' => $meetingId))
1984
        );
1985
        $participantIds = [];
1986
        $return = [];
1987
1988
        foreach ($meetingData as $participantInfo) {
1989
            if (in_array($participantInfo['participant_id'], $participantIds)) {
1990
                continue;
1991
            }
1992
1993
            $participantIds[] = $participantInfo['participant_id'];
1994
1995
            $return[] = [
1996
                'id' => $participantInfo['id'],
1997
                'meeting_id' => $participantInfo['meeting_id'],
1998
                'participant' => api_get_user_entity($participantInfo['participant_id']),
1999
                'in_at' => $participantInfo['in_at'],
2000
                'out_at' => $participantInfo['out_at'],
2001
            ];
2002
        }
2003
2004
        return $return;
2005
    }
2006
2007
    /**
2008
     * Check if the meeting has a capture.m4v video file. If exists then the has_video_m4v field is updated
2009
     *
2010
     * @param int $meetingId
2011
     *
2012
     * @return bool
2013
     */
2014
    public function checkDirectMeetingVideoUrl($meetingId)
2015
    {
2016
        $meetingInfo = Database::select(
2017
            '*',
2018
            'plugin_bbb_meeting',
2019
            [
2020
                'where' => ['id = ?' => intval($meetingId)],
2021
            ],
2022
            'first'
2023
        );
2024
2025
        if (!isset($meetingInfo['video_url'])) {
2026
            return false;
2027
        }
2028
2029
        $hasCapture = SocialManager::verifyUrl($meetingInfo['video_url'] . '/capture.m4v');
2030
2031
        if ($hasCapture) {
2032
            return Database::update(
0 ignored issues
show
Bug Best Practice introduced by
The expression return Database::update(...=> intval($meetingId))) also could return the type integer which is incompatible with the documented return type boolean.
Loading history...
2033
                'plugin_bbb_meeting',
2034
                ['has_video_m4v' => true],
2035
                ['id = ?' => intval($meetingId)]
2036
            );
2037
        }
2038
2039
        return $hasCapture;
2040
    }
2041
}
2042