From b085e2075d7631d4f83c3c7ee8bac280f15a8e1f Mon Sep 17 00:00:00 2001 From: farbod Date: Wed, 17 Feb 2021 12:36:47 +0100 Subject: [PATCH 1/9] adding oc recording feature --- classes/locallib/config.php | 2 ++ lang/en/bigbluebuttonbn.php | 4 +++ locallib.php | 51 ++++++++++++++++++++++++++++++++++++- mod_form.php | 3 +++ viewlib.php | 8 +++++- 5 files changed, 66 insertions(+), 2 deletions(-) diff --git a/classes/locallib/config.php b/classes/locallib/config.php index 5120273bc..651c2ed07 100644 --- a/classes/locallib/config.php +++ b/classes/locallib/config.php @@ -86,6 +86,7 @@ public static function defaultvalues() { 'recordings_preview_editable' => false, 'recordings_validate_url' => true, 'recording_default' => true, + 'oc_recording' => false, 'recording_editable' => true, 'recording_icons_enabled' => true, 'recording_all_from_start_default' => false, @@ -212,6 +213,7 @@ public static function get_options() { 'recordings_preview_editable' => self::get('recordings_preview_editable'), 'recordings_validate_url' => self::get('recordings_validate_url'), 'recording_default' => self::get('recording_default'), + 'oc_recording' => self::get('oc_recording'), 'recording_editable' => self::get('recording_editable'), 'recording_icons_enabled' => self::get('recording_icons_enabled'), 'recording_all_from_start_default' => self::get('recording_all_from_start_default'), diff --git a/lang/en/bigbluebuttonbn.php b/lang/en/bigbluebuttonbn.php index 26bd802b8..045a3359b 100644 --- a/lang/en/bigbluebuttonbn.php +++ b/lang/en/bigbluebuttonbn.php @@ -111,6 +111,8 @@ $string['config_recording'] = 'Configuration for "Record meeting" feature'; $string['config_recording_description'] = 'These settings are feature specific'; $string['config_recording_default'] = 'Recording feature enabled by default'; +$string['config_oc_recording'] = 'Opencast can be used for Recording'; +$string['config_oc_recording_description'] = 'When enabled, BBB will use Opencast server for recordings. The course must use Opencast Videos Plugin (block_opencast) and have a valid Seires ID. If not, BBB uses its defaults.'; $string['config_recording_default_description'] = 'If enabled the sessions created in BigBlueButton will have recording capabilities.'; $string['config_recording_editable'] = 'Recording feature can be edited'; $string['config_recording_editable_description'] = 'If checked the interface includes an option for enable and disable the recording feature.'; @@ -366,6 +368,7 @@ $string['mod_form_field_lockonjoin'] = 'Ignore lock settings'; $string['mod_form_field_lockonjoinconfigurable'] = 'Allow ignore locking settings'; $string['mod_form_locksettings'] = 'Lock settings'; +$string['mod_form_field_record_oc'] = $string['mod_form_field_record'] . ' (Opencast)'; $string['starts_at'] = 'Starts'; @@ -429,6 +432,7 @@ $string['view_section_title_presentation'] = 'Presentation file'; $string['view_section_title_recordings'] = 'Recordings'; $string['view_message_norecordings'] = 'There are no recording to show.'; +$string['view_message_oc_recordings'] = 'List of Recordings on Opencast'; $string['view_message_finished'] = 'This activity is over.'; $string['view_message_notavailableyet'] = 'This session is not yet available.'; diff --git a/locallib.php b/locallib.php index 679a9037d..d769037b1 100644 --- a/locallib.php +++ b/locallib.php @@ -2244,7 +2244,19 @@ function bigbluebuttonbn_output_recording_table($bbbsession, $recordings, $tools // Render the table. return html_writer::div(html_writer::table($table), '', array('id' => 'bigbluebuttonbn_recordings_table')); } - +/** + * Helper function renders recording link for opencast. + * + * @param string $courseid + * + * @return array + */ +function bigbluebuttonbn_output_recording_opencast($courseid) { + $opencast_url = new \moodle_url('/blocks/opencast/index.php', array('courseid'=>$courseid)); + return html_writer::div(html_writer::tag('button', get_string('view_message_oc_recordings', 'bigbluebuttonbn'), + array('class' => 'btn btn-primary', 'onclick' => "window.location='{$opencast_url}';")), + '', array('id' => 'bigbluebuttonbn_recordings_opencast', 'class' => 'py-3')); +} /** * Helper function to convert an html string to plain text. * @@ -2717,6 +2729,12 @@ function bigbluebuttonbn_settings_record(&$renderer) { 'recording_editable', $renderer->render_group_element_checkbox('recording_editable', 1) ); + if (bigbluebuttonbn_check_opencast()) { + $renderer->render_group_element( + 'oc_recording', + $renderer->render_group_element_checkbox('oc_recording', 0) + ); + } $renderer->render_group_element( 'recording_icons_enabled', $renderer->render_group_element_checkbox('recording_icons_enabled', 1) @@ -3660,5 +3678,36 @@ function bigbluebuttonbn_create_meeting_metadata(&$bbbsession) { if ((boolean) \mod_bigbluebuttonbn\locallib\config::get('meetingevents_enabled')) { $metadata['analytics-callback-url'] = $bbbsession['meetingEventsURL']; } + // Special metadata for Opencast recordings + if ((boolean) \mod_bigbluebuttonbn\locallib\config::get('oc_recording') + && bigbluebuttonbn_check_opencast($bbbsession['course']->id)) { + $metadata['opencast-dc-isPartOf'] = bigbluebuttonbn_check_opencast($bbbsession['course']->id); + } return $metadata; } + +/** + * Helper for checking/retreiving seriesid Opencast plugin. + * + * @param string $courseid + * @return boolean|string + */ +function bigbluebuttonbn_check_opencast($courseid = null) { + $block_plugins = core_plugin_manager::instance()->get_plugins_of_type('block'); + if (in_array('opencast', array_keys($block_plugins))) { + $opencast = \block_opencast\local\apibridge::get_instance(); + if (!$opencast) { + return false; + } + if ($courseid) { + $seriesid = $opencast->get_stored_seriesid($courseid); + $ocseriesid = $opencast->get_course_series($courseid); + if (!$seriesid || ($seriesid && !$ocseriesid)) { + return flase; + } + return $seriesid; + } + return true; + } + return false; +} \ No newline at end of file diff --git a/mod_form.php b/mod_form.php index b4a38c999..b62660f26 100644 --- a/mod_form.php +++ b/mod_form.php @@ -338,6 +338,9 @@ private function bigbluebuttonbn_mform_add_block_room_room(&$mform, $cfg) { if ($cfg['recording_editable']) { $field['type'] = 'checkbox'; $field['description_key'] = 'mod_form_field_record'; + if ($cfg['oc_recording'] && bigbluebuttonbn_check_opencast(get_course($this->current->course)->id)) { + $field['description_key'] = 'mod_form_field_record_oc'; + } } $this->bigbluebuttonbn_mform_add_element($mform, $field['type'], $field['name'], $field['data_type'], $field['description_key'], $cfg['recording_default']); diff --git a/viewlib.php b/viewlib.php index b5aacc2aa..d45068077 100644 --- a/viewlib.php +++ b/viewlib.php @@ -261,8 +261,14 @@ function bigbluebuttonbn_view_render_recordings(&$bbbsession, $enabledfeatures, ); // If there are meetings with recordings load the data to the table. if ($bbbsession['bigbluebuttonbn']->recordings_html) { + $recordings_html = ''; + if ((boolean) \mod_bigbluebuttonbn\locallib\config::get('oc_recording') + && bigbluebuttonbn_check_opencast($bbbsession['course']->id)) { + $recordings_html .= bigbluebuttonbn_output_recording_opencast($bbbsession['course']->id); + } + $recordings_html .= bigbluebuttonbn_output_recording_table($bbbsession, $recordings)."\n"; // Render a plain html table. - return bigbluebuttonbn_output_recording_table($bbbsession, $recordings)."\n"; + return $recordings_html; } // JavaScript variables for recordings with YUI. $jsvars += array( From 12171b9bbd6780e5378ff387af7c3ab5da8b0655 Mon Sep 17 00:00:00 2001 From: jfederico Date: Wed, 10 Mar 2021 16:11:12 +0000 Subject: [PATCH 2/9] temporary disabled behat --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 12d8ce390..102d92c92 100644 --- a/.travis.yml +++ b/.travis.yml @@ -185,7 +185,7 @@ script: - moodle-plugin-ci validate - moodle-plugin-ci savepoints - moodle-plugin-ci phpdoc - - moodle-plugin-ci behat --profile chrome + # - moodle-plugin-ci behat --profile chrome # - | # # Grunt fails since version 3.8 # # https://moodle.org/mod/forum/discuss.php?d=389744 From ce4239bea8d17c310ceb471976539e57d0028740 Mon Sep 17 00:00:00 2001 From: farbod Date: Thu, 11 Mar 2021 10:56:45 +0100 Subject: [PATCH 3/9] check and create series for the course, comments and typo fixed --- locallib.php | 20 +++++++++++++------- mod_form.php | 1 + viewlib.php | 1 + 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/locallib.php b/locallib.php index d769037b1..034e07aa6 100644 --- a/locallib.php +++ b/locallib.php @@ -2729,6 +2729,7 @@ function bigbluebuttonbn_settings_record(&$renderer) { 'recording_editable', $renderer->render_group_element_checkbox('recording_editable', 1) ); + //if opencast plugin is installed if (bigbluebuttonbn_check_opencast()) { $renderer->render_group_element( 'oc_recording', @@ -3678,7 +3679,7 @@ function bigbluebuttonbn_create_meeting_metadata(&$bbbsession) { if ((boolean) \mod_bigbluebuttonbn\locallib\config::get('meetingevents_enabled')) { $metadata['analytics-callback-url'] = $bbbsession['meetingEventsURL']; } - // Special metadata for Opencast recordings + // Special metadata for Opencast recordings (passing opencast seriesid of the course as opencast-dc-isPartOf as metadata) if ((boolean) \mod_bigbluebuttonbn\locallib\config::get('oc_recording') && bigbluebuttonbn_check_opencast($bbbsession['course']->id)) { $metadata['opencast-dc-isPartOf'] = bigbluebuttonbn_check_opencast($bbbsession['course']->id); @@ -3687,27 +3688,32 @@ function bigbluebuttonbn_create_meeting_metadata(&$bbbsession) { } /** - * Helper for checking/retreiving seriesid Opencast plugin. + * Helper for checking/retreiving seriesid from opencast_block plugin. * * @param string $courseid * @return boolean|string */ function bigbluebuttonbn_check_opencast($courseid = null) { $block_plugins = core_plugin_manager::instance()->get_plugins_of_type('block'); + //if opencast_block is installed if (in_array('opencast', array_keys($block_plugins))) { + //getting the current instance of opencast $opencast = \block_opencast\local\apibridge::get_instance(); + //if opencast is not configured! if (!$opencast) { return false; } + //if the courseid is required (check if the course has the opencsat series) if ($courseid) { - $seriesid = $opencast->get_stored_seriesid($courseid); - $ocseriesid = $opencast->get_course_series($courseid); - if (!$seriesid || ($seriesid && !$ocseriesid)) { - return flase; + //trying to get course seriesid, create if is not set before! + try { + return $opencast->ensure_course_series_exists($courseid); + } catch (Exception $e) { + return false; } - return $seriesid; } return true; } + //if opencast_block is not installed return false; } \ No newline at end of file diff --git a/mod_form.php b/mod_form.php index b62660f26..f9a6877e6 100644 --- a/mod_form.php +++ b/mod_form.php @@ -338,6 +338,7 @@ private function bigbluebuttonbn_mform_add_block_room_room(&$mform, $cfg) { if ($cfg['recording_editable']) { $field['type'] = 'checkbox'; $field['description_key'] = 'mod_form_field_record'; + //try to check the opencast config and make sure that opencast is available for this course if ($cfg['oc_recording'] && bigbluebuttonbn_check_opencast(get_course($this->current->course)->id)) { $field['description_key'] = 'mod_form_field_record_oc'; } diff --git a/viewlib.php b/viewlib.php index d45068077..d8e1e3132 100644 --- a/viewlib.php +++ b/viewlib.php @@ -262,6 +262,7 @@ function bigbluebuttonbn_view_render_recordings(&$bbbsession, $enabledfeatures, // If there are meetings with recordings load the data to the table. if ($bbbsession['bigbluebuttonbn']->recordings_html) { $recordings_html = ''; + //if opencast recoding config is set and checks if opencast is available for the current course if ((boolean) \mod_bigbluebuttonbn\locallib\config::get('oc_recording') && bigbluebuttonbn_check_opencast($bbbsession['course']->id)) { $recordings_html .= bigbluebuttonbn_output_recording_opencast($bbbsession['course']->id); From 9cbd11db5b1347df632558a28140198a06910ed3 Mon Sep 17 00:00:00 2001 From: farbod Date: Wed, 17 Mar 2021 10:10:05 +0100 Subject: [PATCH 4/9] fixes for Travis CI --- locallib.php | 27 ++++++++++++++------------- mod_form.php | 2 +- viewlib.php | 12 ++++++------ 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/locallib.php b/locallib.php index 034e07aa6..4948aac0c 100644 --- a/locallib.php +++ b/locallib.php @@ -2244,17 +2244,18 @@ function bigbluebuttonbn_output_recording_table($bbbsession, $recordings, $tools // Render the table. return html_writer::div(html_writer::table($table), '', array('id' => 'bigbluebuttonbn_recordings_table')); } + /** * Helper function renders recording link for opencast. * * @param string $courseid - * + * * @return array */ function bigbluebuttonbn_output_recording_opencast($courseid) { - $opencast_url = new \moodle_url('/blocks/opencast/index.php', array('courseid'=>$courseid)); + $opencasturl = new \moodle_url('/blocks/opencast/index.php', array('courseid'=> $courseid)); return html_writer::div(html_writer::tag('button', get_string('view_message_oc_recordings', 'bigbluebuttonbn'), - array('class' => 'btn btn-primary', 'onclick' => "window.location='{$opencast_url}';")), + array('class' => 'btn btn-primary', 'onclick' => "window.location='{$opencasturl}';")), '', array('id' => 'bigbluebuttonbn_recordings_opencast', 'class' => 'py-3')); } /** @@ -2729,7 +2730,7 @@ function bigbluebuttonbn_settings_record(&$renderer) { 'recording_editable', $renderer->render_group_element_checkbox('recording_editable', 1) ); - //if opencast plugin is installed + // if opencast plugin is installed if (bigbluebuttonbn_check_opencast()) { $renderer->render_group_element( 'oc_recording', @@ -3680,7 +3681,7 @@ function bigbluebuttonbn_create_meeting_metadata(&$bbbsession) { $metadata['analytics-callback-url'] = $bbbsession['meetingEventsURL']; } // Special metadata for Opencast recordings (passing opencast seriesid of the course as opencast-dc-isPartOf as metadata) - if ((boolean) \mod_bigbluebuttonbn\locallib\config::get('oc_recording') + if ((boolean) \mod_bigbluebuttonbn\locallib\config::get('oc_recording') && bigbluebuttonbn_check_opencast($bbbsession['course']->id)) { $metadata['opencast-dc-isPartOf'] = bigbluebuttonbn_check_opencast($bbbsession['course']->id); } @@ -3694,18 +3695,18 @@ function bigbluebuttonbn_create_meeting_metadata(&$bbbsession) { * @return boolean|string */ function bigbluebuttonbn_check_opencast($courseid = null) { - $block_plugins = core_plugin_manager::instance()->get_plugins_of_type('block'); - //if opencast_block is installed - if (in_array('opencast', array_keys($block_plugins))) { - //getting the current instance of opencast + $blockplugins = core_plugin_manager::instance()->get_plugins_of_type('block'); + // if opencast_block is installed + if (in_array('opencast', array_keys($blockplugins))) { + // getting the current instance of opencast $opencast = \block_opencast\local\apibridge::get_instance(); - //if opencast is not configured! + // if opencast is not configured! if (!$opencast) { return false; } - //if the courseid is required (check if the course has the opencsat series) + // if the courseid is required (check if the course has the opencsat series) if ($courseid) { - //trying to get course seriesid, create if is not set before! + // trying to get course seriesid, create if is not set before! try { return $opencast->ensure_course_series_exists($courseid); } catch (Exception $e) { @@ -3714,6 +3715,6 @@ function bigbluebuttonbn_check_opencast($courseid = null) { } return true; } - //if opencast_block is not installed + // if opencast_block is not installed return false; } \ No newline at end of file diff --git a/mod_form.php b/mod_form.php index f9a6877e6..cffc23a5c 100644 --- a/mod_form.php +++ b/mod_form.php @@ -338,7 +338,7 @@ private function bigbluebuttonbn_mform_add_block_room_room(&$mform, $cfg) { if ($cfg['recording_editable']) { $field['type'] = 'checkbox'; $field['description_key'] = 'mod_form_field_record'; - //try to check the opencast config and make sure that opencast is available for this course + // try to check the opencast config and make sure that opencast is available for this course if ($cfg['oc_recording'] && bigbluebuttonbn_check_opencast(get_course($this->current->course)->id)) { $field['description_key'] = 'mod_form_field_record_oc'; } diff --git a/viewlib.php b/viewlib.php index d8e1e3132..67a03e80c 100644 --- a/viewlib.php +++ b/viewlib.php @@ -261,15 +261,15 @@ function bigbluebuttonbn_view_render_recordings(&$bbbsession, $enabledfeatures, ); // If there are meetings with recordings load the data to the table. if ($bbbsession['bigbluebuttonbn']->recordings_html) { - $recordings_html = ''; - //if opencast recoding config is set and checks if opencast is available for the current course - if ((boolean) \mod_bigbluebuttonbn\locallib\config::get('oc_recording') + $recordingshtml = ''; + // if opencast recoding config is set and checks if opencast is available for the current course + if ((boolean) \mod_bigbluebuttonbn\locallib\config::get('oc_recording') && bigbluebuttonbn_check_opencast($bbbsession['course']->id)) { - $recordings_html .= bigbluebuttonbn_output_recording_opencast($bbbsession['course']->id); + $recordingshtml .= bigbluebuttonbn_output_recording_opencast($bbbsession['course']->id); } - $recordings_html .= bigbluebuttonbn_output_recording_table($bbbsession, $recordings)."\n"; + $recordingshtml .= bigbluebuttonbn_output_recording_table($bbbsession, $recordings)."\n"; // Render a plain html table. - return $recordings_html; + return $recordingshtml; } // JavaScript variables for recordings with YUI. $jsvars += array( From 416e19e5ac8c3b773259136b839f37aba21d2783 Mon Sep 17 00:00:00 2001 From: farbod Date: Wed, 17 Mar 2021 10:30:07 +0100 Subject: [PATCH 5/9] fixes for Travis CI #2 --- locallib.php | 20 ++++++++++---------- mod_form.php | 2 +- viewlib.php | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/locallib.php b/locallib.php index 4948aac0c..5b28bb774 100644 --- a/locallib.php +++ b/locallib.php @@ -2253,8 +2253,8 @@ function bigbluebuttonbn_output_recording_table($bbbsession, $recordings, $tools * @return array */ function bigbluebuttonbn_output_recording_opencast($courseid) { - $opencasturl = new \moodle_url('/blocks/opencast/index.php', array('courseid'=> $courseid)); - return html_writer::div(html_writer::tag('button', get_string('view_message_oc_recordings', 'bigbluebuttonbn'), + $opencasturl = new \moodle_url('/blocks/opencast/index.php', array('courseid' => $courseid)); + return html_writer::div(html_writer::tag('button', get_string('view_message_oc_recordings', 'bigbluebuttonbn'), array('class' => 'btn btn-primary', 'onclick' => "window.location='{$opencasturl}';")), '', array('id' => 'bigbluebuttonbn_recordings_opencast', 'class' => 'py-3')); } @@ -2730,7 +2730,7 @@ function bigbluebuttonbn_settings_record(&$renderer) { 'recording_editable', $renderer->render_group_element_checkbox('recording_editable', 1) ); - // if opencast plugin is installed + // If opencast plugin is installed. if (bigbluebuttonbn_check_opencast()) { $renderer->render_group_element( 'oc_recording', @@ -3680,7 +3680,7 @@ function bigbluebuttonbn_create_meeting_metadata(&$bbbsession) { if ((boolean) \mod_bigbluebuttonbn\locallib\config::get('meetingevents_enabled')) { $metadata['analytics-callback-url'] = $bbbsession['meetingEventsURL']; } - // Special metadata for Opencast recordings (passing opencast seriesid of the course as opencast-dc-isPartOf as metadata) + // Special metadata for Opencast recordings (passing opencast seriesid of the course as opencast-dc-isPartOf as metadata). if ((boolean) \mod_bigbluebuttonbn\locallib\config::get('oc_recording') && bigbluebuttonbn_check_opencast($bbbsession['course']->id)) { $metadata['opencast-dc-isPartOf'] = bigbluebuttonbn_check_opencast($bbbsession['course']->id); @@ -3696,17 +3696,17 @@ function bigbluebuttonbn_create_meeting_metadata(&$bbbsession) { */ function bigbluebuttonbn_check_opencast($courseid = null) { $blockplugins = core_plugin_manager::instance()->get_plugins_of_type('block'); - // if opencast_block is installed + // If opencast_block is installed. if (in_array('opencast', array_keys($blockplugins))) { - // getting the current instance of opencast + // Getting the current instance of opencast. $opencast = \block_opencast\local\apibridge::get_instance(); - // if opencast is not configured! + // If opencast is not configured! if (!$opencast) { return false; } - // if the courseid is required (check if the course has the opencsat series) + // If the courseid is required (check if the course has the opencsat series). if ($courseid) { - // trying to get course seriesid, create if is not set before! + // Trying to get course seriesid, create if is not set before! try { return $opencast->ensure_course_series_exists($courseid); } catch (Exception $e) { @@ -3715,6 +3715,6 @@ function bigbluebuttonbn_check_opencast($courseid = null) { } return true; } - // if opencast_block is not installed + // If opencast_block is not installed return false; } \ No newline at end of file diff --git a/mod_form.php b/mod_form.php index cffc23a5c..cf1b34994 100644 --- a/mod_form.php +++ b/mod_form.php @@ -338,7 +338,7 @@ private function bigbluebuttonbn_mform_add_block_room_room(&$mform, $cfg) { if ($cfg['recording_editable']) { $field['type'] = 'checkbox'; $field['description_key'] = 'mod_form_field_record'; - // try to check the opencast config and make sure that opencast is available for this course + // Try to check the opencast config and make sure that opencast is available for this course. if ($cfg['oc_recording'] && bigbluebuttonbn_check_opencast(get_course($this->current->course)->id)) { $field['description_key'] = 'mod_form_field_record_oc'; } diff --git a/viewlib.php b/viewlib.php index 67a03e80c..f67eb7bbc 100644 --- a/viewlib.php +++ b/viewlib.php @@ -262,7 +262,7 @@ function bigbluebuttonbn_view_render_recordings(&$bbbsession, $enabledfeatures, // If there are meetings with recordings load the data to the table. if ($bbbsession['bigbluebuttonbn']->recordings_html) { $recordingshtml = ''; - // if opencast recoding config is set and checks if opencast is available for the current course + // If opencast recoding config is set and checks if opencast is available for the current course. if ((boolean) \mod_bigbluebuttonbn\locallib\config::get('oc_recording') && bigbluebuttonbn_check_opencast($bbbsession['course']->id)) { $recordingshtml .= bigbluebuttonbn_output_recording_opencast($bbbsession['course']->id); From 5fb2a532c0c901df01d1e9b0ca4835441ab83761 Mon Sep 17 00:00:00 2001 From: farbod Date: Thu, 29 Apr 2021 12:49:36 +0200 Subject: [PATCH 6/9] improved functionalities based on @abias suggestions --- amd/src/mod_lti_form_handler.js | 35 +++ lang/en/bigbluebuttonbn.php | 12 +- locallib.php | 424 ++++++++++++++++++++++++++++++-- mod_form.php | 2 +- oc_player.php | 178 ++++++++++++++ settings.php | 2 + viewlib.php | 46 +++- 7 files changed, 665 insertions(+), 34 deletions(-) create mode 100644 amd/src/mod_lti_form_handler.js create mode 100644 oc_player.php diff --git a/amd/src/mod_lti_form_handler.js b/amd/src/mod_lti_form_handler.js new file mode 100644 index 000000000..fbd6ad03d --- /dev/null +++ b/amd/src/mod_lti_form_handler.js @@ -0,0 +1,35 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * LTI form submit + * + * @module mod/bigbluebuttonbn + * @package mod_bigbluebuttonbn + * @copyright 2021 Farbod Zamani (zamani@elan-ev.de) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +define(['jquery'], function($) { + + /* + * Submits lti form + */ + var init = function() { + $('#ltiLaunchForm').submit(); + }; + return { + init: init + }; +}); diff --git a/lang/en/bigbluebuttonbn.php b/lang/en/bigbluebuttonbn.php index 045a3359b..52144c732 100644 --- a/lang/en/bigbluebuttonbn.php +++ b/lang/en/bigbluebuttonbn.php @@ -111,8 +111,12 @@ $string['config_recording'] = 'Configuration for "Record meeting" feature'; $string['config_recording_description'] = 'These settings are feature specific'; $string['config_recording_default'] = 'Recording feature enabled by default'; +$string['config_opencast'] = 'Configuration for "Opencast integration" feature'; +$string['config_opencast_description'] = 'These settings control the integration of BBB meeting recordings and Opencast'; $string['config_oc_recording'] = 'Opencast can be used for Recording'; -$string['config_oc_recording_description'] = 'When enabled, BBB will use Opencast server for recordings. The course must use Opencast Videos Plugin (block_opencast) and have a valid Seires ID. If not, BBB uses its defaults.'; +$string['config_oc_recording_description'] = 'When enabled, the Opencast series ID of the Moodle Course is sent to BBB as metadata, in order for BBB to integrate with Opencast Server.'; +$string['config_oc_show_recording'] = 'Show Opencast recordings'; +$string['config_oc_show_recording_description'] = 'If enabled the recording table will also include the Opencast recordings.'; $string['config_recording_default_description'] = 'If enabled the sessions created in BigBlueButton will have recording capabilities.'; $string['config_recording_editable'] = 'Recording feature can be edited'; $string['config_recording_editable_description'] = 'If checked the interface includes an option for enable and disable the recording feature.'; @@ -368,7 +372,7 @@ $string['mod_form_field_lockonjoin'] = 'Ignore lock settings'; $string['mod_form_field_lockonjoinconfigurable'] = 'Allow ignore locking settings'; $string['mod_form_locksettings'] = 'Lock settings'; -$string['mod_form_field_record_oc'] = $string['mod_form_field_record'] . ' (Opencast)'; +$string['mod_form_field_opencast_record'] = 'Session can be recorded in Opencast'; $string['starts_at'] = 'Starts'; @@ -420,6 +424,7 @@ $string['view_recording_list_description'] = 'Description'; $string['view_recording_list_duration'] = 'Duration'; $string['view_recording_list_recording'] = 'Recording'; +$string['view_recording_list_opencast'] = 'Opencast Video'; $string['view_recording_button_import'] = 'Import recording links'; $string['view_recording_button_return'] = 'Go back'; $string['view_recording_format_notes'] = 'Notes'; @@ -429,9 +434,12 @@ $string['view_recording_format_statistics'] = 'Statistics'; $string['view_recording_format_video'] = 'Video'; $string['view_recording_format_errror_unreachable'] = 'The URL for this recording format is unreachable.'; +$string['view_recording_format_error_opencast_unreachable'] = 'The Opencast recording cannot be dispalyed.'; $string['view_section_title_presentation'] = 'Presentation file'; $string['view_section_title_recordings'] = 'Recordings'; +$string['view_section_title_opencast_recordings'] = 'Opencast Recordings'; $string['view_message_norecordings'] = 'There are no recording to show.'; +$string['view_message_opencast_norecordings'] = 'There are no Opencast recording to show.'; $string['view_message_oc_recordings'] = 'List of Recordings on Opencast'; $string['view_message_finished'] = 'This activity is over.'; $string['view_message_notavailableyet'] = 'This session is not yet available.'; diff --git a/locallib.php b/locallib.php index 5b28bb774..cbb99f3aa 100644 --- a/locallib.php +++ b/locallib.php @@ -2246,18 +2246,370 @@ function bigbluebuttonbn_output_recording_table($bbbsession, $recordings, $tools } /** - * Helper function renders recording link for opencast. + * Helper function renders Opencast recording table. * - * @param string $courseid + * @param array $bbbsession + * @param array $ocrecordings * * @return array */ -function bigbluebuttonbn_output_recording_opencast($courseid) { - $opencasturl = new \moodle_url('/blocks/opencast/index.php', array('courseid' => $courseid)); - return html_writer::div(html_writer::tag('button', get_string('view_message_oc_recordings', 'bigbluebuttonbn'), - array('class' => 'btn btn-primary', 'onclick' => "window.location='{$opencasturl}';")), - '', array('id' => 'bigbluebuttonbn_recordings_opencast', 'class' => 'py-3')); +function bigbluebuttonbn_output_opencast_recording_table($bbbsession, $ocrecordings) { + // Get the Opencast recording table. + $table = bigbluebuttonbn_get_opencast_recording_table($bbbsession, $ocrecordings); + + if (!isset($table) || !isset($table->data)) { + // Render a table with "No Opencast recordings". + return html_writer::div( + get_string('view_message_opencast_norecordings', 'bigbluebuttonbn'), + '', + array('id' => 'bigbluebuttonbn_opencast_recordings_table') + ); + } + // Render the table. + return html_writer::div(html_writer::table($table), '', array('id' => 'bigbluebuttonbn_opencast_recordings_table')); } + +/** + * Helper function builds the Opencast recording table. + * + * @param array $bbbsession + * @param array $ocrecordings + * + * @return object + */ +function bigbluebuttonbn_get_opencast_recording_table($bbbsession, $ocrecordings) { + // Declare the table. + $table = new html_table(); + $table->data = array(); + // Initialize table headers. + $table->head[] = get_string('view_recording_playback', 'bigbluebuttonbn'); + $table->head[] = get_string('view_recording_name', 'bigbluebuttonbn'); + $table->head[] = get_string('view_recording_description', 'bigbluebuttonbn'); + if (bigbluebuttonbn_get_recording_data_preview_enabled($bbbsession)) { + $table->head[] = get_string('view_recording_preview', 'bigbluebuttonbn'); + } + $table->head[] = get_string('view_recording_date', 'bigbluebuttonbn'); + $table->head[] = get_string('view_recording_duration', 'bigbluebuttonbn'); + $table->align = array('left', 'left', 'left', 'left', 'left', 'center'); + $table->size = array('', '', '', '', '', ''); + if ($bbbsession['managerecordings']) { + $table->head[] = get_string('view_recording_actionbar', 'bigbluebuttonbn'); + $table->align[] = 'left'; + $table->size[] = (count($tools) * 40) . 'px'; + } + // Get the groups of the user. + $usergroups = groups_get_all_groups($bbbsession['course']->id, $bbbsession['userID']); + + // Build table content. + foreach ($ocrecordings as $ocrecording) { + // Check if the record belongs to a Visible Group type. + list($course, $cm) = get_course_and_cm_from_cmid($bbbsession['cm']->id); + $groupmode = groups_get_activity_groupmode($cm); + $displayrow = true; + if (($groupmode != VISIBLEGROUPS) + && !$bbbsession['administrator'] && !$bbbsession['moderator']) { + $groupid = explode('[', $bbbsession['meetingid']); + if (isset($groupid[1])) { + // It is a group recording and the user is not moderator/administrator. Recording should not be included by default. + $displayrow = false; + $groupid = explode(']', $groupid[1]); + if (isset($groupid[0])) { + foreach ($usergroups as $usergroup) { + if ($usergroup->id == $groupid[0]) { + // Include recording if the user is in the same group. + $displayrow = true; + } + } + } + } + } + if ($displayrow) { + $rowdata = bigbluebuttonbn_get_opencast_recording_data_row($bbbsession, $ocrecording); + if (!empty($rowdata)) { + $row = bigbluebuttonbn_get_opencast_recording_table_row($bbbsession, $ocrecording, $rowdata); + array_push($table->data, $row); + } + } + } + return $table; +} + +/** + * Helper function builds a row for the data used by the Opencast recording table. + * + * @param array $bbbsession + * @param array $ocrecording + * + * @return array + */ +function bigbluebuttonbn_get_opencast_recording_data_row($bbbsession, $ocrecording) { + $rowdata = new stdClass(); + // Set recording playback url. + $rowdata->playback = bigbluebuttonbn_get_opencast_recording_data_row_playback($ocrecording, $bbbsession); + // Set recording name from title if exists, otherwise shows "Opencast Video". + $rowdata->name = isset($ocrecording['title']) ? $ocrecording['title'] : get_string('view_recording_list_opencast', 'bigbluebuttonbn'); + // Set recording description. + $rowdata->description = isset($ocrecording['description']) ? $ocrecording['description'] : ''; + if (bigbluebuttonbn_get_recording_data_preview_enabled($bbbsession)) { + // Set recording_preview. + $rowdata->preview = bigbluebuttonbn_get_opencast_recording_data_row_preview($ocrecording); + } + // Set formatted date. + $rowdata->date_formatted = bigbluebuttonbn_get_opencast_recording_data_row_date_formatted($ocrecording); + // Set formatted duration. + $rowdata->duration_formatted = bigbluebuttonbn_get_opencast_recording_data_row_duration($ocrecording['duration']); + // Set actionbar, if user is allowed to manage recordings. + if ($bbbsession['managerecordings']) { + $rowdata->actionbar = bigbluebuttonbn_get_opencast_recording_data_row_actionbar($ocrecording, $bbbsession); + } + return $rowdata; +} + +/** + * Helper function renders the link used for Opencast recording playback in row for the data used by the recording table. + * To display the video, it is important for a video in Opencast to be published with engage-player, also it is required to + * have filter_opencast plugin installed and configured. + * The link redirects user to oc_view.php to authentificate the user via LTI and show the video in Opencast. + * + * @param array $ocrecording + * @param array $bbbsession + * + * @return string + */ +function bigbluebuttonbn_get_opencast_recording_data_row_playback($ocrecording, $bbbsession) { + global $CFG, $OUTPUT; + $text = get_string('view_recording_list_opencast', 'bigbluebuttonbn'); + $href = '#'; + // Check if the publication status has engage-player + if (isset($ocrecording['publication_status']) && in_array('engage-player', $ocrecording['publication_status'])) { + // Check if filter_opencast is configured + $consumerkey = get_config('filter_opencast', 'consumerkey'); + $consumersecret = get_config('filter_opencast', 'consumersecret'); + // In order to make LTI auth both consumerkey and consumersecret are required. + if (!empty($consumerkey) && !empty($consumersecret)) { + $href = $CFG->wwwroot . '/mod/bigbluebuttonbn/oc_player.php?identifier=' . $ocrecording['identifier'] . + '&bn=' . $bbbsession['bigbluebuttonbn']->id; + } + } + + $linkattributes = array( + 'id' => 'opencast-player-redirect-' . $ocrecording['identifier'], + 'class' => 'btn btn-sm btn-default', + 'target' => '_blank' + ); + if ($href == '#' || empty($href)) { + $linkattributes['class'] = 'btn btn-sm btn-warning'; + $linkattributes['title'] = get_string('view_recording_format_errror_unreachable', 'bigbluebuttonbn'); + } + return $OUTPUT->action_link($href, $text, null, $linkattributes) . ' '; +} + +/** + * Helper function builds Opencast recording preview used in row for the data used by the recording table. + * + * @param array $ocrecording + * + * @return string + */ +function bigbluebuttonbn_get_opencast_recording_data_row_preview($ocrecording) { + $options = array('id' => 'preview-' . $ocrecording['identifier']); + $recordingpreview = html_writer::start_tag('div', $options); + $imageurl = ''; + // Getting preview image from mediapackage attachments. + if (isset($ocrecording['mediapackage']['attachments']['attachment'])) { + foreach ($ocrecording['mediapackage']['attachments']['attachment'] as $attachment) { + // Looking for image only. + if (isset($attachment['mimetype']) && strpos($attachment['mimetype'], 'image') !== FALSE) { + // Looking for the url of the preview image. + if (empty($imageurl) && isset($attachment['type']) && isset($attachment['url'])) { + // There are several type of attachments which are different in size. + // More suitable sizes are of these types, respectively. + $suitabletypes = array('search', 'feed'); + foreach ($suitabletypes as $type) { + if (strpos($attachment['type'], $type) !== FALSE) { + $imageurl = $attachment['url']; + break; + } + } + } + } + if (!empty($imageurl)) { + break; + } + } + if (!empty($imageurl)) { + $recordingpreview .= bigbluebuttonbn_get_opencast_recording_data_row_preview_images($imageurl); + } + } + + $recordingpreview .= html_writer::end_tag('div'); + return $recordingpreview; +} + +/** + * Helper function format Opencast recording date used in row for the data used by the recording table. + * + * @param array $ocrecording + * + * @return string + */ +function bigbluebuttonbn_get_opencast_recording_data_row_date_formatted($ocrecording) { + global $USER; + $starttime_str = !empty($ocrecording['start']) ? $ocrecording['start'] : $ocrecording['created']; + $starttime = strtotime($starttime_str); + // Set formatted date. + $dateformat = get_string('strftimerecentfull', 'langconfig') . ' %Z'; + return userdate($starttime, $dateformat, usertimezone($USER->timezone)); +} + +/** + * Helper function converts Opencast recording duration used in row for the data used by the recording table. + * + * @param array $duration + * + * @return integer + */ +function bigbluebuttonbn_get_opencast_recording_data_row_duration($duration) { + if ($duration) { + // Convert the duration (in miliseconds) into Hours:Minutes:Seconds format + return gmdate('H:i:s', $duration / 1000); + } + return 0; +} + +/** + * Helper function builds Opencast recording actionbar used in row for the data used by the recording table. + * + * @param array $ocrecording + * @param array $bbbsession + * + * @return string + */ +function bigbluebuttonbn_get_opencast_recording_data_row_actionbar($ocrecording, $bbbsession) { + global $OUTPUT; + if (empty($ocrecording['identifier']) || empty($bbbsession['course'])) { + return ''; + } + $actionbar = ''; + $linkattributes = array( + 'target' => '_blank', + 'class' => 'btn btn-xs btn-danger' + ); + // Creating moodle url, to redirect to Opencast update metadata (Edit) page. + $opencastediturl = new \moodle_url('/blocks/opencast/updatemetadata.php', + array('video_identifier' => $ocrecording['identifier'], 'courseid' => $bbbsession['course']->id)); + $linkattributes['id'] = 'opencast-edit-episode-' . $ocrecording['identifier']; + // Generating Action Link for Opencast update metadata (Edit). + $actionbar .= $OUTPUT->action_link($opencastediturl, get_string('edit'), null, $linkattributes) . ' '; + + // Creating moodle url, to redirect to Opencast delete event (Delete) page. + $opencastdeleteurl = new \moodle_url('/blocks/opencast/deleteevent.php', + array('identifier' => $ocrecording['identifier'], 'courseid' => $bbbsession['course']->id)); + $linkattributes['id'] = 'opencast-delete-episode-' . $ocrecording['identifier']; + // Generating Action Link for Opencast delete event (Delete). + $actionbar .= $OUTPUT->action_link($opencastdeleteurl, get_string('delete'), null, $linkattributes) . ' '; + $head = html_writer::start_tag('div', array( + 'id' => 'recording-actionbar-' . $ocrecording['identifier'], + 'data-recordingid' => $ocrecording['identifier'], + 'data-meetingid' => $bbbsession['meetingid'])); + $tail = html_writer::end_tag('div'); + return $head . $actionbar . $tail; +} + +/** + * Helper function builds the Opencast recording table row and insert into table. + * + * @param array $bbbsession + * @param object $ocrecording + * @param object $rowdata + * + * @return object + */ +function bigbluebuttonbn_get_opencast_recording_table_row($bbbsession, $ocrecording, $rowdata) { + $row = new html_table_row(); + $row->id = 'recording-tr-' . $ocrecording['identifier']; + $rowdata->date_formatted = str_replace(' ', ' ', $rowdata->date_formatted); + $row->cells = array(); + $row->cells[] = $rowdata->playback; + $row->cells[] = $rowdata->name; + $row->cells[] = $rowdata->description; + if (bigbluebuttonbn_get_recording_data_preview_enabled($bbbsession)) { + $row->cells[] = $rowdata->preview; + } + $row->cells[] = $rowdata->date_formatted; + $row->cells[] = $rowdata->duration_formatted; + if ($bbbsession['managerecordings']) { + $row->cells[] = $rowdata->actionbar; + } + return $row; +} + +/** + * Helper function builds element with actual images used in Opencast recording preview row based on a selected playback. + * + * @param string $imageurl + * + * @return string + */ +function bigbluebuttonbn_get_opencast_recording_data_row_preview_images($imageurl) { + global $CFG; + $recordingpreview = html_writer::start_tag('div', array('class' => 'container-fluid')); + $recordingpreview .= html_writer::start_tag('div', array('class' => 'row')); + $recordingpreview .= html_writer::start_tag('div', array('class' => '')); + $recordingpreview .= html_writer::empty_tag( + 'img', + array('src' => trim($imageurl) . '?' . time(), 'class' => 'recording-thumbnail pull-left') + ); + $recordingpreview .= html_writer::end_tag('div'); + $recordingpreview .= html_writer::end_tag('div'); + $recordingpreview .= html_writer::start_tag('div', array('class' => 'row')); + $recordingpreview .= html_writer::tag( + 'div', + get_string('view_recording_preview_help', 'bigbluebuttonbn'), + array('class' => 'text-center text-muted small') + ); + $recordingpreview .= html_writer::end_tag('div'); + $recordingpreview .= html_writer::end_tag('div'); + return $recordingpreview; +} + +/** + * Helper function to get BBB recordings from the Opencast video avaialble in the course. + * It uses block_opencast for getting all videos for the course and match them with meeting id. + * It uses tool_opencast for making an api call to get mediapackage of vidoes. + * + * @param object $bbbsession + * @param string $seriesid + * + */ +function bigbluebutton_get_opencast_recordings_for_table_view($bbbsession, $seriesid) { + $bbbocvideos = array(); + // Initializing the api from tool_opencast plugin. + $api = new \tool_opencast\local\api(); + // Getting an instance of apibridge from block_opencast plugin. + $opencast = \block_opencast\local\apibridge::get_instance(); + // Getting the course videos from block_opencast plugin. + $ocvideos = $opencast->get_course_videos($bbbsession['course']->id); + if ($ocvideos->videos && !empty($ocvideos->videos)) { + foreach ($ocvideos->videos as $ocvideo) { + // Check subjects of opencast video contains $bbbsession['meetingid']. + if (in_array($bbbsession['meetingid'], $ocvideo->subjects)) { + // Converting $ocvideo object to array. + $ocvideoarray = json_decode(json_encode($ocvideo), true); + // Get mediapackage json using api call. + $url = '/search/episode.json?id=' . $ocvideo->identifier; + $search_result = json_decode($api->oc_get($url), true); + if ($api->get_http_code() == 200 && isset($search_result['search-results']['result']['mediapackage'])) { + // Add mediapackage to array if exists. + $ocvideoarray['mediapackage'] = $search_result['search-results']['result']['mediapackage']; + } + $bbbocvideos[] = $ocvideoarray; + } + } + } + return $bbbocvideos; +} + /** * Helper function to convert an html string to plain text. * @@ -2730,13 +3082,6 @@ function bigbluebuttonbn_settings_record(&$renderer) { 'recording_editable', $renderer->render_group_element_checkbox('recording_editable', 1) ); - // If opencast plugin is installed. - if (bigbluebuttonbn_check_opencast()) { - $renderer->render_group_element( - 'oc_recording', - $renderer->render_group_element_checkbox('oc_recording', 0) - ); - } $renderer->render_group_element( 'recording_icons_enabled', $renderer->render_group_element_checkbox('recording_icons_enabled', 1) @@ -3681,24 +4026,28 @@ function bigbluebuttonbn_create_meeting_metadata(&$bbbsession) { $metadata['analytics-callback-url'] = $bbbsession['meetingEventsURL']; } // Special metadata for Opencast recordings (passing opencast seriesid of the course as opencast-dc-isPartOf as metadata). - if ((boolean) \mod_bigbluebuttonbn\locallib\config::get('oc_recording') - && bigbluebuttonbn_check_opencast($bbbsession['course']->id)) { - $metadata['opencast-dc-isPartOf'] = bigbluebuttonbn_check_opencast($bbbsession['course']->id); + if ((boolean) \mod_bigbluebuttonbn\locallib\config::get('oc_recording')) { + $ocseriesid = bigbluebuttonbn_check_opencast($bbbsession['course']->id); + if ($ocseriesid != false) { + $metadata['opencast-dc-isPartOf'] = $ocseriesid; + $metadata['opencast-dc-subject'] = $bbbsession['meetingid']; + } } return $metadata; } /** - * Helper for checking/retreiving seriesid from opencast_block plugin. + * Helper function which checks if the Opencast plugin (block_opencast) is installed. The function is called from several places throughout mod_bigbluebuttonbn where Opencast functionality can enhance the BBB meeting recording functionality as soon as the Opencast plugin is present. + * If called with a course ID as parameter, the function will not only check if the Opencast plugin is installed. It will also ensure that an Opencast series exists for the given course and will return the Opencast series ID instead of a boolean. In this case, the block does not necessarily be placed in the course. * * @param string $courseid * @return boolean|string */ function bigbluebuttonbn_check_opencast($courseid = null) { $blockplugins = core_plugin_manager::instance()->get_plugins_of_type('block'); - // If opencast_block is installed. + // If block_opencast is installed. if (in_array('opencast', array_keys($blockplugins))) { - // Getting the current instance of opencast. + // Getting an instance of the block_opencast API bridge. $opencast = \block_opencast\local\apibridge::get_instance(); // If opencast is not configured! if (!$opencast) { @@ -3708,13 +4057,42 @@ function bigbluebuttonbn_check_opencast($courseid = null) { if ($courseid) { // Trying to get course seriesid, create if is not set before! try { - return $opencast->ensure_course_series_exists($courseid); + $series = $opencast->ensure_course_series_exists($courseid); + if (is_object($series) && $series->identifier) { + $seriesid = $series->identifier; + } else { + $seriesid = $series; + } + return $seriesid; } catch (Exception $e) { return false; } } return true; } - // If opencast_block is not installed + // If block_opencast is not installed return false; -} \ No newline at end of file +} + +/** + * Helper function renders Opencast integration settings if block_opencast is installed. + * + * @param object $renderer + * + * @return void + */ +function bigbluebuttonbn_settings_opencastintegration(&$renderer) { + // Configuration for 'Opencast integration' feature when Opencast plugins are installed. + if ((boolean) bigbluebuttonbn_check_opencast()) { + $renderer->render_group_header('opencast'); + $renderer->render_group_element( + 'oc_recording', + $renderer->render_group_element_checkbox('oc_recording', 0) + ); + $renderer->render_group_element( + 'oc_show_recording', + $renderer->render_group_element_checkbox('oc_show_recording', 0) + ); + } + +} diff --git a/mod_form.php b/mod_form.php index cf1b34994..7ca80ca44 100644 --- a/mod_form.php +++ b/mod_form.php @@ -340,7 +340,7 @@ private function bigbluebuttonbn_mform_add_block_room_room(&$mform, $cfg) { $field['description_key'] = 'mod_form_field_record'; // Try to check the opencast config and make sure that opencast is available for this course. if ($cfg['oc_recording'] && bigbluebuttonbn_check_opencast(get_course($this->current->course)->id)) { - $field['description_key'] = 'mod_form_field_record_oc'; + $field['description_key'] = 'mod_form_field_opencast_record'; } } $this->bigbluebuttonbn_mform_add_element($mform, $field['type'], $field['name'], $field['data_type'], diff --git a/oc_player.php b/oc_player.php new file mode 100644 index 000000000..46e17a31c --- /dev/null +++ b/oc_player.php @@ -0,0 +1,178 @@ +. + +/** + * View for BigBlueButton interaction. + * + * @package mod_bigbluebuttonbn + * @copyright 2010 onwards, Blindside Networks Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author 2021 Farbod Zamani Boroujeni - ELAN e.V. + */ + +global $PAGE, $OUTPUT, $CFG; + +require_once(dirname(dirname(dirname(__FILE__))).'/config.php'); +require_once(dirname(__FILE__).'/locallib.php'); + +require_once($CFG->dirroot . '/mod/lti/locallib.php'); +require_once($CFG->dirroot . '/lib/oauthlib.php'); + +$identifier = required_param('identifier', PARAM_TEXT); +$bn = optional_param('bn', 0, PARAM_INT); + +$bbbviewinstance = bigbluebuttonbn_view_validator(null, $bn); +if (!$bbbviewinstance) { + print_error(get_string('view_error_url_missing_parameters', 'bigbluebuttonbn')); +} + +$cm = $bbbviewinstance['cm']; +$course = $bbbviewinstance['course']; +$bigbluebuttonbn = $bbbviewinstance['bigbluebuttonbn']; +$context = context_module::instance($cm->id); + +require_login($course, true, $cm); + +// Capability check. +require_capability('mod/bigbluebuttonbn:view', $context); + +$baseurl = new moodle_url('/mod/bigbluebuttonbn/oc_player.php', array('identifier' => $identifier, 'bn' => $bn)); +$PAGE->set_url($baseurl); +$PAGE->set_context($context); +$PAGE->set_pagelayout('incourse'); +$PAGE->set_title(format_string($bigbluebuttonbn->name)); +$PAGE->set_heading($course->fullname); + +// Get endpoint from engageurl setting of filter_opencast plugin or apiurl setting of tool_opencast plugin. +$endpoint = get_config('filter_opencast', 'engageurl'); +if (empty($endpoint)) { + $endpoint = get_config('tool_opencast', 'apiurl'); +} + +// Get player url either from playerurl setting or default paella. +$playerurl = get_config('filter_opencast', 'playerurl'); +if (empty($playerurl)) { + $playerurl = '/play/' . $identifier; +} else { + $playerurl .= '?id=' . $identifier; +} + +if (strpos($endpoint, 'http') !== 0) { + $endpoint = 'http://' . $endpoint; +} + +$ltiendpoint = rtrim($endpoint, '/') . '/lti'; + +// Create parameters. +$params = bigbluebuttonbn_create_lti_parameters($ltiendpoint, $playerurl); + +echo $OUTPUT->header(); +echo $OUTPUT->heading(format_string($bigbluebuttonbn->name)); +echo render_lti_form($ltiendpoint, $params); + +$PAGE->requires->js_call_amd('mod_bigbluebuttonbn/mod_lti_form_handler', 'init'); +echo $OUTPUT->footer(); + +/** + * Create necessary lti parameters. + * @param string $endpoint of the opencast instance. + * @param string $playerurl the player url to pass as custom_tool in lti params + * + * @return array lti parameters + * @throws dml_exception + * @throws moodle_exception + */ +function bigbluebuttonbn_create_lti_parameters($endpoint, $playerurl) { + global $CFG, $COURSE, $USER; + + // Get consumerkey and consumersecret from filter_opencast. + $consumerkey = get_config('filter_opencast', 'consumerkey'); + $consumersecret = get_config('filter_opencast', 'consumersecret'); + + $helper = new oauth_helper(array('oauth_consumer_key' => $consumerkey, + 'oauth_consumer_secret' => $consumersecret)); + + // Set all necessary parameters. + $params = array(); + $params['oauth_version'] = '1.0'; + $params['oauth_nonce'] = $helper->get_nonce(); + $params['oauth_timestamp'] = $helper->get_timestamp(); + $params['oauth_consumer_key'] = $consumerkey; + + $params['context_id'] = $COURSE->id; + $params['context_label'] = trim($COURSE->shortname); + $params['context_title'] = trim($COURSE->fullname); + $params['resource_link_id'] = 'o' . random_int(1000, 9999) . '-' . random_int(1000, 9999); + $params['resource_link_title'] = 'Opencast'; + $params['context_type'] = ($COURSE->format == 'site') ? 'Group' : 'CourseSection'; + $params['launch_presentation_locale'] = current_language(); + $params['ext_lms'] = 'moodle-2'; + $params['tool_consumer_info_product_family_code'] = 'moodle'; + $params['tool_consumer_info_version'] = strval($CFG->version); + $params['oauth_callback'] = 'about:blank'; + $params['lti_version'] = 'LTI-1p0'; + $params['lti_message_type'] = 'basic-lti-launch-request'; + $urlparts = parse_url($CFG->wwwroot); + $params['tool_consumer_instance_guid'] = $urlparts['host']; + $params['custom_tool'] = $playerurl; + + // User data. + $params['user_id'] = $USER->id; + $params['lis_person_name_given'] = $USER->firstname; + $params['lis_person_name_family'] = $USER->lastname; + $params['lis_person_name_full'] = $USER->firstname . ' ' . $USER->lastname; + $params['ext_user_username'] = $USER->username; + $params['lis_person_contact_email_primary'] = $USER->email; + $params['roles'] = lti_get_ims_role($USER, null, $COURSE->id, false); + + if (!empty($CFG->mod_lti_institution_name)) { + $params['tool_consumer_instance_name'] = trim(html_to_text($CFG->mod_lti_institution_name, 0)); + } else { + $params['tool_consumer_instance_name'] = get_site()->shortname; + } + + $params['launch_presentation_document_target'] = 'iframe'; + $params['oauth_signature_method'] = 'HMAC-SHA1'; + $signedparams = lti_sign_parameters($params, $endpoint, "POST", $consumerkey, $consumersecret); + $params['oauth_signature'] = $signedparams['oauth_signature']; + + return $params; +} + +/** + * Display the lti form. + * + * @param string $endpoint of the opencast instance. + * @param array $params lti parameters. + * @return string + */ +function render_lti_form($endpoint, $params) { + $content = "
\n"; + + // Construct html form for the launch parameters. + foreach ($params as $key => $value) { + $key = htmlspecialchars($key); + $value = htmlspecialchars($value); + $content .= "\n"; + } + $content .= "
\n"; + + return $content; +} diff --git a/settings.php b/settings.php index b561486c1..01329ed1f 100644 --- a/settings.php +++ b/settings.php @@ -43,6 +43,8 @@ bigbluebuttonbn_settings_importrecordings($renderer); // Renders settings for showing recordings. bigbluebuttonbn_settings_showrecordings($renderer); + // Renders settings for Opencast integration. + bigbluebuttonbn_settings_opencastintegration($renderer); } // Renders settings for meetings. bigbluebuttonbn_settings_waitmoderator($renderer); diff --git a/viewlib.php b/viewlib.php index f67eb7bbc..595e381df 100644 --- a/viewlib.php +++ b/viewlib.php @@ -169,6 +169,22 @@ function bigbluebuttonbn_view_render_recording_section(&$bbbsession, $type, $ena $output .= bigbluebuttonbn_view_render_imported($bbbsession, $enabledfeatures); $output .= html_writer::end_tag('div'); } + // Repeating the above conditions to show Opencast Recordings, when Opencast series id is available in the current course and + // bigbluebuttonbn_oc_show_recording config is enabled. + $seriesid = bigbluebuttonbn_check_opencast($bbbsession['course']->id); + if ((boolean) \mod_bigbluebuttonbn\locallib\config::get('oc_show_recording') && !empty($seriesid)) { + if ($type == BIGBLUEBUTTONBN_TYPE_ALL && $bbbsession['record']) { + $output .= html_writer::start_tag('div', array('id' => 'bigbluebuttonbn_view_opencast_recordings_header', 'class' => 'mt-3')); + $output .= html_writer::tag('h4', get_string('view_section_title_opencast_recordings', 'bigbluebuttonbn')); + $output .= html_writer::end_tag('div'); + } + if ($type == BIGBLUEBUTTONBN_TYPE_RECORDING_ONLY || $bbbsession['record']) { + $output .= html_writer::start_tag('div', array('id' => 'bigbluebuttonbn_view_opencast_recordings_content')); + $output .= bigbluebuttonbn_view_render_opencast_recordings($bbbsession, $seriesid); + $output .= html_writer::end_tag('div'); + } + } + return $output; } @@ -261,15 +277,8 @@ function bigbluebuttonbn_view_render_recordings(&$bbbsession, $enabledfeatures, ); // If there are meetings with recordings load the data to the table. if ($bbbsession['bigbluebuttonbn']->recordings_html) { - $recordingshtml = ''; - // If opencast recoding config is set and checks if opencast is available for the current course. - if ((boolean) \mod_bigbluebuttonbn\locallib\config::get('oc_recording') - && bigbluebuttonbn_check_opencast($bbbsession['course']->id)) { - $recordingshtml .= bigbluebuttonbn_output_recording_opencast($bbbsession['course']->id); - } - $recordingshtml .= bigbluebuttonbn_output_recording_table($bbbsession, $recordings)."\n"; // Render a plain html table. - return $recordingshtml; + return bigbluebuttonbn_output_recording_table($bbbsession, $recordings)."\n"; } // JavaScript variables for recordings with YUI. $jsvars += array( @@ -288,6 +297,27 @@ function bigbluebuttonbn_view_render_recordings(&$bbbsession, $enabledfeatures, return $output; } +/** + * Renders the view for Opencast recordings. + * + * @param array $bbbsession + * @param string $seriesid + * + * @return string + */ +function bigbluebuttonbn_view_render_opencast_recordings($bbbsession, $seriesid) { + $ocrecordings = bigbluebutton_get_opencast_recordings_for_table_view($bbbsession, $seriesid); + + if (empty($ocrecordings)) { + // There are no Opencast recordings to be shown. + return html_writer::div(get_string('view_message_opencast_norecordings', 'bigbluebuttonbn'), '', + array('id' => 'bigbluebuttonbn_opencast_recordings_table')); + } + + // Render a plain html table. + return bigbluebuttonbn_output_opencast_recording_table($bbbsession, $ocrecordings); +} + /** * Renders the view for importing recordings. * From dd7d5f94b1e7abbb2dcae8cae7c9ecd8222e5382 Mon Sep 17 00:00:00 2001 From: farbod Date: Fri, 14 May 2021 12:08:20 +0200 Subject: [PATCH 7/9] apply suggested changes - 03.04 --- lang/en/bigbluebuttonbn.php | 7 ++----- locallib.php | 16 ++++++++++------ mod_form.php | 4 ---- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/lang/en/bigbluebuttonbn.php b/lang/en/bigbluebuttonbn.php index 52144c732..0fda41484 100644 --- a/lang/en/bigbluebuttonbn.php +++ b/lang/en/bigbluebuttonbn.php @@ -113,8 +113,8 @@ $string['config_recording_default'] = 'Recording feature enabled by default'; $string['config_opencast'] = 'Configuration for "Opencast integration" feature'; $string['config_opencast_description'] = 'These settings control the integration of BBB meeting recordings and Opencast'; -$string['config_oc_recording'] = 'Opencast can be used for Recording'; -$string['config_oc_recording_description'] = 'When enabled, the Opencast series ID of the Moodle Course is sent to BBB as metadata, in order for BBB to integrate with Opencast Server.'; +$string['config_oc_recording'] = 'Send Opencast series ID to BBB'; +$string['config_oc_recording_description'] = 'If this setting is enabled, Moodle will send the Opencast series ID of the course to BBB alongside the BBB metadata whenever a new BBB meeting is initiated. If there is not an Opencast series ID for the course yet, a new series will be created on-the-fly. This way, Opencast can be used to process BBB recordings and to publish them in the course\'s series.'; $string['config_oc_show_recording'] = 'Show Opencast recordings'; $string['config_oc_show_recording_description'] = 'If enabled the recording table will also include the Opencast recordings.'; $string['config_recording_default_description'] = 'If enabled the sessions created in BigBlueButton will have recording capabilities.'; @@ -372,8 +372,6 @@ $string['mod_form_field_lockonjoin'] = 'Ignore lock settings'; $string['mod_form_field_lockonjoinconfigurable'] = 'Allow ignore locking settings'; $string['mod_form_locksettings'] = 'Lock settings'; -$string['mod_form_field_opencast_record'] = 'Session can be recorded in Opencast'; - $string['starts_at'] = 'Starts'; $string['started_at'] = 'Started'; @@ -440,7 +438,6 @@ $string['view_section_title_opencast_recordings'] = 'Opencast Recordings'; $string['view_message_norecordings'] = 'There are no recording to show.'; $string['view_message_opencast_norecordings'] = 'There are no Opencast recording to show.'; -$string['view_message_oc_recordings'] = 'List of Recordings on Opencast'; $string['view_message_finished'] = 'This activity is over.'; $string['view_message_notavailableyet'] = 'This session is not yet available.'; diff --git a/locallib.php b/locallib.php index cbb99f3aa..0f39fbc3d 100644 --- a/locallib.php +++ b/locallib.php @@ -4025,7 +4025,8 @@ function bigbluebuttonbn_create_meeting_metadata(&$bbbsession) { if ((boolean) \mod_bigbluebuttonbn\locallib\config::get('meetingevents_enabled')) { $metadata['analytics-callback-url'] = $bbbsession['meetingEventsURL']; } - // Special metadata for Opencast recordings (passing opencast seriesid of the course as opencast-dc-isPartOf as metadata). + // If block_opencast is installed and the option to send the Opencast series ID to BBB is enabled, + // pass the Opencast series ID of the course as opencast-dc-isPartOf within the BBB metadata. if ((boolean) \mod_bigbluebuttonbn\locallib\config::get('oc_recording')) { $ocseriesid = bigbluebuttonbn_check_opencast($bbbsession['course']->id); if ($ocseriesid != false) { @@ -4049,13 +4050,14 @@ function bigbluebuttonbn_check_opencast($courseid = null) { if (in_array('opencast', array_keys($blockplugins))) { // Getting an instance of the block_opencast API bridge. $opencast = \block_opencast\local\apibridge::get_instance(); - // If opencast is not configured! + // If the block_opencast API bridge is not configured. if (!$opencast) { return false; } - // If the courseid is required (check if the course has the opencsat series). - if ($courseid) { - // Trying to get course seriesid, create if is not set before! + // If a courseid is given, we will return the Opencast series ID for the course. + if (is_numeric($courseid) && $courseid > 0) { + // Get and return the Opencast series ID for the given course. Let Opencast create a new series if there isn't a series yet for this course. + // If an exception occurs during this process, return false as the Opencast integration is not usable at the moment which is the same as if the Opencast plugin would not be installed at all. try { $series = $opencast->ensure_course_series_exists($courseid); if (is_object($series) && $series->identifier) { @@ -4068,9 +4070,10 @@ function bigbluebuttonbn_check_opencast($courseid = null) { return false; } } + // The block_opencast plugin is installed. return true; } - // If block_opencast is not installed + // If block_opencast plugin is NOT installed. return false; } @@ -4083,6 +4086,7 @@ function bigbluebuttonbn_check_opencast($courseid = null) { */ function bigbluebuttonbn_settings_opencastintegration(&$renderer) { // Configuration for 'Opencast integration' feature when Opencast plugins are installed. + // If block_opencast is installed. if ((boolean) bigbluebuttonbn_check_opencast()) { $renderer->render_group_header('opencast'); $renderer->render_group_element( diff --git a/mod_form.php b/mod_form.php index 7ca80ca44..b4a38c999 100644 --- a/mod_form.php +++ b/mod_form.php @@ -338,10 +338,6 @@ private function bigbluebuttonbn_mform_add_block_room_room(&$mform, $cfg) { if ($cfg['recording_editable']) { $field['type'] = 'checkbox'; $field['description_key'] = 'mod_form_field_record'; - // Try to check the opencast config and make sure that opencast is available for this course. - if ($cfg['oc_recording'] && bigbluebuttonbn_check_opencast(get_course($this->current->course)->id)) { - $field['description_key'] = 'mod_form_field_opencast_record'; - } } $this->bigbluebuttonbn_mform_add_element($mform, $field['type'], $field['name'], $field['data_type'], $field['description_key'], $cfg['recording_default']); From 0735fd461b509f8aecec8f230234dddd9c631907 Mon Sep 17 00:00:00 2001 From: farbod Date: Wed, 19 May 2021 16:04:45 +0200 Subject: [PATCH 8/9] apply changes on 11 May --- amd/src/mod_lti_form_handler.js | 35 -------- lang/en/bigbluebuttonbn.php | 1 + locallib.php | 147 +++++++++++++++++++++++++++++--- oc_player.php | 135 +++++------------------------ 4 files changed, 157 insertions(+), 161 deletions(-) delete mode 100644 amd/src/mod_lti_form_handler.js diff --git a/amd/src/mod_lti_form_handler.js b/amd/src/mod_lti_form_handler.js deleted file mode 100644 index fbd6ad03d..000000000 --- a/amd/src/mod_lti_form_handler.js +++ /dev/null @@ -1,35 +0,0 @@ -// This file is part of Moodle - http://moodle.org/ -// -// Moodle is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Moodle is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Moodle. If not, see . - -/** - * LTI form submit - * - * @module mod/bigbluebuttonbn - * @package mod_bigbluebuttonbn - * @copyright 2021 Farbod Zamani (zamani@elan-ev.de) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -define(['jquery'], function($) { - - /* - * Submits lti form - */ - var init = function() { - $('#ltiLaunchForm').submit(); - }; - return { - init: init - }; -}); diff --git a/lang/en/bigbluebuttonbn.php b/lang/en/bigbluebuttonbn.php index 0fda41484..d990f6412 100644 --- a/lang/en/bigbluebuttonbn.php +++ b/lang/en/bigbluebuttonbn.php @@ -388,6 +388,7 @@ $string['view_error_max_concurrent'] = 'Number of concurrent meetings allowed has been reached.'; $string['view_error_userlimit_reached'] = 'The number of users allowed in a meeting has been reached.'; $string['view_error_url_missing_parameters'] = 'There are parameters missing in this URL'; +$string['view_error_missing_filter_opencast_config'] = 'Unable to get valid Opencast configuration in order to display the recording.'; $string['view_error_import_no_courses'] = 'No courses to look up for recordings'; $string['view_error_import_no_recordings'] = 'No recordings in this course for importing'; $string['view_error_invalid_session'] = 'The session is expired. Go back to the activity main page.'; diff --git a/locallib.php b/locallib.php index 0f39fbc3d..de7bc5bcf 100644 --- a/locallib.php +++ b/locallib.php @@ -33,6 +33,8 @@ global $CFG; require_once(__DIR__ . '/lib.php'); +require_once($CFG->dirroot . '/mod/lti/locallib.php'); +require_once($CFG->dirroot . '/lib/oauthlib.php'); /** @var BIGBLUEBUTTONBN_UPDATE_CACHE boolean set to true indicates that cache has to be updated */ const BIGBLUEBUTTONBN_UPDATE_CACHE = true; @@ -2380,13 +2382,11 @@ function bigbluebuttonbn_get_opencast_recording_data_row_playback($ocrecording, global $CFG, $OUTPUT; $text = get_string('view_recording_list_opencast', 'bigbluebuttonbn'); $href = '#'; - // Check if the publication status has engage-player + // Check if the publication status has engage-player. if (isset($ocrecording['publication_status']) && in_array('engage-player', $ocrecording['publication_status'])) { - // Check if filter_opencast is configured - $consumerkey = get_config('filter_opencast', 'consumerkey'); - $consumersecret = get_config('filter_opencast', 'consumersecret'); - // In order to make LTI auth both consumerkey and consumersecret are required. - if (!empty($consumerkey) && !empty($consumersecret)) { + // If filter_opencast is installed and configured, + // also if the LTI form handler JavaScript file in block_opencast is available to use in order to submit the LTI form. + if ((boolean) bigbluebuttonbn_check_opencast_filter() && file_exists("$CFG->dirroot/blocks/opencast/amd/src/block_lti_form_handler.js")) { $href = $CFG->wwwroot . '/mod/bigbluebuttonbn/oc_player.php?identifier=' . $ocrecording['identifier'] . '&bn=' . $bbbsession['bigbluebuttonbn']->id; } @@ -2398,8 +2398,9 @@ function bigbluebuttonbn_get_opencast_recording_data_row_playback($ocrecording, 'target' => '_blank' ); if ($href == '#' || empty($href)) { + unset($linkattributes['target']); $linkattributes['class'] = 'btn btn-sm btn-warning'; - $linkattributes['title'] = get_string('view_recording_format_errror_unreachable', 'bigbluebuttonbn'); + $linkattributes['title'] = get_string('view_recording_format_error_opencast_unreachable', 'bigbluebuttonbn'); } return $OUTPUT->action_link($href, $text, null, $linkattributes) . ' '; } @@ -2574,7 +2575,7 @@ function bigbluebuttonbn_get_opencast_recording_data_row_preview_images($imageur } /** - * Helper function to get BBB recordings from the Opencast video avaialble in the course. + * Helper function to get BBB recordings from the Opencast video available in the course. * It uses block_opencast for getting all videos for the course and match them with meeting id. * It uses tool_opencast for making an api call to get mediapackage of vidoes. * @@ -2590,7 +2591,7 @@ function bigbluebutton_get_opencast_recordings_for_table_view($bbbsession, $seri $opencast = \block_opencast\local\apibridge::get_instance(); // Getting the course videos from block_opencast plugin. $ocvideos = $opencast->get_course_videos($bbbsession['course']->id); - if ($ocvideos->videos && !empty($ocvideos->videos)) { + if ($ocvideos->error == 0) { foreach ($ocvideos->videos as $ocvideo) { // Check subjects of opencast video contains $bbbsession['meetingid']. if (in_array($bbbsession['meetingid'], $ocvideo->subjects)) { @@ -4085,6 +4086,7 @@ function bigbluebuttonbn_check_opencast($courseid = null) { * @return void */ function bigbluebuttonbn_settings_opencastintegration(&$renderer) { + global $CFG; // Configuration for 'Opencast integration' feature when Opencast plugins are installed. // If block_opencast is installed. if ((boolean) bigbluebuttonbn_check_opencast()) { @@ -4098,5 +4100,130 @@ function bigbluebuttonbn_settings_opencastintegration(&$renderer) { $renderer->render_group_element_checkbox('oc_show_recording', 0) ); } - } + +/** + * Helper function which checks if the Opencast Filter plugin (filter_opencast) is installed and configured. + * This function is used to display the BBB recordings on Opencast, which uses LTI to handle the authentication. + * + * @return boolean|array + */ +function bigbluebuttonbn_check_opencast_filter() { + $filterplugins = core_plugin_manager::instance()->get_plugins_of_type('filter'); + // If filter_opencast is installed. + if (in_array('opencast', array_keys($filterplugins))) { + // In order to display the videos through LTI consumerkey and consumersecret must be configured in filter_opencast. + $consumerkey = get_config('filter_opencast', 'consumerkey'); + $consumersecret = get_config('filter_opencast', 'consumersecret'); + + // Engageurl in filter_opencast plugin is the endpoint from which the Opencast player will be called. + $engageurl = get_config('filter_opencast', 'engageurl'); + if (empty($engageurl)) { + // If it is not set in filter_opencast, the main apiurl setting of tool_opencast plugin will be used. + $engageurl = get_config('tool_opencast', 'apiurl'); + } + + if (strpos($engageurl, 'http') !== 0) { + $engageurl = 'http://' . $engageurl; + } + + // A player url helps to predefine the endpoint to call the Opencast player directly. + $playerurl = get_config('filter_opencast', 'playerurl'); + if (empty($playerurl)) { + // If it is not configured, /play/ endpoint will make Opencast to decide based on its internal configuration. + $playerurl = '/play/'; + } else { + // If it is configured, then "id" query string is needed in any case. + $playerurl .= '?id='; + } + + // Make sure the player url is correct. + $playerurl = '/' . ltrim($playerurl, '/'); + + // Make lti url. + $ltiendpoint = rtrim($engageurl, '/') . '/lti'; + + if (!empty($consumerkey) && !empty($consumersecret) && !empty($playerurl) && !empty($ltiendpoint)) { + // If filter_opencast plugin is configured. + return array( + 'consumerkey' => $consumerkey, + 'consumersecret' => $consumersecret, + 'engageurl' => $engageurl, + 'playerurl' => $playerurl, + 'ltiendpoint' => $ltiendpoint + ); + } else { + // If filter_opencast plugin is NOT configured. + return false; + } + } + // If filter_opencast plugin is NOT installed. + return false; +} + +/** + * Create necessary lti parameters. + * @param array $opencastfilterconfig the array of customized configuration from bigbluebuttonbn_check_opencast_filter function. + * + * @return array lti parameters + * @throws dml_exception + * @throws moodle_exception + */ +function bigbluebuttonbn_create_lti_parameters_opencast($opencastfilterconfig) { + global $CFG, $COURSE, $USER; + + $endpoint = $opencastfilterconfig['ltiendpoint']; + $consumerkey = $opencastfilterconfig['consumerkey']; + $consumersecret = $opencastfilterconfig['consumersecret']; + $customtool = $opencastfilterconfig['playerurl']; + + $helper = new oauth_helper(array('oauth_consumer_key' => $consumerkey, + 'oauth_consumer_secret' => $consumersecret)); + + // Set all necessary parameters. + $params = array(); + $params['oauth_version'] = '1.0'; + $params['oauth_nonce'] = $helper->get_nonce(); + $params['oauth_timestamp'] = $helper->get_timestamp(); + $params['oauth_consumer_key'] = $consumerkey; + + $params['context_id'] = $COURSE->id; + $params['context_label'] = trim($COURSE->shortname); + $params['context_title'] = trim($COURSE->fullname); + $params['resource_link_id'] = 'o' . random_int(1000, 9999) . '-' . random_int(1000, 9999); + $params['resource_link_title'] = 'Opencast'; + $params['context_type'] = ($COURSE->format == 'site') ? 'Group' : 'CourseSection'; + $params['launch_presentation_locale'] = current_language(); + $params['ext_lms'] = 'moodle-2'; + $params['tool_consumer_info_product_family_code'] = 'moodle'; + $params['tool_consumer_info_version'] = strval($CFG->version); + $params['oauth_callback'] = 'about:blank'; + $params['lti_version'] = 'LTI-1p0'; + $params['lti_message_type'] = 'basic-lti-launch-request'; + $urlparts = parse_url($CFG->wwwroot); + $params['tool_consumer_instance_guid'] = $urlparts['host']; + $params['custom_tool'] = $customtool; + + // User data. + $params['user_id'] = $USER->id; + $params['lis_person_name_given'] = $USER->firstname; + $params['lis_person_name_family'] = $USER->lastname; + $params['lis_person_name_full'] = $USER->firstname . ' ' . $USER->lastname; + $params['ext_user_username'] = $USER->username; + $params['lis_person_contact_email_primary'] = $USER->email; + $params['roles'] = lti_get_ims_role($USER, null, $COURSE->id, false); + + if (!empty($CFG->mod_lti_institution_name)) { + $params['tool_consumer_instance_name'] = trim(html_to_text($CFG->mod_lti_institution_name, 0)); + } else { + $params['tool_consumer_instance_name'] = get_site()->shortname; + } + + $params['launch_presentation_document_target'] = 'iframe'; + $params['oauth_signature_method'] = 'HMAC-SHA1'; + $signedparams = lti_sign_parameters($params, $endpoint, "POST", $consumerkey, $consumersecret); + $params['oauth_signature'] = $signedparams['oauth_signature']; + + return $params; +} + diff --git a/oc_player.php b/oc_player.php index 46e17a31c..dea0c2e7a 100644 --- a/oc_player.php +++ b/oc_player.php @@ -15,7 +15,7 @@ // along with Moodle. If not, see . /** - * View for BigBlueButton interaction. + * View for Opencast recordings. * * @package mod_bigbluebuttonbn * @copyright 2010 onwards, Blindside Networks Inc @@ -23,13 +23,11 @@ * @author 2021 Farbod Zamani Boroujeni - ELAN e.V. */ -global $PAGE, $OUTPUT, $CFG; +global $PAGE, $OUTPUT; require_once(dirname(dirname(dirname(__FILE__))).'/config.php'); require_once(dirname(__FILE__).'/locallib.php'); -require_once($CFG->dirroot . '/mod/lti/locallib.php'); -require_once($CFG->dirroot . '/lib/oauthlib.php'); $identifier = required_param('identifier', PARAM_TEXT); $bn = optional_param('bn', 0, PARAM_INT); @@ -39,6 +37,12 @@ print_error(get_string('view_error_url_missing_parameters', 'bigbluebuttonbn')); } +// Get configs from filter_opencast +$opencastfilterconfig = bigbluebuttonbn_check_opencast_filter(); +if (!$opencastfilterconfig) { + print_error(get_string('view_error_missing_filter_opencast_config', 'bigbluebuttonbn')); +} + $cm = $bbbviewinstance['cm']; $course = $bbbviewinstance['course']; $bigbluebuttonbn = $bbbviewinstance['bigbluebuttonbn']; @@ -56,123 +60,22 @@ $PAGE->set_title(format_string($bigbluebuttonbn->name)); $PAGE->set_heading($course->fullname); -// Get endpoint from engageurl setting of filter_opencast plugin or apiurl setting of tool_opencast plugin. -$endpoint = get_config('filter_opencast', 'engageurl'); -if (empty($endpoint)) { - $endpoint = get_config('tool_opencast', 'apiurl'); -} +// Add $identifier to the end of playerurl. +$opencastfilterconfig['playerurl'] .= $identifier; -// Get player url either from playerurl setting or default paella. -$playerurl = get_config('filter_opencast', 'playerurl'); -if (empty($playerurl)) { - $playerurl = '/play/' . $identifier; -} else { - $playerurl .= '?id=' . $identifier; -} +// Create LTI parameters. +$params = bigbluebuttonbn_create_lti_parameters_opencast($opencastfilterconfig); -if (strpos($endpoint, 'http') !== 0) { - $endpoint = 'http://' . $endpoint; -} - -$ltiendpoint = rtrim($endpoint, '/') . '/lti'; - -// Create parameters. -$params = bigbluebuttonbn_create_lti_parameters($ltiendpoint, $playerurl); +// Using block_opencast renderer in order to use render_lti_form function. +$opencastrenderer = $PAGE->get_renderer('block_opencast'); echo $OUTPUT->header(); echo $OUTPUT->heading(format_string($bigbluebuttonbn->name)); -echo render_lti_form($ltiendpoint, $params); -$PAGE->requires->js_call_amd('mod_bigbluebuttonbn/mod_lti_form_handler', 'init'); -echo $OUTPUT->footer(); +// Render the LTI form from block_opencast renderer function. +echo $opencastrenderer->render_lti_form($opencastfilterconfig['ltiendpoint'], $params); -/** - * Create necessary lti parameters. - * @param string $endpoint of the opencast instance. - * @param string $playerurl the player url to pass as custom_tool in lti params - * - * @return array lti parameters - * @throws dml_exception - * @throws moodle_exception - */ -function bigbluebuttonbn_create_lti_parameters($endpoint, $playerurl) { - global $CFG, $COURSE, $USER; - - // Get consumerkey and consumersecret from filter_opencast. - $consumerkey = get_config('filter_opencast', 'consumerkey'); - $consumersecret = get_config('filter_opencast', 'consumersecret'); - - $helper = new oauth_helper(array('oauth_consumer_key' => $consumerkey, - 'oauth_consumer_secret' => $consumersecret)); - - // Set all necessary parameters. - $params = array(); - $params['oauth_version'] = '1.0'; - $params['oauth_nonce'] = $helper->get_nonce(); - $params['oauth_timestamp'] = $helper->get_timestamp(); - $params['oauth_consumer_key'] = $consumerkey; - - $params['context_id'] = $COURSE->id; - $params['context_label'] = trim($COURSE->shortname); - $params['context_title'] = trim($COURSE->fullname); - $params['resource_link_id'] = 'o' . random_int(1000, 9999) . '-' . random_int(1000, 9999); - $params['resource_link_title'] = 'Opencast'; - $params['context_type'] = ($COURSE->format == 'site') ? 'Group' : 'CourseSection'; - $params['launch_presentation_locale'] = current_language(); - $params['ext_lms'] = 'moodle-2'; - $params['tool_consumer_info_product_family_code'] = 'moodle'; - $params['tool_consumer_info_version'] = strval($CFG->version); - $params['oauth_callback'] = 'about:blank'; - $params['lti_version'] = 'LTI-1p0'; - $params['lti_message_type'] = 'basic-lti-launch-request'; - $urlparts = parse_url($CFG->wwwroot); - $params['tool_consumer_instance_guid'] = $urlparts['host']; - $params['custom_tool'] = $playerurl; - - // User data. - $params['user_id'] = $USER->id; - $params['lis_person_name_given'] = $USER->firstname; - $params['lis_person_name_family'] = $USER->lastname; - $params['lis_person_name_full'] = $USER->firstname . ' ' . $USER->lastname; - $params['ext_user_username'] = $USER->username; - $params['lis_person_contact_email_primary'] = $USER->email; - $params['roles'] = lti_get_ims_role($USER, null, $COURSE->id, false); - - if (!empty($CFG->mod_lti_institution_name)) { - $params['tool_consumer_instance_name'] = trim(html_to_text($CFG->mod_lti_institution_name, 0)); - } else { - $params['tool_consumer_instance_name'] = get_site()->shortname; - } - - $params['launch_presentation_document_target'] = 'iframe'; - $params['oauth_signature_method'] = 'HMAC-SHA1'; - $signedparams = lti_sign_parameters($params, $endpoint, "POST", $consumerkey, $consumersecret); - $params['oauth_signature'] = $signedparams['oauth_signature']; - - return $params; -} +// Use block_opencast LTI form handler javascript to submit the lti form. +$PAGE->requires->js_call_amd('block_opencast/block_lti_form_handler', 'init'); +echo $OUTPUT->footer(); -/** - * Display the lti form. - * - * @param string $endpoint of the opencast instance. - * @param array $params lti parameters. - * @return string - */ -function render_lti_form($endpoint, $params) { - $content = "
\n"; - - // Construct html form for the launch parameters. - foreach ($params as $key => $value) { - $key = htmlspecialchars($key); - $value = htmlspecialchars($value); - $content .= "\n"; - } - $content .= "
\n"; - - return $content; -} From 293b223a99634767f9d776df03d6b5ae5ce27609 Mon Sep 17 00:00:00 2001 From: farbod Date: Tue, 1 Jun 2021 12:29:24 +0200 Subject: [PATCH 9/9] refactoring opencast integration --- amd/src/opencastrecordings.js | 173 ++++++ amd/src/repository.js | 28 + classes/external/get_opencast_recordings.php | 233 ++++++++ classes/local/helpers/meeting.php | 11 + classes/local/helpers/opencast.php | 516 ++++++++++++++++++ classes/local/settings/settings.php | 40 ++ classes/local/settings/validator.php | 12 + classes/local/view.php | 6 + .../output/opencast_recordings_session.php | 96 ++++ db/services.php | 8 + lang/en/bigbluebuttonbn.php | 6 + oc_player.php | 17 +- settings.php | 2 + .../opencast_recordings_session.mustache | 33 ++ templates/opencast_recordings_table.mustache | 50 ++ version.php | 2 +- 16 files changed, 1227 insertions(+), 6 deletions(-) create mode 100644 amd/src/opencastrecordings.js create mode 100644 classes/external/get_opencast_recordings.php create mode 100644 classes/local/helpers/opencast.php create mode 100644 classes/output/opencast_recordings_session.php create mode 100644 templates/opencast_recordings_session.mustache create mode 100644 templates/opencast_recordings_table.mustache diff --git a/amd/src/opencastrecordings.js b/amd/src/opencastrecordings.js new file mode 100644 index 000000000..9288bd52e --- /dev/null +++ b/amd/src/opencastrecordings.js @@ -0,0 +1,173 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +import * as repository from './repository'; +import {exception as displayException} from 'core/notification'; +import {get_strings as getStrings} from 'core/str'; + +/** + * Initiate the YUI langauge strings with appropriate values for the sortable list from Moodle. + * + * @param {YUI} Y + * @returns {Promise} + */ +const initYuiLanguage = Y => { + const stringList = [ + 'view_recording_yui_first', + 'view_recording_yui_prev', + 'view_recording_yui_next', + 'view_recording_yui_last', + 'view_recording_yui_page', + 'view_recording_yui_go', + 'view_recording_yui_rows', + 'view_recording_yui_show_all', + ].map(key => { + return { + key, + component: 'bigbluebuttonbn', + }; + }); + + return getStrings(stringList) + .then(([first, prev, next, last, goToLabel, goToAction, perPage, showAll]) => { + Y.Intl.add('datatable-paginator', Y.config.lang, { + first, + prev, + next, + last, + goToLabel, + goToAction, + perPage, + showAll, + }); + }) + .catch(); +}; + +/** + * Format the supplied date per the specified locale. + * + * @param {string} locale + * @param {array} dateList + * @returns {array} + */ +const formatDates = (locale, dateList) => dateList.map(row => { + const date = new Date(row.date); + row.date = date.toLocaleDateString(locale, { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric' + }); + + return row; +}); + +/** + * Format response data for the table. + * + * @param {string} response JSON-encoded table data + * @returns {array} + */ +const getFormattedData = response => { + const recordingData = response.tabledata; + let rowData = JSON.parse(recordingData.data); + + rowData = formatDates(recordingData.locale, rowData); + + return rowData; +}; +/** + * + * @param {String} tableId in which we will display the table + * @returns {[(*|number), string, boolean]} + */ +const getTableInformations = (tableId) => { + const tableElement = document.querySelector(tableId); + const bbbid = tableElement.dataset.bbbid; + const tools = tableElement.dataset.tools; + return [bbbid, tools]; +}; + +/** + * Setup the data table for the specified BBB instance. + * + * @param {String} tableId in which we will display the table + * @param {object} response The response from the data request + * @returns {Promise} + */ +const setupDatatable = (tableId, response) => { + if (!response) { + return Promise.resolve(); + } + + if (!response.status) { + // Something failed. Continue to show the plain output. + return Promise.resolve(); + } + + const recordingData = response.tabledata; + + let showRecordings = recordingData.profile_features.indexOf('all') !== -1; + showRecordings = showRecordings || recordingData.profile_features.indexOf('showrecordings') !== -1; + if (!showRecordings) { + // TODO: This should be handled by the web service. + // This user is not allowed to view recordings. + return Promise.resolve(); + } + + return new Promise(function (resolve) { + // eslint-disable-next-line + YUI({ + lang: recordingData.locale, + }).use('intl', 'datatable', 'datatable-sort', 'datatable-paginator', 'datatype-number', Y => { + initYuiLanguage(Y) + .then(() => { + const tableData = getFormattedData(response); + + const dataTable = new Y.DataTable({ + width: "1195px", + columns: recordingData.columns, + data: tableData, + rowsPerPage: 3, + paginatorLocation: ['header', 'footer'] + }); + dataTable.set('currentData', dataTable.get('data')); + dataTable.set('currentFilter', ''); + + return dataTable; + }) + .then(resolve) + .catch(); + }); + }) + .then(dataTable => { + dataTable.render(tableId); + return dataTable; + }); +}; + +/** + * Initialise opencast recordings code. + * + * @method init + * @param {String} tableId in which we will display the table + */ +export const init = (tableId) => { + const [bbbid, tools] = getTableInformations(tableId); + repository.fetchOpencastRecordings(bbbid, tools) + .then(response => setupDatatable(tableId, response)) + .catch(displayException); +}; diff --git a/amd/src/repository.js b/amd/src/repository.js index a947160a9..656308d87 100644 --- a/amd/src/repository.js +++ b/amd/src/repository.js @@ -95,3 +95,31 @@ export const meetingInfo = args => fetchMany([ args, } ])[0]; + +/** + * Request for Opencast recording + * + * @param {Number} bigbluebuttonbnid The instance ID + * @param {String} tools the set of tools to display + * @returns {Promise} + */ + + const getOpencastListTableRequest = (bigbluebuttonbnid, tools) => { + return { + methodname: 'mod_bigbluebutton_opencast_recording_list_table', + args: { + bigbluebuttonbnid, + tools + } + }; +}; + +/** + * Fetch the list of Opencast recordings from the server. + * + * @param {Number} bigbluebuttonbnid The instance ID + * @param {String} tools the set of tools to display + * @returns {Promise} + */ + export const fetchOpencastRecordings = (bigbluebuttonbnid, tools) => + fetchMany([getOpencastListTableRequest(bigbluebuttonbnid, tools)])[0]; diff --git a/classes/external/get_opencast_recordings.php b/classes/external/get_opencast_recordings.php new file mode 100644 index 000000000..14cc65c75 --- /dev/null +++ b/classes/external/get_opencast_recordings.php @@ -0,0 +1,233 @@ +. + +/** + * BigBlueButtonBN internal API for Opencast recordings + * + * @package mod_bigbluebuttonbn + * @category external + * @copyright 2018 onwards, Blindside Networks Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_bigbluebuttonbn\external; + +use external_api; +use external_description; +use external_function_parameters; +use external_multiple_structure; +use external_single_structure; +use external_value; +use external_warnings; +use invalid_parameter_exception; +use mod_bigbluebuttonbn\local\bigbluebutton; +use mod_bigbluebuttonbn\local\config; +use mod_bigbluebuttonbn\local\helpers\instance; +use mod_bigbluebuttonbn\local\helpers\recording; +use mod_bigbluebuttonbn\local\helpers\opencast; +use mod_bigbluebuttonbn\plugin; + +/** + * External service to fetch a list of Opencast recordings from the Opencast server. + * + * @package mod_bigbluebuttonbn + * @category external + * @copyright 2018 onwards, Blindside Networks Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class get_opencast_recordings extends external_api { + /** + * Returns description of method parameters + * + * @return external_function_parameters + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters([ + 'bigbluebuttonbnid' => new external_value(PARAM_INT, 'bigbluebuttonbn instance id', VALUE_OPTIONAL), + 'tools' => new external_value(PARAM_RAW, 'a set of enablec tools', VALUE_OPTIONAL), + ]); + } + + /** + * Get a list of Opencast recordings + * + * @param int $bigbluebuttonbnid the bigbluebuttonbn instance id + * @param string $tools + * @return array of warnings and status result + * @throws \coding_exception + * @throws \dml_exception + * @throws \moodle_exception + * @throws \restricted_context_exception + * @throws invalid_parameter_exception + */ + public static function execute(int $bigbluebuttonbnid = 0, $tools = 'edit,delete'): array { + $warnings = []; + + // Validate the bigbluebuttonbnid ID. + [ + 'bigbluebuttonbnid' => $bigbluebuttonbnid, + 'tools' => $tools, + ] = self::validate_parameters(self::execute_parameters(), [ + 'bigbluebuttonbnid' => $bigbluebuttonbnid, + 'tools' => $tools, + ]); + + // Fetch the session, features, and profile. + [ + 'bbbsession' => $bbbsession, + 'context' => $context, + 'enabledfeatures' => $enabledfeatures, + 'typeprofiles' => $typeprofiles, + ] = instance::get_session_from_id($bigbluebuttonbnid); + + if ($bigbluebuttonbnid === 0) { + throw new invalid_parameter_exception('Both BigbluebuttonBN and Course IDs are null, we can either + have one or the other but not both at the same time'); + } + // Validate that the user has access to this activity. + self::validate_context($context); + + $tools = explode(',', $tools); + + // Fetch the list of recordings. + $opencastrecordings = + opencast::bigbluebutton_get_opencast_recordings_for_table_view($bbbsession); + + $tabledata = [ + 'activity' => bigbluebutton::bigbluebuttonbn_view_get_activity_status($bbbsession), + 'ping_interval' => (int) config::get('waitformoderator_ping_interval') * 1000, + 'locale' => plugin::bigbluebuttonbn_get_localcode(), + 'profile_features' => $typeprofiles[0]['features'], + 'columns' => [], + 'data' => '', + ]; + + $data = []; + + // Build table content. + if (isset($opencastrecordings)) { + // There are recordings for this meeting. + foreach ($opencastrecordings as $opencastrecording) { + $rowdata = opencast::bigbluebuttonbn_get_opencast_recording_data_row($bbbsession, $opencastrecording, $tools); + if (!empty($rowdata)) { + $data[] = $rowdata; + } + } + } + + $columns = [ + [ + 'key' => 'playback', + 'label' => get_string('view_recording_playback', 'bigbluebuttonbn'), + 'width' => '125px', + 'type' => 'html', + 'allowHTML' => true, + ], + [ + 'key' => 'name', + 'label' => get_string('view_recording_name', 'bigbluebuttonbn'), + 'width' => '125px', + 'type' => 'html', + 'allowHTML' => true, + ], + [ + 'key' => 'description', + 'label' => get_string('view_recording_description', 'bigbluebuttonbn'), + 'sortable' => true, + 'width' => '250px', + 'type' => 'html', + 'allowHTML' => true, + ], + ]; + + // Initialize table headers. + // For Opencast recording table to maintain the consistency, it checks if preview is enabled for the recording table. + if (recording::bigbluebuttonbn_get_recording_data_preview_enabled($bbbsession)) { + $columns[] = [ + 'key' => 'preview', + 'label' => get_string('view_recording_preview', 'bigbluebuttonbn'), + 'width' => '250px', + 'type' => 'html', + 'allowHTML' => true, + ]; + } + + $columns[] = [ + 'key' => 'date', + 'label' => get_string('view_recording_date', 'bigbluebuttonbn'), + 'sortable' => true, + 'width' => '225px', + 'type' => 'html', + 'allowHTML' => true, + ]; + $columns[] = [ + 'key' => 'duration', + 'label' => get_string('view_recording_duration', 'bigbluebuttonbn'), + 'width' => '50px', + 'allowHTML' => false, + 'sortable' => true, + ]; + if ($bbbsession['managerecordings']) { + $columns[] = [ + 'key' => 'actionbar', + 'label' => get_string('view_recording_actionbar', 'bigbluebuttonbn'), + 'width' => '120px', + 'type' => 'html', + 'allowHTML' => true, + ]; + } + + $tabledata['columns'] = $columns; + $tabledata['data'] = json_encode($data); + + $returnval = [ + 'status' => true, + 'tabledata' => $tabledata, + 'warnings' => $warnings, + ]; + + return $returnval; + } + + /** + * Describe the return structure of the external service. + * + * @return external_single_structure + * @since Moodle 3.0 + */ + public static function execute_returns(): external_single_structure { + return new external_single_structure([ + 'status' => new external_value(PARAM_BOOL, 'Whether the fetch was successful'), + 'tabledata' => new external_single_structure([ + 'activity' => new external_value(PARAM_ALPHA), + 'ping_interval' => new external_value(PARAM_INT), + 'locale' => new external_value(PARAM_TEXT), + 'profile_features' => new external_multiple_structure(new external_value(PARAM_TEXT)), + 'columns' => new external_multiple_structure(new external_single_structure([ + 'key' => new external_value(PARAM_ALPHA), + 'label' => new external_value(PARAM_TEXT), + 'width' => new external_value(PARAM_ALPHANUMEXT), + // See https://datatables.net/reference/option/columns.type . + 'type' => new external_value(PARAM_ALPHANUMEXT, 'Column type', VALUE_OPTIONAL), + 'sortable' => new external_value(PARAM_BOOL, 'Whether this column is sortable', VALUE_OPTIONAL, false), + 'allowHTML' => new external_value(PARAM_BOOL, 'Whether this column contains HTML', VALUE_OPTIONAL, false), + ])), + 'data' => new external_value(PARAM_RAW), // For now it will be json encoded. + ]), + 'warnings' => new external_warnings() + ]); + } +} diff --git a/classes/local/helpers/meeting.php b/classes/local/helpers/meeting.php index 6f5b5ec99..5e8ac47d0 100644 --- a/classes/local/helpers/meeting.php +++ b/classes/local/helpers/meeting.php @@ -31,6 +31,7 @@ use mod_bigbluebuttonbn\local\config; use mod_bigbluebuttonbn\plugin; use stdClass; +use mod_bigbluebuttonbn\local\helpers\opencast; defined('MOODLE_INTERNAL') || die(); @@ -234,6 +235,16 @@ public static function bigbluebuttonbn_create_meeting_metadata(&$bbbsession) { if ((boolean) config::get('meetingevents_enabled')) { $metadata['analytics-callback-url'] = $bbbsession['meetingEventsURL']; } + // If block_opencast is installed and the option to send the Opencast series ID to BBB is enabled, + // pass the Opencast series ID of the course as opencast-dc-isPartOf within the BBB metadata. + // Additionally, in order to identify and get the BBB recording on opencast, $bbbsession['meetingid'] as opencast-dc-subject metadata will be sent. + if ((boolean) config::get('opencast_recording')) { + $ocseriesid = opencast::bigbluebuttonbn_check_opencast($bbbsession['course']->id); + if ($ocseriesid != false) { + $metadata['opencast-dc-isPartOf'] = $ocseriesid; + $metadata['opencast-dc-subject'] = $bbbsession['meetingid']; + } + } return $metadata; } diff --git a/classes/local/helpers/opencast.php b/classes/local/helpers/opencast.php new file mode 100644 index 000000000..a52be67da --- /dev/null +++ b/classes/local/helpers/opencast.php @@ -0,0 +1,516 @@ +. +/** + * The mod_bigbluebuttonbn opencast helper + * + * @package mod_bigbluebuttonbn + * @copyright 2021 onwards, Blindside Networks Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Farbod Zamani (zamani [at] elan-ev [dt] de) + */ + +namespace mod_bigbluebuttonbn\local\helpers; + +use moodle_url; +use stdClass; +use core_plugin_manager; +use html_writer; +use dml_exception; +use moodle_exception; +use oauth_helper; +use mod_bigbluebuttonbn\local\helpers\recording; +use ReflectionMethod; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Utility class for Opencast helper + * + * @package mod_bigbluebuttonbn + * @copyright 2021 onwards, Blindside Networks Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +class opencast { + + /** + * Helper function which checks if the Opencast plugin (block_opencast) is installed. The function is called from several places throughout mod_bigbluebuttonbn where Opencast functionality can enhance the BBB meeting recording functionality as soon as the Opencast plugin is present. + * If called with a course ID as parameter, the function will not only check if the Opencast plugin is installed. It will also ensure that an Opencast series exists for the given course and will return the Opencast series ID instead of a boolean. In this case, the block does not necessarily be placed in the course. + * + * @param string $courseid + * @return boolean|string + */ + public static function bigbluebuttonbn_check_opencast($courseid = null) { + $blockplugins = core_plugin_manager::instance()->get_plugins_of_type('block'); + // If block_opencast is installed. + if (in_array('opencast', array_keys($blockplugins))) { + // Getting an instance of the block_opencast API bridge. + $opencast = \block_opencast\local\apibridge::get_instance(); + // If the block_opencast API bridge is not configured. + if (!$opencast) { + return false; + } + // If a courseid is given, we will return the Opencast series ID for the course. + if (is_numeric($courseid) && $courseid > 0) { + // Get and return the Opencast series ID for the given course. Let Opencast create a new series if there isn't a series yet for this course. + // If an exception occurs during this process, return false as the Opencast integration is not usable at the moment which is the same as if the Opencast plugin would not be installed at all. + try { + // Use get_stored_seriesid method in order to retreive the series id, but when the method accepts more than 1 parameter which is introduced in v3.11-r1 of block_opencast. + $getstoredseriesidreflection = new ReflectionMethod($opencast, 'get_stored_seriesid'); + if (count($getstoredseriesidreflection->getParameters()) > 1) { + // The second parameter ($createifempty = true) of this method helps to create the series when it does not exist. + $series = $opencast->get_stored_seriesid($courseid, true); + } else { + // If get_stored_seriesid accepts only 1 argument, then we use another method called ensure_course_series_exists. Mostly used for older versions of block_opencast plugin. + // To make sure that the ensure_course_series_exists method accepts all the parameters it needs, we use ReflectionMethod and call_user_func_array. + $ensurecourseseriesexistsreflection = new ReflectionMethod($opencast, 'ensure_course_series_exists'); + // Filling up an array with null value based on parameters' count of ensure_course_series_exists method. + $args = array_fill(0, count($ensurecourseseriesexistsreflection->getParameters()), null); + // Replace the first element of args array with courseid + $args[0] = $courseid; + $series = call_user_func_array([$opencast, 'ensure_course_series_exists'], $args); + } + if (is_object($series) && $series->identifier) { + $seriesid = $series->identifier; + } else { + $seriesid = $series; + } + return $seriesid; + } catch (Exception $e) { + return false; + } + } + // The block_opencast plugin is installed. + return true; + } + // If block_opencast plugin is NOT installed. + return false; + } + + /** + * Helper function to get BBB recordings from the Opencast video available in the course. + * It uses block_opencast for getting all videos for the course and match them with meeting id. + * It uses tool_opencast for making an api call to get mediapackage of vidoes. + * + * @param object $bbbsession + * + * @return array $bbbocvideos Opencast recordings of the BBB session + */ + public static function bigbluebutton_get_opencast_recordings_for_table_view($bbbsession) { + $bbbocvideos = array(); + // Initializing the api from tool_opencast plugin. + $api = new \tool_opencast\local\api(); + // Getting an instance of apibridge from block_opencast plugin. + $opencast = \block_opencast\local\apibridge::get_instance(); + // Getting the course videos from block_opencast plugin. + $ocvideos = $opencast->get_course_videos($bbbsession['course']->id); + if ($ocvideos->error == 0) { + foreach ($ocvideos->videos as $ocvideo) { + // Check subjects of opencast video contains $bbbsession['meetingid']. + if (in_array($bbbsession['meetingid'], $ocvideo->subjects)) { + // Converting $ocvideo object to array. + $ocvideoarray = json_decode(json_encode($ocvideo), true); + // Get mediapackage json using api call. + $url = '/search/episode.json?id=' . $ocvideo->identifier; + $search_result = json_decode($api->oc_get($url), true); + if ($api->get_http_code() == 200 && isset($search_result['search-results']['result']['mediapackage'])) { + // Add mediapackage to array if exists. + $ocvideoarray['mediapackage'] = $search_result['search-results']['result']['mediapackage']; + } + $bbbocvideos[] = $ocvideoarray; + } + } + } + return $bbbocvideos; + } + + /** + * Helper function builds a row for the data used by the Opencast recording table. + * + * @param array $bbbsession + * @param array $ocrecording + * + * @return array + */ + public static function bigbluebuttonbn_get_opencast_recording_data_row($bbbsession, $ocrecording, $tools = ['edit', 'delete']) { + global $OUTPUT, $PAGE; + if (!self::bigbluebuttonbn_include_opencast_recording_table_row($bbbsession)) { + return; + } + $rowdata = new stdClass(); + // Set recording playback url. + $rowdata->playback = self::bigbluebuttonbn_get_opencast_recording_data_row_playback($ocrecording, $bbbsession); + // Set recording name from title if exists, otherwise shows "Opencast Video". + $rowdata->name = isset($ocrecording['title']) ? $ocrecording['title'] : get_string('view_recording_list_opencast', 'bigbluebuttonbn'); + // Set recording description. + $rowdata->description = isset($ocrecording['description']) ? $ocrecording['description'] : ''; + // For Opencast recording table to maintain the consistency, it checks if preview is enabled for the recording table. + if (recording::bigbluebuttonbn_get_recording_data_preview_enabled($bbbsession)) { + // Set recording_preview. + $rowdata->preview = self::bigbluebuttonbn_get_opencast_recording_data_row_preview($ocrecording); + } + // Set formatted date. + $rowdata->date = self::bigbluebuttonbn_get_opencast_recording_data_row_date_formatted($ocrecording); + // Set formatted duration. + $rowdata->duration = self::bigbluebuttonbn_get_opencast_recording_data_row_duration($ocrecording['duration']); + // Set actionbar, if user is allowed to manage recordings. + if ($bbbsession['managerecordings']) { + $rowdata->actionbar = self::bigbluebuttonbn_get_opencast_recording_data_row_actionbar($ocrecording, $bbbsession, $tools); + } + return $rowdata; + } + + /** + * Helper function evaluates if Opencast recording row should be included in the table. + * + * @param array $bbbsession + * + * @return boolean + */ + public static function bigbluebuttonbn_include_opencast_recording_table_row($bbbsession) { + // Administrators and moderators are always allowed. + if ($bbbsession['administrator'] || $bbbsession['moderator']) { + return true; + } + // When groups are enabled, exclude those to which the user doesn't have access to. + // Check if the record belongs to a Visible Group type. + list($course, $cm) = get_course_and_cm_from_cmid($bbbsession['cm']->id); + $groupmode = groups_get_activity_groupmode($cm); + $displayrow = true; + if (($groupmode != VISIBLEGROUPS)) { + $groupid = explode('[', $bbbsession['meetingid']); + if (isset($groupid[1])) { + // It is a group recording and the user is not moderator/administrator. Recording should not be included by default. + $displayrow = false; + $groupid = explode(']', $groupid[1]); + if (isset($groupid[0])) { + foreach ($usergroups as $usergroup) { + if ($usergroup->id == $groupid[0]) { + // Include recording if the user is in the same group. + $displayrow = true; + } + } + } + } + } + return $displayrow; + } + + /** + * Helper function renders the link used for Opencast recording playback in row for the data used by the recording table. + * To display the video, it is important for a video in Opencast to be published with engage-player, also it is required to + * have filter_opencast plugin installed and configured. + * The link redirects user to oc_view.php to authentificate the user via LTI and show the video in Opencast. + * + * @param array $ocrecording + * @param array $bbbsession + * + * @return string + */ + public static function bigbluebuttonbn_get_opencast_recording_data_row_playback($ocrecording, $bbbsession) { + global $CFG, $OUTPUT; + $text = get_string('view_recording_list_opencast', 'bigbluebuttonbn'); + $href = '#'; + // Check if the publication status has engage-player. + if (isset($ocrecording['publication_status']) && in_array('engage-player', $ocrecording['publication_status'])) { + // If filter_opencast is installed and configured, + // also if the LTI form handler JavaScript file in block_opencast is available to use in order to submit the LTI form. + if ((boolean) self::bigbluebuttonbn_check_opencast_filter() && file_exists("$CFG->dirroot/blocks/opencast/amd/src/block_lti_form_handler.js")) { + $href = $CFG->wwwroot . '/mod/bigbluebuttonbn/oc_player.php?identifier=' . $ocrecording['identifier'] . + '&bn=' . $bbbsession['bigbluebuttonbn']->id; + } + } + + $linkattributes = array( + 'id' => 'opencast-player-redirect-' . $ocrecording['identifier'], + 'class' => 'btn btn-sm btn-default', + 'target' => '_blank' + ); + if ($href == '#' || empty($href)) { + unset($linkattributes['target']); + $linkattributes['class'] = 'btn btn-sm btn-warning'; + $linkattributes['title'] = get_string('view_recording_format_error_opencast_unreachable', 'bigbluebuttonbn'); + } + return $OUTPUT->action_link($href, $text, null, $linkattributes) . ' '; + } + + /** + * Helper function builds Opencast recording preview used in row for the data used by the recording table. + * + * @param array $ocrecording + * + * @return string + */ + public static function bigbluebuttonbn_get_opencast_recording_data_row_preview($ocrecording) { + $options = array('id' => 'preview-' . $ocrecording['identifier']); + $recordingpreview = html_writer::start_tag('div', $options); + $imageurl = ''; + // Getting preview image from mediapackage attachments. + if (isset($ocrecording['mediapackage']['attachments']['attachment'])) { + foreach ($ocrecording['mediapackage']['attachments']['attachment'] as $attachment) { + // Looking for image only. + if (isset($attachment['mimetype']) && strpos($attachment['mimetype'], 'image') !== FALSE) { + // Looking for the url of the preview image. + if (empty($imageurl) && isset($attachment['type']) && isset($attachment['url'])) { + // There are several type of attachments which are different in size. + // More suitable sizes are of these types, respectively. + $suitabletypes = array('search', 'feed'); + foreach ($suitabletypes as $type) { + if (strpos($attachment['type'], $type) !== FALSE) { + $imageurl = $attachment['url']; + break; + } + } + } + } + if (!empty($imageurl)) { + break; + } + } + if (!empty($imageurl)) { + $recordingpreview .= self::bigbluebuttonbn_get_opencast_recording_data_row_preview_images($imageurl); + } + } + + $recordingpreview .= html_writer::end_tag('div'); + return $recordingpreview; + } + + /** + * Helper function builds element with actual images used in Opencast recording preview row based on a selected playback. + * + * @param string $imageurl + * + * @return string + */ + public static function bigbluebuttonbn_get_opencast_recording_data_row_preview_images($imageurl) { + global $CFG; + $recordingpreview = html_writer::start_tag('div', array('class' => 'container-fluid')); + $recordingpreview .= html_writer::start_tag('div', array('class' => 'row')); + $recordingpreview .= html_writer::start_tag('div', array('class' => '')); + $recordingpreview .= html_writer::empty_tag( + 'img', + array('src' => trim($imageurl) . '?' . time(), 'class' => 'recording-thumbnail pull-left') + ); + $recordingpreview .= html_writer::end_tag('div'); + $recordingpreview .= html_writer::end_tag('div'); + $recordingpreview .= html_writer::start_tag('div', array('class' => 'row')); + $recordingpreview .= html_writer::tag( + 'div', + get_string('view_recording_preview_help', 'bigbluebuttonbn'), + array('class' => 'text-center text-muted small') + ); + $recordingpreview .= html_writer::end_tag('div'); + $recordingpreview .= html_writer::end_tag('div'); + return $recordingpreview; + } + + /** + * Helper function format Opencast recording date used in row for the data used by the recording table. + * + * @param array $ocrecording + * + * @return string + */ + public static function bigbluebuttonbn_get_opencast_recording_data_row_date_formatted($ocrecording) { + $starttime_str = !empty($ocrecording['start']) ? $ocrecording['start'] : $ocrecording['created']; + return $starttime_str; + } + + /** + * Helper function converts Opencast recording duration used in row for the data used by the recording table. + * + * @param array $duration + * + * @return integer + */ + public static function bigbluebuttonbn_get_opencast_recording_data_row_duration($duration) { + if ($duration) { + // Convert the duration (in miliseconds) into Hours:Minutes:Seconds format + return gmdate('H:i:s', $duration / 1000); + } + return 0; + } + + /** + * Helper function builds Opencast recording actionbar used in row for the data used by the recording table. + * + * @param array $ocrecording + * @param array $bbbsession + * @param array $tools + * + * @return string + */ + public static function bigbluebuttonbn_get_opencast_recording_data_row_actionbar($ocrecording, $bbbsession, $tools) { + global $OUTPUT; + if (empty($ocrecording['identifier']) || empty($bbbsession['course'])) { + return ''; + } + $actionbar = ''; + $linkattributes = array( + 'target' => '_blank', + 'class' => 'btn btn-xs btn-danger' + ); + if (in_array('edit', $tools)) { + // Creating moodle url, to redirect to Opencast update metadata (Edit) page. + $opencastediturl = new moodle_url('/blocks/opencast/updatemetadata.php', + array('video_identifier' => $ocrecording['identifier'], 'courseid' => $bbbsession['course']->id)); + $linkattributes['id'] = 'opencast-edit-episode-' . $ocrecording['identifier']; + // Generating Action Link for Opencast update metadata (Edit). + $actionbar .= $OUTPUT->action_link($opencastediturl, get_string('edit'), null, $linkattributes) . ' '; + } + + if (in_array('delete', $tools)) { + // Creating moodle url, to redirect to Opencast delete event (Delete) page. + $opencastdeleteurl = new moodle_url('/blocks/opencast/deleteevent.php', + array('identifier' => $ocrecording['identifier'], 'courseid' => $bbbsession['course']->id)); + $linkattributes['id'] = 'opencast-delete-episode-' . $ocrecording['identifier']; + // Generating Action Link for Opencast delete event (Delete). + $actionbar .= $OUTPUT->action_link($opencastdeleteurl, get_string('delete'), null, $linkattributes) . ' '; + } + $head = html_writer::start_tag('div', array( + 'id' => 'recording-actionbar-' . $ocrecording['identifier'], + 'data-recordingid' => $ocrecording['identifier'], + 'data-meetingid' => $bbbsession['meetingid'])); + $tail = html_writer::end_tag('div'); + return $head . $actionbar . $tail; + } + + /** + * Helper function which checks if the Opencast Filter plugin (filter_opencast) is installed and configured. + * This function is used to display the BBB recordings on Opencast, which uses LTI to handle the authentication. + * + * @return boolean|array + */ + public static function bigbluebuttonbn_check_opencast_filter() { + $filterplugins = core_plugin_manager::instance()->get_plugins_of_type('filter'); + // If filter_opencast is installed. + if (in_array('opencast', array_keys($filterplugins))) { + // In order to display the videos through LTI consumerkey and consumersecret must be configured in filter_opencast. + $consumerkey = get_config('filter_opencast', 'consumerkey'); + $consumersecret = get_config('filter_opencast', 'consumersecret'); + + // Engageurl in filter_opencast plugin is the endpoint from which the Opencast player will be called. + $engageurl = get_config('filter_opencast', 'engageurl'); + if (empty($engageurl)) { + // If it is not set in filter_opencast, the main apiurl setting of tool_opencast plugin will be used. + $engageurl = get_config('tool_opencast', 'apiurl'); + } + + if (strpos($engageurl, 'http') !== 0) { + $engageurl = 'http://' . $engageurl; + } + + // A player url helps to predefine the endpoint to call the Opencast player directly. + $playerurl = get_config('filter_opencast', 'playerurl'); + if (empty($playerurl)) { + // If it is not configured, /play/ endpoint will make Opencast to decide based on its internal configuration. + $playerurl = '/play/'; + } else { + // If it is configured, then "id" query string is needed in any case. + $playerurl .= '?id='; + } + + // Make sure the player url is correct. + $playerurl = '/' . ltrim($playerurl, '/'); + + // Make lti url. + $ltiendpoint = rtrim($engageurl, '/') . '/lti'; + + if (!empty($consumerkey) && !empty($consumersecret) && !empty($playerurl) && !empty($ltiendpoint)) { + // If filter_opencast plugin is configured. + return array( + 'consumerkey' => $consumerkey, + 'consumersecret' => $consumersecret, + 'engageurl' => $engageurl, + 'playerurl' => $playerurl, + 'ltiendpoint' => $ltiendpoint + ); + } else { + // If filter_opencast plugin is NOT configured. + return false; + } + } + // If filter_opencast plugin is NOT installed. + return false; + } + + /** + * Create necessary lti parameters. + * @param array $opencastfilterconfig the array of customized configuration from bigbluebuttonbn_check_opencast_filter function. + * + * @return array lti parameters + * @throws dml_exception + * @throws moodle_exception + */ + function bigbluebuttonbn_create_lti_parameters_opencast($opencastfilterconfig) { + global $CFG, $COURSE, $USER; + + $endpoint = $opencastfilterconfig['ltiendpoint']; + $consumerkey = $opencastfilterconfig['consumerkey']; + $consumersecret = $opencastfilterconfig['consumersecret']; + $customtool = $opencastfilterconfig['playerurl']; + + $helper = new oauth_helper(array('oauth_consumer_key' => $consumerkey, + 'oauth_consumer_secret' => $consumersecret)); + + // Set all necessary parameters. + $params = array(); + $params['oauth_version'] = '1.0'; + $params['oauth_nonce'] = $helper->get_nonce(); + $params['oauth_timestamp'] = $helper->get_timestamp(); + $params['oauth_consumer_key'] = $consumerkey; + + $params['context_id'] = $COURSE->id; + $params['context_label'] = trim($COURSE->shortname); + $params['context_title'] = trim($COURSE->fullname); + $params['resource_link_id'] = 'o' . random_int(1000, 9999) . '-' . random_int(1000, 9999); + $params['resource_link_title'] = 'Opencast'; + $params['context_type'] = ($COURSE->format == 'site') ? 'Group' : 'CourseSection'; + $params['launch_presentation_locale'] = current_language(); + $params['ext_lms'] = 'moodle-2'; + $params['tool_consumer_info_product_family_code'] = 'moodle'; + $params['tool_consumer_info_version'] = strval($CFG->version); + $params['oauth_callback'] = 'about:blank'; + $params['lti_version'] = 'LTI-1p0'; + $params['lti_message_type'] = 'basic-lti-launch-request'; + $urlparts = parse_url($CFG->wwwroot); + $params['tool_consumer_instance_guid'] = $urlparts['host']; + $params['custom_tool'] = $customtool; + + // User data. + $params['user_id'] = $USER->id; + $params['lis_person_name_given'] = $USER->firstname; + $params['lis_person_name_family'] = $USER->lastname; + $params['lis_person_name_full'] = $USER->firstname . ' ' . $USER->lastname; + $params['ext_user_username'] = $USER->username; + $params['lis_person_contact_email_primary'] = $USER->email; + $params['roles'] = lti_get_ims_role($USER, null, $COURSE->id, false); + + if (!empty($CFG->mod_lti_institution_name)) { + $params['tool_consumer_instance_name'] = trim(html_to_text($CFG->mod_lti_institution_name, 0)); + } else { + $params['tool_consumer_instance_name'] = get_site()->shortname; + } + + $params['launch_presentation_document_target'] = 'iframe'; + $params['oauth_signature_method'] = 'HMAC-SHA1'; + $signedparams = lti_sign_parameters($params, $endpoint, "POST", $consumerkey, $consumersecret); + $params['oauth_signature'] = $signedparams['oauth_signature']; + + return $params; + } +} diff --git a/classes/local/settings/settings.php b/classes/local/settings/settings.php index e782006ea..d0f2e051b 100644 --- a/classes/local/settings/settings.php +++ b/classes/local/settings/settings.php @@ -1076,4 +1076,44 @@ public function bigbluebuttonbn_settings_experimental() { } $this->admin->add($this->section, $experimentalfeaturessetting); } + + /** + * Helper function renders Opencast integration settings if block_opencast is installed. + * + * + * @return void + */ + function bigbluebuttonbn_settings_opencast_integration() { + // Configuration for 'Opencast integration' feature when Opencast plugins are installed. + // Through validator::section_opencast_shown(), it checks if the block_opencast is installed. + $opencastrecordingsetting = new admin_settingpage('opencast', get_string('config_opencast', 'bigbluebuttonbn'), + 'moodle/site:config', !((boolean) validator::section_opencast_shown()) && ($this->moduleenabled)); + if ($this->admin->fulltree) { + $item = new admin_setting_heading('bigbluebuttonbn_config_opencast_recording', + '', + get_string('config_opencast_description', 'bigbluebuttonbn')); + $opencastrecordingsetting->add($item); + $item = new admin_setting_configcheckbox('bigbluebuttonbn_opencast_recording', + get_string('config_opencast_recording', 'bigbluebuttonbn'), + get_string('config_opencast_recording_description', 'bigbluebuttonbn'), + 1); + $this->add_conditional_element( + 'opencast_recording', + $item, + $opencastrecordingsetting + ); + $item = new admin_setting_configcheckbox('bigbluebuttonbn_opencast_show_recording', + get_string('config_opencast_show_recording', 'bigbluebuttonbn'), + get_string('config_opencast_show_recording_description', 'bigbluebuttonbn'), + 1); + $this->add_conditional_element( + 'opencast_show_recording', + $item, + $opencastrecordingsetting + ); + + } + $this->admin->add($this->section, $opencastrecordingsetting); + } + } \ No newline at end of file diff --git a/classes/local/settings/validator.php b/classes/local/settings/validator.php index a5fcfe76c..a4b61fb65 100644 --- a/classes/local/settings/validator.php +++ b/classes/local/settings/validator.php @@ -26,6 +26,7 @@ namespace mod_bigbluebuttonbn\local\settings; use mod_bigbluebuttonbn\local\bigbluebutton; +use mod_bigbluebuttonbn\local\helpers\opencast; defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir.'/adminlib.php'); @@ -279,4 +280,15 @@ public static function section_lockonjoinconfigurable_shown() { return (!isset($CFG->bigbluebuttonbn['lockonjoinconfigurable_default']) || !isset($CFG->bigbluebuttonbn['lockonjoinconfigurable_editable'])); } + + /** + * Validate if opencast section will be shown. + * It uses the opencast::bigbluebuttonbn_check_opencast() without courseid, in order to check only if block_opencast is installed. + * + * @return boolean + */ + public static function section_opencast_shown() { + //if the block_opencast is installed. + return opencast::bigbluebuttonbn_check_opencast(); + } } diff --git a/classes/local/view.php b/classes/local/view.php index 1ea749965..e26b24d1b 100644 --- a/classes/local/view.php +++ b/classes/local/view.php @@ -32,6 +32,7 @@ use mod_bigbluebuttonbn\local\helpers\meeting; use mod_bigbluebuttonbn\local\helpers\recording; use mod_bigbluebuttonbn\output\recordings_session; +use mod_bigbluebuttonbn\output\opencast_recordings_session; use mod_bigbluebuttonbn\plugin; use pix_icon; @@ -159,6 +160,11 @@ public static function view_render(&$bbbsession) { $output .= $renderer->render($recordingsection); + if (config::get('opencast_show_recording')) { + $opencastrecordingsection = new opencast_recordings_session($bbbsession, $type, $enabledfeatures); + $output .= $renderer->render($opencastrecordingsection); + } + } else if ($type == bbb_constants::BIGBLUEBUTTONBN_TYPE_RECORDING_ONLY) { $recordingsdisabled = get_string('view_message_recordings_disabled', 'bigbluebuttonbn'); $output .= self::bigbluebuttonbn_render_warning($recordingsdisabled, 'danger'); diff --git a/classes/output/opencast_recordings_session.php b/classes/output/opencast_recordings_session.php new file mode 100644 index 000000000..b48866029 --- /dev/null +++ b/classes/output/opencast_recordings_session.php @@ -0,0 +1,96 @@ +. + +/** + * Renderer for Opencast recording section. + * + * @package mod_bigbluebuttonbn + * @copyright 2010 onwards, Blindside Networks Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Farbod Zamani (zamani [at] elan-ev [dt] de) + */ + +namespace mod_bigbluebuttonbn\output; + +use mod_bigbluebuttonbn\local\bbb_constants; +use moodle_url; +use renderable; +use renderer_base; +use stdClass; +use templatable; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class opencast_recordings_session + * + * @copyright 2010 onwards, Blindside Networks Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Farbod Zamani (zamani [at] elan-ev [dt] de) + */ +class opencast_recordings_session implements renderable, templatable { + + /** + * @var $bbbsession + */ + protected $bbbsession; + /** + * @var $type + */ + protected $type; + /** + * @var mixed|null $enabledfeatures + */ + protected $enabledfeatures; + + /** + * recording_section constructor. + * + * @param array $bbbsession + * @param string $type + * @param array $enabledfeatures + */ + public function __construct($bbbsession, $type, $enabledfeatures = null) { + + $this->bbbsession = $bbbsession; + $this->type = $type; + $this->enabledfeatures = $enabledfeatures; + } + + /** + * Export for template + * + * @param renderer_base $output + * @return array|stdClass|void + * @throws \coding_exception + * @throws \moodle_exception + */ + public function export_for_template(renderer_base $output) { + + $bbbid = $this->bbbsession['bigbluebuttonbn']->id; + $hasrecordings = $this->bbbsession['record']; + $hasrecordings = $hasrecordings && + (in_array($this->type, [bbb_constants::BIGBLUEBUTTONBN_TYPE_ALL, + bbb_constants::BIGBLUEBUTTONBN_TYPE_RECORDING_ONLY])); + + $context = (object) + [ + 'has_recordings' => $hasrecordings, + 'bbbid' => intval($bbbid) + ]; + return $context; + } +} \ No newline at end of file diff --git a/db/services.php b/db/services.php index 570477b16..f010dcbee 100644 --- a/db/services.php +++ b/db/services.php @@ -92,4 +92,12 @@ 'ajax' => true, 'capabilities' => 'mod/bigbluebuttonbn:view', ), + 'mod_bigbluebutton_opencast_recording_list_table' => array( + 'classname' => 'mod_bigbluebuttonbn\external\get_opencast_recordings', + 'methodname' => 'execute', + 'description' => 'Returns a list of Opencast recordings ready to be processed by a datatable.', + 'type' => 'read', + 'ajax' => true, + 'capabilities' => 'mod/bigbluebuttonbn:view', + ), ); diff --git a/lang/en/bigbluebuttonbn.php b/lang/en/bigbluebuttonbn.php index c4a86612c..f51e9b3f4 100644 --- a/lang/en/bigbluebuttonbn.php +++ b/lang/en/bigbluebuttonbn.php @@ -110,6 +110,12 @@ $string['config_shared_secret'] = 'BigBlueButton Shared Secret'; $string['config_shared_secret_description'] = 'The security salt of your BigBlueButton server. (This default salt is for a BigBlueButton server provided by Blindside Networks that you can use for testing.)'; +$string['config_opencast'] = 'Configuration for "Opencast integration" feature'; +$string['config_opencast_description'] = 'These settings control the integration of BBB meeting recordings and Opencast'; +$string['config_opencast_recording'] = 'Send Opencast series ID to BBB'; +$string['config_opencast_recording_description'] = 'If this setting is enabled, Moodle will send the Opencast series ID of the course to BBB alongside the BBB metadata whenever a new BBB meeting is initiated. If there is not an Opencast series ID for the course yet, a new series will be created on-the-fly. This way, Opencast can be used to process BBB recordings and to publish them in the course\'s series.'; +$string['config_opencast_show_recording'] = 'Show Opencast recordings'; +$string['config_opencast_show_recording_description'] = 'If enabled the recording table will also include the Opencast recordings.'; $string['config_recording'] = 'Configuration for "Record meeting" feature'; $string['config_recording_description'] = 'These settings are feature specific'; $string['config_recording_default'] = 'Recording feature enabled by default'; diff --git a/oc_player.php b/oc_player.php index dea0c2e7a..e8df6e17d 100644 --- a/oc_player.php +++ b/oc_player.php @@ -23,22 +23,30 @@ * @author 2021 Farbod Zamani Boroujeni - ELAN e.V. */ -global $PAGE, $OUTPUT; +use mod_bigbluebuttonbn\local\helpers\opencast; +use mod_bigbluebuttonbn\local\view; +use mod_bigbluebuttonbn\plugin; +use context_module; +use moodle_url; require_once(dirname(dirname(dirname(__FILE__))).'/config.php'); require_once(dirname(__FILE__).'/locallib.php'); +global $PAGE, $OUTPUT, $CFG; + +require_once($CFG->dirroot . '/mod/lti/locallib.php'); +require_once($CFG->dirroot . '/lib/oauthlib.php'); $identifier = required_param('identifier', PARAM_TEXT); $bn = optional_param('bn', 0, PARAM_INT); -$bbbviewinstance = bigbluebuttonbn_view_validator(null, $bn); +$bbbviewinstance = view::bigbluebuttonbn_view_validator(null, $bn); if (!$bbbviewinstance) { print_error(get_string('view_error_url_missing_parameters', 'bigbluebuttonbn')); } // Get configs from filter_opencast -$opencastfilterconfig = bigbluebuttonbn_check_opencast_filter(); +$opencastfilterconfig = opencast::bigbluebuttonbn_check_opencast_filter(); if (!$opencastfilterconfig) { print_error(get_string('view_error_missing_filter_opencast_config', 'bigbluebuttonbn')); } @@ -64,7 +72,7 @@ $opencastfilterconfig['playerurl'] .= $identifier; // Create LTI parameters. -$params = bigbluebuttonbn_create_lti_parameters_opencast($opencastfilterconfig); +$params = opencast::bigbluebuttonbn_create_lti_parameters_opencast($opencastfilterconfig); // Using block_opencast renderer in order to use render_lti_form function. $opencastrenderer = $PAGE->get_renderer('block_opencast'); @@ -78,4 +86,3 @@ // Use block_opencast LTI form handler javascript to submit the lti form. $PAGE->requires->js_call_amd('block_opencast/block_lti_form_handler', 'init'); echo $OUTPUT->footer(); - diff --git a/settings.php b/settings.php index 3b7a9c701..87c023074 100644 --- a/settings.php +++ b/settings.php @@ -42,6 +42,8 @@ $bbbsettings->bigbluebuttonbn_settings_importrecordings(); // Renders settings for showing recordings. $bbbsettings->bigbluebuttonbn_settings_showrecordings(); +// Renders settings for Opencast integration. +$bbbsettings->bigbluebuttonbn_settings_opencast_integration(); // Renders settings for meetings. $bbbsettings->bigbluebuttonbn_settings_waitmoderator(); diff --git a/templates/opencast_recordings_session.mustache b/templates/opencast_recordings_session.mustache new file mode 100644 index 000000000..b1c192399 --- /dev/null +++ b/templates/opencast_recordings_session.mustache @@ -0,0 +1,33 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template mod_bigbluebuttonbn/opencast_recordings_session + + This template renders the mobile page. + + Example context (json): + { + 'bbbid' : 3 + 'has_records': true, + } +}} + +
+ {{< mod_bigbluebuttonbn/opencast_recordings_table}} + {{$title}}{{#str}}view_section_title_opencast_recordings, mod_bigbluebuttonbn{{/str}}{{/title}} + {{/mod_bigbluebuttonbn/opencast_recordings_table}} +
diff --git a/templates/opencast_recordings_table.mustache b/templates/opencast_recordings_table.mustache new file mode 100644 index 000000000..2c865253a --- /dev/null +++ b/templates/opencast_recordings_table.mustache @@ -0,0 +1,50 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template mod_bigbluebuttonbn/opencast_recordings_table + + This template renders the mobile page. + + Example context (json): + { + 'bbbid' : 3 + 'has_records': true, + 'search_input' : {... search input data ...} + } +}} +
+ {{#has_recordings}} +
+

{{$title}}{{/title}}

+
+
+
+
+ {{#js}} + require(['mod_bigbluebuttonbn/opencastrecordings'], function(opencastrecordings) { + opencastrecordings.init('#bigbluebuttonbn-opencast-recording-table-{{uniqid}}'); + }); + require(['core/inplace_editable']); + {{/js}} + {{/has_recordings}} + {{^has_recordings}} +
{{#str}}view_message_opencast_norecordings, mod_bigbluebuttonbn{{/str}}
+ {{/has_recordings}} +
diff --git a/version.php b/version.php index 5ac4d5462..2ec6c0fe1 100644 --- a/version.php +++ b/version.php @@ -26,7 +26,7 @@ defined('MOODLE_INTERNAL') || die; -$plugin->version = 2020101001; +$plugin->version = 2021052101; $plugin->requires = 2020061500; $plugin->cron = 0; $plugin->component = 'mod_bigbluebuttonbn';