Completed
Push — master ( a73e85...13dd2e )
by Julito
08:28
created

bbb::getUrlParams()   B

Complexity

Conditions 9
Paths 6

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 15
c 0
b 0
f 0
nc 6
nop 3
dl 0
loc 27
rs 8.0555
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
        $this->courseCode = api_get_course_id();
50
        $this->courseId = api_get_course_int_id();
51
        $this->sessionId = api_get_session_id();
52
        $this->groupId = api_get_group_id();
53
54
        // Initialize video server settings from global settings
55
        $this->plugin = BBBPlugin::create();
56
        $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

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

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

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