first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
@@ -0,0 +1,47 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Activity base class.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
namespace mod_bigbluebuttonbn\analytics\indicator;
use core_analytics\local\indicator\community_of_inquiry_activity;
/**
* Activity base class.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class activity_base extends community_of_inquiry_activity {
/**
* No need to fetch grades for resources.
*
* @return bool
*/
public function feedback_check_grades() {
// BigBlueButtonBN's feedback is not contained in grades.
return false;
}
}
@@ -0,0 +1,70 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Cognitive depth indicator - BigBlueButtonBN.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
namespace mod_bigbluebuttonbn\analytics\indicator;
use cm_info;
use lang_string;
/**
* Cognitive depth indicator - bigbluebuttonbn.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cognitive_depth extends activity_base {
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return lang_string
*/
public static function get_name(): lang_string {
return new lang_string('indicator:cognitivedepth', 'mod_bigbluebuttonbn');
}
/**
* Returns the indicator type.
*
* @return string
*/
public function get_indicator_type() {
return self::INDICATOR_COGNITIVE;
}
/**
* Returns the cognitive depth level.
*
* @param cm_info $cm
*
* @return int
*/
public function get_cognitive_depth_level(cm_info $cm) {
return self::COGNITIVE_LEVEL_4;
}
}
@@ -0,0 +1,70 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Social breadth indicator - BigBlueButtonBN.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
namespace mod_bigbluebuttonbn\analytics\indicator;
use cm_info;
use lang_string;
/**
* Social breadth indicator - BigBlueButtonBN.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class social_breadth extends activity_base {
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return lang_string
*/
public static function get_name(): lang_string {
return new lang_string('indicator:socialbreadth', 'mod_bigbluebuttonbn');
}
/**
* Returns the indicator type.
*
* @return string
*/
public function get_indicator_type() {
return self::INDICATOR_SOCIAL;
}
/**
* Returns the social breadth level.
*
* @param cm_info $cm
*
* @return int
*/
public function get_social_breadth_level(cm_info $cm) {
return self::SOCIAL_LEVEL_1;
}
}
+193
View File
@@ -0,0 +1,193 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn;
use Exception;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use mod_bigbluebuttonbn\local\config;
/**
* The broker routines
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
class broker {
/** @var array List of required params */
protected $requiredparams = [
'recording_ready' => [
'bigbluebuttonbn' => 'The BigBlueButtonBN instance ID must be specified.',
'signed_parameters' => 'A JWT encoded string must be included as [signed_parameters].'
],
'meeting_events' => [
'bigbluebuttonbn' => 'The BigBlueButtonBN instance ID must be specified.'
],
];
/**
* Validate the supplied list of parameters, providing feedback about any missing or incorrect values.
*
* @param array $params
* @return null|string
*/
public function validate_parameters(array $params): ?string {
if (!isset($params['action']) || empty($params['action']) ) {
return 'Parameter ['.$params['action'].'] was not included';
}
$action = strtolower($params['action']);
if (!array_key_exists($action, $this->requiredparams)) {
return "Action {$params['action']} can not be performed.";
}
return $this->validate_parameters_message($params, $this->requiredparams[$action]);
}
/**
* Check whether the specified parameter is valid.
*
* @param array $params
* @param array $requiredparams
* @return null|string
*/
protected static function validate_parameters_message(array $params, array $requiredparams): ?string {
foreach ($requiredparams as $param => $message) {
if (!array_key_exists($param, $params) || $params[$param] == '') {
return $message;
}
}
// Everything is valid.
return null;
}
/**
* Helper for responding when recording ready is performed.
*
* @param instance $instance
* @param array $params
*/
public static function process_recording_ready(instance $instance, array $params): void {
// Decodes the received JWT string.
try {
$decodedparameters = JWT::decode(
$params['signed_parameters'],
new Key(config::get('shared_secret'), 'HS256')
);
} catch (Exception $e) {
$error = 'Caught exception: ' . $e->getMessage();
header('HTTP/1.0 400 Bad Request. ' . $error);
return;
}
// Validations.
if (!isset($decodedparameters->record_id)) {
header('HTTP/1.0 400 Bad request. Missing record_id parameter');
return;
}
$recording = recording::get_record(['recordingid' => $decodedparameters->record_id]);
if (!isset($recording)) {
header('HTTP/1.0 400 Bad request. Invalid record_id');
return;
}
// Sends the messages.
try {
// We make sure messages are sent only once.
if ($recording->get('status') != recording::RECORDING_STATUS_NOTIFIED) {
$task = new \mod_bigbluebuttonbn\task\send_recording_ready_notification();
$task->set_instance_id($instance->get_instance_id());
\core\task\manager::queue_adhoc_task($task);
$recording->set('status', recording::RECORDING_STATUS_NOTIFIED);
$recording->update();
}
header('HTTP/1.0 202 Accepted');
} catch (Exception $e) {
$error = 'Caught exception: ' . $e->getMessage();
header('HTTP/1.0 503 Service Unavailable. ' . $error);
}
}
/**
* Process meeting events for instance with provided HTTP headers.
*
* @param instance $instance
* @return void
*/
public static function process_meeting_events(instance $instance) {
try {
// Get the HTTP headers.
$authorization = self::get_authorization_token();
// Pull the Bearer from the headers.
if (empty($authorization)) {
$msg = 'Authorization failed';
header('HTTP/1.0 400 Bad Request. ' . $msg);
return;
}
// Verify the authenticity of the request.
$token = \Firebase\JWT\JWT::decode(
$authorization[1],
new Key(config::get('shared_secret'), 'HS512')
);
// Get JSON string from the body.
$jsonstr = file_get_contents('php://input');
// Convert JSON string to a JSON object.
$jsonobj = json_decode($jsonstr);
$headermsg = meeting::meeting_events($instance, $jsonobj);
header($headermsg);
} catch (Exception $e) {
$msg = 'Caught exception: ' . $e->getMessage();
header('HTTP/1.0 400 Bad Request. ' . $msg);
}
}
/**
* Get authorisation token
*
* We could use getallheaders but this is only compatible with apache types of servers
* some explanations and examples here: https://www.php.net/manual/en/function.getallheaders.php#127190
*
* @return array|null an array composed of the Authorization token provided in the header.
*/
private static function get_authorization_token(): ?array {
$autorization = null;
if (isset($_SERVER['Authorization'])) {
$autorization = trim($_SERVER["Authorization"]);
} else if (isset($_SERVER['HTTP_AUTHORIZATION'])) {
$autorization = trim($_SERVER["HTTP_AUTHORIZATION"]);
} else if (function_exists('apache_request_headers')) {
$requestheaders = apache_request_headers();
$requestheaders = array_combine(array_map('ucwords',
array_keys($requestheaders)), array_values($requestheaders));
if (isset($requestheaders['Authorization'])) {
$autorization = trim($requestheaders['Authorization']);
}
}
return empty($autorization) ? null : explode(" ", $autorization);
}
}
@@ -0,0 +1,328 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\completion;
use cm_info;
use core_completion\activity_custom_completion;
use mod_bigbluebuttonbn\extension;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\logger;
use moodle_exception;
use stdClass;
/**
* Class custom_completion
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent@call-learning.fr)
*/
class custom_completion extends activity_custom_completion {
/**
* Filters for logs
*/
const FILTERS = [
'completionattendance' => [logger::EVENT_SUMMARY],
'completionengagementchats' => [logger::EVENT_SUMMARY],
'completionengagementtalks' => [logger::EVENT_SUMMARY],
'completionengagementraisehand' => [logger::EVENT_SUMMARY],
'completionengagementpollvotes' => [logger::EVENT_SUMMARY],
'completionengagementemojis' => [logger::EVENT_SUMMARY],
];
/**
* @var array $completionaddons array of extension class for the completion
*/
private $completionaddons;
/**
* activity_custom_completion constructor.
*
* @param cm_info $cm
* @param int $userid
* @param array|null $completionstate The current state of the core completion criteria
*/
public function __construct(cm_info $cm, int $userid, ?array $completionstate = null) {
parent::__construct($cm, $userid, $completionstate);
$completionaddonsclasses = extension::custom_completion_addons_instances($cm, $userid, $completionstate);
$this->completionaddons = array_map(function($targetclassname) use ($cm, $userid, $completionstate) {
return new $targetclassname($cm, $userid, $completionstate);
}, $completionaddonsclasses);
}
/**
* Get current state
*
* @param string $rule
* @return int
*/
public function get_state(string $rule): int {
// Get instance details.
$instance = instance::get_from_cmid($this->cm->id);
if (empty($instance)) {
throw new moodle_exception("Can't find bigbluebuttonbn instance {$this->cm->instance}");
}
// Default return value.
$returnedvalue = COMPLETION_INCOMPLETE;
$filters = self::FILTERS[$rule] ?? [logger::EVENT_SUMMARY];
$logs = logger::get_user_completion_logs($instance, $this->userid, $filters);
if (method_exists($this, "get_{$rule}_value")) {
$completionvalue = $this->aggregate_values($logs, self::class . "::get_{$rule}_value");
if ($completionvalue) {
// So in this case we check the value set in the module setting. If we go over the threshold, then
// this is complete.
$rulevalue = $instance->get_instance_var($rule);
if (!is_null($rulevalue)) {
if ($rulevalue <= $completionvalue) {
$returnedvalue = COMPLETION_COMPLETE;
}
} else {
// If there is at least a hit, we consider it as complete.
$returnedvalue = $completionvalue ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE;
}
}
}
// Check for any completion for this rule in addons / extensions.
foreach ($this->completionaddons as $customcompletion) {
if (in_array($rule, $customcompletion->get_defined_custom_rules())) {
$returnedvalue = $returnedvalue || $customcompletion->get_state($rule);
}
}
return $returnedvalue;
}
/**
* Compute current state from logs.
*
* @param array $logs
* @param callable $logvaluegetter
* @return int the sum of all values for this particular event (it can be a duration or a number of hits)
*/
protected function aggregate_values(array $logs, callable $logvaluegetter): int {
if (empty($logs)) {
// As completion by engagement with $rulename hand was required, the activity hasn't been completed.
return 0;
}
$value = 0;
foreach ($logs as $log) {
$value += $logvaluegetter($log);
}
return $value;
}
/**
* Fetch the list of custom completion rules that this module defines.
*
* @return array
*/
public static function get_defined_custom_rules(): array {
$rules = [
'completionattendance',
'completionengagementchats',
'completionengagementtalks',
'completionengagementraisehand',
'completionengagementpollvotes',
'completionengagementemojis',
];
$completionaddonsclasses = extension::custom_completion_addons_classes();
foreach ($completionaddonsclasses as $customcompletion) {
$rules = array_merge($rules, $customcompletion::get_defined_custom_rules());
}
return $rules;
}
/**
* Returns an associative array of the descriptions of custom completion rules.
*
* @return array
*/
public function get_custom_rule_descriptions(): array {
$completionengagementchats = $this->cm->customdata['customcompletionrules']['completionengagementchats'] ?? 1;
$completionengagementtalks = $this->cm->customdata['customcompletionrules']['completionengagementtalks'] ?? 1;
$completionengagementraisehand = $this->cm->customdata['customcompletionrules']['completionengagementraisehand'] ?? 1;
$completionengagementpollvotes = $this->cm->customdata['customcompletionrules']['completionengagementpollvotes'] ?? 1;
$completionengagementemojis = $this->cm->customdata['customcompletionrules']['completionengagementemojis'] ?? 1;
$completionattendance = $this->cm->customdata['customcompletionrules']['completionattendance'] ?? 1;
$descriptions = [
'completionengagementchats' => get_string('completionengagementchats_desc', 'mod_bigbluebuttonbn',
$completionengagementchats),
'completionengagementtalks' => get_string('completionengagementtalks_desc', 'mod_bigbluebuttonbn',
$completionengagementtalks),
'completionengagementraisehand' => get_string('completionengagementraisehand_desc', 'mod_bigbluebuttonbn',
$completionengagementraisehand),
'completionengagementpollvotes' => get_string('completionengagementpollvotes_desc', 'mod_bigbluebuttonbn',
$completionengagementpollvotes),
'completionengagementemojis' => get_string('completionengagementemojis_desc', 'mod_bigbluebuttonbn',
$completionengagementemojis),
'completionattendance' => get_string('completionattendance_desc', 'mod_bigbluebuttonbn',
$completionattendance),
];
// Check for any completion for this rule in addons / extensions.
foreach ($this->completionaddons as $customcompletion) {
$descriptions = array_merge($descriptions, $customcompletion->get_custom_rule_descriptions());
}
return $descriptions;
}
/**
* Returns an array of all completion rules, in the order they should be displayed to users.
*
* @return array
*/
public function get_sort_order(): array {
$rules = self::get_defined_custom_rules();
array_unshift($rules, 'completionview');
return $rules;
}
/**
* Get current states of completion in a human-friendly version
*
* @return string[]
*/
public function get_printable_states(): array {
$result = [];
foreach ($this->get_available_custom_rules() as $rule) {
$result[] = $this->get_printable_state($rule);
}
return $result;
}
/**
* Get current states of completion for a rule in a human-friendly version
*
* @param string $rule
* @return string
*/
private function get_printable_state(string $rule): string {
// Get instance details.
$instance = instance::get_from_cmid($this->cm->id);
if (empty($instance)) {
throw new moodle_exception("Can't find bigbluebuttonbn instance {$this->cm->instance}");
}
$summary = "";
$filters = self::FILTERS[$rule] ?? [logger::EVENT_SUMMARY];
$logs = logger::get_user_completion_logs($instance, $this->userid, $filters);
if (method_exists($this, "get_{$rule}_value")) {
$summary = get_string(
$rule . '_event_desc',
'mod_bigbluebuttonbn',
$this->aggregate_values($logs, self::class . "::get_{$rule}_value")
);
}
return $summary;
}
/**
* Get current state in a friendly version
*
* @param string $rule
* @return string
*/
public function get_last_log_timestamp(string $rule): string {
// Get instance details.
$instance = instance::get_from_cmid($this->cm->id);
if (empty($instance)) {
throw new moodle_exception("Can't find bigbluebuttonbn instance {$this->cm->instance}");
}
$filters = self::FILTERS[$rule] ?? [logger::EVENT_SUMMARY];
return logger::get_user_completion_logs_max_timestamp($instance, $this->userid, $filters);
}
/**
* Get attendance summary value
*
* @param stdClass $log
* @return int
*/
protected static function get_completionattendance_value(stdClass $log): int {
$summary = json_decode($log->meta);
return empty($summary->data->duration) ? 0 : (int)($summary->data->duration / 60);
}
/**
* Get general completion engagement value
*
* @param stdClass $log
* @return int
*/
protected static function get_completionengagementchats_value(stdClass $log): int {
return self::get_completionengagement_value($log, 'chats');
}
/**
* Get general completion engagement value
*
* @param stdClass $log
* @return int
*/
protected static function get_completionengagementtalks_value(stdClass $log): int {
return self::get_completionengagement_value($log, 'talks');
}
/**
* Get general completion engagement value
*
* @param stdClass $log
* @return int
*/
protected static function get_completionengagementraisehand_value(stdClass $log): int {
return self::get_completionengagement_value($log, 'raisehand');
}
/**
* Get general completion engagement value
*
* @param stdClass $log
* @return int
*/
protected static function get_completionengagementpollvotes_value(stdClass $log): int {
return self::get_completionengagement_value($log, 'poll_votes');
}
/**
* Get general completion engagement value
*
* @param stdClass $log
* @return int
*/
protected static function get_completionengagementemojis_value(stdClass $log): int {
return self::get_completionengagement_value($log, 'emojis');
}
/**
* Get general completion engagement value
*
* @param stdClass $log
* @param string $type
* @return int
*/
protected static function get_completionengagement_value(stdClass $log, string $type): int {
$summary = json_decode($log->meta);
return intval($summary->data->engagement->$type ?? 0);
}
}
@@ -0,0 +1,56 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn activity management viewed event.
*
* @package mod_bigbluebuttonbn
* @copyright 2010-2017 Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v2 or later
*/
class activity_management_viewed extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_PARTICIPATING) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' viewed the bigbluebuttonbn activity management page for " .
"the course module id '##contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return 'BigBlueButtonBN activity management viewed';
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
+108
View File
@@ -0,0 +1,108 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\event;
use coding_exception;
use moodle_url;
/**
* The mod_bigbluebuttonbn abstract base event class. Most mod_bigbluebuttonbn events can extend this class.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class base extends \core\event\base {
/**
* Object Id Mapping.
*
* @var array
*/
protected static $objectidmapping = ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
/** @var $bigbluebuttonbn */
protected $bigbluebuttonbn;
/**
* Description.
*
* @var string
*/
protected $description;
/**
* Legacy log data.
*
* @var array
*/
protected $legacylogdata;
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
$vars = [
'userid' => $this->userid,
'courseid' => $this->courseid,
'objectid' => $this->objectid,
'contextinstanceid' => $this->contextinstanceid,
'other' => $this->other
];
$string = $this->description;
foreach ($vars as $key => $value) {
$string = str_replace("##" . $key, $value, $string);
}
return $string;
}
/**
* Returns relevant URL.
*
* @return moodle_url
*/
public function get_url() {
return new moodle_url('/mod/bigbluebuttonbn/view.php', ['id' => $this->contextinstanceid]);
}
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_PARTICIPATING) {
$this->data['crud'] = $crud;
$this->data['edulevel'] = $edulevel;
$this->data['objecttable'] = 'bigbluebuttonbn';
}
/**
* Custom validation.
*
* @throws coding_exception
*/
protected function validate_data() {
parent::validate_data();
if ($this->contextlevel != CONTEXT_MODULE) {
throw new coding_exception('Context level must be CONTEXT_MODULE.');
}
}
}
@@ -0,0 +1,45 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn activity viewed event.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_module_viewed extends \core\event\course_module_viewed {
/**
* Init method.
*
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'bigbluebuttonbn';
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,52 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn class for event name definition.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class events {
/**
* Event name matcher.
*
* @var $events
*/
public static $events = [
'create' => 'activity_created',
'view' => 'course_module_viewed',
'update' => 'activity_updated',
'delete' => 'activity_deleted',
'meeting_create' => 'meeting_created',
'meeting_end' => 'meeting_ended',
'meeting_join' => 'meeting_joined',
'meeting_left' => 'meeting_left',
'recording_delete' => 'recording_deleted',
'recording_import' => 'recording_imported',
'recording_protect' => 'recording_protected',
'recording_publish' => 'recording_published',
'recording_unprotect' => 'recording_unprotected',
'recording_unpublish' => 'recording_unpublished',
'recording_edit' => 'recording_edited',
'recording_play' => 'recording_viewed',
'live_session' => 'live_session'
];
}
@@ -0,0 +1,57 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn live_session (Experimental: for being triggered when external events are received).
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class live_session_event extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_OTHER) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' triggered action ##other in a " .
"bigbluebutton meeting for the bigbluebuttonbn activity with id " .
"'##objectid' for the course id '##courseid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_live_session', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,56 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn meeting created event, triggered when the meeting is created before join.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class meeting_created extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_OTHER) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' created a bigbluebutton meeting for " .
"the bigbluebuttonbn activity with id '##objectid' for the course id '##courseid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_meeting_created', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,58 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn meeting ended event, triggered when the meeting is ended by the user.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class meeting_ended extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_OTHER) {
parent::init($crud, $edulevel);
$this->description = "A bigbluebutton meeting for the bigbluebuttonbn activity with id " .
"'##objectid' for the course id '##courseid' has been forcibly " .
"ended by the user with id '##userid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_meeting_ended', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,57 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn meeting joined event, triggered when the user joins the session.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class meeting_joined extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_PARTICIPATING) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' has joined a bigbluebutton meeting for " .
"the bigbluebuttonbn activity with id '##objectid' for the course id " .
"'##courseid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_meeting_joined', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,57 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn meeting left event, triggered when the user lefts the meeting using the logout button.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class meeting_left extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_PARTICIPATING) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' has left a bigbluebutton meeting for " .
"the bigbluebuttonbn activity with id '##objectid' for the course id " .
"'##courseid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_meeting_left', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,56 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn recording deleted event.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class recording_deleted extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_OTHER) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' has deleted a recording with id " .
"'##other' from the course id '##courseid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_recording_deleted', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,56 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn recording edited event.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class recording_edited extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_OTHER) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' has edited a recording with id " .
"'##other' in the course id '##courseid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_recording_edited', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,56 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn recording imported event.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class recording_imported extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_OTHER) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' has imported a recording with id " .
"'##other' in the course id '##courseid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_recording_imported', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,55 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn recording protected event.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class recording_protected extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_OTHER) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' has protected a recording with id " .
"'##other' in the course id '##courseid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_recording_protected', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,56 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn recording published event.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class recording_published extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_OTHER) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' has published a recording with id " .
"'##other' in the course id '##courseid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_recording_published', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,56 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn recording unprotected event.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class recording_unprotected extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_OTHER) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' has unprotected a recording with id " .
"'##other' in the course id '##courseid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_recording_unprotected', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,56 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn recording unpublished event.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class recording_unpublished extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_OTHER) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' has unpublished a recording with id " .
"'##other' in the course id '##courseid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_recording_unpublished', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,56 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn recording viewed event.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class recording_viewed extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_OTHER) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' has viewed a recording with id " .
"'##other' from the course id '##courseid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_recording_viewed', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
+220
View File
@@ -0,0 +1,220 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn;
use cache;
use cm_info;
use mod_bigbluebuttonbn\local\extension\action_url_addons;
use mod_bigbluebuttonbn\local\extension\custom_completion_addons;
use mod_bigbluebuttonbn\local\extension\mod_form_addons;
use mod_bigbluebuttonbn\local\extension\mod_instance_helper;
use stdClass;
use core_plugin_manager;
/**
* Generic subplugin management helper
*
* @package mod_bigbluebuttonbn
* @copyright 2023 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent@call-learning.fr)
*/
class extension {
/**
* Plugin name for extension
*/
const BBB_EXTENSION_PLUGIN_NAME = 'bbbext';
/**
* Invoke a subplugin hook that will return additional parameters
*
* @param string $action
* @param array $data
* @param array $metadata
* @param int|null $instanceid
* @return array associative array with the additional data and metadata (indexed by 'data' and
* 'metadata' keys).
*/
public static function action_url_addons(
string $action = '',
array $data = [],
array $metadata = [],
?int $instanceid = null
): array {
$allmutationclass = self::get_instances_implementing(action_url_addons::class);
$additionaldata = [];
$additionalmetadata = [];
foreach ($allmutationclass as $mutationclass) {
// Here we intentionally just pass data and metadata and not the result as we
// do not want subplugin to assume that another subplugin is doing a modification.
['data' => $newdata, 'metadata' => $newmetadata] = $mutationclass->execute($action, $data, $metadata, $instanceid);
$additionaldata = array_merge($additionaldata, $newdata ?? []);
$additionalmetadata = array_merge($additionalmetadata, $newmetadata ?? []);
}
return [
'data' => $additionaldata,
'metadata' => $additionalmetadata
];
}
/**
* Get new instance of classes that are named on the base of this classname and implementing this class
*
* @param string $classname
* @param array|null $newparameters additional parameters for the constructor.
* @return array
*/
protected static function get_instances_implementing(string $classname, ?array $newparameters = []): array {
$classes = self::get_classes_implementing($classname);
sort($classes); // Make sure all extension classes are returned in the same order. This is arbitrarily in
// alphabetical order and depends on the classname but this one way to ensure consistency across calls.
return array_map(function($targetclassname) use ($newparameters) {
// If $newparameters is null, the constructor will be called without parameters.
return new $targetclassname(...$newparameters);
}, $classes);
}
/**
* Get classes are named on the base of this classname and implementing this class
*
* @param string $classname
* @return array
*/
protected static function get_classes_implementing(string $classname): array {
// Get the class basename without Reflection API.
$classnamecomponents = explode("\\", $classname);
$classbasename = end($classnamecomponents);
$allsubs = core_plugin_manager::instance()->get_plugins_of_type(self::BBB_EXTENSION_PLUGIN_NAME);
$extensionclasses = [];
foreach ($allsubs as $sub) {
if (!$sub->is_enabled()) {
continue;
}
$targetclassname = "\\bbbext_{$sub->name}\\bigbluebuttonbn\\$classbasename";
if (!class_exists($targetclassname)) {
continue;
}
if (!is_subclass_of($targetclassname, $classname)) {
debugging("The class $targetclassname should extend $classname in the subplugin {$sub->name}. Ignoring.");
continue;
}
$extensionclasses[] = $targetclassname;
}
return $extensionclasses;
}
/**
* Get all custom_completion addons classes.
*
* @return array of custom completion addon classes.
*/
public static function custom_completion_addons_classes(): array {
return self::get_classes_implementing(custom_completion_addons::class);
}
/**
* Get all custom_completion addons classes instances.
*
* @param cm_info $cm
* @param int $userid
* @param array|null $completionstate
* @return array of custom completion addon instances.
*/
public static function custom_completion_addons_instances(cm_info $cm, int $userid, ?array $completionstate = null): array {
return self::get_instances_implementing(custom_completion_addons::class, [$cm, $userid, $completionstate]);
}
/**
* Get all mod_form addons classes instances
*
* @param \MoodleQuickForm $mform
* @param stdClass|null $bigbluebuttondata
* @param string|null $suffix
* @return array of custom completion addon classes instances
*/
public static function mod_form_addons_instances(\MoodleQuickForm $mform, ?stdClass $bigbluebuttondata = null,
string $suffix = null): array {
return self::get_instances_implementing(mod_form_addons::class, [$mform, $bigbluebuttondata, $suffix]);
}
/**
* Get additional join tables for instance when extension activated
*
* @return array of additional tables names. They all have a field called bigbluebuttonbnid that identifies the bbb instance.
*/
public static function get_join_tables(): array {
global $DB;
// We use cache here as it will be called very often.
$cache = cache::make('mod_bigbluebuttonbn', 'subplugins');
if ($cache->get('additionaltables')) {
return $cache->get('additionaltables');
}
$additionaltablesclasses = self::get_instances_implementing(mod_instance_helper::class);
$tables = [];
foreach ($additionaltablesclasses as $tableclass) {
$tables = array_merge($tables, $tableclass->get_join_tables() ?? []);
}
// Warning and removal for tables that do not have the bigbluebuttonid field.
foreach ($tables as $index => $table) {
$columns = $DB->get_columns($table);
if (empty($columns['bigbluebuttonbnid'])) {
debugging("get_instance_additional_tables: $table should have a column named bigbluebuttonid");
unset($tables[$index]);
}
}
$cache->set('additionaltables', $tables);
return $tables;
}
/**
* Add instance processing
*
* @param stdClass $data data to persist
* @return void
*/
public static function add_instance(stdClass $data): void {
$formmanagersclasses = self::get_instances_implementing(mod_instance_helper::class);
foreach ($formmanagersclasses as $fmclass) {
$fmclass->add_instance($data);
}
}
/**
* Update instance processing
*
* @param stdClass $data data to persist
* @return void
*/
public static function update_instance(stdClass $data): void {
$formmanagersclasses = self::get_instances_implementing(mod_instance_helper::class);
foreach ($formmanagersclasses as $fmclass) {
$fmclass->update_instance($data);
}
}
/**
* Delete instance processing
*
* @param int $id instance id
* @return void
*/
public static function delete_instance(int $id): void {
$formmanagersclasses = self::get_instances_implementing(mod_instance_helper::class);
foreach ($formmanagersclasses as $fmclass) {
$fmclass->delete_instance($id);
}
}
}
+107
View File
@@ -0,0 +1,107 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\external;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\restricted_context_exception;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\meeting;
/**
* External service to check whether a user can join a meeting.
*
* This is mainly used by the mobile application.
*
* @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 can_join 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([
'cmid' => new external_value(PARAM_INT, 'course module id', VALUE_REQUIRED),
'groupid' => new external_value(PARAM_INT, 'bigbluebuttonbn group id', VALUE_DEFAULT, 0),
]);
}
/**
* Updates a recording
*
* @param int $cmid the bigbluebuttonbn course module id
* @param null|int $groupid
* @return array (empty array for now)
* @throws \restricted_context_exception
*/
public static function execute(
int $cmid,
?int $groupid = 0
): array {
// Validate the cmid ID.
[
'cmid' => $cmid,
'groupid' => $groupid,
] = self::validate_parameters(self::execute_parameters(), [
'cmid' => $cmid,
'groupid' => $groupid,
]);
$result = [
'can_join' => false,
'cmid' => $cmid,
];
$instance = instance::get_from_cmid($cmid);
if (empty($instance)) {
return $result;
}
// Validate the groupid.
if (!groups_group_visible($groupid, $instance->get_course(), $instance->get_cm())) {
throw new restricted_context_exception();
}
$instance->set_group_id($groupid);
self::validate_context($instance->get_context());
$meeting = new meeting($instance);
$result['can_join'] = $meeting->can_join();
return $result;
}
/**
* Describe the return structure of the external service.
*
* @return external_single_structure
* @since Moodle 3.3
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'can_join' => new external_value(PARAM_BOOL, 'Can join session'),
'cmid' => new external_value(PARAM_INT, 'course module id', VALUE_REQUIRED),
]);
}
}
@@ -0,0 +1,110 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\external;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy;
/**
* External service to validate completion.
*
* @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 completion_validate 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'),
]);
}
/**
* Mark activity as complete
*
* @param int $bigbluebuttonbnid the bigbluebuttonbn instance id
* @return array (empty array for now)
*/
public static function execute(
int $bigbluebuttonbnid
): array {
// Validate the bigbluebuttonbnid ID.
[
'bigbluebuttonbnid' => $bigbluebuttonbnid,
] = self::validate_parameters(self::execute_parameters(), [
'bigbluebuttonbnid' => $bigbluebuttonbnid,
]);
$result = ['warnings' => []];
// Fetch the session, features, and profile.
$instance = instance::get_from_instanceid($bigbluebuttonbnid);
if ($instance) {
$context = $instance->get_context();
// Validate that the user has access to this activity.
self::validate_context($context);
// Get list with all the users enrolled in the course.
[$sort, $sqlparams] = users_order_by_sql('u');
if (has_capability('moodle/course:update', $context)) {
$users = get_enrolled_users($context, 'mod/bigbluebuttonbn:view', 0, 'u.*', $sort);
foreach ($users as $user) {
// Enqueue a task for processing the completion.
bigbluebutton_proxy::enqueue_completion_event($instance->get_instance_data(), $user->id);
}
} else {
$result['warnings'][] = [
'item' => 'mod_bigbluebuttonbn',
'itemid' => $instance->get_instance_id(),
'warningcode' => 'nopermissions',
'message' => get_string('nopermissions', 'error', 'completion_validate')
];
}
} else {
$result['warnings'][] = [
'item' => 'mod_bigbluebuttonbn',
'itemid' => $bigbluebuttonbnid,
'warningcode' => 'indexerrorbbtn',
'message' => get_string('index_error_bbtn', 'mod_bigbluebuttonbn', $bigbluebuttonbnid)
];
}
// We might want to return a status here or some warnings.
return $result;
}
/**
* 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([
'warnings' => new external_warnings(),
]);
}
}
+126
View File
@@ -0,0 +1,126 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\external;
use core\notification;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use core_external\restricted_context_exception;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\exceptions\bigbluebutton_exception;
use mod_bigbluebuttonbn\logger;
use mod_bigbluebuttonbn\meeting;
/**
* External service to end a meeting.
*
* @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 end_meeting 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'),
'groupid' => new external_value(PARAM_INT, 'bigbluebuttonbn group id', VALUE_DEFAULT, 0),
]);
}
/**
* Updates a recording
*
* @param int $bigbluebuttonbnid the bigbluebuttonbn instance id
* @param int $groupid the groupid (either 0 or the groupid)
* @return array (empty array for now)
* @throws \invalid_parameter_exception
* @throws \moodle_exception
* @throws restricted_context_exception
*/
public static function execute(
int $bigbluebuttonbnid,
int $groupid
): array {
// Validate the bigbluebuttonbnid ID.
[
'bigbluebuttonbnid' => $bigbluebuttonbnid,
'groupid' => $groupid,
] = self::validate_parameters(self::execute_parameters(), [
'bigbluebuttonbnid' => $bigbluebuttonbnid,
'groupid' => $groupid,
]);
// Fetch the session, features, and profile.
$instance = instance::get_from_instanceid($bigbluebuttonbnid);
if (empty($instance)) {
throw new \moodle_exception('Unknown Instance');
}
if (!groups_group_visible($groupid, $instance->get_course(), $instance->get_cm())) {
throw new restricted_context_exception();
}
$instance->set_group_id($groupid);
$context = $instance->get_context();
// Validate that the user has access to this activity and to manage recordings.
self::validate_context($context);
if (!$instance->user_can_end_meeting()) {
throw new restricted_context_exception();
}
// Execute the end command.
$meeting = new meeting($instance);
try {
$meeting->end_meeting();
} catch (bigbluebutton_exception $e) {
return [
'warnings' => [
[
'item' => $instance->get_meeting_name(),
'itemid' => $instance->get_instance_id(),
'warningcode' => 'notFound',
'message' => $e->getMessage()
]
]
];
}
logger::log_meeting_ended_event($instance);
// Update the cache.
$meeting->update_cache();
notification::add(get_string('end_session_notification', 'mod_bigbluebuttonbn'), notification::INFO);
return [];
}
/**
* 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([
'warnings' => new external_warnings(),
]);
}
}
@@ -0,0 +1,115 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\external;
use core_course\external\helper_for_get_mods_by_courses;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use core_external\util as external_util;
/**
* External service to get activity per course
*
* This is mainly used by the mobile application.
*
* @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_bigbluebuttonbns_by_courses extends external_api {
/**
* Describes the parameters for get_bigbluebuttonbns_by_courses.
*
* @return external_function_parameters
* @since Moodle 3.11
*/
public static function execute_parameters() {
return new external_function_parameters([
'courseids' => new external_multiple_structure(
new external_value(PARAM_INT, 'Course id'), 'Array of course ids', VALUE_DEFAULT, []
),
]
);
}
/**
* Returns a list of bigbluebuttonbns in a provided list of courses.
* If no list is provided all bigbluebuttonbns that the user can view will be returned.
*
* @param array $courseids course ids
* @return array of warnings and bigbluebuttonbns
* @since Moodle 3.11
*/
public static function execute($courseids = []) {
global $USER;
$warnings = [];
$returnedbigbluebuttonbns = [];
['courseids' => $courseids] = self::validate_parameters(self::execute_parameters(), ['courseids' => $courseids]);
$mycourses = [];
if (empty($courseids)) {
$mycourses = enrol_get_my_courses();
$courseids = array_keys($mycourses);
}
// Ensure there are courseids to loop through.
if (!empty($courseids)) {
[$courses, $warnings] = external_util::validate_courses($courseids, $mycourses);
// Get the bigbluebuttonbns in this course, this function checks users visibility permissions.
// We can avoid then additional validate_context calls.
$bigbluebuttonbns = get_all_instances_in_courses("bigbluebuttonbn", $courses, $USER->id);
foreach ($bigbluebuttonbns as $bigbluebuttonbn) {
helper_for_get_mods_by_courses::format_name_and_intro($bigbluebuttonbn, 'mod_bigbluebuttonbn');
$returnedbigbluebuttonbns[] = $bigbluebuttonbn;
}
}
$result = [
'bigbluebuttonbns' => $returnedbigbluebuttonbns,
'warnings' => $warnings
];
return $result;
}
/**
* Describes the get_bigbluebuttonbns_by_courses return value.
*
* @return external_single_structure
* @since Moodle 3.11
*/
public static function execute_returns() {
return new external_single_structure([
'bigbluebuttonbns' => new external_multiple_structure(
new external_single_structure(array_merge(
helper_for_get_mods_by_courses::standard_coursemodule_elements_returns(),
[
'meetingid' => new external_value(PARAM_RAW, 'Meeting id'),
'timemodified' => new external_value(PARAM_INT, 'Last time the instance was modified'),
]
))
),
'warnings' => new external_warnings(),
]
);
}
}
+117
View File
@@ -0,0 +1,117 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\external;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use core_external\restricted_context_exception;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\exceptions\meeting_join_exception;
use mod_bigbluebuttonbn\meeting;
/**
* External service to create the meeting (if needed), check user limit, and return the join URL when we can join.
*
* This is mainly used by the mobile application.
*
* @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_join_url 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([
'cmid' => new external_value(PARAM_INT, 'course module id', VALUE_REQUIRED),
'groupid' => new external_value(PARAM_INT, 'bigbluebuttonbn group id', VALUE_DEFAULT, 0),
]);
}
/**
* Updates a recording
*
* @param int $cmid the bigbluebuttonbn course module id
* @param null|int $groupid
* @return array (empty array for now)
*
* @throws restricted_context_exception
*/
public static function execute(
int $cmid,
?int $groupid = 0
): array {
// Validate the cmid ID.
[
'cmid' => $cmid,
'groupid' => $groupid,
] = self::validate_parameters(self::execute_parameters(), [
'cmid' => $cmid,
'groupid' => $groupid,
]);
$result = ['warnings' => []];
$instance = instance::get_from_cmid($cmid);
if (empty($instance)) {
throw new \moodle_exception('nosuchinstance', 'mod_bigbluebuttonbn', null,
['entity' => get_string('module', 'course'), 'id' => $cmid]);
}
// Validate the groupid.
if (!groups_group_visible($groupid, $instance->get_course(), $instance->get_cm())) {
throw new restricted_context_exception();
}
$instance->set_group_id($groupid);
// Validate that the user has access to this activity and to join the meeting.
self::validate_context($instance->get_context());
if (!$instance->can_join()) {
throw new restricted_context_exception();
}
try {
$result['join_url'] = meeting::join_meeting($instance);
} catch (meeting_join_exception $e) {
$result['warnings'][] = [
'item' => 'mod_bigbluebuttonbn',
'itemid' => $instance->get_instance_id(),
'warningcode' => $e->errorcode,
'message' => $e->getMessage()
];
}
return $result;
}
/**
* Describe the return structure of the external service.
*
* @return external_single_structure
* @since Moodle 3.3
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'join_url' => new external_value(PARAM_RAW, 'Can join session', VALUE_OPTIONAL),
'warnings' => new external_warnings(),
]);
}
}
+156
View File
@@ -0,0 +1,156 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\external;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use core_external\restricted_context_exception;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\bigbluebutton\recordings\recording_data;
use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy;
/**
* External service to fetch a list of recordings from the BBB service.
*
* @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_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'),
'tools' => new external_value(PARAM_RAW, 'a set of enabled tools', VALUE_DEFAULT,
'protect,unprotect,publish,unpublish,delete'),
'groupid' => new external_value(PARAM_INT, 'Group ID', VALUE_DEFAULT, null),
]);
}
/**
* Get a list of recordings
*
* @param int $bigbluebuttonbnid the bigbluebuttonbn instance id to which the recordings are referred.
* @param string|null $tools
* @param int|null $groupid
* @return array of warnings and status result
* @throws \invalid_parameter_exception
* @throws restricted_context_exception
*/
public static function execute(
int $bigbluebuttonbnid = 0,
?string $tools = 'protect,unprotect,publish,unpublish,delete',
?int $groupid = null
): array {
global $USER;
$returnval = [
'status' => false,
'warnings' => [],
];
// Validate the bigbluebuttonbnid ID.
[
'bigbluebuttonbnid' => $bigbluebuttonbnid,
'tools' => $tools,
'groupid' => $groupid,
] = self::validate_parameters(self::execute_parameters(), [
'bigbluebuttonbnid' => $bigbluebuttonbnid,
'tools' => $tools,
'groupid' => $groupid,
]);
$tools = explode(',', $tools ?? 'protect,unprotect,publish,unpublish,delete');
// Fetch the session, features, and profile.
$instance = instance::get_from_instanceid($bigbluebuttonbnid);
if (!$instance) {
$returnval['warnings'][] = [
'item' => $bigbluebuttonbnid,
'warningcode' => 'nosuchinstance',
'message' => get_string('nosuchinstance', 'mod_bigbluebuttonbn',
(object) ['id' => $bigbluebuttonbnid, 'entity' => 'bigbluebuttonbn'])
];
} else {
$typeprofiles = bigbluebutton_proxy::get_instance_type_profiles();
$profilefeature = $typeprofiles[$instance->get_type()]['features'];
$showrecordings = in_array('all', $profilefeature) || in_array('showrecordings', $profilefeature);
if ($showrecordings) {
$context = $instance->get_context();
// Validate that the user has access to this activity.
self::validate_context($context);
if (!$instance->user_has_group_access($USER, $groupid)) {
new restricted_context_exception();
}
if ($groupid) {
$instance->set_group_id($groupid);
}
$recordings = $instance->get_recordings([], $instance->get_instance_var('recordings_deleted') ?? false);
$tabledata = recording_data::get_recording_table($recordings, $tools, $instance);
$returnval['tabledata'] = $tabledata;
$returnval['status'] = true;
} else {
$returnval['warnings'][] = [
'item' => $bigbluebuttonbnid,
'warningcode' => 'instanceprofilewithoutrecordings',
'message' => get_string('instanceprofilewithoutrecordings', 'mod_bigbluebuttonbn')
];
}
}
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_ALPHANUMEXT),
'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),
'formatter' => new external_value(PARAM_ALPHANUMEXT, 'Formatter name', VALUE_OPTIONAL),
])),
'data' => new external_value(PARAM_RAW), // For now it will be json encoded.
], '', VALUE_OPTIONAL),
'warnings' => new external_warnings()
]);
}
}
@@ -0,0 +1,203 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\external;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\bigbluebutton\recordings\recording_data;
use mod_bigbluebuttonbn\recording;
/**
* External service to fetch a list of recordings from the BBB service.
*
* @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_recordings_to_import 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([
'destinationinstanceid' => new external_value(
PARAM_INT,
'Id of the other BBB we target for importing recordings into.
The idea here is to remove already imported recordings.',
VALUE_REQUIRED
),
'sourcebigbluebuttonbnid' => new external_value(PARAM_INT,
'bigbluebuttonbn instance id',
VALUE_DEFAULT,
0),
'sourcecourseid' => new external_value(PARAM_INT,
'source courseid to filter by',
VALUE_DEFAULT,
0),
'tools' => new external_value(PARAM_RAW, 'a set of enabled tools', VALUE_DEFAULT,
'protect,unprotect,publish,unpublish,delete'),
'groupid' => new external_value(PARAM_INT, 'Group ID', VALUE_DEFAULT, null),
]);
}
/**
* Get a list of recordings
*
* @param int $destinationinstanceid the bigbluebuttonbn instance id where recordings have been already imported.
* @param int|null $sourcebigbluebuttonbnid the bigbluebuttonbn instance id to which the recordings are referred.
* @param int|null $sourcecourseid the source courseid to filter by
* @param string|null $tools
* @param int|null $groupid
* @return array of warnings and status result
* @throws \invalid_parameter_exception
* @throws \restricted_context_exception
*/
public static function execute(
int $destinationinstanceid,
?int $sourcebigbluebuttonbnid = 0,
?int $sourcecourseid = 0,
?string $tools = 'protect,unprotect,publish,unpublish,delete',
?int $groupid = null
): array {
global $USER, $DB;
$returnval = [
'status' => false,
'warnings' => [],
];
// Validate the sourcebigbluebuttonbnid ID.
[
'destinationinstanceid' => $destinationinstanceid,
'sourcebigbluebuttonbnid' => $sourcebigbluebuttonbnid,
'sourcecourseid' => $sourcecourseid,
'tools' => $tools,
'groupid' => $groupid
] = self::validate_parameters(self::execute_parameters(), [
'destinationinstanceid' => $destinationinstanceid,
'sourcebigbluebuttonbnid' => $sourcebigbluebuttonbnid,
'sourcecourseid' => $sourcecourseid,
'tools' => $tools,
'groupid' => $groupid
]);
$tools = explode(',', $tools ?? 'protect,unprotect,publish,unpublish,delete');
// Fetch the session, features, and profile.
$sourceinstance = null;
$sourcecourse = null;
if ($sourcecourseid) {
$sourcecourse = $DB->get_record('course', ['id' => $sourcecourseid], '*', MUST_EXIST);
}
if (!empty($sourcebigbluebuttonbnid)) {
$sourceinstance = instance::get_from_instanceid($sourcebigbluebuttonbnid);
if (!$sourceinstance) {
throw new \invalid_parameter_exception('Source Bigbluebutton Id is invalid');
}
$sourcecourse = $sourceinstance->get_course();
// Validate that the user has access to this activity.
self::validate_context($sourceinstance->get_context());
}
$destinstance = instance::get_from_instanceid($destinationinstanceid);
// Validate that the user has access to this activity.
self::validate_context($destinstance->get_context());
if (!$destinstance->user_has_group_access($USER, $groupid)) {
throw new \invalid_parameter_exception('Invalid group for this user ' . $groupid);
}
if ($groupid) {
$destinstance->set_group_id($groupid);
}
// Exclude itself from the list if in import mode.
$excludedids = [$destinstance->get_instance_id()];
if ($sourceinstance) {
$recordings = $sourceinstance->get_recordings($excludedids);
} else {
// There is a course id or a 0, so we fetch all recording including deleted recordings from this course.
$recordings = recording::get_recordings_for_course(
$sourcecourseid,
$excludedids,
true,
false,
($sourcecourseid == 0 || $sourcebigbluebuttonbnid == 0),
($sourcecourseid == 0 || $sourcebigbluebuttonbnid == 0)
);
}
if ($destinationinstanceid) {
// Remove recording already imported in this specific activity.
$destinationinstance = instance::get_from_instanceid($destinationinstanceid);
$importedrecordings = recording::get_recordings_for_instance(
$destinationinstance,
true,
true
);
// Unset from $recordings if recording is already imported.
// Recording $recordings are indexed by $id (moodle table column id).
foreach ($recordings as $index => $recording) {
foreach ($importedrecordings as $irecord) {
if ($irecord->get('recordingid') == $recording->get('recordingid')) {
unset($recordings[$index]);
}
}
}
}
$tabledata = recording_data::get_recording_table($recordings, $tools, $sourceinstance, $sourcecourseid);
$returnval['tabledata'] = $tabledata;
$returnval['status'] = true;
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_ALPHANUMEXT),
'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),
'formatter' => new external_value(PARAM_ALPHANUMEXT, 'Formatter name', VALUE_OPTIONAL),
])),
'data' => new external_value(PARAM_RAW), // For now it will be json encoded.
], '', VALUE_OPTIONAL),
'warnings' => new external_warnings()
]);
}
}
+152
View File
@@ -0,0 +1,152 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\external;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\restricted_context_exception;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy;
use mod_bigbluebuttonbn\meeting;
/**
* External service to fetch meeting information.
*
* @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 meeting_info 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'),
'groupid' => new external_value(PARAM_INT, 'bigbluebuttonbn group id', VALUE_DEFAULT, 0),
'updatecache' => new external_value(PARAM_BOOL, 'update cache ?', VALUE_DEFAULT, false),
]);
}
/**
* Fetch meeting information.
*
* @param int $bigbluebuttonbnid the bigbluebuttonbn instance id
* @param int $groupid
* @param bool $updatecache
* @return array
* @throws \moodle_exception
* @throws restricted_context_exception
*/
public static function execute(
int $bigbluebuttonbnid,
int $groupid,
bool $updatecache = false
): array {
// Validate the bigbluebuttonbnid ID.
[
'bigbluebuttonbnid' => $bigbluebuttonbnid,
'groupid' => $groupid,
'updatecache' => $updatecache,
] = self::validate_parameters(self::execute_parameters(), [
'bigbluebuttonbnid' => $bigbluebuttonbnid,
'groupid' => $groupid,
'updatecache' => $updatecache,
]);
// Fetch the session, features, and profile.
$instance = instance::get_from_instanceid($bigbluebuttonbnid);
$instance->set_group_id($groupid);
if (!groups_group_visible($groupid, $instance->get_course(), $instance->get_cm())) {
throw new restricted_context_exception();
}
$context = $instance->get_context();
// Validate that the user has access to this activity and to manage recordings.
self::validate_context($context);
// Check if the BBB server is working.
$serverversion = bigbluebutton_proxy::get_server_version();
if ($serverversion === null) {
throw new \moodle_exception('general_error_no_answer', 'mod_bigbluebuttonbn',
bigbluebutton_proxy::get_server_not_available_url($instance),
bigbluebutton_proxy::get_server_not_available_message($instance));
}
$meetinginfo = (array) meeting::get_meeting_info_for_instance($instance, $updatecache);
// Make the structure WS friendly.
array_walk($meetinginfo['features'], function(&$value, $key){
$value = ['name' => $key, 'isenabled' => (bool) $value];
});
return $meetinginfo;
}
/**
* 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([
'cmid' => new external_value(PARAM_INT, 'CM id'),
'userlimit' => new external_value(PARAM_INT, 'User limit'),
'bigbluebuttonbnid' => new external_value(PARAM_RAW, 'bigbluebuttonbn instance id'),
'groupid' => new external_value(PARAM_INT, 'bigbluebuttonbn group id', VALUE_DEFAULT, 0),
'meetingid' => new external_value(PARAM_RAW, 'Meeting id'),
'openingtime' => new external_value(PARAM_INT, 'Opening time', VALUE_OPTIONAL),
'closingtime' => new external_value(PARAM_INT, 'Closing time', VALUE_OPTIONAL),
'statusrunning' => new external_value(PARAM_BOOL, 'Status running', VALUE_OPTIONAL),
'statusclosed' => new external_value(PARAM_BOOL, 'Status closed', VALUE_OPTIONAL),
'statusopen' => new external_value(PARAM_BOOL, 'Status open', VALUE_OPTIONAL),
'statusmessage' => new external_value(PARAM_TEXT, 'Status message', VALUE_OPTIONAL),
'startedat' => new external_value(PARAM_INT, 'Started at', VALUE_OPTIONAL),
'moderatorcount' => new external_value(PARAM_INT, 'Moderator count', VALUE_OPTIONAL),
'participantcount' => new external_value(PARAM_INT, 'Participant count', VALUE_OPTIONAL),
'moderatorplural' => new external_value(PARAM_BOOL, 'Several moderators ?', VALUE_OPTIONAL),
'participantplural' => new external_value(PARAM_BOOL, 'Several participants ?', VALUE_OPTIONAL),
'canjoin' => new external_value(PARAM_BOOL, 'Can join'),
'ismoderator' => new external_value(PARAM_BOOL, 'Is moderator'),
'presentations' => new external_multiple_structure(
new external_single_structure([
'url' => new external_value(PARAM_URL, 'presentation URL'),
'iconname' => new external_value(PARAM_RAW, 'icon name'),
'icondesc' => new external_value(PARAM_TEXT, 'icon text'),
'name' => new external_value(PARAM_TEXT, 'presentation name'),
])
),
'joinurl' => new external_value(PARAM_URL, 'Join URL'),
'guestaccessenabled' => new external_value(PARAM_BOOL, 'Guest access enabled', VALUE_OPTIONAL),
'guestjoinurl' => new external_value(PARAM_URL, 'Guest URL', VALUE_OPTIONAL),
'guestpassword' => new external_value(PARAM_RAW, 'Guest join password', VALUE_OPTIONAL),
'features' => new external_multiple_structure(
new external_single_structure([
'name' => new external_value(PARAM_ALPHA, 'Feature name.'),
'isenabled' => new external_value(PARAM_BOOL, 'Whether the feature is enabled.'),
]), 'List of features for the instance', VALUE_OPTIONAL
),
]
);
}
}
@@ -0,0 +1,134 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\external;
use coding_exception;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\bigbluebutton\recordings\recording_action;
use mod_bigbluebuttonbn\recording;
/**
* External service to update the details of one recording.
*
* @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 update_recording extends external_api {
/**
* Updates a recording
*
* @param int $bigbluebuttonbnid the bigbluebuttonbn instance id, either the same as the one set in the recording or a new
* instance to import the recording into.
* @param int $recordingid
* @param string $action
* @param string|null $additionaloptions
* @return array (empty array for now)
*/
public static function execute(
int $bigbluebuttonbnid,
int $recordingid,
string $action,
string $additionaloptions = null
): array {
// Validate the bigbluebuttonbnid ID.
[
'bigbluebuttonbnid' => $bigbluebuttonbnid,
'recordingid' => $recordingid,
'action' => $action,
'additionaloptions' => $additionaloptions,
] = self::validate_parameters(self::execute_parameters(), [
'bigbluebuttonbnid' => $bigbluebuttonbnid,
'recordingid' => $recordingid,
'action' => $action,
'additionaloptions' => $additionaloptions,
]);
switch ($action) {
case 'delete':
case 'edit':
case 'protect':
case 'publish':
case 'unprotect':
case 'unpublish':
case 'import':
break;
default:
throw new coding_exception("Unknown action '{$action}'");
}
$instance = instance::get_from_instanceid($bigbluebuttonbnid);
$recordingcontext = $instance->get_context();
self::validate_context($recordingcontext);
require_capability('mod/bigbluebuttonbn:managerecordings', $recordingcontext);
require_capability("mod/bigbluebuttonbn:{$action}recordings", $recordingcontext);
// Fetch the session, features, and profile.
$recording = new recording($recordingid);
// Check both the recording instance context and the bbb context.
$relatedinstance = instance::get_from_instanceid($recording->get('bigbluebuttonbnid'));
if ($relatedinstance) {
$recordingcontext = $relatedinstance->get_context();
// Validate that the user has access to this activity and to manage recordings.
self::validate_context($recordingcontext);
require_capability('mod/bigbluebuttonbn:managerecordings', $recordingcontext);
require_capability("mod/bigbluebuttonbn:{$action}recordings", $recordingcontext);
}
$additionaloptionsobject = $additionaloptions ? json_decode($additionaloptions) : null;
// Specific action such as import, delete, publish, unpublish, edit,....
if (method_exists(recording_action::class, "$action")) {
forward_static_call(
['\mod_bigbluebuttonbn\local\bigbluebutton\recordings\recording_action', "$action"],
$recording,
$instance,
$additionaloptionsobject
);
}
return [];
}
/**
* 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, this might be a different one from the one set in recordingid in case of importing'
),
'recordingid' => new external_value(PARAM_INT, 'The moodle internal recording ID'),
'action' => new external_value(PARAM_ALPHANUMEXT, 'The action to perform'),
'additionaloptions' => new external_value(PARAM_RAW, 'Additional options', VALUE_REQUIRED, null),
]);
}
/**
* 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([]);
}
}
@@ -0,0 +1,108 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\external;
use context_module;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use mod_bigbluebuttonbn\instance;
/**
* External service to trigger the course module viewed event and update the module completion status
*
* This is mainly used by the mobile application.
*
* @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 view_bigbluebuttonbn extends external_api {
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 3.0
*/
public static function execute_parameters() {
return new external_function_parameters([
'bigbluebuttonbnid' => new external_value(PARAM_INT, 'bigbluebuttonbn instance id'),
]
);
}
/**
* Trigger the course module viewed event and update the module completion status.
*
* @param int $instanceid the bigbluebuttonbn instance id
* @return array of warnings and status result
* @since Moodle 3.0
*/
public static function execute($instanceid) {
global $CFG;
require_once($CFG->dirroot . "/mod/bigbluebuttonbn/lib.php");
['bigbluebuttonbnid' => $instanceid] = self::validate_parameters(
self::execute_parameters(),
['bigbluebuttonbnid' => $instanceid]
);
$instance = instance::get_from_instanceid($instanceid);
if (empty($instance)) {
return [
'status' => false,
'warnings' => [
[
'item' => 'mod_bigbluebuttonbn',
'itemid' => 0,
'warningcode' => 'nosuchinstance',
'message' => get_string('nosuchinstance', 'mod_bigbluebuttonbn',
(object) ['id' => $instanceid, 'entity' => 'bigbluebuttonbn'])
]
]
];
}
$context = context_module::instance($instance->get_cm_id());
self::validate_context($context);
require_capability('mod/bigbluebuttonbn:view', $context);
// Call the bigbluebuttonbn/lib API.
bigbluebuttonbn_view($instance->get_instance_data(), $instance->get_course(), $instance->get_cm(), $context);
return [
'status' => true,
'warnings' => []
];
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 3.0
*/
public static function execute_returns() {
return new external_single_structure([
'status' => new external_value(PARAM_BOOL, 'status: true if success'),
'warnings' => new external_warnings()
]
);
}
}
@@ -0,0 +1,220 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\form;
use context;
use core_form\dynamic_form;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\exceptions\bigbluebutton_exception;
use mod_bigbluebuttonbn\task\send_guest_emails;
use moodle_exception;
use moodle_url;
use MoodleQuickForm;
/**
* Popup form to add new guests to a meeting and show/copy credential to access the guest login page.
*
* @package mod_bigbluebuttonbn
* @copyright 2022 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
*/
class guest_add extends dynamic_form {
/**
* Max length for credential and url fields.
*/
const MAX_INPUT_LENGTH = 35;
/**
* Process the form submission, used if form was submitted via AJAX.
*
* @return array
*/
public function process_dynamic_submission(): array {
global $USER;
$data = $this->get_data();
$allmails = [];
if (!empty($data->emails)) {
$emails = explode(',', $data->emails);
foreach ($emails as $email) {
$email = trim($email);
if (validate_email($email)) {
$allmails[] = $email;
}
}
$adhoctask = new send_guest_emails();
$adhoctask->set_custom_data(
[
'emails' => $allmails,
'useridfrom' => $USER->id
]
);
$adhoctask->set_instance_id($data->id);
\core\task\manager::queue_adhoc_task($adhoctask);
}
return [
'result' => true,
'emails' => join(', ', $allmails),
'emailcount' => count($allmails),
'errors' => ''
];
}
/**
* Perform some validation.
*
* @param array $formdata
* @param array $files
* @return array
*/
public function validation($formdata, $files): array {
$errors = [];
$emailserrors = [];
if (!empty($formdata['emails'])) {
$emails = explode(',', $formdata['emails']);
foreach ($emails as $email) {
$email = trim($email);
if (!validate_email($email)) {
$emailserrors[] .= get_string('guestaccess_emails_invalidemail', 'mod_bigbluebuttonbn', $email);
}
}
}
if (!empty($emailserrors)) {
$errors['emails'] = \html_writer::alist($emailserrors);
}
return $errors;
}
/**
* Load in existing data as form defaults (not applicable).
*
* @return void
*/
public function set_data_for_dynamic_submission(): void {
$instance = $this->get_instance_from_params();
$data = [
'id' => $instance->get_instance_id(),
'groupid' => $instance->get_group_id(),
'guestjoinurl' => $instance->get_guest_access_url(),
'guestpassword' => $instance->get_guest_access_password(),
];
$this->set_data($data);
}
/**
* Get BigblueButton instance from context params
*
* @return instance
* @throws moodle_exception
*/
protected function get_instance_from_params(): instance {
$bbid = $this->optional_param('id', null, PARAM_INT);
$groupid = $this->optional_param('groupid', null, PARAM_INT);
if (empty($bbid)) {
throw new moodle_exception('guestaccess_add_no_id', 'mod_bigbluebuttonbn');
}
$instance = instance::get_from_instanceid($bbid);
if ($groupid) {
$instance->set_group_id($groupid);
}
return $instance;
}
/**
* Form definition
*/
protected function definition() {
self::add_meeting_links_elements($this->_form);
$mform = $this->_form;
$mform->addElement('text', 'emails',
get_string('guestaccess_emails', 'mod_bigbluebuttonbn'),
);
$mform->addHelpButton('emails', 'guestaccess_emails', 'mod_bigbluebuttonbn');
$mform->setDefault('emails', '');
$mform->setType('emails', PARAM_RAW);
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
$mform->addElement('hidden', 'groupid');
$mform->setType('groupid', PARAM_INT);
}
/**
* Add meeting links element. Helper for this form and the mod_form (module form)
*
* @param MoodleQuickForm $mform
* @return void
*/
public static function add_meeting_links_elements(MoodleQuickForm &$mform): void {
global $CFG;
MoodleQuickForm::registerElementType('text_with_copy',
"$CFG->dirroot/mod/bigbluebuttonbn/classes/form/text_with_copy_element.php",
text_with_copy_element::class);
$mform->addElement('text_with_copy', 'guestjoinurl',
get_string('guestaccess_meeting_link', 'mod_bigbluebuttonbn'),
[
'copylabel' => get_string('guestaccess_copy_link', 'mod_bigbluebuttonbn'),
'size' => self::MAX_INPUT_LENGTH,
'readonly' => 'readonly'
]
);
$mform->setType('guestjoinurl', PARAM_URL);
$mform->addElement('text_with_copy', 'guestpassword',
get_string('guestaccess_meeting_password', 'mod_bigbluebuttonbn'),
[
'copylabel' => get_string('guestaccess_copy_password', 'mod_bigbluebuttonbn'),
'readonly' => 'readonly',
'size' => self::MAX_INPUT_LENGTH,
]
);
$mform->setType('guestpassword', PARAM_RAW);
}
/**
* Check if current user has access to this form, otherwise throw exception.
*
* @return void
* @throws moodle_exception
*/
protected function check_access_for_dynamic_submission(): void {
$context = $this->get_context_for_dynamic_submission();
$instance = instance::get_from_cmid($context->instanceid);
if (!$instance->is_moderator()) {
throw new \restricted_context_exception();
}
}
/**
* Return form context
*
* @return context
*/
protected function get_context_for_dynamic_submission(): context {
$instance = $this->get_instance_from_params();
return $instance->get_context();
}
/**
* Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX.
*
* @return moodle_url
*/
protected function get_page_url_for_dynamic_submission(): moodle_url {
$context = $this->get_context_for_dynamic_submission();
return new moodle_url('/mod/bigbluebuttonbn/view.php', ['id' => $context->instanceid]);
}
}
@@ -0,0 +1,78 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\form;
defined('MOODLE_INTERNAL') || die;
global $CFG;
require_once($CFG->libdir . '/formslib.php');
/**
* Guest login form.
*
* @package mod_bigbluebuttonbn
* @copyright 2022 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
*/
class guest_login extends \moodleform {
/**
* Form definition
*/
protected function definition() {
global $USER;
$mform = $this->_form;
$mform->addElement('text', 'username',
get_string('guestaccess_username', 'mod_bigbluebuttonbn'));
$mform->setType('username', PARAM_NOTAGS);
$mform->addRule('username',
get_string('required'), 'required', null, 'client');
if (isloggedin() && !isguestuser()) {
$mform->setConstant('username', fullname($USER));
$mform->freeze('username');
}
$mform->addElement('password', 'password',
get_string('guestaccess_password', 'mod_bigbluebuttonbn'));
$mform->setType('password', PARAM_RAW);
$mform->addRule('password',
get_string('required'), 'required', null, 'client');
$mform->addElement('hidden', 'uid', $this->_customdata['uid']);
$mform->setType('uid', PARAM_ALPHANUMEXT);
$this->add_action_buttons(false, get_string('guestaccess_join_meeting', 'mod_bigbluebuttonbn'));
}
/**
* Validate form
*
* @param array $data
* @param array $files
* @return array
* @throws \coding_exception
*/
public function validation($data, $files): array {
$errors = parent::validation($data, $files);
$instance = $this->_customdata['instance'];
if ($data['password'] != $instance->get_guest_access_password()) {
$errors['password'] = get_string('guestaccess_meeting_invalid_password', 'mod_bigbluebuttonbn');
}
return $errors;
}
}
@@ -0,0 +1,98 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\form;
use MoodleQuickForm_text;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("$CFG->libdir/form/text.php");
/**
* Text type form element with a copy widget
*
* Contains HTML class for a text type element and a link that will copy its content in the copy/paste buffer
*
* @package mod_bigbluebuttonbn
* @copyright 2022 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
*/
class text_with_copy_element extends MoodleQuickForm_text {
/** @var string an element template. */
public $_groupElementTemplate;
/**
* Accepts a renderer
*
* @param object $renderer An HTML_QuickForm_Renderer object
* @param bool $required Whether an element is required
* @param string $error An error message associated with an element
* @return void
*/
public function accept(&$renderer, $required = false, $error = null) {
global $OUTPUT;
$elementname = $this->getName();
// Make sure the element has an id.
$this->_generateId();
$advanced = isset($renderer->_advancedElements[$elementname]);
$elementcontext = $this->export_for_template($OUTPUT);
$helpbutton = '';
if (method_exists($this, 'getHelpButton')) {
$helpbutton = $this->getHelpButton();
}
$label = $this->getLabel();
$text = '';
if (method_exists($this, 'getText')) {
// There currently exists code that adds a form element with an empty label.
// If this is the case then set the label to the description.
if (empty($label)) {
$label = $this->getText();
} else {
$text = $this->getText();
}
}
$context = array(
'element' => $elementcontext,
'label' => $label,
'text' => $text,
'required' => $required,
'advanced' => $advanced,
'helpbutton' => $helpbutton,
'error' => $error,
'copylabel' => $this->_attributes['copylabel'] ?? get_string('copy', 'core_editor')
);
$html = $OUTPUT->render_from_template('mod_bigbluebuttonbn/element_text_with_copy', $context);
if ($renderer->_inGroup) {
$this->_groupElementTemplate = $html;
}
if (($renderer->_inGroup) && !empty($renderer->_groupElementTemplate)) {
$renderer->_groupElementTemplate = $html;
} else if (!isset($renderer->_templates[$elementname])) {
$renderer->_templates[$elementname] = $html;
}
if (in_array($elementname, $renderer->_stopFieldsetElements) && $renderer->_fieldsetsOpen > 0) {
$renderer->_html .= $renderer->_closeFieldsetTemplate;
$renderer->_fieldsetsOpen--;
}
$renderer->_html .= $html;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,135 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\local\bigbluebutton\recordings;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\recording;
use mod_bigbluebuttonbn\local\config;
/**
* Collection of helper methods for handling recordings actions in Moodle.
*
* Utility class for meeting actions
*
* @package mod_bigbluebuttonbn
* @copyright 2021 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class recording_action {
/**
* Import recording
*
* @param recording $recording
* @param instance $targetinstance
*/
public static function import(recording $recording, instance $targetinstance): void {
$recording->create_imported_recording($targetinstance);
}
/**
* Helper for performing delete on recordings.
*
* @param recording $recording
*/
public static function delete(recording $recording): void {
// As the recordingid was not identified as imported recording link, execute delete on a real recording.
// Step 1, delete imported links associated to the recording.
$recordingstodelete = recording::get_records(['recordingid' => $recording->get('recordingid'),
'imported' => true]);
foreach ($recordingstodelete as $rec) {
$rec->delete();
}
$recording->delete();
}
/**
* Helper for performing edit on recordings.
*
* @param recording $recording
*/
public static function edit(recording $recording): void {
$recording->update();
}
/**
* Helper for performing unprotect on recordings.
*
* @param recording $recording
*/
public static function unprotect(recording $recording): void {
if (!(boolean) config::get('recording_protect_editable')) {
// Recording protect action through UI is disabled, there is no need to do anything else.
throw new \moodle_exception('cannotperformaction', 'mod_bigblubuebuttobn', '', 'unprotect');
}
if ($recording->get('imported')) {
// Imported recordings can not be unprotected. There is no need to do anything else.
throw new \moodle_exception('cannotperformaction', 'mod_bigblubuebuttobn', '', 'unprotect');
}
$recording->set('protected', false);
$recording->update();
}
/**
* Helper for performing protect on recordings.
*
* @param recording $recording
*/
public static function protect(recording $recording): void {
if (!(boolean) config::get('recording_protect_editable')) {
// Recording protect action through UI is disabled, there is no need to do anything else.
throw new \moodle_exception('cannotperformaction', 'mod_bigblubuebuttobn', '', 'protect');
}
if ($recording->get('imported')) {
// Imported recordings can not be unprotected. There is no need to do anything else.
throw new \moodle_exception('cannotperformaction', 'mod_bigblubuebuttobn', '', 'protect');
}
$recording->set('protected', true);
$recording->update();
}
/**
* Helper for performing unpublish on recordings.
*
* @param recording $recording
*/
public static function unpublish(recording $recording): void {
if ($recording->get('imported')) {
/* Since the recording link is the one fetched from the BBB server, imported recordings can not be
* unpublished. There is no need to do anything else.
*/
throw new \moodle_exception('cannotperformaction', 'mod_bigblubuebuttobn', '', 'unpublish');
}
$recording->set('published', false);
$recording->update();
}
/**
* Helper for performing publish on recordings.
*
* @param recording $recording
*/
public static function publish(recording $recording): void {
if ($recording->get('imported')) {
/* Since the recording link is the one fetched from the BBB server, imported recordings can not be
* unpublished. There is no need to do anything else.
*/
throw new \moodle_exception('cannotperformaction', 'mod_bigblubuebuttobn', '', 'publish');
}
$recording->set('published', true);
$recording->update();
}
}
@@ -0,0 +1,319 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\local\bigbluebutton\recordings;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\config;
use mod_bigbluebuttonbn\local\helpers\roles;
use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy;
use mod_bigbluebuttonbn\output\recording_description_editable;
use mod_bigbluebuttonbn\output\recording_name_editable;
use mod_bigbluebuttonbn\output\recording_row_actionbar;
use mod_bigbluebuttonbn\output\recording_row_playback;
use mod_bigbluebuttonbn\output\recording_row_preview;
use mod_bigbluebuttonbn\recording;
use stdClass;
/**
* The recordings_data.
*
* @package mod_bigbluebuttonbn
* @copyright 2021 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent.david [at] call-learning [dt] fr)
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
class recording_data {
/**
* Get the full recording table
*
* @param array $recordings
* @param array $tools
* @param instance|null $instance
* @param int $courseid
* @return array
*/
public static function get_recording_table(array $recordings, array $tools, instance $instance = null,
int $courseid = 0): array {
$typeprofiles = bigbluebutton_proxy::get_instance_type_profiles();
$typeprofile = empty($instance) ? $typeprofiles[0] : $typeprofiles[$instance->get_type()];
$lang = get_string('locale', 'core_langconfig');
$locale = substr($lang, 0, strpos($lang, '.'));
$tabledata = [
'activity' => empty($instance) ? '' : bigbluebutton_proxy::view_get_activity_status($instance),
'ping_interval' => (int) config::get('waitformoderator_ping_interval') * 1000,
'locale' => substr($locale, 0, strpos($locale, '_')),
'profile_features' => $typeprofile['features'],
'columns' => [],
'data' => '',
];
$hascapabilityincourse = empty($instance) && roles::has_capability_in_course($courseid,
'mod/bigbluebuttonbn:managerecordings');
$data = [];
// Build table content.
foreach ($recordings as $recording) {
$rowtools = $tools;
// Protected recordings may be enabled or disabled from UI through configuration.
if (!(boolean) config::get('recording_protect_editable')) {
$rowtools = array_diff($rowtools, ['protect', 'unprotect']);
}
// Protected recordings is not a standard feature, remove actions when protected flag is not present.
if (in_array('protect', $rowtools) && $recording->get('protected') === null) {
$rowtools = array_diff($rowtools, ['protect', 'unprotect']);
}
$rowdata = self::row($instance, $recording, $rowtools);
if (!empty($rowdata)) {
$data[] = $rowdata;
}
}
$columns = [
[
'key' => 'playback',
'label' => get_string('view_recording_playback', 'bigbluebuttonbn'),
'width' => '125px',
'type' => 'html',
'allowHTML' => true,
],
[
'key' => 'recording',
'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.
$ispreviewenabled = !empty($instance) && self::preview_enabled($instance);
$ispreviewenabled = $ispreviewenabled || $hascapabilityincourse;
if ($ispreviewenabled) {
$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',
'formatter' => 'customDate',
];
$columns[] = [
'key' => 'duration',
'label' => get_string('view_recording_duration', 'bigbluebuttonbn'),
'width' => '50px',
'allowHTML' => false,
'sortable' => true,
];
// Either instance is empty and we must show the toolbar (with restricted content) or we check
// specific rights related to the instance.
$canmanagerecordings = !empty($instance) && $instance->can_manage_recordings();
$canmanagerecordings = $canmanagerecordings || $hascapabilityincourse;
if ($canmanagerecordings) {
$columns[] = [
'key' => 'actionbar',
'label' => get_string('view_recording_actionbar', 'bigbluebuttonbn'),
'width' => '120px',
'type' => 'html',
'allowHTML' => true,
];
}
$tabledata['columns'] = $columns;
$tabledata['data'] = json_encode($data);
return $tabledata;
}
/**
* Helper function builds a row for the data used by the recording table.
*
* TODO: replace this with templates whenever possible so we just
* return the data via the API.
*
* @param instance|null $instance $instance
* @param recording $rec a recording row
* @param array|null $tools
* @param int|null $courseid
* @return stdClass|null
*/
public static function row(?instance $instance, recording $rec, ?array $tools = null, ?int $courseid = 0): ?stdClass {
global $PAGE;
$hascapabilityincourse = empty($instance) && roles::has_capability_in_course($courseid,
'mod/bigbluebuttonbn:managerecordings');
$renderer = $PAGE->get_renderer('mod_bigbluebuttonbn');
foreach ($tools as $key => $tool) {
if ((!empty($instance) && !$instance->can_perform_on_recordings($tool))
|| (empty($instance) && !$hascapabilityincourse)) {
unset($tools[$key]);
}
}
if (!self::include_recording_table_row($instance, $rec)) {
return null;
}
$rowdata = new stdClass();
// Set recording_playback.
$recordingplayback = new recording_row_playback($rec, $instance);
$rowdata->playback = $renderer->render($recordingplayback);
if (empty($instance)) {
// Set activity name.
$rowdata->recording = $rec->get('name');
// Set activity description.
$rowdata->description = $rec->get('description');
} else {
// Set activity name.
$recordingname = new recording_name_editable($rec, $instance);
$rowdata->recording = $renderer->render_inplace_editable($recordingname);
// Set activity description.
$recordingdescription = new recording_description_editable($rec, $instance);
$rowdata->description = $renderer->render_inplace_editable($recordingdescription);
}
if ((!empty($instance) && self::preview_enabled($instance)) || $hascapabilityincourse) {
// Set recording_preview.
$rowdata->preview = '';
if ($rec->get('playbacks')) {
$rowpreview = new recording_row_preview($rec);
$rowdata->preview = $renderer->render($rowpreview);
}
}
// Set date.
$starttime = $rec->get('starttime');
$rowdata->date = !is_null($starttime) ? floatval($starttime) : 0;
// Set duration.
$rowdata->duration = self::row_duration($rec);
// Set actionbar, if user is allowed to manage recordings.
if ((!empty($instance) && $instance->can_manage_recordings()) || $hascapabilityincourse) {
$actionbar = new recording_row_actionbar($rec, $tools);
$rowdata->actionbar = $renderer->render($actionbar);
}
return $rowdata;
}
/**
* Helper function evaluates if recording preview should be included.
*
* @param instance $instance
* @return bool
*/
public static function preview_enabled(instance $instance): bool {
return $instance->get_instance_var('recordings_preview') == '1';
}
/**
* Helper function converts recording duration used in row for the data used by the recording table.
*
* @param recording $recording
* @return int
*/
protected static function row_duration(recording $recording): int {
$playbacks = $recording->get('playbacks');
if (empty($playbacks)) {
return 0;
}
foreach ($playbacks as $playback) {
// Ignore restricted playbacks.
if (array_key_exists('restricted', $playback) && strtolower($playback['restricted']) == 'true') {
continue;
}
// Take the length form the fist playback with an actual value.
if (!empty($playback['length'])) {
return intval($playback['length']);
}
}
return 0;
}
/**
* Helper function to handle yet unknown recording types
*
* @param string $playbacktype : for now presentation, video, statistics, capture, notes, podcast
* @return string the matching language string or a capitalised version of the provided string
*/
public static function type_text(string $playbacktype): string {
// Check first if string exists, and if it does not, just default to the capitalised version of the string.
$text = ucwords($playbacktype);
$typestringid = 'view_recording_format_' . $playbacktype;
if (get_string_manager()->string_exists($typestringid, 'bigbluebuttonbn')) {
$text = get_string($typestringid, 'bigbluebuttonbn');
}
return $text;
}
/**
* Helper function evaluates if recording row should be included in the table.
*
* @param instance|null $instance
* @param recording $rec a bigbluebuttonbn_recordings row
* @return bool
*/
protected static function include_recording_table_row(?instance $instance, recording $rec): bool {
if (empty($instance)) {
return roles::has_capability_in_course($rec->get('courseid'), 'mod/bigbluebuttonbn:managerecordings');
}
// Exclude unpublished recordings, only if user has no rights to manage them.
if (!$rec->get('published') && !$instance->can_manage_recordings()) {
return false;
}
// Imported recordings are always shown as long as they are published.
if ($rec->get('imported')) {
return true;
}
// When show imported recordings only is enabled, exclude all other recordings.
if ($instance->get_recordings_imported() && !$rec->get('imported')) {
return false;
}
// Administrators and moderators are always allowed.
if ($instance->is_admin() || $instance->is_moderator()) {
return true;
}
// When groups are enabled, exclude those to which the user doesn't have access to.
if ($instance->uses_groups() && !$instance->can_manage_recordings()) {
if (groups_get_activity_groupmode($instance->get_cm()) == VISIBLEGROUPS) {
// In case we are in visible group mode, we show all recordings.
return true;
}
// Else we check if the Recording group is the same as the instance. Instance group
// being the group chosen for this instance.
return intval($rec->get('groupid')) === $instance->get_group_id();
}
return true;
}
}
@@ -0,0 +1,283 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\local;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy;
use mod_bigbluebuttonbn\recording;
/**
* Handles the global configuration based on config.php.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
class config {
/** @var string Default bigbluebutton server url */
public const DEFAULT_SERVER_URL = 'https://test-moodle.blindsidenetworks.com/bigbluebutton/';
/** @var string Default bigbluebutton server shared secret */
public const DEFAULT_SHARED_SECRET = '0b21fcaf34673a8c3ec8ed877d76ae34';
/** @var string the default bigbluebutton checksum algorithm */
public const DEFAULT_CHECKSUM_ALGORITHM = 'SHA256';
/** @var array list of supported bigbluebutton checksum algorithm */
const CHECKSUM_ALGORITHMS = [
self::DEFAULT_CHECKSUM_ALGORITHM,
'SHA1',
'SHA512'
];
/**
* Returns moodle version.
*
* @return string
*/
protected static function get_moodle_version_major(): string {
global $CFG;
$versionarray = explode('.', $CFG->version);
return $versionarray[0];
}
/**
* Returns configuration default values.
*
* @return array
*/
protected static function defaultvalues(): array {
return [
'server_url' => '',
'shared_secret' => '',
'voicebridge_editable' => false,
'importrecordings_enabled' => false,
'importrecordings_from_deleted_enabled' => false,
'waitformoderator_default' => false,
'waitformoderator_editable' => true,
'waitformoderator_ping_interval' => '10',
'waitformoderator_cache_ttl' => '60',
'userlimit_default' => '0',
'userlimit_editable' => false,
'preuploadpresentation_editable' => false,
'recordingready_enabled' => false,
'recordingstatus_enabled' => false,
'meetingevents_enabled' => false,
'participant_moderator_default' => '0',
'profile_picture_enabled' => false,
'scheduled_pre_opening' => '10',
'recordings_enabled' => true,
'recordings_deleted_default' => false,
'recordings_deleted_editable' => false,
'recordings_imported_default' => false,
'recordings_imported_editable' => false,
'recordings_preview_default' => true,
'recordings_preview_editable' => false,
'recording_default' => true,
'recording_editable' => true,
'recording_refresh_period' => recording::RECORDING_REFRESH_DEFAULT_PERIOD,
'recording_all_from_start_default' => false,
'recording_all_from_start_editable' => false,
'recording_hide_button_default' => false,
'recording_hide_button_editable' => false,
'recording_protect_editable' => true,
'general_warning_message' => '',
'general_warning_roles' => 'editingteacher,teacher',
'general_warning_box_type' => 'info',
'general_warning_button_text' => '',
'general_warning_button_href' => '',
'general_warning_button_class' => '',
'muteonstart_default' => false,
'muteonstart_editable' => false,
'disablecam_default' => false,
'disablecam_editable' => true,
'disablemic_default' => false,
'disablemic_editable' => true,
'disableprivatechat_default' => false,
'disableprivatechat_editable' => true,
'disablepublicchat_default' => false,
'disablepublicchat_editable' => true,
'disablenote_default' => false,
'disablenote_editable' => true,
'hideuserlist_default' => false,
'hideuserlist_editable' => true,
'welcome_default' => '',
'welcome_editable' => true,
'default_dpa_accepted' => false,
'poll_interval' => bigbluebutton_proxy::DEFAULT_POLL_INTERVAL,
'checksum_algorithm' => self::DEFAULT_CHECKSUM_ALGORITHM,
];
}
/**
* Returns default value for an specific setting.
*
* @param string $setting
* @return string|null
*/
public static function defaultvalue(string $setting): ?string {
$defaultvalues = self::defaultvalues();
if (!array_key_exists($setting, $defaultvalues)) {
return null;
}
return $defaultvalues[$setting];
}
/**
* Returns value for an specific setting.
*
* @param string $setting
* @return string
*/
public static function get(string $setting): string {
global $CFG;
if (isset($CFG->bigbluebuttonbn[$setting])) {
return (string) $CFG->bigbluebuttonbn[$setting];
}
if (isset($CFG->{'bigbluebuttonbn_' . $setting})) {
return (string) $CFG->{'bigbluebuttonbn_' . $setting};
}
return (string) self::defaultvalue($setting);
}
/**
* Validates if recording settings are enabled.
*
* @return bool
*/
public static function recordings_enabled(): bool {
return (boolean) self::get('recordings_enabled');
}
/**
* Validates if imported recording settings are enabled.
*
* @return bool
*/
public static function importrecordings_enabled(): bool {
return (boolean) self::get('importrecordings_enabled');
}
/**
* Check if bbb server credentials are invalid.
*
* @return bool
*/
public static function server_credentials_invalid(): bool {
// Test server credentials across all versions of the plugin are flagged.
$parsedurl = parse_url(self::get('server_url'));
$defaultserverurl = parse_url(self::DEFAULT_SERVER_URL);
if (!isset($parsedurl['host'])) {
return false;
}
if (strpos($parsedurl['host'], $defaultserverurl['host']) === 0) {
return true;
}
if (strpos($parsedurl['host'], 'test-install.blindsidenetworks.com') === 0) {
return true;
}
return false;
}
/**
* Wraps current settings in an array.
*
* @return array
*/
public static function get_options(): array {
return [
'version_major' => self::get_moodle_version_major(),
'voicebridge_editable' => self::get('voicebridge_editable'),
'importrecordings_enabled' => self::get('importrecordings_enabled'),
'importrecordings_from_deleted_enabled' => self::get('importrecordings_from_deleted_enabled'),
'waitformoderator_default' => self::get('waitformoderator_default'),
'waitformoderator_editable' => self::get('waitformoderator_editable'),
'userlimit_default' => self::get('userlimit_default'),
'userlimit_editable' => self::get('userlimit_editable'),
'preuploadpresentation_editable' => self::get('preuploadpresentation_editable'),
'recordings_enabled' => self::get('recordings_enabled'),
'meetingevents_enabled' => self::get('meetingevents_enabled'),
'recordings_deleted_default' => self::get('recordings_deleted_default'),
'recordings_deleted_editable' => self::get('recordings_deleted_editable'),
'recordings_imported_default' => self::get('recordings_imported_default'),
'recordings_imported_editable' => self::get('recordings_imported_editable'),
'recordings_preview_default' => self::get('recordings_preview_default'),
'recordings_preview_editable' => self::get('recordings_preview_editable'),
'recording_default' => self::get('recording_default'),
'recording_editable' => self::get('recording_editable'),
'recording_refresh_period' => self::get('recording_refresh_period'),
'recording_all_from_start_default' => self::get('recording_all_from_start_default'),
'recording_all_from_start_editable' => self::get('recording_all_from_start_editable'),
'recording_hide_button_default' => self::get('recording_hide_button_default'),
'recording_hide_button_editable' => self::get('recording_hide_button_editable'),
'recording_protect_editable' => self::get('recording_protect_editable'),
'general_warning_message' => self::get('general_warning_message'),
'general_warning_box_type' => self::get('general_warning_box_type'),
'general_warning_button_text' => self::get('general_warning_button_text'),
'general_warning_button_href' => self::get('general_warning_button_href'),
'general_warning_button_class' => self::get('general_warning_button_class'),
'muteonstart_editable' => self::get('muteonstart_editable'),
'muteonstart_default' => self::get('muteonstart_default'),
'disablecam_editable' => self::get('disablecam_editable'),
'disablecam_default' => self::get('disablecam_default'),
'disablemic_editable' => self::get('disablemic_editable'),
'disablemic_default' => self::get('disablemic_default'),
'disableprivatechat_editable' => self::get('disableprivatechat_editable'),
'disableprivatechat_default' => self::get('disableprivatechat_default'),
'disablepublicchat_editable' => self::get('disablepublicchat_editable'),
'disablepublicchat_default' => self::get('disablepublicchat_default'),
'disablenote_editable' => self::get('disablenote_editable'),
'disablenote_default' => self::get('disablenote_default'),
'hideuserlist_editable' => self::get('hideuserlist_editable'),
'hideuserlist_default' => self::get('hideuserlist_default'),
'welcome_default' => self::get('welcome_default'),
'welcome_editable' => self::get('welcome_editable'),
'poll_interval' => self::get('poll_interval'),
'guestaccess_enabled' => self::get('guestaccess_enabled'),
];
}
/**
* Helper function returns an array with enabled features for an specific profile type.
*
* @param array $typeprofiles
* @param string|null $type
*
* @return array
*/
public static function get_enabled_features(array $typeprofiles, ?string $type = null): array {
$enabledfeatures = [];
$features = $typeprofiles[instance::TYPE_ALL]['features'];
if (!is_null($type) && key_exists($type, $typeprofiles)) {
$features = $typeprofiles[$type]['features'];
}
$enabledfeatures['showroom'] = (in_array('all', $features) || in_array('showroom', $features));
// Evaluates if recordings are enabled for the Moodle site.
$enabledfeatures['showrecordings'] = false;
if (self::recordings_enabled()) {
$enabledfeatures['showrecordings'] = (in_array('all', $features) || in_array('showrecordings', $features));
}
$enabledfeatures['importrecordings'] = false;
if (self::importrecordings_enabled()) {
$enabledfeatures['importrecordings'] = (in_array('all', $features) || in_array('importrecordings', $features));
}
return $enabledfeatures;
}
}
@@ -0,0 +1,40 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\local\exceptions;
use mod_bigbluebuttonbn\plugin;
/**
* Class bigbluebutton_exception generic exception. This is supposed to be recoverable.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
*/
class bigbluebutton_exception extends \moodle_exception {
/**
* Constructor
*
* @param string $errorcode The name of the string from error.php to print
* @param mixed $additionalinfo Extra words and phrases that might be required in the error string
*/
public function __construct($errorcode, $additionalinfo = null) {
parent::__construct($errorcode, plugin::COMPONENT, '', $additionalinfo);
}
}
@@ -0,0 +1,38 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\local\exceptions;
use mod_bigbluebuttonbn\plugin;
/**
* The mod_bigbluebuttonbn cannot join meeting exception.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
*/
class meeting_join_exception extends \moodle_exception {
/**
* Constructor
*
* @param string $errorcode The name of the string from error.php to print
*/
public function __construct($errorcode) {
parent::__construct($errorcode, plugin::COMPONENT);
}
}
@@ -0,0 +1,55 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\local\exceptions;
/**
* Class server_not_available_exception
*
* This kind of error cannot be recovered and should be displayed to the user
* signaling that there is an error in the configuration.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
*/
class server_not_available_exception extends \moodle_exception {
/**
* Constructor
*
* @param string $errorcode The name of the string from error.php to print
* @param string $module name of module
* @param string $link The url where the user will be prompted to continue. If no url is provided the user will be directed to
* the site index page.
* @param mixed $a Extra words and phrases that might be required in the error string
* @param string $debuginfo optional debugging information
*/
public function __construct($errorcode, $module = '', $link = '', $a = null, $debuginfo = null) {
global $CFG;
$hasdebugdeveloper = (
isset($CFG->debugdisplay) &&
isset($CFG->debug) &&
$CFG->debugdisplay &&
$CFG->debug === DEBUG_DEVELOPER
);
if ($hasdebugdeveloper && is_null($debuginfo)) {
$debuginfo = $this->getTraceAsString();
}
parent::__construct($errorcode, $module, $link, $a, $debuginfo);
}
}
@@ -0,0 +1,43 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\local\extension;
/**
* A single action class to mutate the action URL.
*
* @package mod_bigbluebuttonbn
* @copyright 2023 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent@call-learning.fr)
*/
class action_url_addons {
/**
* Mutate the action URL.
*
* By design:
* 1. we should only add parameters
* 2. we cannot count on the order the subplugins are called
*
* @param string $action
* @param array $data
* @param array $metadata
* @return array associative array with the additional data and metadata (indexed by 'data' and
* 'metadata' keys).
*/
public function execute(string $action = '', array $data = [], array $metadata = []): array {
return ['data' => [], 'metadata' => []];
}
}
@@ -0,0 +1,79 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\local\extension;
use cm_info;
/**
* A class to deal with completion rules addons in a subplugin
*
* @package mod_bigbluebuttonbn
* @copyright 2023 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent@call-learning.fr)
*/
abstract class custom_completion_addons {
/** @var cm_info The course module information object. */
protected $cm;
/** @var int The user's ID. */
protected $userid;
/** @var array The current state of core completion */
protected $completionstate;
/**
* activity_custom_completion constructor.
*
* @param cm_info $cm
* @param int $userid
* @param array|null $completionstate The current state of the core completion criteria
*/
public function __construct(cm_info $cm, int $userid, ?array $completionstate = null) {
$this->cm = $cm;
$this->userid = $userid;
$this->completionstate = $completionstate;
}
/**
* Fetches the completion state for a given completion rule.
*
* @param string $rule The completion rule.
* @return int The completion state.
*/
abstract public function get_state(string $rule): int;
/**
* Fetch the list of custom completion rules that this module defines.
*
* @return array
*/
abstract public static function get_defined_custom_rules(): array;
/**
* Returns an associative array of the descriptions of custom completion rules.
*
* @return array
*/
abstract public function get_custom_rule_descriptions(): array;
/**
* Returns an array of all completion rules, in the order they should be displayed to users.
*
* @return array
*/
abstract public function get_sort_order(): array;
}
@@ -0,0 +1,113 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\local\extension;
use stdClass;
/**
* A class for the main mod form extension
*
* @package mod_bigbluebuttonbn
* @copyright 2023 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent@call-learning.fr)
*/
abstract class mod_form_addons {
/**
* @var \MoodleQuickForm|null moodle form
*/
protected $mform = null;
/**
* @var stdClass|null $bigbluebuttonbndata BigBlueButton data if any
*/
protected $bigbluebuttonbndata = null;
/**
* @var string|null $suffix suffix for form elements
*/
protected $suffix = null;
/**
* Constructor
*
* @param \MoodleQuickForm $mform
* @param stdClass|null $bigbluebuttonbndata
* @param string|null $suffix
*/
public function __construct(\MoodleQuickForm &$mform, ?stdClass $bigbluebuttonbndata = null, string $suffix = null) {
$this->mform = $mform;
$this->bigbluebuttonbndata = $bigbluebuttonbndata;
$this->suffix = $suffix;
}
/**
* Add new form field definition
*/
abstract public function add_fields(): void;
/**
* Validate form and returns an array of errors indexed by field name
*
* @param array $data
* @param array $files
* @return array
*/
abstract public function validation(array $data, array $files): array;
/**
* Allows modules to modify the data returned by form get_data().
* This method is also called in the bulk activity completion form.
*
* Only available on moodleform_mod.
*
* @param stdClass $data passed by reference
*/
abstract public function data_postprocessing(\stdClass &$data): void;
/**
* Can be overridden to add custom completion rules if the module wishes
* them. If overriding this, you should also override completion_rule_enabled.
* <p>
* Just add elements to the form as needed and return the list of IDs. The
* system will call disabledIf and handle other behaviour for each returned
* ID.
*
* @return string[] Array of string IDs of added items, empty array if none
*/
abstract public function add_completion_rules(): array;
/**
* Called during validation. Override to indicate, based on the data, whether
* a custom completion rule is enabled (selected).
*
* @param array $data Input data (not yet validated)
* @return bool True if one or more rules is enabled, false if none are;
* default returns false
*/
public function completion_rule_enabled(array $data): bool {
return false;
}
/**
* Form adjustments after setting data
*
* @return void
*/
public function definition_after_data() {
// Nothing for now.
}
}
@@ -0,0 +1,60 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\local\extension;
use stdClass;
/**
* Class defining a way to deal with instance save/update/delete in extension
*
* @package mod_bigbluebuttonbn
* @copyright 2023 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent@call-learning.fr)
*/
class mod_instance_helper {
/**
* Runs any processes that must run before a bigbluebuttonbn insert/update.
*
* @param stdClass $bigbluebuttonbn BigBlueButtonBN form data
*/
public function add_instance(stdClass $bigbluebuttonbn) {
// Nothing for now.
}
/**
* Runs any processes that must be run after a bigbluebuttonbn insert/update.
*
* @param stdClass $bigbluebuttonbn BigBlueButtonBN form data
*/
public function update_instance(stdClass $bigbluebuttonbn): void {
// Nothing for now.
}
/**
* Runs any processes that must be run after a bigbluebuttonbn delete.
*
* @param int $cmid
*/
public function delete_instance(int $cmid): void {
}
/**
* Get any join table name that is used to store additional data for the instance.
* @return string[]
*/
public function get_join_tables(): array {
return [];
}
}
@@ -0,0 +1,324 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* The mod_bigbluebuttonbn files helper
*
* @package mod_bigbluebuttonbn
* @copyright 2021 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
*/
namespace mod_bigbluebuttonbn\local\helpers;
use cache;
use cache_store;
use context;
use context_module;
use context_system;
use mod_bigbluebuttonbn\instance;
use moodle_url;
use stdClass;
/**
* Utility class for all files routines helper
*
* @package mod_bigbluebuttonbn
* @copyright 2021 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class files {
/**
* Helper for validating pluginfile.
*
* @param stdClass $context context object
* @param string $filearea file area
*
* @return bool|null false if file not valid
*/
public static function pluginfile_valid(stdClass $context, string $filearea): ?bool {
// Can be in context module or in context_system (if is the presentation by default).
if (!in_array($context->contextlevel, [CONTEXT_MODULE, CONTEXT_SYSTEM])) {
return false;
}
if (!array_key_exists($filearea, self::get_file_areas())) {
return false;
}
return true;
}
/**
* Helper for getting pluginfile.
*
* @param stdClass|null $course course object
* @param stdClass|null $cm course module object
* @param context $context context object
* @param string $filearea file area
* @param array $args extra arguments
*
* @return \stored_file|bool
*/
public static function pluginfile_file(?stdClass $course, ?stdClass $cm, context $context, string $filearea, array $args) {
$filename = self::get_plugin_filename($course, $cm, $context, $args);
if (!$filename) {
return false;
}
$fullpath = "/$context->id/mod_bigbluebuttonbn/$filearea/0/" . $filename;
$fs = get_file_storage();
$file = $fs->get_file_by_hash(sha1($fullpath));
if (!$file || $file->is_directory()) {
return false;
}
return $file;
}
/**
* Get a full path to the file attached as a preuploaded presentation
* or if there is none, set the presentation field will be set to blank.
*
* @param stdClass $bigbluebuttonformdata BigBlueButtonBN form data
* Note that $bigbluebuttonformdata->presentation is the id of the filearea whereas the bbb instance table
* stores the file name/path
* @return string
*/
public static function save_media_file(stdClass &$bigbluebuttonformdata): string {
if (!isset($bigbluebuttonformdata->presentation) || $bigbluebuttonformdata->presentation == '') {
return '';
}
$context = context_module::instance($bigbluebuttonformdata->coursemodule);
// Set the filestorage object.
$fs = get_file_storage();
// Save the file if it exists that is currently in the draft area.
file_save_draft_area_files($bigbluebuttonformdata->presentation, $context->id, 'mod_bigbluebuttonbn', 'presentation', 0);
// Get the file if it exists.
$files = $fs->get_area_files(
$context->id,
'mod_bigbluebuttonbn',
'presentation',
0,
'itemid, filepath, filename',
false
);
// Check that there is a file to process.
$filesrc = '';
if (count($files) == 1) {
// Get the first (and only) file.
$file = reset($files);
$filesrc = '/' . $file->get_filename();
}
return $filesrc;
}
/**
* Helper return array containing the file descriptor for a preuploaded presentation.
*
* @param context $context
* @param string $presentation matching presentation file name
* @param int $id bigbluebutton instance id
* @param bool $withnonce add nonce to the url
* @return array|null the representation of the presentation as an associative array
*/
public static function get_presentation(context $context, string $presentation, $id = null, $withnonce = false): ?array {
global $CFG;
$fs = get_file_storage();
$files = [];
$defaultpresentation = $fs->get_area_files(
context_system::instance()->id,
'mod_bigbluebuttonbn',
'presentationdefault',
0,
"filename",
false
);
$activitypresentation = $files = $fs->get_area_files(
$context->id,
'mod_bigbluebuttonbn',
'presentation',
false,
'itemid, filepath, filename',
false
);
// Presentation upload logic based on config settings.
if (empty($defaultpresentation)) {
if (empty($activitypresentation) || !\mod_bigbluebuttonbn\local\config::get('preuploadpresentation_editable')) {
return null;
}
$files = $activitypresentation;
} else {
if (empty($activitypresentation) || !\mod_bigbluebuttonbn\local\config::get('preuploadpresentation_editable')) {
$files = $defaultpresentation;
$id = null;
} else {
$files = $activitypresentation;
}
}
$pnoncevalue = 0;
if ($withnonce) {
$nonceid = 0;
if (!is_null($id)) {
$instance = instance::get_from_instanceid($id);
$nonceid = $instance->get_instance_id();
}
$pnoncevalue = self::generate_nonce($nonceid);
}
$file = null;
foreach ($files as $f) {
if (basename($f->get_filename()) == basename($presentation)) {
$file = $f;
}
}
if (!$file && !empty($files)) {
$file = reset($files);
}
if (empty($file)) {
return null; // File was not found.
}
// Note: $pnoncevalue is an int.
$url = moodle_url::make_pluginfile_url(
$file->get_contextid(),
$file->get_component(),
$file->get_filearea(),
$withnonce ? $pnoncevalue : null, // Hack: item id as a nonce.
$file->get_filepath(),
$file->get_filename()
);
return [
'icondesc' => get_mimetype_description($file),
'iconname' => file_file_icon($file),
'name' => $file->get_filename(),
'url' => $url->out(false),
];
}
/**
* Helper for getting pluginfile name.
*
* @param stdClass|null $course course object
* @param stdClass|null $cm course module object
* @param context $context context object
* @param array $args extra arguments
*
* @return string|null
*/
public static function get_plugin_filename(?stdClass $course, ?stdClass $cm, context $context, array $args): ?string {
global $DB;
if ($context->contextlevel != CONTEXT_SYSTEM) {
// Plugin has a file to use as default in general setting.
// The difference with the standard bigbluebuttonbn_pluginfile_filename() are.
// - Context is system, so we don't need to check the cmid in this case.
// - The area is "presentationdefault_cache".
if (!$DB->get_record('bigbluebuttonbn', ['id' => $cm->instance])) {
return null;
}
}
// Plugin has a file to use as default in general setting.
// The difference with the standard bigbluebuttonbn_pluginfile_filename() are.
// - Context is system, so we don't need to check the cmid in this case.
// - The area is "presentationdefault_cache".
if (count($args) > 1) {
$id = 0;
if ($cm) {
$instance = instance::get_from_cmid($cm->id);
$id = $instance->get_instance_id();
}
$actualnonce = self::get_nonce($id);
return ($args['0'] == $actualnonce) ? $args['1'] : null;
}
if (!empty($course)) {
require_course_login($course, true, $cm, true, true);
} else {
require_login(null, true, $cm, true, true);
}
if (!has_capability('mod/bigbluebuttonbn:join', $context)) {
return null;
}
return implode('/', $args);
}
/**
* Helper generates a salt used for the preuploaded presentation callback url.
*
* @param int $id
* @return int
*/
protected static function get_nonce(int $id): int {
$cache = static::get_nonce_cache();
$pnoncekey = sha1($id);
$existingnoncedata = $cache->get($pnoncekey);
if ($existingnoncedata) {
if ($existingnoncedata->counter > 0) {
$existingnoncedata->counter--;
$cache->set($pnoncekey, $existingnoncedata);
return $existingnoncedata->nonce;
}
}
// The item id was adapted for granting public access to the presentation once in order to allow BigBlueButton to gather
// the file once.
return static::generate_nonce($id);
}
/**
* Generate a nonce and store it in the cache
*
* @param int $id
* @return int
*/
protected static function generate_nonce($id): int {
$cache = static::get_nonce_cache();
$pnoncekey = sha1($id);
// The item id was adapted for granting public access to the presentation once in order to allow BigBlueButton to gather
// the file once.
$pnoncevalue = ((int) microtime()) + mt_rand();
$cache->set($pnoncekey, (object) ['nonce' => $pnoncevalue, 'counter' => 2]);
return $pnoncevalue;
}
/**
* Get cache for nonce
*
* @return \cache_application|\cache_session|cache_store
*/
private static function get_nonce_cache() {
return cache::make_from_params(
cache_store::MODE_APPLICATION,
'mod_bigbluebuttonbn',
'presentation_cache'
);
}
/**
* Returns an array of file areas.
*
* @return array a list of available file areas
*
*/
protected static function get_file_areas(): array {
$areas = [];
$areas['presentation'] = get_string('mod_form_block_presentation', 'bigbluebuttonbn');
$areas['presentationdefault'] = get_string('mod_form_block_presentation_default', 'bigbluebuttonbn');
return $areas;
}
}
@@ -0,0 +1,209 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\local\helpers;
use calendar_event;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\logger;
use mod_bigbluebuttonbn\plugin;
use stdClass;
/**
* Utility class for all instance (module) routines helper.
*
* @package mod_bigbluebuttonbn
* @copyright 2021 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
*/
class mod_helper {
/**
* Runs any processes that must run before a bigbluebuttonbn insert/update.
*
* @param stdClass $bigbluebuttonbn BigBlueButtonBN form data
**/
public static function process_pre_save(stdClass $bigbluebuttonbn) {
self::process_pre_save_instance($bigbluebuttonbn);
self::process_pre_save_checkboxes($bigbluebuttonbn);
self::process_pre_save_common($bigbluebuttonbn);
$bigbluebuttonbn->participants = htmlspecialchars_decode($bigbluebuttonbn->participants, ENT_COMPAT);
}
/**
* Runs process for defining the instance (insert/update).
*
* @param stdClass $bigbluebuttonbn BigBlueButtonBN form data
**/
protected static function process_pre_save_instance(stdClass $bigbluebuttonbn): void {
$bigbluebuttonbn->timemodified = time();
if ((integer) $bigbluebuttonbn->instance == 0) {
$bigbluebuttonbn->meetingid = 0;
$bigbluebuttonbn->timecreated = time();
$bigbluebuttonbn->timemodified = 0;
// As it is a new activity, assign passwords.
$bigbluebuttonbn->moderatorpass = plugin::random_password(12);
$bigbluebuttonbn->viewerpass = plugin::random_password(12, $bigbluebuttonbn->moderatorpass);
}
}
/**
* Runs process for assigning default value to checkboxes.
*
* @param stdClass $bigbluebuttonbn BigBlueButtonBN form data
**/
protected static function process_pre_save_checkboxes($bigbluebuttonbn) {
if (!isset($bigbluebuttonbn->wait)) {
$bigbluebuttonbn->wait = 0;
}
if (!isset($bigbluebuttonbn->record)) {
$bigbluebuttonbn->record = 0;
}
if (!isset($bigbluebuttonbn->recordallfromstart)) {
$bigbluebuttonbn->recordallfromstart = 0;
}
if (!isset($bigbluebuttonbn->recordhidebutton)) {
$bigbluebuttonbn->recordhidebutton = 0;
}
if (!isset($bigbluebuttonbn->recordings_html)) {
$bigbluebuttonbn->recordings_html = 0;
}
if (!isset($bigbluebuttonbn->recordings_deleted)) {
$bigbluebuttonbn->recordings_deleted = 0;
}
if (!isset($bigbluebuttonbn->recordings_imported)) {
$bigbluebuttonbn->recordings_imported = 0;
}
if (!isset($bigbluebuttonbn->recordings_preview)) {
$bigbluebuttonbn->recordings_preview = 0;
}
if (!isset($bigbluebuttonbn->muteonstart)) {
$bigbluebuttonbn->muteonstart = 0;
}
if (!isset($bigbluebuttonbn->disablecam)) {
$bigbluebuttonbn->disablecam = 0;
}
if (!isset($bigbluebuttonbn->disablemic)) {
$bigbluebuttonbn->disablemic = 0;
}
if (!isset($bigbluebuttonbn->disableprivatechat)) {
$bigbluebuttonbn->disableprivatechat = 0;
}
if (!isset($bigbluebuttonbn->disablepublicchat)) {
$bigbluebuttonbn->disablepublicchat = 0;
}
if (!isset($bigbluebuttonbn->disablenote)) {
$bigbluebuttonbn->disablenote = 0;
}
if (!isset($bigbluebuttonbn->hideuserlist)) {
$bigbluebuttonbn->hideuserlist = 0;
}
}
/**
* Runs process for wipping common settings when 'recordings only'.
*
* @param stdClass $bigbluebuttonbn BigBlueButtonBN form data
**/
protected static function process_pre_save_common(stdClass $bigbluebuttonbn): void {
// Make sure common settings are removed when 'recordings only'.
if ($bigbluebuttonbn->type == instance::TYPE_RECORDING_ONLY) {
$bigbluebuttonbn->groupmode = 0;
$bigbluebuttonbn->groupingid = 0;
}
}
/**
* Runs any processes that must be run after a bigbluebuttonbn insert/update.
*
* @param stdClass $bigbluebuttonbn BigBlueButtonBN form data
**/
public static function process_post_save(stdClass $bigbluebuttonbn): void {
self::process_post_save_event($bigbluebuttonbn);
self::process_post_save_completion($bigbluebuttonbn);
}
/**
* Generates an event after a bigbluebuttonbn insert/update.
*
* @param stdClass $bigbluebuttonbn BigBlueButtonBN form data
**/
protected static function process_post_save_event(stdClass $bigbluebuttonbn): void {
global $CFG, $DB;
require_once($CFG->dirroot . '/calendar/lib.php');
$eventid = $DB->get_field('event', 'id', [
'modulename' => 'bigbluebuttonbn',
'instance' => $bigbluebuttonbn->id,
'eventtype' => logger::EVENT_MEETING_START
]);
// Delete the event from calendar when/if openingtime is NOT set.
if (!isset($bigbluebuttonbn->openingtime) || !$bigbluebuttonbn->openingtime) {
if ($eventid) {
$calendarevent = calendar_event::load($eventid);
$calendarevent->delete();
}
return;
}
// Add event to the calendar as openingtime is set.
$event = (object) [
'eventtype' => logger::EVENT_MEETING_START,
'type' => CALENDAR_EVENT_TYPE_ACTION,
'name' => get_string('calendarstarts', 'bigbluebuttonbn', $bigbluebuttonbn->name),
'description' => format_module_intro('bigbluebuttonbn', $bigbluebuttonbn, $bigbluebuttonbn->coursemodule, false),
'format' => FORMAT_HTML,
'courseid' => $bigbluebuttonbn->course,
'groupid' => 0,
'userid' => 0,
'modulename' => 'bigbluebuttonbn',
'instance' => $bigbluebuttonbn->id,
'timestart' => $bigbluebuttonbn->openingtime,
'timeduration' => 0,
'timesort' => $bigbluebuttonbn->openingtime,
'visible' => instance_is_visible('bigbluebuttonbn', $bigbluebuttonbn),
'priority' => null,
];
// Update the event in calendar when/if eventid was found.
if ($eventid) {
$event->id = $eventid;
$calendarevent = calendar_event::load($eventid);
$calendarevent->update($event);
return;
}
calendar_event::create($event);
}
/**
* Generates an event after a bigbluebuttonbn activity is completed.
*
* @param stdClass $bigbluebuttonbn BigBlueButtonBN form data
**/
protected static function process_post_save_completion(stdClass $bigbluebuttonbn): void {
if (empty($bigbluebuttonbn->completionexpected)) {
return;
}
\core_completion\api::update_completion_date_event(
$bigbluebuttonbn->coursemodule,
'bigbluebuttonbn',
$bigbluebuttonbn->id,
$bigbluebuttonbn->completionexpected
);
}
}
@@ -0,0 +1,131 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* The mod_bigbluebuttonbn resetting instance helper
*
* @package mod_bigbluebuttonbn
* @copyright 2021 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
*/
namespace mod_bigbluebuttonbn\local\helpers;
use context_module;
use core_tag_tag;
use mod_bigbluebuttonbn\local\config;
use mod_bigbluebuttonbn\recording;
/**
* Utility class for resetting instance routines helper
*
* @package mod_bigbluebuttonbn
* @copyright 2021 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class reset {
/**
* Used by the reset_course_userdata for deleting recordings
*
* This will delete recordings in the database and not in the remote BBB server.
*
* @param int $courseid
*/
public static function reset_recordings(int $courseid): void {
// Criteria for search : courseid or bigbluebuttonbn=null or subset=false or includedeleted=true.
$recordings = recording::get_recordings_for_course(
$courseid,
[], // Exclude itself.
false,
true
);
if ($recordings) {
// Remove all the recordings.
foreach ($recordings as $recording) {
$recording->delete();
}
}
}
/**
* Used by the reset_course_userdata for deleting tags linked to bigbluebuttonbn instances in the course.
*
* @param int $courseid
*/
public static function reset_tags(int $courseid): void {
global $DB;
// Remove all the tags linked to the room/activities in this course.
if ($bigbluebuttonbns = $DB->get_records('bigbluebuttonbn', ['course' => $courseid])) {
foreach ($bigbluebuttonbns as $bigbluebuttonbn) {
if (!$cm = get_coursemodule_from_instance('bigbluebuttonbn', $bigbluebuttonbn->id, $courseid)) {
continue;
}
$context = context_module::instance($cm->id);
core_tag_tag::delete_instances('mod_bigbluebuttonbn', null, $context->id);
}
}
}
/**
* Used by the reset_course_userdata for deleting events linked to bigbluebuttonbn instances in the course.
*
* @param string $courseid
* @return bool status
*/
public static function reset_events($courseid) {
global $DB;
// Remove all the events.
return $DB->delete_records('event', ['modulename' => 'bigbluebuttonbn', 'courseid' => $courseid]);
}
/**
* Returns status used on every defined reset action.
*
* @param string $item
* @return array status array
*/
public static function reset_getstatus(string $item): array {
return ['component' => get_string('modulenameplural', 'bigbluebuttonbn'),
'item' => get_string("removed{$item}", 'bigbluebuttonbn'),
'error' => false];
}
/**
* Define items to be reset by course/reset.php
*
* @return array
*/
public static function reset_course_items(): array {
$items = ["events" => 0, "tags" => 0, "logs" => 0];
// Include recordings only if enabled.
if ((boolean) config::recordings_enabled()) {
$items["recordings"] = 0;
}
return $items;
}
/**
* Reset logs for each BBB instance of this course
*
* @param int $courseid
* @return bool status
*/
public static function reset_logs(int $courseid) {
global $DB;
return $DB->delete_records('bigbluebuttonbn_logs', ['courseid' => $courseid]);
}
}
@@ -0,0 +1,452 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* The mod_bigbluebuttonbn roles helper
*
* @package mod_bigbluebuttonbn
* @copyright 2021 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
*/
namespace mod_bigbluebuttonbn\local\helpers;
use cache;
use cache_store;
use context;
use context_course;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy;
use stdClass;
/**
* Utility class for all roles routines helper
*
* @package mod_bigbluebuttonbn
* @copyright 2021 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class roles {
/** @var int The bigbluebutton viewer role */
public const ROLE_VIEWER = 'viewer';
/** @var string The bigbluebutton moderator role */
public const ROLE_MODERATOR = 'moderator';
/**
* Returns user roles in a context.
*
* @param context $context
* @param int $userid
*
* @return array $userroles
*/
public static function get_user_roles(context $context, int $userid) {
global $DB;
$userroles = get_user_roles($context, $userid);
if ($userroles) {
$where = '';
foreach ($userroles as $userrole) {
$where .= (empty($where) ? ' WHERE' : ' OR') . ' id=' . $userrole->roleid;
}
$userroles = $DB->get_records_sql('SELECT * FROM {role}' . $where);
}
return $userroles;
}
/**
* Returns guest role wrapped in an array.
*
* @return array
*/
protected static function get_guest_role() {
$guestrole = get_guest_role();
return [$guestrole->id => $guestrole];
}
/**
* Returns an array containing all the users in a context wrapped for html select element.
*
* @param context_course $context
* @param null $bbactivity
* @return array $users
*/
public static function get_users_array(context_course $context, $bbactivity = null) {
// CONTRIB-7972, check the group of current user and course group mode.
$groups = null;
$users = (array) get_enrolled_users($context, '', 0, 'u.*', null, 0, 0, true);
$course = get_course($context->instanceid);
$groupmode = groups_get_course_groupmode($course);
if ($bbactivity) {
list($bbcourse, $cm) = get_course_and_cm_from_instance($bbactivity->id, 'bigbluebuttonbn');
$groupmode = groups_get_activity_groupmode($cm);
}
if ($groupmode == SEPARATEGROUPS && !has_capability('moodle/site:accessallgroups', $context)) {
global $USER;
$groups = groups_get_all_groups($course->id, $USER->id);
$users = [];
foreach ($groups as $g) {
$users += (array) get_enrolled_users($context, '', $g->id, 'u.*', null, 0, 0, true);
}
}
return array_map(
function($u) {
return ['id' => $u->id, 'name' => fullname($u)];
},
$users);
}
/**
* Can do some administration in this course, likely manage recordings
*
* @param int $courseid
* @param string $capability
*/
public static function has_capability_in_course(int $courseid, string $capability) {
global $DB;
if (empty($courseid) || !$DB->record_exists('course', ['id' => $courseid])) {
return has_capability('moodle/site:config', \context_system::instance());
}
$coursecontext = context_course::instance($courseid);
return has_capability($capability, $coursecontext);
}
/**
* Returns an array containing all the roles in a context.
*
* @param context|null $context $context
* @param bool|null $onlyviewableroles
*
* @return array $roles
*/
public static function get_roles(?context $context = null, ?bool $onlyviewableroles = true) {
global $CFG;
if ($onlyviewableroles == true && $CFG->branch >= 35) {
$roles = (array) get_viewable_roles($context);
foreach ($roles as $key => $value) {
$roles[$key] = $value;
}
} else {
$roles = (array) role_get_names($context);
foreach ($roles as $key => $value) {
$roles[$key] = $value->localname;
}
}
return $roles;
}
/**
* Returns an array containing all the roles in a context wrapped for html select element.
*
* @param context|null $context $context
* @param bool $onlyviewableroles
*
* @return array $users
*/
protected static function get_roles_select(context $context = null, bool $onlyviewableroles = true) {
global $CFG;
if ($onlyviewableroles == true && $CFG->branch >= 35) {
$roles = (array) get_viewable_roles($context);
foreach ($roles as $key => $value) {
$roles[$key] = ['id' => $key, 'name' => $value];
}
} else {
$roles = (array) role_get_names($context);
foreach ($roles as $key => $value) {
$roles[$key] = ['id' => $value->id, 'name' => $value->localname];
}
}
return $roles;
}
/**
* Returns role that corresponds to an id.
*
* @param string|integer $id
*
* @return stdClass|null $role
*/
protected static function get_role($id): ?stdClass {
$roles = (array) role_get_names();
if (is_numeric($id) && isset($roles[$id])) {
return (object) $roles[$id];
}
foreach ($roles as $role) {
if ($role->shortname == $id) {
return $role;
}
}
return null;
}
/**
* Returns an array to populate a list of participants used in mod_form.js.
*
* @param context $context
* @param null|stdClass $bbactivity
* @return array $data
*/
public static function get_participant_data(context $context, ?stdClass $bbactivity = null) {
$data = [
'all' => [
'name' => get_string('mod_form_field_participant_list_type_all', 'bigbluebuttonbn'),
'children' => []
],
];
$data['role'] = [
'name' => get_string('mod_form_field_participant_list_type_role', 'bigbluebuttonbn'),
'children' => self::get_roles_select($context, true)
];
$data['user'] = [
'name' => get_string('mod_form_field_participant_list_type_user', 'bigbluebuttonbn'),
'children' => self::get_users_array($context, $bbactivity),
];
return $data;
}
/**
* Returns an array to populate a list of participants used in mod_form.php.
*
* @param stdClass|null $bigbluebuttonbn
* @param context $context
*
* @return array
*/
public static function get_participant_list(?stdClass $bigbluebuttonbn, context $context): array {
global $USER;
if ($bigbluebuttonbn == null) {
return self::get_participant_rules_encoded(
self::get_participant_list_default($context, $USER->id)
);
}
if (empty($bigbluebuttonbn->participants)) {
$bigbluebuttonbn->participants = "[]";
}
$rules = json_decode($bigbluebuttonbn->participants, true);
if (empty($rules)) {
$rules = self::get_participant_list_default($context,
bigbluebutton_proxy::get_instance_ownerid($bigbluebuttonbn));
}
return self::get_participant_rules_encoded($rules);
}
/**
* Returns an array to populate a list of participants used in mod_form.php with default values.
*
* @param context $context
* @param int|null $ownerid
*
* @return array
*/
protected static function get_participant_list_default(context $context, ?int $ownerid = null) {
$participantlist = [];
$participantlist[] = [
'selectiontype' => 'all',
'selectionid' => 'all',
'role' => self::ROLE_VIEWER,
];
$defaultrules = explode(',', \mod_bigbluebuttonbn\local\config::get('participant_moderator_default'));
foreach ($defaultrules as $defaultrule) {
if ($defaultrule == '0') {
if (!empty($ownerid) && is_enrolled($context, $ownerid)) {
$participantlist[] = [
'selectiontype' => 'user',
'selectionid' => (string) $ownerid,
'role' => self::ROLE_MODERATOR];
}
continue;
}
$participantlist[] = [
'selectiontype' => 'role',
'selectionid' => $defaultrule,
'role' => self::ROLE_MODERATOR];
}
return $participantlist;
}
/**
* Returns an array to populate a list of participants used in mod_form.php with bigbluebuttonbn values.
*
* @param array $rules
*
* @return array
*/
protected static function get_participant_rules_encoded(array $rules): array {
foreach ($rules as $key => $rule) {
if ($rule['selectiontype'] !== 'role' || is_numeric($rule['selectionid'])) {
continue;
}
$role = self::get_role($rule['selectionid']);
if ($role == null) {
unset($rules[$key]);
continue;
}
$rule['selectionid'] = $role->id;
$rules[$key] = $rule;
}
return $rules;
}
/**
* Returns an array to populate a list of participant_selection used in mod_form.php.
*
* @return array
*/
public static function get_participant_selection_data(): array {
return [
'type_options' => [
'all' => get_string('mod_form_field_participant_list_type_all', 'bigbluebuttonbn'),
'role' => get_string('mod_form_field_participant_list_type_role', 'bigbluebuttonbn'),
'user' => get_string('mod_form_field_participant_list_type_user', 'bigbluebuttonbn'),
],
'type_selected' => 'all',
'options' => ['all' => '---------------'],
'selected' => 'all',
];
}
/**
* Evaluate if a user in a context is moderator based on roles and participation rules.
*
* @param context $context
* @param array $participantlist
* @param int $userid
*
* @return bool
*/
public static function is_moderator(context $context, array $participantlist, ?int $userid = null): bool {
global $USER;
// If an admin, then also a moderator.
if (has_capability('moodle/site:config', $context)) {
return true;
}
if (!is_array($participantlist)) {
return false;
}
if (empty($userid)) {
$userid = $USER->id;
}
$userroles = self::get_guest_role();
if (!isguestuser()) {
$userroles = self::get_user_roles($context, $userid);
}
return self::is_moderator_validator($participantlist, $userid, $userroles);
}
/**
* Iterates participant list rules to evaluate if a user is moderator.
*
* @param array $participantlist
* @param int $userid
* @param array $userroles
*
* @return bool
*/
protected static function is_moderator_validator(array $participantlist, int $userid, array $userroles): bool {
// Iterate participant rules.
foreach ($participantlist as $participant) {
if (self::is_moderator_validate_rule($participant, $userid, $userroles)) {
return true;
}
}
return false;
}
/**
* Evaluate if a user is moderator based on roles and a particular participation rule.
*
* @param array $participant
* @param int $userid
* @param array $userroles
*
* @return bool
*/
protected static function is_moderator_validate_rule(array $participant, int $userid, array $userroles): bool {
if ($participant['role'] == self::ROLE_VIEWER) {
return false;
}
// Validation for the 'all' rule.
if ($participant['selectiontype'] == 'all') {
return true;
}
// Validation for a 'user' rule.
if ($participant['selectiontype'] == 'user') {
if ($participant['selectionid'] == $userid) {
return true;
}
return false;
}
// Validation for a 'role' rule.
$role = self::get_role($participant['selectionid']);
if ($role != null && array_key_exists($role->id, $userroles)) {
return true;
}
return false;
}
/**
* Updates the meeting info cached object when a participant has joined.
*
* @param string $meetingid
* @param bool $ismoderator
*
* @return void
*/
public static function participant_joined(string $meetingid, bool $ismoderator): void {
$cache = cache::make_from_params(cache_store::MODE_APPLICATION, 'mod_bigbluebuttonbn', 'meetings_cache');
$result = $cache->get($meetingid);
$meetinginfo = json_decode($result['meeting_info']);
$meetinginfo->participantCount += 1;
if ($ismoderator) {
$meetinginfo->moderatorCount += 1;
}
$cache->set($meetingid, ['creation_time' => $result['creation_time'],
'meeting_info' => json_encode($meetinginfo)]);
}
/**
* Helper function returns a list of courses a user has access to, wrapped in an array that can be used
* by a html select.
*
* @param instance $instance
* @return array
*/
public static function import_get_courses_for_select(instance $instance): array {
if ($instance->is_admin()) {
$courses = get_courses('all', 'c.fullname ASC');
// It includes the name of the site as a course (category 0), so remove the first one.
unset($courses['1']);
} else {
$courses = enrol_get_users_courses($instance->get_user_id(), false, 'id,shortname,fullname');
}
$courses = array_filter($courses, function($course) {
$modules = get_fast_modinfo($course->id);
return !empty($modules->instances['bigbluebuttonbn']);
});
$coursesforselect = [];
foreach ($courses as $course) {
$coursesforselect[$course->id] = $course->fullname . " (" . $course->shortname . ")";
}
return $coursesforselect;
}
}
@@ -0,0 +1,73 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\local\helpers;
use cm_info;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\logger;
use stdClass;
/**
* Utility class for all user information
*
* Used mainly in user_outline and user_complete
*
* @package mod_bigbluebuttonbn
* @copyright 2022 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
*/
class user_info {
/**
* Event to watch for.
*/
const EVENT_TO_WATCH = [
'join' => logger::EVENT_JOIN,
'play_recording' => logger::EVENT_PLAYED
];
/**
* Get user outline and complete info
*
* @param stdClass $course
* @param stdClass $user
* @param cm_info $mod
* @return array[] an array of infos and timestamps (latest timestamp)
*/
public static function get_user_info_outline(stdClass $course, stdClass $user, cm_info $mod): array {
$completion = new \completion_info($course);
$cdata = $completion->get_data($mod, false, $user->id);
$logtimestamps = [];
$infos = [];
if (!empty($cdata->viewed) && $cdata->viewed) {
$infos[] = get_string('report_room_view', 'mod_bigbluebuttonbn');
$logtimestamps[] = $cdata->timemodified;
}
$instance = instance::get_from_cmid($mod->id);
foreach (self::EVENT_TO_WATCH as $eventtype => $logtype) {
$logs = logger::get_user_completion_logs($instance, $user->id, [$logtype]);
if ($logs) {
$infos[] = get_string("report_{$eventtype}_info", 'mod_bigbluebuttonbn', count($logs));
$latesttime = array_reduce($logs,
function($acc, $log) {
return ($acc > $log->timecreated) ? $acc : $log->timecreated;
}, 0);
$logtimestamps[] = $latesttime;
}
}
return [$infos, $logtimestamps];
}
}
@@ -0,0 +1,81 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\local\plugins;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/adminlib.php');
use admin_externalpage;
use core_component;
use core_text;
use mod_bigbluebuttonbn\extension;
use moodle_url;
/**
* Admin external page that displays a list of the installed extension plugins.
*
* @package mod_bigbluebuttonbn
* @copyright 2023 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent@call-learning.fr)
*/
class admin_page_manage_extensions extends admin_externalpage {
/**
* Global URL for page.
*/
const ADMIN_PAGE_URL = '/mod/bigbluebuttonbn/adminmanageplugins.php';
/**
* The constructor - calls parent constructor
*
*/
public function __construct() {
$url = new moodle_url(self::ADMIN_PAGE_URL);
$managepagename = 'manage' . extension::BBB_EXTENSION_PLUGIN_NAME . 'plugins';
parent::__construct(
$managepagename,
get_string($managepagename, 'mod_bigbluebuttonbn'),
$url
);
}
/**
* Search plugins for the specified string
*
* @param string $query The string to search for
* @return array
*/
public function search($query): array {
if ($result = parent::search($query)) {
return $result;
}
foreach (core_component::get_plugin_list(extension::BBB_EXTENSION_PLUGIN_NAME ) as $name => $notused) {
$pluginname = core_text::strtolower(
get_string('pluginname', extension::BBB_EXTENSION_PLUGIN_NAME . '_' . $name)
);
if (str_contains($pluginname, $query) !== false) {
$result = (object)[
'page' => $this,
'settings' => [],
];
return [$this->name => $result];
}
}
return [];
}
}
@@ -0,0 +1,329 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\local\plugins;
use cache_helper;
use context_system;
use core_component;
use core_plugin_manager;
use flexible_table;
use html_writer;
use mod_bigbluebuttonbn\extension;
use moodle_url;
use pix_icon;
/**
* Class that handles the display and configuration of the list of extension plugins.
*
* This is directly taken from the mod_assign code. We might need to have a global API there for this.
*
* @package mod_bigbluebuttonbn
* @copyright 2023 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent@call-learning.fr)
*/
class admin_plugin_manager {
/** @var object the url of the manage submission plugin page */
private $pageurl;
/**
* Constructor for this assignment plugin manager
*
*/
public function __construct() {
$this->pageurl = new moodle_url(admin_page_manage_extensions::ADMIN_PAGE_URL);
}
/**
* This is the entry point for this controller class.
*
* @param string|null $action - The action to perform
* @param string|null $plugin - Optional name of a plugin type to perform the action on
* @return void
*/
public function execute(?string $action = null, ?string $plugin = null): void {
if (empty($action) || empty($plugin)) {
$action = 'view';
}
$this->check_permissions();
$actionname = "plugins_$action";
if (method_exists($this, $actionname)) {
$nextaction = $this->$actionname($plugin);
if ($nextaction) {
$this->execute($nextaction, $plugin);
}
}
}
/**
* Check this user has permission to edit the list of installed plugins
*
* @return void
*/
private function check_permissions(): void {
require_login();
$systemcontext = context_system::instance();
require_capability('moodle/site:config', $systemcontext);
}
/**
* Write the HTML for the submission plugins table.
*
* @return void
*/
private function plugins_view(): void {
global $OUTPUT, $CFG;
require_once($CFG->libdir . '/tablelib.php');
$this->print_header();
$table = new flexible_table(extension::BBB_EXTENSION_PLUGIN_NAME . 'pluginsadminttable');
$table->define_baseurl($this->pageurl);
$table->define_columns([
'pluginname',
'version',
'hideshow',
'order',
'settings',
'uninstall'
]);
$table->define_headers([
get_string('subplugintype_bbbext', 'mod_bigbluebuttonbn'),
get_string('version'), get_string('hide') . '/' . get_string('show'),
get_string('order'),
get_string('settings'),
get_string('uninstallplugin', 'core_admin')
]);
$table->set_attribute('id', extension::BBB_EXTENSION_PLUGIN_NAME . 'plugins');
$table->set_attribute('class', 'admintable generaltable');
$table->setup();
$plugins = $this->get_sorted_plugins_list();
$instances = core_plugin_manager::instance()->get_plugins_of_type(extension::BBB_EXTENSION_PLUGIN_NAME);
foreach ($plugins as $idx => $plugin) {
$componentname = extension::BBB_EXTENSION_PLUGIN_NAME . '_' . $plugin;
$typebasedir = "";
if (in_array($plugin, array_keys($instances))) {
$typebasedir = ($instances[$plugin])->typerootdir;
}
$row = [];
$class = '';
$pluginversion = get_config($componentname, 'version');
$row[] = get_string('pluginname', $componentname);
$row[] = $pluginversion;
$visible = !get_config($componentname, 'disabled');
if ($visible) {
$row[] = $this->format_icon_link('hide', $plugin, 't/hide', get_string('disable'));
} else {
$row[] = $this->format_icon_link('show', $plugin, 't/show', get_string('enable'));
$class = 'dimmed_text';
}
$movelinks = '';
if (!$idx == 0) {
$movelinks .= $this->format_icon_link('moveup', $plugin, 't/up', get_string('up')) . ' ';
} else {
$movelinks .= $OUTPUT->spacer(['width' => 16]);
}
if ($idx != count($plugins) - 1) {
$movelinks .= $this->format_icon_link('movedown', $plugin, 't/down', get_string('down')) . ' ';
}
$row[] = $movelinks;
$exists = file_exists($typebasedir . '/' . $plugin . '/settings.php');
// We do not display settings for plugin who have not yet been installed (so have no version yet).
if (!empty($pluginversion) && $exists) {
$row[] = html_writer::link(
new moodle_url('/admin/settings.php', ['section' => $componentname]),
get_string('settings')
);
} else {
$row[] = '&nbsp;';
}
$url = core_plugin_manager::instance()->get_uninstall_url(
$componentname,
'manage'
);
if ($url) {
$row[] = html_writer::link($url, get_string('uninstallplugin', 'core_admin'));
} else {
$row[] = '&nbsp;';
}
$table->add_data($row, $class);
}
$table->finish_output();
$this->print_footer();
}
/**
* Write the page header
*
* @return void
*/
private function print_header(): void {
global $OUTPUT;
$pageidentifier = 'manage' . extension::BBB_EXTENSION_PLUGIN_NAME . 'plugins';
admin_externalpage_setup($pageidentifier);
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string($pageidentifier, 'mod_bigbluebuttonbn'));
}
/**
* Return a list of plugins sorted by the order defined in the admin interface
*
* @return array The list of plugins
*/
public function get_sorted_plugins_list(): array {
$names = core_component::get_plugin_list(extension::BBB_EXTENSION_PLUGIN_NAME);
$result = [];
foreach ($names as $name => $path) {
$idx = get_config(extension::BBB_EXTENSION_PLUGIN_NAME . '_' . $name, 'sortorder');
if (!$idx) {
$idx = 0;
}
while (array_key_exists($idx, $result)) {
$idx += 1;
}
$result[$idx] = $name;
}
ksort($result);
return $result;
}
/**
* Util function for writing an action icon link
*
* @param string $action URL parameter to include in the link
* @param string $plugin URL parameter to include in the link
* @param string $icon The key to the icon to use (e.g. 't/up')
* @param string $alt The string description of the link used as the title and alt text
* @return string The icon/link
*/
private function format_icon_link(string $action, string $plugin, string $icon, string $alt): string {
global $OUTPUT;
return $OUTPUT->action_icon(
new moodle_url(
$this->pageurl,
['action' => $action, 'plugin' => $plugin, 'sesskey' => sesskey()]
),
new pix_icon($icon, $alt, 'moodle', ['title' => $alt]),
null,
['title' => $alt]
);
}
/**
* Write the page footer
*
* @return void
*/
private function print_footer(): void {
global $OUTPUT;
echo $OUTPUT->footer();
}
/**
* Hide this plugin.
*
* @param string $plugin - The plugin to hide
* @return string The next page to display
*/
private function plugins_hide(string $plugin): string {
$class = \core_plugin_manager::resolve_plugininfo_class(extension::BBB_EXTENSION_PLUGIN_NAME);
$class::enable_plugin($plugin, false);
cache_helper::purge_by_event('mod_bigbluebuttonbn/pluginenabledisabled');
// Also clear the cache for all BigBlueButtonModules.
rebuild_course_cache(0, true);
return 'view';
}
/**
* Show this plugin.
*
* @param string $plugin - The plugin to show
* @return string The next page to display
*/
private function plugins_show(string $plugin): string {
$class = \core_plugin_manager::resolve_plugininfo_class(extension::BBB_EXTENSION_PLUGIN_NAME);
$class::enable_plugin($plugin, true);
cache_helper::purge_by_event('mod_bigbluebuttonbn/pluginenabledisabled');
return 'view';
}
/**
* Move this plugin up
*
* We need this function so we can call directly (without the dir parameter)
* @param string $plugintomove - The plugin to move
* @return string The next page to display
*/
private function plugins_moveup(string $plugintomove): string {
return $this->move_plugin($plugintomove, 'up');
}
/**
* Move this plugin down
*
* We need this function so we can call directly (without the dir parameter)
* @param string $plugintomove - The plugin to move
* @return string The next page to display
*/
private function plugins_movedown(string $plugintomove): string {
return $this->move_plugin($plugintomove, 'down');
}
/**
* Change the order of this plugin.
*
* @param string $plugintomove - The plugin to move
* @param string $dir - up or down
* @return string The next page to display
*/
private function move_plugin(string $plugintomove, string $dir): string {
$plugins = $this->get_sorted_plugins_list();
$plugins = array_values($plugins);
$currentindex = array_search($plugintomove, $plugins);
if ($currentindex === false) {
return 'view';
}
// Make the switch.
if ($dir === 'up') {
if ($currentindex > 0) {
$tempplugin = $plugins[$currentindex - 1];
$plugins[$currentindex - 1] = $plugins[$currentindex];
$plugins[$currentindex] = $tempplugin;
}
} else if ($dir === 'down') {
if ($currentindex < (count($plugins) - 1)) {
$tempplugin = $plugins[$currentindex + 1];
$plugins[$currentindex + 1] = $plugins[$currentindex];
$plugins[$currentindex] = $tempplugin;
}
}
// Save the new normal order.
foreach ($plugins as $key => $plugin) {
set_config('sortorder', $key, extension::BBB_EXTENSION_PLUGIN_NAME . '_' . $plugin);
}
return 'view';
}
}
@@ -0,0 +1,567 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\local\proxy;
use cache;
use completion_info;
use Exception;
use mod_bigbluebuttonbn\completion\custom_completion;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\config;
use mod_bigbluebuttonbn\local\exceptions\bigbluebutton_exception;
use mod_bigbluebuttonbn\local\exceptions\server_not_available_exception;
use moodle_url;
use stdClass;
use user_picture;
/**
* The bigbluebutton proxy class.
*
* This class acts as a proxy between Moodle and the BigBlueButton API server,
* and handles all requests relating to the server and meetings.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
class bigbluebutton_proxy extends proxy_base {
/**
* Minimum poll interval for remote bigbluebutton server in seconds.
*/
const MIN_POLL_INTERVAL = 2;
/**
* Default poll interval for remote bigbluebutton server in seconds.
*/
const DEFAULT_POLL_INTERVAL = 5;
/**
* Builds and returns a url for joining a BigBlueButton meeting.
*
* @param instance $instance
* @param string|null $createtime
*
* @return string
*/
public static function get_join_url(
instance $instance,
?string $createtime
): string {
return self::internal_get_join_url($instance, $createtime);
}
/**
* Builds and returns a url for joining a BigBlueButton meeting.
*
* @param instance $instance
* @param string|null $createtime
* @param string $username
* @return string
*/
public static function get_guest_join_url(
instance $instance,
?string $createtime,
string $username
): string {
return self::internal_get_join_url($instance, $createtime, $username, true);
}
/**
* Internal helper method to builds and returns a url for joining a BigBlueButton meeting.
*
* @param instance $instance
* @param string|null $jointime = null
* @param string|null $userfullname
* @param bool $isguestjoin
* @return string
*/
private static function internal_get_join_url(
instance $instance,
?string $jointime,
string $userfullname = null,
bool $isguestjoin = false
): string {
$data = [
'meetingID' => $instance->get_meeting_id(),
'fullName' => $userfullname ?? $instance->get_user_fullname(),
'password' => $instance->get_current_user_password(),
'logoutURL' => $isguestjoin ? $instance->get_guest_access_url()->out(false) : $instance->get_logout_url()->out(false),
'role' => $instance->get_current_user_role()
];
if (!$isguestjoin) {
$data['userID'] = $instance->get_user_id();
$data['guest'] = "false";
} else {
$data['guest'] = "true";
}
if (!is_null($jointime)) {
$data['createTime'] = $jointime;
}
$currentlang = current_language();
if (!empty(trim($currentlang))) {
$data['userdata-bbb_override_default_locale'] = $currentlang;
}
if ($instance->is_profile_picture_enabled()) {
$user = $instance->get_user();
if (!empty($user->picture)) {
$data['avatarURL'] = self::get_avatar_url($user)->out(false);
}
}
return self::action_url('join', $data, [], $instance->get_instance_id());
}
/**
* Get user avatar URL
*
* @param stdClass $user
* @return moodle_url
*/
private static function get_avatar_url(stdClass $user): moodle_url {
global $PAGE;
$userpicture = new user_picture($user);
$userpicture->includetoken = true;
$userpicture->size = 3; // Size f3.
return $userpicture->get_url($PAGE);
}
/**
* Perform api request on BBB.
*
* @return null|string
*/
public static function get_server_version(): ?string {
$cache = cache::make('mod_bigbluebuttonbn', 'serverinfo');
$serverversion = $cache->get('serverversion');
if (!$serverversion) {
$xml = self::fetch_endpoint_xml('');
if (!$xml || $xml->returncode != 'SUCCESS') {
return null;
}
if (!isset($xml->version)) {
return null;
}
$serverversion = (string) $xml->version;
$cache->set('serverversion', $serverversion);
}
return (double) $serverversion;
}
/**
* Helper for getting the owner userid of a bigbluebuttonbn instance.
*
* @param stdClass $bigbluebuttonbn BigBlueButtonBN instance
* @return int ownerid (a valid user id or null if not registered/found)
*/
public static function get_instance_ownerid(stdClass $bigbluebuttonbn): int {
global $DB;
$filters = [
'bigbluebuttonbnid' => $bigbluebuttonbn->id,
'log' => 'Add',
];
return (int) $DB->get_field('bigbluebuttonbn_logs', 'userid', $filters);
}
/**
* Helper evaluates if a voicebridge number is unique.
*
* @param int $instance
* @param int $voicebridge
* @return bool
*/
public static function is_voicebridge_number_unique(int $instance, int $voicebridge): bool {
global $DB;
if ($voicebridge == 0) {
return true;
}
$select = 'voicebridge = ' . $voicebridge;
if ($instance != 0) {
$select .= ' AND id <>' . $instance;
}
if (!$DB->get_records_select('bigbluebuttonbn', $select)) {
return true;
}
return false;
}
/**
* Helper function validates a remote resource.
*
* @param string $url
* @return bool
*/
public static function is_remote_resource_valid(string $url): bool {
$urlhost = parse_url($url, PHP_URL_HOST);
$serverurlhost = parse_url(\mod_bigbluebuttonbn\local\config::get('server_url'), PHP_URL_HOST);
if ($urlhost == $serverurlhost) {
// Skip validation when the recording URL host is the same as the configured BBB server.
return true;
}
$cache = cache::make('mod_bigbluebuttonbn', 'validatedurls');
if ($cachevalue = $cache->get($urlhost)) {
// Skip validation when the recording URL was already validated.
return $cachevalue == 1;
}
$curl = new curl();
$curl->head($url);
$isvalid = false;
if ($info = $curl->get_info()) {
if ($info['http_code'] == 200) {
$isvalid = true;
} else {
debugging(
"Resources hosted by {$urlhost} are unreachable. Server responded with {$info['http_code']}",
DEBUG_DEVELOPER
);
$isvalid = false;
}
// Note: When a cache key is not found, it returns false.
// We need to distinguish between a result not found, and an invalid result.
$cache->set($urlhost, $isvalid ? 1 : 0);
}
return $isvalid;
}
/**
* Helper function enqueues one user for being validated as for completion.
*
* @param stdClass $bigbluebuttonbn
* @param int $userid
* @return void
*/
public static function enqueue_completion_event(stdClass $bigbluebuttonbn, int $userid): void {
try {
// Create the instance of completion_update_state task.
$task = new \mod_bigbluebuttonbn\task\completion_update_state();
// Add custom data.
$data = [
'bigbluebuttonbn' => $bigbluebuttonbn,
'userid' => $userid,
];
$task->set_custom_data($data);
// CONTRIB-7457: Task should be executed by a user, maybe Teacher as Student won't have rights for overriding.
// $ task -> set_userid ( $ user -> id );.
// Enqueue it.
\core\task\manager::queue_adhoc_task($task);
} catch (Exception $e) {
mtrace("Error while enqueuing completion_update_state task. " . (string) $e);
}
}
/**
* Helper function enqueues completion trigger.
*
* @param stdClass $bigbluebuttonbn
* @param int $userid
* @return void
*/
public static function update_completion_state(stdClass $bigbluebuttonbn, int $userid) {
global $CFG;
require_once($CFG->libdir . '/completionlib.php');
list($course, $cm) = get_course_and_cm_from_instance($bigbluebuttonbn, 'bigbluebuttonbn');
$completion = new completion_info($course);
if (!$completion->is_enabled($cm)) {
mtrace("Completion not enabled");
return;
}
$bbbcompletion = new custom_completion($cm, $userid);
if ($bbbcompletion->get_overall_completion_state()) {
mtrace("Completion for userid $userid and bigbluebuttonid {$bigbluebuttonbn->id} updated.");
$completion->update_state($cm, COMPLETION_COMPLETE, $userid, true);
} else {
// Still update state to current value (prevent unwanted caching).
$completion->update_state($cm, COMPLETION_UNKNOWN, $userid);
mtrace("Activity not completed for userid $userid and bigbluebuttonid {$bigbluebuttonbn->id}.");
}
}
/**
* Helper function returns an array with the profiles (with features per profile) for the different types
* of bigbluebuttonbn instances.
*
* @return array
*/
public static function get_instance_type_profiles(): array {
$instanceprofiles = [
instance::TYPE_ALL => [
'id' => instance::TYPE_ALL,
'name' => get_string('instance_type_default', 'bigbluebuttonbn'),
'features' => ['all']
],
instance::TYPE_ROOM_ONLY => [
'id' => instance::TYPE_ROOM_ONLY,
'name' => get_string('instance_type_room_only', 'bigbluebuttonbn'),
'features' => ['showroom', 'welcomemessage', 'voicebridge', 'waitformoderator', 'userlimit',
'recording', 'sendnotifications', 'lock', 'preuploadpresentation', 'permissions', 'schedule', 'groups',
'modstandardelshdr', 'availabilityconditionsheader', 'tagshdr', 'competenciessection',
'completionattendance', 'completionengagement', 'availabilityconditionsheader']
],
instance::TYPE_RECORDING_ONLY => [
'id' => instance::TYPE_RECORDING_ONLY,
'name' => get_string('instance_type_recording_only', 'bigbluebuttonbn'),
'features' => ['showrecordings', 'importrecordings', 'availabilityconditionsheader']
],
];
return $instanceprofiles;
}
/**
* Helper function returns an array with the profiles (with features per profile) for the different types
* of bigbluebuttonbn instances that the user is allowed to create.
*
* @param bool $room
* @param bool $recording
*
* @return array
*/
public static function get_instance_type_profiles_create_allowed(bool $room, bool $recording): array {
$profiles = self::get_instance_type_profiles();
if (!$room) {
unset($profiles[instance::TYPE_ROOM_ONLY]);
unset($profiles[instance::TYPE_ALL]);
}
if (!$recording) {
unset($profiles[instance::TYPE_RECORDING_ONLY]);
unset($profiles[instance::TYPE_ALL]);
}
return $profiles;
}
/**
* Helper function returns an array with the profiles (with features per profile) for the different types
* of bigbluebuttonbn instances.
*
* @param array $profiles
*
* @return array
*/
public static function get_instance_profiles_array(array $profiles = []): array {
$profilesarray = [];
foreach ($profiles as $key => $profile) {
$profilesarray[$profile['id']] = $profile['name'];
}
return $profilesarray;
}
/**
* Return the status of an activity [open|not_started|ended].
*
* @param instance $instance
* @return string
*/
public static function view_get_activity_status(instance $instance): string {
$now = time();
if (!empty($instance->get_instance_var('openingtime')) && $now < $instance->get_instance_var('openingtime')) {
// The activity has not been opened.
return 'not_started';
}
if (!empty($instance->get_instance_var('closingtime')) && $now > $instance->get_instance_var('closingtime')) {
// The activity has been closed.
return 'ended';
}
// The activity is open.
return 'open';
}
/**
* Ensure that the remote server was contactable.
*
* @param instance $instance
*/
public static function require_working_server(instance $instance): void {
$version = null;
try {
$version = self::get_server_version();
} catch (server_not_available_exception $e) {
self::handle_server_not_available($instance);
}
if (empty($version)) {
self::handle_server_not_available($instance);
}
}
/**
* Handle the server not being available.
*
* @param instance $instance
*/
public static function handle_server_not_available(instance $instance): void {
\core\notification::add(
self::get_server_not_available_message($instance),
\core\notification::ERROR
);
redirect(self::get_server_not_available_url($instance));
}
/**
* Get message when server not available
*
* @param instance $instance
* @return string
*/
public static function get_server_not_available_message(instance $instance): string {
if ($instance->is_admin()) {
return get_string('view_error_unable_join', 'mod_bigbluebuttonbn');
} else if ($instance->is_moderator()) {
return get_string('view_error_unable_join_teacher', 'mod_bigbluebuttonbn');
} else {
return get_string('view_error_unable_join_student', 'mod_bigbluebuttonbn');
}
}
/**
* Get URL to the page displaying that the server is not available
*
* @param instance $instance
* @return string
*/
public static function get_server_not_available_url(instance $instance): string {
if ($instance->is_admin()) {
return new moodle_url('/admin/settings.php', ['section' => 'modsettingbigbluebuttonbn']);
} else if ($instance->is_moderator()) {
return new moodle_url('/course/view.php', ['id' => $instance->get_course_id()]);
} else {
return new moodle_url('/course/view.php', ['id' => $instance->get_course_id()]);
}
}
/**
* Create a Meeting
*
* @param array $data
* @param array $metadata
* @param string|null $presentationname
* @param string|null $presentationurl
* @param int|null $instanceid
* @return array
* @throws bigbluebutton_exception
*/
public static function create_meeting(
array $data,
array $metadata,
?string $presentationname = null,
?string $presentationurl = null,
?int $instanceid = null
): array {
$createmeetingurl = self::action_url('create', $data, $metadata, $instanceid);
$curl = new curl();
if (!is_null($presentationname) && !is_null($presentationurl)) {
$payload = "<?xml version='1.0' encoding='UTF-8'?><modules><module name='presentation'><document url='" .
$presentationurl . "' /></module></modules>";
$xml = $curl->post($createmeetingurl, $payload);
} else {
$xml = $curl->get($createmeetingurl);
}
self::assert_returned_xml($xml);
if (empty($xml->meetingID)) {
throw new bigbluebutton_exception('general_error_cannot_create_meeting');
}
if ($xml->hasBeenForciblyEnded === 'true') {
throw new bigbluebutton_exception('index_error_forciblyended');
}
return [
'meetingID' => (string) $xml->meetingID,
'internalMeetingID' => (string) $xml->internalMeetingID,
'attendeePW' => (string) $xml->attendeePW,
'moderatorPW' => (string) $xml->moderatorPW
];
}
/**
* Get meeting info for a given meeting id
*
* @param string $meetingid
* @param int|null $instanceid
* @return array
*/
public static function get_meeting_info(string $meetingid, ?int $instanceid = null): array {
$xmlinfo = self::fetch_endpoint_xml('getMeetingInfo', ['meetingID' => $meetingid], [], $instanceid);
self::assert_returned_xml($xmlinfo, ['meetingid' => $meetingid]);
return (array) $xmlinfo;
}
/**
* Perform end meeting on BBB.
*
* @param string $meetingid
* @param string $modpw
* @param int|null $instanceid
*/
public static function end_meeting(string $meetingid, string $modpw, ?int $instanceid = null): void {
$xml = self::fetch_endpoint_xml('end', ['meetingID' => $meetingid, 'password' => $modpw], [], $instanceid);
self::assert_returned_xml($xml, ['meetingid' => $meetingid]);
}
/**
* Helper evaluates if the bigbluebutton server used belongs to blindsidenetworks domain.
*
* @return bool
*/
public static function is_bn_server() {
if (config::get('bn_server')) {
return true;
}
$parsedurl = parse_url(config::get('server_url'));
if (!isset($parsedurl['host'])) {
return false;
}
$h = $parsedurl['host'];
$hends = explode('.', $h);
$hendslength = count($hends);
return ($hends[$hendslength - 1] == 'com' && $hends[$hendslength - 2] == 'blindsidenetworks');
}
/**
* Get the poll interval as it is set in the configuration
*
* If configuration value is under the threshold of {@see self::MIN_POLL_INTERVAL},
* then return the {@see self::MIN_POLL_INTERVAL} value.
*
* @return int the poll interval in seconds
*/
public static function get_poll_interval(): int {
$pollinterval = intval(config::get('poll_interval'));
if ($pollinterval < self::MIN_POLL_INTERVAL) {
$pollinterval = self::MIN_POLL_INTERVAL;
}
return $pollinterval;
}
}
@@ -0,0 +1,157 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* A curl wrapper for bbb.
*
* @package mod_bigbluebuttonbn
* @copyright 2021 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_bigbluebuttonbn\local\proxy;
use SimpleXMLElement;
defined('MOODLE_INTERNAL') || die;
global $CFG;
require_once("{$CFG->libdir}/filelib.php");
/**
* A curl wrapper for bbb.
*
* @package mod_bigbluebuttonbn
* @copyright 2021 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class curl extends \curl {
/** @var string */
protected $contenttype;
/**
* Constructor for the class.
*/
public function __construct() {
$settings = [];
if (debugging()) {
$settings = ['ignoresecurity' => true];
}
parent::__construct($settings);
$this->setopt(['SSL_VERIFYPEER' => true]);
$this->set_content_type('application/xml');
}
/**
* Fetch the content type.
*/
public function get_content_type(): string {
return $this->contenttype;
}
/**
* Set the desired current content type.
*
* @param string $type
* @return self
*/
public function set_content_type(string $type): self {
$this->contenttype = $type;
return $this;
}
/**
* HTTP POST method
*
* @param string $url
* @param array|string $params
* @param array $options
* @return null|SimpleXMLElement Null on error
*/
public function post($url, $params = '', $options = []) {
if (!is_string($params)) {
debugging('Only string parameters are supported', DEBUG_DEVELOPER);
$params = '';
}
$options = array_merge($options, [
'CURLOPT_HTTPHEADER' => [
'Content-Type: ' . $this->get_content_type(),
'Content-Length: ' . strlen($params),
'Content-Language: en-US',
]
]);
return $this->handle_response(parent::post($url, $params, $options));
}
/**
* Fetch the specified URL via a HEAD request.
*
* @param string $url
* @param array $options
*/
public function head($url, $options = []) {
$options['followlocation'] = true;
$options['timeout'] = 1;
parent::head($url, $options);
return $this->get_info();
}
/**
* Fetch the specified URL via a GET request.
*
* @param string $url
* @param string $params
* @param array $options
*/
public function get($url, $params = [], $options = []) {
return $this->handle_response(parent::get($url, $params, $options));
}
/**
* Handle the response.
*
* @param mixed $response
* @return null|SimpleXMLElement Null on error
*/
protected function handle_response($response): ?SimpleXMLElement {
if (!$response) {
debugging('No response returned for call', DEBUG_DEVELOPER);
return null;
}
$previous = libxml_use_internal_errors(true);
try {
$xml = simplexml_load_string($response, 'SimpleXMLElement', LIBXML_NOCDATA | LIBXML_NOBLANKS);
} catch (Exception $e) {
libxml_use_internal_errors($previous);
debugging('Caught exception: ' . $e->getMessage(), DEBUG_DEVELOPER);
return null;
}
if ($xml instanceof SimpleXMLElement) {
return $xml;
}
$debugabstract = html_to_text($response);
$debugabstract = substr($debugabstract, 0, 1024); // Limit to small amount of info so we do not overload logs.
debugging('Issue retrieving information from the server: ' . $debugabstract, DEBUG_DEVELOPER);
return null;
}
}
@@ -0,0 +1,206 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\local\proxy;
use mod_bigbluebuttonbn\extension;
use mod_bigbluebuttonbn\local\config;
use mod_bigbluebuttonbn\local\exceptions\bigbluebutton_exception;
use mod_bigbluebuttonbn\local\exceptions\server_not_available_exception;
use mod_bigbluebuttonbn\plugin;
use moodle_url;
/**
* The abstract proxy base class.
*
* This class provides common and shared functions used when interacting with
* the BigBlueButton API server.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
abstract class proxy_base {
/**
* Sometimes the server sends back some error and errorKeys that
* can be converted to Moodle error messages
*/
const BBB_TO_MOODLE_ERROR_CODE = [
'checksumError' => 'index_error_checksum',
'notFound' => 'general_error_not_found',
'maxConcurrent' => 'view_error_max_concurrent',
];
/**
* Returns the right URL for the action specified.
*
* @param string $action
* @param array $data
* @param array $metadata
* @param int|null $instanceid
* @return string
*/
protected static function action_url(
string $action = '',
array $data = [],
array $metadata = [],
?int $instanceid = null
): string {
$baseurl = self::sanitized_url() . $action . '?';
['data' => $additionaldata, 'metadata' => $additionalmetadata] =
extension::action_url_addons($action, $data, $metadata, $instanceid);
$data = array_merge($data, $additionaldata ?? []);
$metadata = array_merge($metadata, $additionalmetadata ?? []);
$metadata = array_combine(array_map(function($k) {
return 'meta_' . $k;
}, array_keys($metadata)), $metadata);
$params = http_build_query($data + $metadata, '', '&');
$checksum = self::get_checksum($action, $params);
return $baseurl . $params . '&checksum=' . $checksum;
}
/**
* Makes sure the url used doesn't is in the format required.
*
* @return string
*/
protected static function sanitized_url(): string {
$serverurl = trim(config::get('server_url'));
if (PHPUNIT_TEST) {
$serverurl = (new moodle_url(TEST_MOD_BIGBLUEBUTTONBN_MOCK_SERVER))->out(false);
}
if (substr($serverurl, -1) == '/') {
$serverurl = rtrim($serverurl, '/');
}
if (substr($serverurl, -4) == '/api') {
$serverurl = rtrim($serverurl, '/api');
}
return $serverurl . '/api/';
}
/**
* Makes sure the shared_secret used doesn't have trailing white characters.
*
* @return string
*/
protected static function sanitized_secret(): string {
return trim(config::get('shared_secret'));
}
/**
* Throw an exception if there is a problem in the returned XML value
*
* @param \SimpleXMLElement|bool $xml
* @param array|null $additionaldetails
* @throws bigbluebutton_exception
* @throws server_not_available_exception
*/
protected static function assert_returned_xml($xml, ?array $additionaldetails = null): void {
$messagekey = '';
if (!empty($xml)) {
$messagekey = (string) ($xml->messageKey ?? '');
}
if (empty($xml) || static::is_known_server_unavailable_errorcode($messagekey)) {
$errorcode = self::get_errorcode_from_xml_messagekey($messagekey);
throw new server_not_available_exception(
$errorcode,
plugin::COMPONENT,
(new moodle_url('/admin/settings.php?section=modsettingbigbluebuttonbn'))->out(),
);
}
// If it is a checksum error, this is equivalent to the server not being available.
// So we treat it the same way as if there is not answer.
if (is_bool($xml) && $xml) {
// Nothing to do here, this might be a post returning that everything went well.
return;
}
if ((string) $xml->returncode == 'FAILED') {
$errorcode = self::get_errorcode_from_xml_messagekey($messagekey);
if (!$additionaldetails) {
$additionaldetails = [];
}
$additionaldetails['xmlmessage'] = (string) $xml->message ?? '';
throw new bigbluebutton_exception($errorcode, json_encode($additionaldetails));
}
}
/**
* Get Moodle error code from returned Message Key
*
* @param string $messagekey
* @return string
*/
private static function get_errorcode_from_xml_messagekey(string $messagekey): string {
$errorcode = 'general_error_no_answer';
if ($messagekey) {
$errorcode = self::BBB_TO_MOODLE_ERROR_CODE[$messagekey] ?? $errorcode;
}
return $errorcode;
}
/**
* Get Moodle error code from returned Message Key
*
* @param string $messagekey
* @return string
*/
private static function is_known_server_unavailable_errorcode(string $messagekey): string {
// For now, only checksumError is supposed to mean that the server is unavailable.
// Other errors are recoverable.
return in_array($messagekey, ['checksumError']);
}
/**
* Fetch the XML from an endpoint and test for success.
*
* If the result could not be loaded, or the returncode was not 'SUCCESS', a null value is returned.
*
* @param string $action
* @param array $data
* @param array $metadata
* @param int|null $instanceid
* @return null|bool|\SimpleXMLElement
*/
protected static function fetch_endpoint_xml(
string $action,
array $data = [],
array $metadata = [],
?int $instanceid = null
) {
if (PHPUNIT_TEST && !defined('TEST_MOD_BIGBLUEBUTTONBN_MOCK_SERVER')) {
return true; // In case we still use fetch and mock server is not defined, this prevents
// an error. This can happen if a function from lib.php is called in test from other modules
// for example.
}
$curl = new curl();
return $curl->get(self::action_url($action, $data, $metadata, $instanceid));
}
/**
* Get checksum
*
* @param string $action
* @param string $params
* @return string
*/
public static function get_checksum(string $action, string $params): string {
return hash(config::get('checksum_algorithm'), $action . $params . self::sanitized_secret());
}
}
@@ -0,0 +1,405 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\local\proxy;
use cache;
use cache_helper;
use SimpleXMLElement;
/**
* The recording proxy.
*
* This class acts as a proxy between Moodle and the BigBlueButton API server,
* and deals with all requests relating to recordings.
*
* @package mod_bigbluebuttonbn
* @copyright 2021 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
class recording_proxy extends proxy_base {
/**
* Invalidate the MUC cache for the specified recording.
*
* @param string $recordid
*/
protected static function invalidate_cache_for_recording(string $recordid): void {
cache_helper::invalidate_by_event('mod_bigbluebuttonbn/recordingchanged', [$recordid]);
}
/**
* Perform deleteRecordings on BBB.
*
* @param string $recordid a recording id
* @return bool
*/
public static function delete_recording(string $recordid): bool {
$result = self::fetch_endpoint_xml('deleteRecordings', ['recordID' => $recordid]);
if (!$result || $result->returncode != 'SUCCESS') {
return false;
}
return true;
}
/**
* Perform publishRecordings on BBB.
*
* @param string $recordid
* @param string $publish
* @return bool
*/
public static function publish_recording(string $recordid, string $publish = 'true'): bool {
$result = self::fetch_endpoint_xml('publishRecordings', [
'recordID' => $recordid,
'publish' => $publish,
]);
self::invalidate_cache_for_recording($recordid);
if (!$result || $result->returncode != 'SUCCESS') {
return false;
}
return true;
}
/**
* Perform publishRecordings on BBB.
*
* @param string $recordid
* @param string $protected
* @return bool
*/
public static function protect_recording(string $recordid, string $protected = 'true'): bool {
global $CFG;
// Ignore action if recording_protect_editable is set to false.
if (empty($CFG->bigbluebuttonbn_recording_protect_editable)) {
return false;
}
$result = self::fetch_endpoint_xml('updateRecordings', [
'recordID' => $recordid,
'protect' => $protected,
]);
self::invalidate_cache_for_recording($recordid);
if (!$result || $result->returncode != 'SUCCESS') {
return false;
}
return true;
}
/**
* Perform updateRecordings on BBB.
*
* @param string $recordid a single record identifier
* @param array $params ['key'=>param_key, 'value']
*/
public static function update_recording(string $recordid, array $params): bool {
$result = self::fetch_endpoint_xml('updateRecordings', array_merge([
'recordID' => $recordid
], $params));
self::invalidate_cache_for_recording($recordid);
return $result ? $result->returncode == 'SUCCESS' : false;
}
/**
* Helper function to fetch a single recording from a BigBlueButton server.
*
* @param string $recordingid
* @return null|array
*/
public static function fetch_recording(string $recordingid): ?array {
$data = self::fetch_recordings([$recordingid]);
if (array_key_exists($recordingid, $data)) {
return $data[$recordingid];
}
return null;
}
/**
* Check whether the current recording is a protected recording and purge the cache if necessary.
*
* @param string $recordingid
*/
public static function purge_protected_recording(string $recordingid): void {
$cache = cache::make('mod_bigbluebuttonbn', 'recordings');
$recording = $cache->get($recordingid);
if (empty($recording)) {
// This value was not cached to begin with.
return;
}
$currentfetchcache = cache::make('mod_bigbluebuttonbn', 'currentfetch');
if ($currentfetchcache->has($recordingid)) {
// This item was fetched in the current request.
return;
}
if (array_key_exists('protected', $recording) && $recording['protected'] === 'true') {
// This item is protected. Purge it from the cache.
$cache->delete($recordingid);
return;
}
}
/**
* Helper function to fetch recordings from a BigBlueButton server.
*
* We use a cache to store recording indexed by keyids/recordingID.
* @param array $keyids list of recordingids
* @return array (associative) with recordings indexed by recordID, each recording is a non sequential array
* and sorted by {@see recording_proxy::sort_recordings}
*/
public static function fetch_recordings(array $keyids = []): array {
$recordings = [];
// If $ids is empty return array() to prevent a getRecordings with meetingID and recordID set to ''.
if (empty($keyids)) {
return $recordings;
}
$cache = cache::make('mod_bigbluebuttonbn', 'recordings');
$currentfetchcache = cache::make('mod_bigbluebuttonbn', 'currentfetch');
$recordings = array_filter($cache->get_many($keyids));
$missingkeys = array_diff(array_values($keyids), array_keys($recordings));
$recordings += self::do_fetch_recordings($missingkeys);
$cache->set_many($recordings);
$currentfetchcache->set_many(array_flip(array_keys($recordings)));
return $recordings;
}
/**
* Helper function to retrieve recordings that failed to be fetched from a BigBlueButton server.
*
* @param array $keyids list of recordingids
* @return array array of recording recordingids not fetched from server
* and sorted by {@see recording_proxy::sort_recordings}
*/
public static function fetch_missing_recordings(array $keyids = []): array {
$unfetchedids = [];
$pagesize = 25;
// If $ids is empty return array() to prevent a getRecordings with meetingID and recordID set to ''.
if (empty($keyids)) {
return $unfetchedids;
}
while ($ids = array_splice($keyids, 0, $pagesize)) {
// We make getRecordings API call to check recordings are successfully retrieved.
$xml = self::fetch_endpoint_xml('getRecordings', ['recordID' => implode(',', $ids), 'state' => 'any']);
if (!$xml || $xml->returncode != 'SUCCESS' || !isset($xml->recordings)) {
$unfetchedids = array_merge($unfetchedids, $ids);
continue; // We will keep record of all unfetched ids.
}
}
return $unfetchedids;
}
/**
* Helper function to fetch recordings from a BigBlueButton server.
*
* @param array $keyids list of meetingids
* @return array (associative) with recordings indexed by recordID, each recording is a non sequential array
* and sorted by {@see recording_proxy::sort_recordings}
*/
public static function fetch_recording_by_meeting_id(array $keyids = []): array {
$recordings = [];
// If $ids is empty return array() to prevent a getRecordings with meetingID and recordID set to ''.
if (empty($keyids)) {
return $recordings;
}
$recordings = self::do_fetch_recordings($keyids, 'meetingID');
return $recordings;
}
/**
* Helper function to fetch recordings from a BigBlueButton server.
*
* @param array $keyids list of meetingids or recordingids
* @param string $key the param name used for the BBB request (<recordID>|meetingID)
* @return array (associative) with recordings indexed by recordID, each recording is a non sequential array.
* and sorted {@see recording_proxy::sort_recordings}
*/
private static function do_fetch_recordings(array $keyids = [], string $key = 'recordID'): array {
$recordings = [];
$pagesize = 25;
while ($ids = array_splice($keyids, 0, $pagesize)) {
$fetchrecordings = self::fetch_recordings_page($ids, $key);
$recordings += $fetchrecordings;
}
// Sort recordings.
return self::sort_recordings($recordings);
}
/**
* Helper function to fetch a page of recordings from the remote server.
*
* @param array $ids
* @param string $key
* @return array
*/
private static function fetch_recordings_page(array $ids, $key = 'recordID'): array {
// The getRecordings call is executed using a method GET (supported by all versions of BBB).
$xml = self::fetch_endpoint_xml('getRecordings', [$key => implode(',', $ids), 'state' => 'any']);
if (!$xml) {
return [];
}
if ($xml->returncode != 'SUCCESS') {
return [];
}
if (!isset($xml->recordings)) {
return [];
}
$recordings = [];
// If there were recordings already created.
foreach ($xml->recordings->recording as $recordingxml) {
$recording = self::parse_recording($recordingxml);
$recordings[$recording['recordID']] = $recording;
// Check if there are any child.
if (isset($recordingxml->breakoutRooms->breakoutRoom)) {
$breakoutrooms = [];
foreach ($recordingxml->breakoutRooms->breakoutRoom as $breakoutroom) {
$breakoutrooms[] = trim((string) $breakoutroom);
}
if ($breakoutrooms) {
$xml = self::fetch_endpoint_xml('getRecordings', ['recordID' => implode(',', $breakoutrooms)]);
if ($xml && $xml->returncode == 'SUCCESS' && isset($xml->recordings)) {
// If there were already created meetings.
foreach ($xml->recordings->recording as $subrecordingxml) {
$recording = self::parse_recording($subrecordingxml);
$recordings[$recording['recordID']] = $recording;
}
}
}
}
}
return $recordings;
}
/**
* Helper function to sort an array of recordings. It compares the startTime in two recording objects.
*
* @param array $recordings
* @return array
*/
public static function sort_recordings(array $recordings): array {
global $CFG;
uasort($recordings, function($a, $b) {
if ($a['startTime'] < $b['startTime']) {
return -1;
}
if ($a['startTime'] == $b['startTime']) {
return 0;
}
return 1;
});
return $recordings;
}
/**
* Helper function to parse an xml recording object and produce an array in the format used by the plugin.
*
* @param SimpleXMLElement $recording
*
* @return array
*/
public static function parse_recording(SimpleXMLElement $recording): array {
// Add formats.
$playbackarray = [];
foreach ($recording->playback->format as $format) {
$playbackarray[(string) $format->type] = [
'type' => (string) $format->type,
'url' => trim((string) $format->url), 'length' => (string) $format->length
];
// Add preview per format when existing.
if ($format->preview) {
$playbackarray[(string) $format->type]['preview'] =
self::parse_preview_images($format->preview);
}
}
// Add the metadata to the recordings array.
$metadataarray =
self::parse_recording_meta(get_object_vars($recording->metadata));
$recordingarray = [
'recordID' => (string) $recording->recordID,
'meetingID' => (string) $recording->meetingID,
'meetingName' => (string) $recording->name,
'published' => (string) $recording->published,
'state' => (string) $recording->state,
'startTime' => (string) $recording->startTime,
'endTime' => (string) $recording->endTime,
'playbacks' => $playbackarray
];
if (isset($recording->protected)) {
$recordingarray['protected'] = (string) $recording->protected;
}
return $recordingarray + $metadataarray;
}
/**
* Helper function to convert an xml recording metadata object to an array in the format used by the plugin.
*
* @param array $metadata
*
* @return array
*/
public static function parse_recording_meta(array $metadata): array {
$metadataarray = [];
foreach ($metadata as $key => $value) {
if (is_object($value)) {
$value = '';
}
$metadataarray['meta_' . $key] = $value;
}
return $metadataarray;
}
/**
* Helper function to convert an xml recording preview images to an array in the format used by the plugin.
*
* @param SimpleXMLElement $preview
*
* @return array
*/
public static function parse_preview_images(SimpleXMLElement $preview): array {
$imagesarray = [];
foreach ($preview->images->image as $image) {
$imagearray = ['url' => trim((string) $image)];
foreach ($image->attributes() as $attkey => $attvalue) {
$imagearray[$attkey] = (string) $attvalue;
}
array_push($imagesarray, $imagearray);
}
return $imagesarray;
}
}
+500
View File
@@ -0,0 +1,500 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn;
use mod_bigbluebuttonbn\event\events;
use stdClass;
/**
* Utility class for all logs routines helper.
*
* @package mod_bigbluebuttonbn
* @copyright 2021 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
*/
class logger {
/** @var string The bigbluebuttonbn Add event */
public const EVENT_ADD = 'Add';
/** @var string The bigbluebuttonbn Edit event */
public const EVENT_EDIT = 'Edit';
/** @var string The bigbluebuttonbn Create event */
public const EVENT_CREATE = 'Create';
/** @var string The bigbluebuttonbn Join event */
public const EVENT_JOIN = 'Join';
/** @var string The bigbluebuttonbn Playback event */
public const EVENT_PLAYED = 'Played';
/** @var string The bigbluebuttonbn Logout event */
public const EVENT_LOGOUT = 'Logout';
/** @var string The bigbluebuttonbn Import event */
public const EVENT_IMPORT = 'Import';
/** @var string The bigbluebuttonbn Delete event */
public const EVENT_DELETE = 'Delete';
/** @var string The bigbluebuttonbn Callback event */
public const EVENT_CALLBACK = 'Callback';
/** @var string The bigbluebuttonbn Summary event */
public const EVENT_SUMMARY = 'Summary';
/** @var string This is a specific log to mark this log as upgraded: used only in the upgrade process from 2.4
*
* Note: Migrated event name change: once a log has been migrated we mark
* it as migrated by changing its log name. This will help to recover
* manually if we have an issue in the migration process.
*/
public const EVENT_IMPORT_MIGRATED = 'import-migrated';
/** @var string This is a specific log to mark this log as upgraded: used only in the upgrade process from 2.4 */
public const EVENT_CREATE_MIGRATED = 'create-migrated';
/** @var string The bigbluebuttonbn meeting_start event */
public const EVENT_MEETING_START = 'meeting_start';
/** @var int The user accessed the session from activity page */
public const ORIGIN_BASE = 0;
/** @var int The user accessed the session from Timeline */
public const ORIGIN_TIMELINE = 1;
/** @var int The user accessed the session from Index */
public const ORIGIN_INDEX = 2;
/**
* Get the user event logs related to completion, for the specified user in the named instance.
*
* @param instance $instance
* @param int|null $userid
* @param array|null $filters
* @param int|null $timestart
* @return array
*/
public static function get_user_completion_logs(
instance $instance,
?int $userid,
?array $filters,
?int $timestart = 0
): array {
global $DB;
$filters = $filters ?? [self::EVENT_JOIN, self::EVENT_PLAYED, self::EVENT_SUMMARY];
[$wheresql, $params] = static::get_user_completion_sql_params($instance, $userid, $filters, $timestart);
return $DB->get_records_select('bigbluebuttonbn_logs', $wheresql, $params);
}
/**
* Get the user event logs related to completion, for the specified user in the named instance.
*
* @param instance $instance
* @param int|null $userid
* @param array|null $filters
* @param int|null $timestart
* @return array
*/
public static function get_user_completion_logs_with_userfields(
instance $instance,
?int $userid,
?array $filters,
?int $timestart = 0
): array {
global $DB;
$filters = $filters ?? [self::EVENT_JOIN, self::EVENT_PLAYED, self::EVENT_SUMMARY];
[$wheresql, $params] = static::get_user_completion_sql_params($instance, $userid, $filters, $timestart, 'l');
$userfieldsapi = \core_user\fields::for_userpic();
$userfields = $userfieldsapi->get_sql('u', false, '', 'userid', false)->selects;
$logtable = new \core\dml\table('bigbluebuttonbn_logs', 'l', '');
$logtableselect = $logtable->get_field_select();
$logtablefrom = $logtable->get_from_sql();
$usertable = new \core\dml\table('user', 'u', '');
$usertablefrom = $usertable->get_from_sql();
$sql = <<<EOF
SELECT {$logtableselect}, {$userfields}
FROM {$logtablefrom}
INNER JOIN {$usertablefrom} ON u.id = l.userid
WHERE $wheresql
EOF;
return $DB->get_records_sql($sql, $params);
}
/**
* Get the latest timestamp for any event logs related to completion, for the specified user in the named instance.
*
* @param instance $instance
* @param int|null $userid
* @param array|null $filters
* @param int|null $timestart
* @return int
*/
public static function get_user_completion_logs_max_timestamp(
instance $instance,
?int $userid,
?array $filters,
?int $timestart = 0
): int {
global $DB;
[$wheresql, $params] = static::get_user_completion_sql_params($instance, $userid, $filters, $timestart);
$select = "SELECT MAX(timecreated) ";
$lastlogtime = $DB->get_field_sql($select . ' FROM {bigbluebuttonbn_logs} WHERE ' . $wheresql, $params);
return $lastlogtime ?? 0;
}
/**
* Helper method to get the right SQL query for completion
*
* @param instance $instance
* @param int|null $userid
* @param array|null $filters
* @param int|null $timestart
* @param string|null $logtablealias
* @return array
*/
protected static function get_user_completion_sql_params(instance $instance, ?int $userid, ?array $filters, ?int $timestart,
?string $logtablealias = null) {
global $DB;
$filters = $filters ?? [self::EVENT_JOIN, self::EVENT_PLAYED, self::EVENT_SUMMARY];
[$insql, $params] = $DB->get_in_or_equal($filters, SQL_PARAMS_NAMED);
$wheres = [];
$wheres['bigbluebuttonbnid'] = '= :instanceid';
$wheres['courseid'] = '= :courseid'; // This speeds up the requests masively as courseid is an index.
if ($timestart) {
$wheres['timecreated'] = ' > :timestart';
$params['timestart'] = $timestart;
}
if ($userid) {
$wheres['userid'] = ' = :userid';
$params['userid'] = $userid;
}
$params['instanceid'] = $instance->get_instance_id();
$params['courseid'] = $instance->get_course_id();
$wheres['log'] = " $insql";
$wheresqls = [];
foreach ($wheres as $key => $val) {
$prefix = !empty($logtablealias) ? "$logtablealias." : "";
$wheresqls[] = "$prefix$key $val";
}
return [join(' AND ', $wheresqls), $params];
}
/**
* Log that an instance was created.
*
* Note: This event cannot take the instance class as it is typically called before the cm has been configured.
*
* @param stdClass $instancedata
*/
public static function log_instance_created(stdClass $instancedata): void {
self::raw_log(
self::EVENT_ADD,
$instancedata->id,
$instancedata->course,
$instancedata->meetingid
);
}
/**
* Log that an instance was updated.
*
* @param instance $instance
*/
public static function log_instance_updated(instance $instance): void {
self::log($instance, self::EVENT_EDIT);
}
/**
* Log an instance deleted event.
*
* @param instance $instance
*/
public static function log_instance_deleted(instance $instance): void {
global $DB;
$wheresql = 'bigbluebuttonbnid = :instanceid AND log = :logtype AND ' . $DB->sql_compare_text('meta') . ' = :meta';
$logs = $DB->get_records_select('bigbluebuttonbn_logs', $wheresql, [
'instanceid' => $instance->get_instance_id(),
'logtype' => self::EVENT_CREATE,
'meta' => "{\"record\":true}"
]);
$meta = "{\"has_recordings\":" . empty($logs) ? "true" : "false" . "}";
self::log($instance, self::EVENT_DELETE, [], $meta);
}
/**
* Log an event callback.
*
* @param instance $instance
* @param array $overrides
* @param array $meta
* @return int The new count of callback events
*/
public static function log_event_callback(instance $instance, array $overrides, array $meta): int {
self::log(
$instance,
self::EVENT_CALLBACK,
$overrides,
json_encode($meta)
);
return self::count_callback_events($meta['internalmeetingid'], 'meeting_events');
}
/**
* Log an event summary event.
*
* @param instance $instance
* @param array $overrides
* @param array $meta
*/
public static function log_event_summary(instance $instance, array $overrides = [], array $meta = []): void {
self::log(
$instance,
self::EVENT_SUMMARY,
$overrides,
json_encode($meta)
);
}
/**
* Log that an instance was viewed.
*
* @param instance $instance
*/
public static function log_instance_viewed(instance $instance): void {
self::log_moodle_event($instance, events::$events['view']);
}
/**
* Log the events for when a meeting was ended.
*
* @param instance $instance
*/
public static function log_meeting_ended_event(instance $instance): void {
// Moodle event logger: Create an event for meeting ended.
self::log_moodle_event($instance, events::$events['meeting_end']);
}
/**
* Log the relevant events for when a meeting was joined.
*
* @param instance $instance
* @param int $origin
*/
public static function log_meeting_joined_event(instance $instance, int $origin): void {
// Moodle event logger: Create an event for meeting joined.
self::log_moodle_event($instance, events::$events['meeting_join']);
// Internal logger: Instert a record with the meeting created.
self::log(
$instance,
self::EVENT_JOIN,
['meetingid' => $instance->get_meeting_id()],
json_encode((object) ['origin' => $origin])
);
}
/**
* Log the relevant events for when a user left a meeting.
*
* @param instance $instance
*/
public static function log_meeting_left_event(instance $instance): void {
// Moodle event logger: Create an event for meeting left.
self::log_moodle_event($instance, events::$events['meeting_left']);
}
/**
* Log the relevant events for when a recording has been played.
*
* @param instance $instance
* @param int $rid RecordID
*/
public static function log_recording_played_event(instance $instance, int $rid): void {
// Moodle event logger: Create an event for recording played.
self::log_moodle_event($instance, events::$events['recording_play'], ['other' => $rid]);
// Internal logger: Insert a record with the playback played.
self::log(
$instance,
self::EVENT_PLAYED,
[
'meetingid' => $instance->get_meeting_id(),
],
json_encode(['recordingid' => $rid])
);
}
/**
* Register a bigbluebuttonbn event from an instance.
*
* @param instance $instance
* @param string $event
* @param array $overrides
* @param string|null $meta
* @return bool
*/
protected static function log(instance $instance, string $event, array $overrides = [], ?string $meta = null): bool {
return self::raw_log(
$event,
$instance->get_instance_id(),
$instance->get_course_id(),
$instance->get_meeting_id(),
$overrides,
$meta
);
}
/**
* Register a bigbluebuttonbn event from raw data.
*
* @param string $event
* @param int $instanceid
* @param int $courseid
* @param string $meetingid
* @param array $overrides
* @param string|null $meta
* @return bool
*/
protected static function raw_log(
string $event,
int $instanceid,
int $courseid,
string $meetingid,
array $overrides = [],
?string $meta = null
): bool {
global $DB, $USER;
$log = (object) array_merge([
// Default values.
'courseid' => $courseid,
'bigbluebuttonbnid' => $instanceid,
'userid' => $USER->id,
'meetingid' => $meetingid,
'timecreated' => time(),
'log' => $event,
'meta' => $meta,
], $overrides);
return !!$DB->insert_record('bigbluebuttonbn_logs', $log);
}
/**
* Helper register a bigbluebuttonbn event.
*
* @param instance $instance
* @param string $type
* @param array $options [timecreated, userid, other]
*/
protected static function log_moodle_event(instance $instance, string $type, array $options = []): void {
if (!in_array($type, \mod_bigbluebuttonbn\event\events::$events)) {
// No log will be created.
return;
}
$params = [
'context' => $instance->get_context(),
'objectid' => $instance->get_instance_id(),
];
if (array_key_exists('timecreated', $options)) {
$params['timecreated'] = $options['timecreated'];
}
if (array_key_exists('userid', $options)) {
$params['userid'] = $options['userid'];
}
if (array_key_exists('other', $options)) {
$params['other'] = $options['other'];
}
$event = call_user_func_array("\\mod_bigbluebuttonbn\\event\\{$type}::create", [$params]);
$event->add_record_snapshot('course_modules', $instance->get_cm());
$event->add_record_snapshot('course', $instance->get_course());
$event->add_record_snapshot('bigbluebuttonbn', $instance->get_instance_data());
$event->trigger();
}
/**
* Helper function to count the number of callback logs matching the supplied specifications.
*
* @param string $id
* @param string $callbacktype
* @return int
*/
protected static function count_callback_events(string $id, string $callbacktype = 'recording_ready'): int {
global $DB;
// Look for a log record that is of "Callback" type and is related to the given event.
$conditions = [
"log = :logtype",
$DB->sql_like('meta', ':cbtypelike')
];
$params = [
'logtype' => self::EVENT_CALLBACK,
'cbtypelike' => "%meeting_events%" // All callbacks are meeting events, even recording events.
];
$basesql = 'SELECT COUNT(DISTINCT id) FROM {bigbluebuttonbn_logs}';
switch ($callbacktype) {
case 'recording_ready':
$conditions[] = $DB->sql_like('meta', ':isrecordid');
$params['isrecordid'] = '%recordid%'; // The recordid field in the meta field (json encoded).
break;
case 'meeting_events':
$conditions[] = $DB->sql_like('meta', ':idlike');
$params['idlike'] = "%$id%"; // The unique id of the meeting is the meta field (json encoded).
break;
}
$wheresql = join(' AND ', $conditions);
return $DB->count_records_sql($basesql . ' WHERE ' . $wheresql, $params);
}
/**
* Log event to string that can be internationalised via get_string.
*/
const LOG_TO_STRING = [
self::EVENT_JOIN => 'event_meeting_joined',
self::EVENT_PLAYED => 'event_recording_viewed',
self::EVENT_IMPORT => 'event_recording_imported',
self::EVENT_ADD => 'event_activity_created',
self::EVENT_DELETE => 'event_activity_deleted',
self::EVENT_EDIT => 'event_activity_updated',
self::EVENT_SUMMARY => 'event_meeting_summary',
self::EVENT_LOGOUT => 'event_meeting_left',
self::EVENT_MEETING_START => 'event_meeting_joined',
];
/**
* Get the event name (human friendly version)
*
* @param object $log object as returned by get_user_completion_logs_with_userfields
*/
public static function get_printable_event_name(object $log) {
$logstringname = self::LOG_TO_STRING[$log->log] ?? 'event_unknown';
return get_string($logstringname, 'mod_bigbluebuttonbn');
}
}
+582
View File
@@ -0,0 +1,582 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn;
use cache;
use cache_store;
use context_course;
use core_tag_tag;
use Exception;
use Firebase\JWT\Key;
use mod_bigbluebuttonbn\local\config;
use mod_bigbluebuttonbn\local\exceptions\bigbluebutton_exception;
use mod_bigbluebuttonbn\local\exceptions\meeting_join_exception;
use mod_bigbluebuttonbn\local\helpers\roles;
use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy;
use stdClass;
/**
* Class to describe a BBB Meeting.
*
* @package mod_bigbluebuttonbn
* @copyright 2021 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class meeting {
/** @var instance The bbb instance */
protected $instance;
/** @var stdClass Info about the meeting */
protected $meetinginfo = null;
/**
* Constructor for the meeting object.
*
* @param instance $instance
*/
public function __construct(instance $instance) {
$this->instance = $instance;
}
/**
* Helper to join a meeting.
*
*
* It will create the meeting if not already created.
*
* @param instance $instance
* @param int $origin
* @return string
* @throws meeting_join_exception this is sent if we cannot join (meeting full, user needs to wait...)
*/
public static function join_meeting(instance $instance, $origin = logger::ORIGIN_BASE): string {
// See if the session is in progress.
$meeting = new meeting($instance);
// As the meeting doesn't exist, try to create it.
if (empty($meeting->get_meeting_info(true)->createtime)) {
$meeting->create_meeting();
}
return $meeting->join($origin);
}
/**
* Get currently stored meeting info
*
* @return stdClass
*/
public function get_meeting_info() {
if (!$this->meetinginfo) {
$this->meetinginfo = $this->do_get_meeting_info();
}
return $this->meetinginfo;
}
/**
* Return meeting information for the specified instance.
*
* @param instance $instance
* @param bool $updatecache Whether to update the cache when fetching the information
* @return stdClass
*/
public static function get_meeting_info_for_instance(instance $instance, bool $updatecache = false): stdClass {
$meeting = new self($instance);
return $meeting->do_get_meeting_info($updatecache);
}
/**
* Helper function returns a sha1 encoded string that is unique and will be used as a seed for meetingid.
*
* @return string
*/
public static function get_unique_meetingid_seed() {
global $DB;
do {
$encodedseed = sha1(plugin::random_password(12));
$meetingid = (string) $DB->get_field('bigbluebuttonbn', 'meetingid', ['meetingid' => $encodedseed]);
} while ($meetingid == $encodedseed);
return $encodedseed;
}
/**
* Is meeting running ?
*
* @return bool
*/
public function is_running() {
return $this->get_meeting_info()->statusrunning ?? false;
}
/**
* Force update the meeting in cache.
*/
public function update_cache() {
$this->meetinginfo = $this->do_get_meeting_info(true);
}
/**
* Get meeting attendees
*
* @return array[]
*/
public function get_attendees(): array {
return $this->get_meeting_info()->attendees ?? [];
}
/**
* Can the meeting be joined ?
*
* @return bool
*/
public function can_join() {
return $this->get_meeting_info()->canjoin;
}
/**
* Total number of moderators and viewers.
*
* @return int
*/
public function get_participant_count() {
return $this->get_meeting_info()->totalusercount;
}
/**
* Creates a bigbluebutton meeting, send the message to BBB and returns the response in an array.
*
* @return array
*/
public function create_meeting() {
$data = $this->create_meeting_data();
$metadata = $this->create_meeting_metadata();
$presentation = $this->instance->get_presentation_for_bigbluebutton_upload(); // The URL must contain nonce.
$presentationname = $presentation['name'] ?? null;
$presentationurl = $presentation['url'] ?? null;
$response = bigbluebutton_proxy::create_meeting(
$data,
$metadata,
$presentationname,
$presentationurl,
$this->instance->get_instance_id()
);
// New recording management: Insert a recordingID that corresponds to the meeting created.
if ($this->instance->is_recorded()) {
$recording = new recording(0, (object) [
'courseid' => $this->instance->get_course_id(),
'bigbluebuttonbnid' => $this->instance->get_instance_id(),
'recordingid' => $response['internalMeetingID'],
'groupid' => $this->instance->get_group_id()]
);
$recording->create();
}
return $response;
}
/**
* Send an end meeting message to BBB server
*/
public function end_meeting() {
bigbluebutton_proxy::end_meeting(
$this->instance->get_meeting_id(),
$this->instance->get_moderator_password(),
$this->instance->get_instance_id()
);
}
/**
* Get meeting join URL
*
* @return string
*/
public function get_join_url(): string {
return bigbluebutton_proxy::get_join_url($this->instance, $this->get_meeting_info()->createtime);
}
/**
* Get meeting join URL for guest
*
* @param string $userfullname
* @return string
*/
public function get_guest_join_url(string $userfullname): string {
return bigbluebutton_proxy::get_guest_join_url($this->instance, $this->get_meeting_info()->createtime, $userfullname);
}
/**
* Return meeting information for this meeting.
*
* @param bool $updatecache Whether to update the cache when fetching the information
* @return stdClass
*/
protected function do_get_meeting_info(bool $updatecache = false): stdClass {
$instance = $this->instance;
$meetinginfo = (object) [
'instanceid' => $instance->get_instance_id(),
'bigbluebuttonbnid' => $instance->get_instance_id(),
'groupid' => $instance->get_group_id(),
'meetingid' => $instance->get_meeting_id(),
'cmid' => $instance->get_cm_id(),
'ismoderator' => $instance->is_moderator(),
'joinurl' => $instance->get_join_url()->out(),
'userlimit' => $instance->get_user_limit(),
'presentations' => [],
];
if ($instance->get_instance_var('openingtime')) {
$meetinginfo->openingtime = intval($instance->get_instance_var('openingtime'));
}
if ($instance->get_instance_var('closingtime')) {
$meetinginfo->closingtime = intval($instance->get_instance_var('closingtime'));
}
$activitystatus = bigbluebutton_proxy::view_get_activity_status($instance);
// This might raise an exception if info cannot be retrieved.
// But this might be totally fine as the meeting is maybe not yet created on BBB side.
$totalusercount = 0;
// This is the default value for any meeting that has not been created.
$meetinginfo->statusrunning = false;
$meetinginfo->createtime = null;
$info = self::retrieve_cached_meeting_info($this->instance, $updatecache);
if (!empty($info)) {
$meetinginfo->statusrunning = $info['running'] === 'true';
$meetinginfo->createtime = $info['createTime'] ?? null;
$totalusercount = isset($info['participantCount']) ? $info['participantCount'] : 0;
}
$meetinginfo->statusclosed = $activitystatus === 'ended';
$meetinginfo->statusopen = !$meetinginfo->statusrunning && $activitystatus === 'open';
$meetinginfo->totalusercount = $totalusercount;
$canjoin = !$instance->user_must_wait_to_join() || $meetinginfo->statusrunning;
// Limit has not been reached.
$canjoin = $canjoin && (!$instance->has_user_limit_been_reached($totalusercount));
// User should only join during scheduled session start and end time, if defined.
$canjoin = $canjoin && ($instance->is_currently_open());
// Double check that the user has the capabilities to join.
$canjoin = $canjoin && $instance->can_join();
$meetinginfo->canjoin = $canjoin;
// If user is administrator, moderator or if is viewer and no waiting is required, join allowed.
if ($meetinginfo->statusrunning) {
$meetinginfo->startedat = floor(intval($info['startTime']) / 1000); // Milliseconds.
$meetinginfo->moderatorcount = $info['moderatorCount'];
$meetinginfo->moderatorplural = $info['moderatorCount'] > 1;
$meetinginfo->participantcount = $totalusercount - $meetinginfo->moderatorcount;
$meetinginfo->participantplural = $meetinginfo->participantcount > 1;
}
$meetinginfo->statusmessage = $this->get_status_message($meetinginfo, $instance);
$presentation = $instance->get_presentation(); // This is for internal use.
if (!empty($presentation)) {
$meetinginfo->presentations[] = $presentation;
}
$meetinginfo->attendees = [];
if (!empty($info['attendees'])) {
// Ensure each returned attendee is cast to an array, rather than a simpleXML object.
foreach ($info['attendees'] as $attendee) {
$meetinginfo->attendees[] = (array) $attendee;
}
}
$meetinginfo->guestaccessenabled = $instance->is_guest_allowed();
if ($meetinginfo->guestaccessenabled && $instance->is_moderator()) {
$meetinginfo->guestjoinurl = $instance->get_guest_access_url()->out();
$meetinginfo->guestpassword = $instance->get_guest_access_password();
}
$meetinginfo->features = $instance->get_enabled_features();
return $meetinginfo;
}
/**
* Deduce status message from the current meeting info and the instance
*
* Returns the human-readable message depending on if the user must wait to join, the meeting has not
* yet started ...
* @param object $meetinginfo
* @param instance $instance
* @return string
*/
protected function get_status_message(object $meetinginfo, instance $instance): string {
if ($instance->has_user_limit_been_reached($meetinginfo->totalusercount)) {
return get_string('view_message_conference_user_limit_reached', 'bigbluebuttonbn');
}
if ($meetinginfo->statusrunning) {
return get_string('view_message_conference_in_progress', 'bigbluebuttonbn');
}
if ($instance->user_must_wait_to_join() && !$instance->user_can_force_join()) {
return get_string('view_message_conference_wait_for_moderator', 'bigbluebuttonbn');
}
if ($instance->before_start_time()) {
return get_string('view_message_conference_not_started', 'bigbluebuttonbn');
}
if ($instance->has_ended()) {
return get_string('view_message_conference_has_ended', 'bigbluebuttonbn');
}
return get_string('view_message_conference_room_ready', 'bigbluebuttonbn');
}
/**
* Gets a meeting info object cached or fetched from the live session.
*
* @param instance $instance
* @param bool $updatecache
*
* @return array
*/
protected static function retrieve_cached_meeting_info(instance $instance, $updatecache = false) {
$meetingid = $instance->get_meeting_id();
$cachettl = (int) config::get('waitformoderator_cache_ttl');
$cache = cache::make_from_params(cache_store::MODE_APPLICATION, 'mod_bigbluebuttonbn', 'meetings_cache');
$result = $cache->get($meetingid);
$now = time();
if (!$updatecache && !empty($result) && $now < ($result['creation_time'] + $cachettl)) {
// Use the value in the cache.
return (array) json_decode($result['meeting_info']);
}
// We set the cache to an empty value so then if get_meeting_info raises an exception we still have the
// info about the last creation_time, so we don't ask the server again for a bit.
$defaultcacheinfo = ['creation_time' => time(), 'meeting_info' => '[]'];
// Pings again and refreshes the cache.
try {
$meetinginfo = bigbluebutton_proxy::get_meeting_info($meetingid);
$cache->set($meetingid, ['creation_time' => time(), 'meeting_info' => json_encode($meetinginfo)]);
} catch (bigbluebutton_exception $e) {
// The meeting is not created on BBB side, so we set the value in the cache so we don't poll again
// and return an empty array.
$cache->set($meetingid, $defaultcacheinfo);
return [];
}
return $meetinginfo;
}
/**
* Conversion between form settings and lockSettings as set in BBB API.
*/
const LOCK_SETTINGS_MEETING_DATA = [
'disablecam' => 'lockSettingsDisableCam',
'disablemic' => 'lockSettingsDisableMic',
'disableprivatechat' => 'lockSettingsDisablePrivateChat',
'disablepublicchat' => 'lockSettingsDisablePublicChat',
'disablenote' => 'lockSettingsDisableNotes',
'hideuserlist' => 'lockSettingsHideUserList'
];
/**
* Helper to prepare data used for create meeting.
* @todo moderatorPW and attendeePW will be removed from create after release of BBB v2.6.
*
* @return array
*/
protected function create_meeting_data() {
$data = ['meetingID' => $this->instance->get_meeting_id(),
'name' => \mod_bigbluebuttonbn\plugin::html2text($this->instance->get_meeting_name(), 64),
'attendeePW' => $this->instance->get_viewer_password(),
'moderatorPW' => $this->instance->get_moderator_password(),
'logoutURL' => $this->instance->get_logout_url()->out(false),
];
$data['record'] = $this->instance->should_record() ? 'true' : 'false';
// Check if auto_start_record is enable.
if ($data['record'] == 'true' && $this->instance->should_record_from_start()) {
$data['autoStartRecording'] = 'true';
}
// Check if hide_record_button is enable.
if (!$this->instance->should_show_recording_button()) {
$data['allowStartStopRecording'] = 'false';
}
$data['welcome'] = trim($this->instance->get_welcome_message());
$voicebridge = intval($this->instance->get_voice_bridge());
if ($voicebridge > 0 && $voicebridge < 79999) {
$data['voiceBridge'] = $voicebridge;
}
$maxparticipants = intval($this->instance->get_user_limit());
if ($maxparticipants > 0) {
$data['maxParticipants'] = $maxparticipants;
}
if ($this->instance->get_mute_on_start()) {
$data['muteOnStart'] = 'true';
}
// Here a bit of a change compared to the API default behaviour: we should not allow guest to join
// a meeting managed by Moodle by default.
if ($this->instance->is_guest_allowed()) {
$data['guestPolicy'] = $this->instance->is_moderator_approval_required() ? 'ASK_MODERATOR' : 'ALWAYS_ACCEPT';
}
// Locks settings.
foreach (self::LOCK_SETTINGS_MEETING_DATA as $instancevarname => $lockname) {
$instancevar = $this->instance->get_instance_var($instancevarname);
if (!is_null($instancevar)) {
$data[$lockname] = $instancevar ? 'true' : 'false';
if ($instancevar) {
$data['lockSettingsLockOnJoin'] = 'true'; // This will be locked whenever one settings is locked.
}
}
}
return $data;
}
/**
* Helper for preparing metadata used while creating the meeting.
*
* @return array
*/
protected function create_meeting_metadata() {
global $USER;
// Create standard metadata.
$origindata = $this->instance->get_origin_data();
$metadata = [
'bbb-origin' => $origindata->origin,
'bbb-origin-version' => $origindata->originVersion,
'bbb-origin-server-name' => $origindata->originServerName,
'bbb-origin-server-common-name' => $origindata->originServerCommonName,
'bbb-origin-tag' => $origindata->originTag,
'bbb-context' => $this->instance->get_course()->fullname,
'bbb-context-id' => $this->instance->get_course_id(),
'bbb-context-name' => trim(html_to_text($this->instance->get_course()->fullname, 0)),
'bbb-context-label' => trim(html_to_text($this->instance->get_course()->shortname, 0)),
'bbb-recording-name' => plugin::html2text($this->instance->get_meeting_name(), 64),
'bbb-recording-description' => plugin::html2text($this->instance->get_meeting_description(),
64),
'bbb-recording-tags' =>
implode(',', core_tag_tag::get_item_tags_array('core',
'course_modules', $this->instance->get_cm_id())), // Same as $id.
];
// Special metadata for recording processing.
if ((boolean) config::get('recordingstatus_enabled')) {
$metadata["bn-recording-status"] = json_encode(
[
'email' => ['"' . fullname($USER) . '" <' . $USER->email . '>'],
'context' => $this->instance->get_view_url(),
]
);
}
if ((boolean) config::get('recordingready_enabled')) {
$metadata['bbb-recording-ready-url'] = $this->instance->get_record_ready_url()->out(false);
}
if ((boolean) config::get('meetingevents_enabled')) {
$metadata['analytics-callback-url'] = $this->instance->get_meeting_event_notification_url()->out(false);
}
return $metadata;
}
/**
* Helper for responding when storing live meeting events is requested.
*
* The callback with a POST request includes:
* - Authentication: Bearer <A JWT token containing {"exp":<TIMESTAMP>} encoded with HS512>
* - Content Type: application/json
* - Body: <A JSON Object>
*
* @param instance $instance
* @param object $data
* @return string
*/
public static function meeting_events(instance $instance, object $data): string {
$bigbluebuttonbn = $instance->get_instance_data();
// Validate that the bigbluebuttonbn activity corresponds to the meeting_id received.
$meetingidelements = explode('[', $data->{'meeting_id'});
$meetingidelements = explode('-', $meetingidelements[0]);
if (!isset($bigbluebuttonbn) || $bigbluebuttonbn->meetingid != $meetingidelements[0]) {
return 'HTTP/1.0 410 Gone. The activity may have been deleted';
}
// We make sure events are processed only once.
$overrides = ['meetingid' => $data->{'meeting_id'}];
$meta['internalmeetingid'] = $data->{'internal_meeting_id'};
$meta['callback'] = 'meeting_events';
$meta['meetingid'] = $data->{'meeting_id'};
$eventcount = logger::log_event_callback($instance, $overrides, $meta);
if ($eventcount === 1) {
// Process the events.
self::process_meeting_events($instance, $data);
return 'HTTP/1.0 200 Accepted. Enqueued.';
} else {
return 'HTTP/1.0 202 Accepted. Already processed.';
}
}
/**
* Helper function enqueues list of meeting events to be stored and processed as for completion.
*
* @param instance $instance
* @param stdClass $jsonobj
*/
protected static function process_meeting_events(instance $instance, stdClass $jsonobj) {
$meetingid = $jsonobj->{'meeting_id'};
$recordid = $jsonobj->{'internal_meeting_id'};
$attendees = $jsonobj->{'data'}->{'attendees'};
foreach ($attendees as $attendee) {
$userid = $attendee->{'ext_user_id'};
$overrides['meetingid'] = $meetingid;
$overrides['userid'] = $userid;
$meta['recordid'] = $recordid;
$meta['data'] = $attendee;
// Stores the log.
logger::log_event_summary($instance, $overrides, $meta);
// Enqueue a task for processing the completion.
bigbluebutton_proxy::enqueue_completion_event($instance->get_instance_data(), $userid);
}
}
/**
* Prepare join meeting action
*
* @param int $origin
* @return void
*/
protected function prepare_meeting_join_action(int $origin) {
$this->do_get_meeting_info(true);
if ($this->is_running()) {
if ($this->instance->has_user_limit_been_reached($this->get_participant_count())) {
throw new meeting_join_exception('userlimitreached');
}
} else if ($this->instance->user_must_wait_to_join()) {
// If user is not administrator nor moderator (user is student) and waiting is required.
throw new meeting_join_exception('waitformoderator');
}
// Moodle event logger: Create an event for meeting joined.
logger::log_meeting_joined_event($this->instance, $origin);
// Before executing the redirect, increment the number of participants.
roles::participant_joined($this->instance->get_meeting_id(), $this->instance->is_moderator());
}
/**
* Join a meeting.
*
* @param int $origin The spec
* @return string The URL to redirect to
* @throws meeting_join_exception
*/
public function join(int $origin): string {
$this->prepare_meeting_join_action($origin);
return $this->get_join_url();
}
/**
* Join a meeting as a guest.
*
* @param int $origin The spec
* @param string $userfullname Fullname for the guest user
* @return string The URL to redirect to
* @throws meeting_join_exception
*/
public function guest_join(int $origin, string $userfullname): string {
$this->prepare_meeting_join_action($origin);
return $this->get_join_url();
}
}
@@ -0,0 +1,149 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\output;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\config;
use mod_bigbluebuttonbn\local\helpers\roles;
use renderable;
use renderer_base;
use stdClass;
use templatable;
/**
* Renderable for the import page.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Darko Miletic (darko.miletic [at] gmail [dt] com)
*/
class import_view implements renderable, templatable {
/**
* @var instance $destinationinstance
*/
protected $destinationinstance;
/**
* @var int|null $sourceinstanceid the source instance id or null if it is not yet set.
*/
protected $sourceinstanceid;
/**
* @var int|null $sourcecourseid the source instance id or null if it is not yet set.
*/
protected $sourcecourseid;
/**
* import_view constructor.
*
* @param instance $destinationinstance
* @param int $sourcecourseid
* @param int $sourceinstanceid
*/
public function __construct(instance $destinationinstance, int $sourcecourseid, int $sourceinstanceid) {
$this->destinationinstance = $destinationinstance;
$this->sourcecourseid = $sourcecourseid >= 0 ? $sourcecourseid : null;
$this->sourceinstanceid = $sourceinstanceid >= 0 ? $sourceinstanceid : null;
}
/**
* Defer to template.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output): stdClass {
$courses = roles::import_get_courses_for_select($this->destinationinstance);
if (config::get('importrecordings_from_deleted_enabled')) {
$courses[0] = get_string('recordings_from_deleted_activities', 'mod_bigbluebuttonbn');
ksort($courses);
}
$context = (object) [
'bbbid' => $this->destinationinstance->get_instance_id(),
'has_recordings' => true,
'bbbsourceid' => 0
];
if (!empty($this->sourceinstanceid)) {
$context->sourceid = $this->sourceinstanceid;
$context->search = [
'value' => ''
];
$sourceinstance = instance::get_from_instanceid($this->sourceinstanceid);
if ($sourceinstance->is_type_room_only()) {
$context->has_recordings = false;
}
$context->bbbsourceid = $sourceinstance->get_instance_id();
}
// Now the selects.
if (!empty($this->sourcecourseid)) {
$selectrecords = [];
$cms = get_fast_modinfo($this->sourcecourseid)->instances['bigbluebuttonbn'];
foreach ($cms as $cm) {
if ($cm->id == $this->destinationinstance->get_cm_id()) {
// Skip the target instance.
continue;
}
if ($cm->deletioninprogress) {
// Check if the BBB is not currently scheduled for deletion.
continue;
}
$selectrecords[$cm->instance] = $cm->name;
}
if (config::get('importrecordings_from_deleted_enabled')) {
$selectrecords[0] =
get_string('recordings_from_deleted_activities', 'mod_bigbluebuttonbn');
}
$actionurl = $this->destinationinstance->get_import_url();
$actionurl->param('sourcecourseid', $this->sourcecourseid);
$select = new \single_select(
$actionurl,
'sourcebn',
$selectrecords,
$this->sourceinstanceid ?? ""
);
$context->bbb_select = $select->export_for_template($output);
}
$context->sourcecourseid = $this->sourcecourseid ?? 0;
// Course selector.
$context->course_select = (new \single_select(
$this->destinationinstance->get_import_url(),
'sourcecourseid',
$courses,
$this->sourcecourseid ?? ""
))->export_for_template($output);
if (!is_null($this->sourcecourseid)) {
$context->has_selected_course = true;
}
// Back button.
$context->back_button = (new \single_button(
$this->destinationinstance->get_view_url(),
get_string('view_recording_button_return', 'mod_bigbluebuttonbn')
))->export_for_template($output);
return $context;
}
}
@@ -0,0 +1,222 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\output;
use html_table;
use html_writer;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\meeting;
use mod_bigbluebuttonbn\plugin;
use renderable;
use renderer_base;
use stdClass;
/**
* Renderer for the Index page.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class index implements renderable {
/** @var stdClass */
protected $course;
/** @var stdClass[] */
protected $instances;
/**
* Constructor for the index renderable.
*
* @param stdClass $course
* @param instance[] List of bbbbn instances
*/
public function __construct(stdClass $course, array $instances) {
$this->course = $course;
$this->instances = $instances;
}
/**
* Get the table for the index page.
*
* @param renderer_base $output
* @return html_table
*/
public function get_table(renderer_base $output): html_table {
// Print the list of instances.
$table = new html_table();
if (course_format_uses_sections($this->course->format)) {
$sectionheading = get_string('sectionname', "format_{$this->course->format}");
} else {
$sectionheading = '';
}
$table->head = [
$sectionheading,
get_string('index_heading_name', plugin::COMPONENT),
get_string('index_heading_group', plugin::COMPONENT),
get_string('index_heading_users', plugin::COMPONENT),
get_string('index_heading_viewer', plugin::COMPONENT),
get_string('index_heading_moderator', plugin::COMPONENT),
get_string('index_heading_recording', plugin::COMPONENT),
get_string('index_heading_actions', plugin::COMPONENT),
];
$table->align = ['center', 'left', 'center', 'center', 'center', 'center', 'center'];
foreach ($this->instances as $instance) {
$this->add_instance_to_table($output, $table, $instance);
}
return $table;
}
/**
* Add details of the bigbluebuttonbn instance to the table.
*
* @param renderer_base $output
* @param html_table $table
* @param instance $instance
*/
protected function add_instance_to_table(renderer_base $output, html_table $table, instance $instance): void {
$cm = $instance->get_cm();
if (!$cm->uservisible) {
return;
}
if (groups_get_activity_groupmode($cm) == 0) {
$table->data[] = $this->add_room_row_to_table($output, $instance);
} else {
// Add 'All participants' room information.
$table->data[] = $this->add_room_row_to_table($output, $instance, 0);
// Add data for the groups belonging to the bbb instance, if any.
$groups = groups_get_activity_allowed_groups($cm);
foreach ($groups as $group) {
$table->data[] = $this->add_room_row_to_table($output, $instance, $group->id);
}
}
}
/**
* Displays the general view.
*
* @param renderer_base $output
* @param instance $instance
* @param int|null $group
* @return array
*/
protected function add_room_row_to_table(renderer_base $output, instance $instance, ?int $group = null): array {
if ($group) {
$instance = instance::get_group_instance_from_instance($instance, $group);
}
$meeting = new meeting($instance);
if (course_format_uses_sections($this->course->format)) {
$sectionname = get_section_name($this->course, $instance->get_cm()->sectionnum);
} else {
$sectionname = '';
}
$viewurl = $instance->get_view_url();
if ($groupid = $instance->get_group_id()) {
$viewurl->param('group', $groupid);
}
$joinurl = html_writer::link($viewurl, format_string($instance->get_meeting_name()));
// The meeting info was returned.
if ($meeting->is_running()) {
return [
$sectionname,
$joinurl,
$instance->get_group_name(),
$this->get_room_usercount($meeting),
$this->get_room_attendee_list($meeting, 'VIEWER'),
$this->get_room_attendee_list($meeting, 'MODERATOR'),
$this->get_room_record_info($output, $instance),
$this->get_room_actions($output, $instance, $meeting),
];
}
return [$sectionname, $joinurl, $instance->get_group_name(), '', '', '', '', ''];
}
/**
* Count the number of users in the meeting.
*
* @param meeting $meeting
* @return int
*/
protected function get_room_usercount(meeting $meeting): int {
return count($meeting->get_attendees());
}
/**
* Returns attendee list.
*
* @param meeting $meeting
* @param string $role
* @return string
*/
protected function get_room_attendee_list(meeting $meeting, string $role): string {
$attendees = [];
// Iterate attendees, matching by their "role" property.
foreach ($meeting->get_attendees() as $attendee) {
if (strcmp((string) $attendee['role'], $role) === 0) {
$attendees[] = $attendee['fullName'];
}
}
return implode(', ', $attendees);
}
/**
* Returns indication of recording enabled.
*
* @param renderer_base $output
* @param instance $instance
* @return string
*/
protected function get_room_record_info(renderer_base $output, instance $instance): string {
if ($instance->is_recorded()) {
// If it has been set when meeting created, set the variable on/off.
return get_string('index_enabled', 'bigbluebuttonbn');
}
return '';
}
/**
* Returns room actions.
*
* @param renderer_base $output
* @param instance $instance
* @param meeting $meeting
* @return string
*/
protected function get_room_actions(renderer_base $output, instance $instance, meeting $meeting): string {
if ($instance->is_moderator()) {
return $output->render_from_template('mod_bigbluebuttonbn/end_session_button', (object) [
'bigbluebuttonbnid' => $instance->get_instance_id(),
'groupid' => $instance->get_group_id(),
'statusrunning' => $meeting->is_running(),
]);
}
return '';
}
}
@@ -0,0 +1,79 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\output;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\helpers\roles;
use moodle_url;
use renderable;
use renderer_base;
use stdClass;
use templatable;
/**
* Renderable for the instance notification updated message
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Darko Miletic (darko.miletic [at] gmail [dt] com)
*/
class instance_updated_message implements renderable, templatable {
/** @var int The activity was created */
const TYPE_CREATED = 0;
/** @var int The activity was updated */
const TYPE_UPDATED = 1;
/**
* @var instance $instance
*/
protected $instance;
/** @var int activity type. */
protected $type;
/**
* Instance updated constructor
*
* @param instance $instance
* @param int $type
*/
public function __construct(instance $instance, int $type) {
$this->instance = $instance;
$this->type = $type;
}
/**
* Defer to template.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output): stdClass {
return (object) [
'is_update' => $this->type === self::TYPE_UPDATED,
'is_create' => $this->type === self::TYPE_CREATED,
'name' => $this->instance->get_meeting_name(),
'link' => $this->instance->get_view_url()->out(),
'description' => $this->instance->get_meeting_description(),
'openingtime' => $this->instance->get_instance_var('openingtime'),
'closingtime' => $this->instance->get_instance_var('closingtime'),
];
}
}
@@ -0,0 +1,227 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Mobile output class for bigbluebuttonbn
*
* @package mod_bigbluebuttonbn
* @copyright 2018 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
namespace mod_bigbluebuttonbn\output;
defined('MOODLE_INTERNAL') || die();
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\exceptions\bigbluebutton_exception;
use mod_bigbluebuttonbn\local\exceptions\meeting_join_exception;
use mod_bigbluebuttonbn\local\exceptions\server_not_available_exception;
use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy;
use mod_bigbluebuttonbn\logger;
use mod_bigbluebuttonbn\meeting;
global $CFG;
require_once($CFG->dirroot . '/lib/grouplib.php');
/**
* Mobile output class for bigbluebuttonbn
*
* @package mod_bigbluebuttonbn
* @copyright 2018 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
class mobile {
/**
* Returns the bigbluebuttonbn course view for the mobile app.
*
* @param mixed $args
* @return array HTML, javascript and other data.
*/
public static function mobile_course_view($args): array {
global $OUTPUT;
$args = (object) $args;
$versionname = $args->appversioncode >= 3950 ? 'latest' : 'ionic3';
$instance = instance::get_from_cmid($args->cmid);
if (!$instance) {
return self::mobile_print_error(get_string('view_error_url_missing_parameters', 'bigbluebuttonbn'));
}
$cm = $instance->get_cm();
$course = $instance->get_course();
// Check activity status.
if ($instance->before_start_time()) {
$message = get_string('view_message_conference_not_started', 'bigbluebuttonbn');
$notstarted = [
'starts_at' => '',
'ends_at' => '',
];
if (!empty($instance->get_instance_var('openingtime'))) {
$notstarted['starts_at'] = sprintf(
'%s: %s',
get_string('mod_form_field_openingtime', 'bigbluebuttonbn'),
userdate($instance->get_instance_var('openingtime'))
);
}
if (!empty($instance->get_instance_var('closingtime'))) {
$notstarted['ends_at'] = sprintf(
'%s: %s',
get_string('mod_form_field_closingtime', 'bigbluebuttonbn'),
userdate($instance->get_instance_var('closingtime'))
);
}
return self::mobile_print_notification($instance, $message, $notstarted);
}
if ($instance->has_ended()) {
$message = get_string('view_message_conference_has_ended', 'bigbluebuttonbn');
return self::mobile_print_notification($instance, $message);
}
// Check if the BBB server is working.
$serverversion = bigbluebutton_proxy::get_server_version();
if ($serverversion === null) {
return self::mobile_print_error(bigbluebutton_proxy::get_server_not_available_message($instance));
}
// Mark viewed by user (if required).
$completion = new \completion_info($course);
$completion->set_module_viewed($cm);
// Validate if the user is in a role allowed to join.
if (!$instance->can_join()) {
return self::mobile_print_error(get_string('view_nojoin', 'bigbluebuttonbn'));
}
// Note: This logic should match bbb_view.php.
// Logic of bbb_view for join to session.
if ($instance->user_must_wait_to_join()) {
// If user is not administrator nor moderator (user is student) and waiting is required.
return self::mobile_print_notification(
$instance,
get_string('view_message_conference_wait_for_moderator', 'bigbluebuttonbn')
);
}
// See if the BBB session is already in progress.
$urltojoin = '';
try {
$urltojoin = meeting::join_meeting($instance);
} catch (meeting_join_exception $e) {
return self::mobile_print_notification($instance, $e->getMessage());
} catch (server_not_available_exception $e) {
return self::mobile_print_error(bigbluebutton_proxy::get_server_not_available_message($instance));
}
// Check groups access and show message.
$msjgroup = [];
$groupmode = groups_get_activity_groupmode($instance->get_cm());
if ($groupmode != NOGROUPS) {
$msjgroup['message'] = get_string('view_mobile_message_groups_not_supported', 'bigbluebuttonbn');
}
$data = [
'bigbluebuttonbn' => $instance->get_instance_data(),
'msjgroup' => $msjgroup,
'urltojoin' => $urltojoin,
'cmid' => $cm->id,
'courseid' => $course->id,
];
// We want to show a notification when user excedded 45 seconds without click button.
$jstimecreatedmeeting = 'setTimeout(function(){
document.getElementById("bigbluebuttonbn-mobile-notifications").style.display = "block";
document.getElementById("bigbluebuttonbn-mobile-join").disabled = true;
document.getElementById("bigbluebuttonbn-mobile-meetingready").style.display = "none";
}, 45000);';
return [
'templates' => [
[
'id' => 'main',
'html' => $OUTPUT->render_from_template("mod_bigbluebuttonbn/mobile_view_page_$versionname", $data),
],
],
'javascript' => $jstimecreatedmeeting,
'otherdata' => '',
'files' => '',
];
}
/**
* Returns the view for errors.
*
* @param string $error Error to display.
* @return array HTML, javascript and otherdata
*/
protected static function mobile_print_error($error): array {
global $OUTPUT;
return [
'templates' => [
[
'id' => 'main',
'html' => $OUTPUT->render_from_template('mod_bigbluebuttonbn/mobile_view_error', [
'error' => $error,
]),
],
],
'javascript' => '',
'otherdata' => '',
'files' => '',
];
}
/**
* Returns the view for messages.
*
* @param instance $instance
* @param string $message Message to display.
* @param array $notstarted Extra messages for not started session.
* @return array HTML, javascript and otherdata
*/
protected static function mobile_print_notification(instance $instance, $message, $notstarted = []): array {
global $OUTPUT, $CFG;
$data = [
'bigbluebuttonbn' => $instance->get_instance_data(),
'cmid' => $instance->get_cm_id(),
'message' => $message,
'not_started' => $notstarted,
];
return [
'templates' => [
[
'id' => 'main',
'html' => $OUTPUT->render_from_template('mod_bigbluebuttonbn/mobile_view_notification', $data),
],
],
'javascript' => file_get_contents($CFG->dirroot . '/mod/bigbluebuttonbn/mobileapp/mobile.notification.js'),
'otherdata' => '',
'files' => ''
];
}
}
@@ -0,0 +1,60 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\output;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\recording;
/**
* Renderer for recording name in place editable.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent.david [at] call-learning [dt] fr)
*/
class recording_description_editable extends recording_editable {
/**
* Specific constructor with the right label/hint for this editable
*
* @param recording $rec
* @param instance $instance
*/
public function __construct(recording $rec, instance $instance) {
parent::__construct($rec, $instance,
get_string('view_recording_description_editlabel', 'mod_bigbluebuttonbn'),
get_string('view_recording_description_edithint', 'mod_bigbluebuttonbn'));
}
/**
* Get the value to display
*
* @param recording $recording a recording
* @return string
*/
public function get_recording_value(recording $recording): string {
$metadescription = $recording->get('description');
return \html_writer::span($metadescription);
}
/**
* Get the type of editable
*/
protected static function get_type() {
return 'description';
}
}
@@ -0,0 +1,137 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\output;
use lang_string;
use mod_bigbluebuttonbn\local\bigbluebutton;
use moodle_exception;
use core\output\inplace_editable;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy;
use mod_bigbluebuttonbn\local\proxy\recording_proxy;
use mod_bigbluebuttonbn\recording;
use stdClass;
/**
* Renderer for recording in place editable.
*
* Generic class
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent.david [at] call-learning [dt] fr)
*/
abstract class recording_editable extends \core\output\inplace_editable {
/** @var instance The bbb instance */
protected $instance;
/**
* Constructor.
*
* @param recording $rec
* @param instance $instance
* @param string $edithint
* @param string $editlabel
*/
public function __construct(recording $rec, instance $instance, string $edithint, string $editlabel) {
$this->instance = $instance;
$editable = $this->check_capability();
$displayvalue = format_string(
$this->get_recording_value($rec),
true,
[
'context' => $instance->get_context(),
]
);
// Hack here: the ID is the recordID and the meeting ID.
parent::__construct(
'mod_bigbluebuttonbn',
static::get_type(),
$rec->get('id'),
$editable,
$displayvalue,
$displayvalue,
$edithint,
$editlabel
);
}
/**
* Check user can access and or modify this item.
*
* @return bool
* @throws \moodle_exception
*/
protected function check_capability() {
global $USER;
if (!can_access_course($this->instance->get_course(), $USER)) {
throw new moodle_exception('noaccess', 'mod_bigbluebuttonbn');
}
return $this->instance->can_manage_recordings();
}
/**
* Get the type of editable
*/
protected static function get_type() {
return '';
}
/**
* Get the real recording value
*
* @param recording $rec
* @return mixed
*/
abstract public function get_recording_value(recording $rec): string;
/**
* Update the recording with the new value
*
* @param int $itemid
* @param mixed $value
* @return recording_editable
*/
public static function update($itemid, $value) {
$recording = recording::get_record(['id' => $itemid]);
$instance = instance::get_from_instanceid($recording->get('bigbluebuttonbnid'));
require_login($instance->get_course(), true, $instance->get_cm());
require_capability('mod/bigbluebuttonbn:managerecordings', $instance->get_context());
$recording->set(static::get_type(), $value);
$recording->update();
return new static($recording, $instance);
}
/**
* Helper function evaluates if a row for the data used by the recording table is editable.
*
* @return bool
*/
protected function row_editable() {
// Since the request to BBB are cached, it is safe to use the wrapper to check the server version.
return $this->instance->can_manage_recordings()
&& (bigbluebutton_proxy::get_server_version() >= 1.0 || $this->instance->is_blindside_network_server());
}
}
@@ -0,0 +1,60 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\output;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\recording;
/**
* Renderer for recording name in place editable.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent.david [at] call-learning [dt] fr)
*/
class recording_name_editable extends recording_editable {
/**
* Specific constructor with the right label/hint for this editable
*
* @param recording $rec
* @param instance $instance
*/
public function __construct(recording $rec, instance $instance) {
parent::__construct($rec, $instance,
get_string('view_recording_name_editlabel', 'mod_bigbluebuttonbn'),
get_string('view_recording_name_edithint', 'mod_bigbluebuttonbn'));
}
/**
* Get the value to display
*
* @param recording $recording
* @return string
*/
public function get_recording_value(recording $recording): string {
$metaname = $recording->get('name');
return \html_writer::span($metaname);
}
/**
* Get the type of editable
*/
protected static function get_type() {
return 'name';
}
}
@@ -0,0 +1,179 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\output;
use mod_bigbluebuttonbn\recording;
use pix_icon;
use renderable;
use renderer_base;
use stdClass;
use templatable;
/**
* Renderer for recording row actionbar column
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent.david [at] call-learning [dt] fr)
*/
class recording_row_actionbar implements renderable, templatable {
/**
* @var $recording
*/
protected $recording;
/**
* @var $tools
*/
protected $tools;
/**
* @var array TOOLS_DEFINITION a list of definition for the the specific tools
*/
const TOOL_ACTION_DEFINITIONS = [
'protect' => [
'action' => 'unprotect',
'icon' => 'lock',
'hidewhen' => '!protected',
'requireconfirmation' => true,
'disablewhen' => 'imported'
],
'unprotect' => [
'action' => 'protect',
'icon' => 'unlock',
'hidewhen' => 'protected',
'requireconfirmation' => true,
'disablewhen' => 'imported'
],
'publish' => [
'action' => 'publish',
'icon' => 'show',
'hidewhen' => 'published',
'requireconfirmation' => true,
'disablewhen' => 'imported'
],
'unpublish' => [
'action' => 'unpublish',
'icon' => 'hide',
'hidewhen' => '!published',
'requireconfirmation' => true,
'disablewhen' => 'imported'
],
'delete' => [
'action' => 'delete',
'icon' => 'trash',
'requireconfirmation' => true
],
'import' => [
'action' => 'import',
'icon' => 'import',
]
];
/**
* recording_row_actionbar constructor.
*
* @param recording $recording
* @param array $tools
*/
public function __construct(recording $recording, array $tools) {
$this->recording = $recording;
$this->tools = $tools;
}
/**
* Export for template
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output): stdClass {
$context = new stdClass();
$context->id = 'recording-actionbar-' . $this->recording->get('id');
$context->recordingid = $this->recording->get('id');
$context->tools = [];
foreach ($this->tools as $tool) {
if (!empty(self::TOOL_ACTION_DEFINITIONS[$tool])) {
$buttonpayload = self::TOOL_ACTION_DEFINITIONS[$tool];
$conditionalhiding = $buttonpayload['hidewhen'] ?? null;
$disabledwhen = $buttonpayload['disablewhen'] ?? null;
$this->actionbar_update_diplay($buttonpayload, $disabledwhen, $this->recording, 'disabled');
$this->actionbar_update_diplay($buttonpayload, $conditionalhiding, $this->recording);
if (!empty($buttonpayload)) {
$iconortext = '';
$target = $buttonpayload['action'];
if (isset($buttonpayload['target'])) {
$target .= '-' . $buttonpayload['target'];
}
$id = 'recording-' . $target . '-' . $this->recording->get('recordingid');
$iconattributes = [
'id' => $id,
'class' => 'iconsmall',
];
$linkattributes = [
'id' => $id,
'data-action' => $buttonpayload['action'],
'data-require-confirmation' => !empty($buttonpayload['requireconfirmation']),
'class' => 'action-icon'
];
if ($this->recording->get('imported')) {
$linkattributes['data-links'] = recording::count_records(
[
'recordingid' => $this->recording->get('recordingid'),
'imported' => true,
]
);
}
if (isset($buttonpayload['disabled'])) {
$iconattributes['class'] .= ' fa-' . $buttonpayload['disabled'];
$linkattributes['class'] .= ' disabled';
}
$icon = new pix_icon(
'i/' . $buttonpayload['icon'],
get_string('view_recording_list_actionbar_' . $buttonpayload['action'], 'bigbluebuttonbn'),
'moodle',
$iconattributes
);
$iconortext = $output->render($icon);
$actionlink = new \action_link(new \moodle_url('#'), $iconortext, null, $linkattributes);
$context->tools[] = $actionlink->export_for_template($output);
}
}
}
return $context;
}
/**
* Read the settings for this action and disable or hide the tool from the toolbar
*
* @param array $buttonpayload
* @param string $condition
* @param recording $rec
* @param string $value
*/
private function actionbar_update_diplay(&$buttonpayload, $condition, $rec, $value = 'invisible') {
if ($condition) {
$negates = $condition[0] === '!';
$conditionalvariable = ltrim($condition, '!');
if ($rec->get($conditionalvariable) xor $negates) {
$buttonpayload['disabled'] = $value;
}
}
}
}
@@ -0,0 +1,123 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\output;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\bigbluebutton\recordings\recording_data;
use mod_bigbluebuttonbn\local\config;
use mod_bigbluebuttonbn\local\helpers\roles;
use mod_bigbluebuttonbn\recording;
use renderable;
use renderer_base;
use stdClass;
use templatable;
/**
* Renderer for recording row playback column
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent.david [at] call-learning [dt] fr)
*/
class recording_row_playback implements renderable, templatable {
/**
* @var $instance
*/
protected $instance;
/**
* @var $recording
*/
protected $recording;
/**
* recording_row_playback constructor.
*
* @param recording $rec
* @param instance|null $instance $instance
*/
public function __construct(recording $rec, ?instance $instance) {
$this->instance = $instance ?? null;
$this->recording = $rec;
}
/**
* Export for template
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output): stdClass {
$ispublished = $this->recording->get('published');
$recordingid = $this->recording->get('id');
$context = (object) [
'dataimported' => $this->recording->get('imported'),
'id' => 'playbacks-' . $this->recording->get('id'),
'recordingid' => $recordingid,
'additionaloptions' => '',
'playbacks' => [],
];
$playbacks = $this->recording->get('playbacks');
if ($ispublished && $playbacks) {
foreach ($playbacks as $playback) {
if ($this->should_be_included($playback)) {
$linkattributes = [
'id' => "recording-play-{$playback['type']}-{$recordingid}",
'class' => 'btn btn-sm btn-default',
'data-action' => 'play',
'data-target' => $playback['type'],
];
$actionlink = new \action_link(
$playback['url'],
recording_data::type_text($playback['type']),
null,
$linkattributes
);
$context->playbacks[] = $actionlink->export_for_template($output);
}
}
}
return $context;
}
/**
* Helper function renders the link used for recording type in row for the data used by the recording table.
*
* @param array $playback
* @return bool
*/
protected function should_be_included(array $playback): bool {
// All types that are not restricted are included.
if (array_key_exists('restricted', $playback) && strtolower($playback['restricted']) == 'false') {
return true;
}
$canmanagerecordings = roles::has_capability_in_course(
$this->recording->get('courseid'), 'mod/bigbluebuttonbn:managerecordings');
$canviewallformats = roles::has_capability_in_course(
$this->recording->get('courseid'), 'mod/bigbluebuttonbn:viewallrecordingformats');
$issafeformat = false;
// Now check the list of safe formats.
if ($safeformats = config::get('recording_safe_formats')) {
$safeformatarray = str_getcsv($safeformats);
$issafeformat = in_array($playback['type'], $safeformatarray);
}
return ($canmanagerecordings && $canviewallformats) || $issafeformat;
}
}
@@ -0,0 +1,89 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\output;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\bigbluebutton\recordings\recording_data;
use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy;
use mod_bigbluebuttonbn\recording;
use renderable;
use renderer_base;
use stdClass;
use templatable;
/**
* Renderer for recording_row_preview column
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent.david [at] call-learning [dt] fr)
*/
class recording_row_preview implements renderable, templatable {
/**
* @var $recording
*/
protected $recording;
/**
* recording_row_playback constructor.
*
* @param recording $rec
*/
public function __construct(recording $rec) {
$this->recording = $rec;
}
/**
* Export for template
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output): stdClass {
global $CFG;
$context = (object) [
'id' => 'preview-' . $this->recording->get('id'),
'hidden' => !$this->recording->get('published'),
'recordingpreviews' => [],
];
$playbacks = $this->recording->get('playbacks');
foreach ($playbacks as $playback) {
$thumbnails = [];
if (isset($playback['preview'])) {
foreach ($playback['preview'] as $image) {
$url = trim($image['url']);
$validated = bigbluebutton_proxy::is_remote_resource_valid($url);
if ($validated) {
$thumbnails[] = $url . '?' . time();
}
}
if (!empty($thumbnails)) {
$context->recordingpreviews[] = (object) [
'thumbnails' => $thumbnails,
];
}
}
}
return $context;
}
}
@@ -0,0 +1,77 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\output;
use mod_bigbluebuttonbn\instance;
use renderable;
use renderer_base;
use stdClass;
use templatable;
/**
* Renderer for 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 Laurent David (laurent.david [at] call-learning [dt] fr)
*/
class recordings_session implements renderable, templatable {
/**
* @var instance
*/
protected $instance;
/**
* recording_section constructor.
*
* @param instance $instance
*/
public function __construct(instance $instance) {
$this->instance = $instance;
}
/**
* Export for template
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output): stdClass {
$isrecordedtype = $this->instance->is_type_room_and_recordings() || $this->instance->is_type_recordings_only();
$context = (object) [
'bbbid' => $this->instance->get_instance_id(),
'groupid' => $this->instance->get_group_id(),
'has_recordings' => $this->instance->is_recorded() && $isrecordedtype,
'searchbutton' => [
'value' => '',
],
];
if ($this->instance->can_import_recordings()) {
$button = new \single_button(
$this->instance->get_import_url(),
get_string('view_recording_button_import', 'mod_bigbluebuttonbn')
);
$context->import_button = $button->export_for_template($output);
}
return $context;
}
}
@@ -0,0 +1,110 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\output;
use core\notification;
use core\output\inplace_editable;
use html_table;
use html_writer;
use mod_bigbluebuttonbn\instance;
use plugin_renderer_base;
/**
* Renderer for the mod_bigbluebuttonbn plugin.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Darko Miletic (darko.miletic [at] gmail [dt] com)
*/
class renderer extends plugin_renderer_base {
/**
* Render the index table.
*
* @param index $index
* @return string
*/
protected function render_index(index $index): string {
$this->page->requires->js_call_amd('mod_bigbluebuttonbn/index', 'init');
return html_writer::table($index->get_table($this));
}
/**
* Render the groups selector.
*
* @param instance $instance
* @return string
*/
public function render_groups_selector(instance $instance): string {
$groupmode = groups_get_activity_groupmode($instance->get_cm());
if ($groupmode == NOGROUPS) {
return '';
}
// Separate or visible group mode.
$groups = groups_get_activity_allowed_groups($instance->get_cm());
if (empty($groups)) {
// No groups in this course.
notification::add(get_string('view_groups_nogroups_warning', 'bigbluebuttonbn'), notification::INFO);
return '';
}
// Assign group default values.
if (count($groups) == 0) {
// Only the All participants group exists.
notification::add(get_string('view_groups_notenrolled_warning', 'bigbluebuttonbn'), notification::INFO);
return '';
}
if (count($groups) > 1) {
notification::add(get_string('view_groups_selection_warning', 'bigbluebuttonbn'), notification::INFO);
}
$groupsmenu = groups_print_activity_menu(
$instance->get_cm(),
$instance->get_view_url(),
true
);
return $groupsmenu . '<br><br>';
}
/**
* Render the view page.
*
* @param view_page $page
* @return string
*/
public function render_view_page(view_page $page): string {
return $this->render_from_template(
'mod_bigbluebuttonbn/view_page',
$page->export_for_template($this)
);
}
/**
* Render inplace editable
*
* @param inplace_editable $e
* @return bool|string
*/
public function render_inplace_editable(inplace_editable $e) {
return $this->render_from_template('core/inplace_editable', $e->export_for_template($this));
}
}
@@ -0,0 +1,147 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\output;
use core\check\result;
use core\output\notification;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\config;
use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy;
use mod_bigbluebuttonbn\meeting;
use renderable;
use renderer_base;
use stdClass;
use templatable;
use tool_task\check\cronrunning;
/**
* View Page template renderable.
*
* @package mod_bigbluebuttonbn
* @copyright 2021 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class view_page implements renderable, templatable {
/** @var instance The instance being rendered */
protected $instance;
/**
* Constructor for the View Page.
*
* @param instance $instance
*/
public function __construct(instance $instance) {
$this->instance = $instance;
}
/**
* Export the content required to render the template.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output): stdClass {
$pollinterval = bigbluebutton_proxy::get_poll_interval();
$templatedata = (object) [
'instanceid' => $this->instance->get_instance_id(),
'pollinterval' => $pollinterval * 1000, // Javascript poll interval is in miliseconds.
'groupselector' => $output->render_groups_selector($this->instance),
'meetingname' => $this->instance->get_meeting_name(),
'description' => $this->instance->get_meeting_description(true),
'joinurl' => $this->instance->get_join_url(),
];
$viewwarningmessage = config::get('general_warning_message');
if ($this->show_view_warning() && !empty($viewwarningmessage)) {
$templatedata->sitenotification = (object) [
'message' => $viewwarningmessage,
'type' => config::get('general_warning_box_type'),
'icon' => [
'pix' => 'i/bullhorn',
'component' => 'core',
],
];
if ($url = config::get('general_warning_button_href')) {
$templatedata->sitenotification->actions = [[
'url' => $url,
'title' => config::get('general_warning_button_text'),
]];
}
}
if ($this->instance->is_feature_enabled('showroom')) {
$roomdata = meeting::get_meeting_info_for_instance($this->instance);
$roomdata->haspresentations = false;
if (!empty($roomdata->presentations)) {
$roomdata->haspresentations = true;
}
$templatedata->room = $roomdata;
}
$templatedata->recordingwarnings = [];
$check = new cronrunning();
$result = $check->get_result();
if ($result->get_status() != result::OK && $this->instance->is_moderator()) {
$templatedata->recordingwarnings[] = (new notification(
get_string('view_message_cron_disabled', 'mod_bigbluebuttonbn',
$result->get_summary()),
notification::NOTIFY_ERROR,
false
))->export_for_template($output);
}
if ($this->instance->is_feature_enabled('showrecordings') && $this->instance->is_recorded()) {
$recordings = new recordings_session($this->instance);
$templatedata->recordings = $recordings->export_for_template($output);
} else if ($this->instance->is_type_recordings_only()) {
$templatedata->recordingwarnings[] = (new notification(
get_string('view_message_recordings_disabled', 'mod_bigbluebuttonbn'),
notification::NOTIFY_WARNING,
false
))->export_for_template($output);
}
return $templatedata;
}
/**
* Whether to show the view warning.
*
* @return bool
*/
protected function show_view_warning(): bool {
if ($this->instance->is_admin()) {
return true;
}
$generalwarningroles = explode(',', config::get('general_warning_roles'));
$userroles = \mod_bigbluebuttonbn\local\helpers\roles::get_user_roles(
$this->instance->get_context(),
$this->instance->get_user_id()
);
foreach ($userroles as $userrole) {
if (in_array($userrole->shortname, $generalwarningroles)) {
return true;
}
}
return false;
}
}
+79
View File
@@ -0,0 +1,79 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn;
/**
* Class plugin.
*
* @package mod_bigbluebuttonbn
* @copyright 2019 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Darko Miletic (darko.miletic [at] gmail [dt] com)
*/
abstract class plugin {
/**
* Component name.
*/
const COMPONENT = 'mod_bigbluebuttonbn';
/**
* Helper function to convert an html string to plain text.
*
* @param string $html
* @param int $len
*
* @return string
*/
public static function html2text($html, $len = 0) {
$text = strip_tags($html);
$text = str_replace('&nbsp;', ' ', $text);
$textlen = strlen($text);
$text = mb_substr($text, 0, $len);
if ($textlen > $len) {
$text .= '...';
}
return $text;
}
/**
* Helper generates a random password.
*
* @param int $length
* @param string $unique
*
* @return string
*/
public static function random_password($length = 8, $unique = "") {
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
do {
$password = substr(str_shuffle($chars), 0, $length);
} while ($unique == $password);
return $password;
}
/**
* Generate random credentials for guest access
*
* @return array
*/
public static function generate_guest_meeting_credentials(): array {
$password = self::random_password();
$guestlinkuid = sha1(self::random_password(1024));
return [$guestlinkuid, $password];
}
}
@@ -0,0 +1,159 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\plugininfo;
use core\plugininfo\base;
use mod_bigbluebuttonbn\extension;
/**
* Subplugin extension info class.
*
* @package mod_bigbluebuttonbn
* @copyright 2022 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent@call-learning.fr)
*/
class bbbext extends base {
/**
* Check if BigBlueButton plugin is enabled
* @return bool
*/
private static function is_bbb_enabled(): bool {
$enabledplugins = \core\plugininfo\mod::get_enabled_plugins();
return isset($enabledplugins['bigbluebuttonbn']);
}
/**
* Get a list of enabled plugins
*
* @return array
* @throws \dml_exception
*/
public static function get_enabled_plugins(): array {
// If the mod_bigbluebuttonbn is not enabled, then all subplugin are disabled.
if (!self::is_bbb_enabled()) {
return [];
}
// Get all available plugins.
$plugins = \core_plugin_manager::instance()->get_installed_plugins(extension::BBB_EXTENSION_PLUGIN_NAME);
if (!$plugins) {
return [];
}
// Check they are enabled using get_config (which is cached and hopefully fast).
$enabled = [];
foreach ($plugins as $plugin => $version) {
$disabled = get_config(extension::BBB_EXTENSION_PLUGIN_NAME . '_' . $plugin, 'disabled');
if (empty($disabled)) {
$enabled[$plugin] = $plugin;
}
}
return $enabled;
}
/**
* Enable the plugin
*
* @param string $pluginname
* @param int $enabled
* @return bool
* @throws \dml_exception
*/
public static function enable_plugin(string $pluginname, int $enabled): bool {
$haschanged = false;
// If the mod_bigbluebuttonbn is not enabled, then all subplugin are disabled.
if (!self::is_bbb_enabled()) {
return false;
}
$plugin = extension::BBB_EXTENSION_PLUGIN_NAME. '_' . $pluginname;
$oldvalue = get_config($plugin, 'disabled');
$disabled = !$enabled;
// Only set value if there is no config setting or if the value is different from the previous one.
if ($oldvalue == false && $disabled) {
set_config('disabled', $disabled, $plugin);
$haschanged = true;
} else if ($oldvalue != false && !$disabled) {
unset_config('disabled', $plugin);
$haschanged = true;
}
if ($haschanged) {
add_to_config_log('disabled', $oldvalue, $disabled, $plugin);
\core_plugin_manager::reset_caches();
}
return $haschanged;
}
/**
* Loads plugin settings to the settings tree
*
* This function usually includes settings.php file in plugins folder.
* Alternatively it can create a link to some settings page (instance of admin_externalpage)
*
* @param \part_of_admin_tree $adminroot
* @param string $parentnodename
* @param bool $hassiteconfig whether the current user has moodle/site:config capability
*/
public function load_settings(\part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
// If the mod_bigbluebuttonbn is not enabled, then all subplugin are disabled.
if (!self::is_bbb_enabled()) {
return;
}
$ADMIN = $adminroot;
$plugininfo = $this;
if (!$this->is_installed_and_upgraded()) {
return;
}
if (!$hassiteconfig || !file_exists($this->full_path('settings.php'))) {
return;
}
$section = $this->get_settings_section_name();
$settings = new \admin_settingpage($section, $this->displayname, 'moodle/site:config',
$this->is_enabled() === false);
if ($adminroot->fulltree) {
$shortsubtype = substr($this->type, strlen(extension::BBB_EXTENSION_PLUGIN_NAME));
include($this->full_path('settings.php'));
}
$adminroot->add($this->type . 'plugins', $settings);
}
/**
* Get settings section name
*
* @return string
*/
public function get_settings_section_name() {
return $this->type . '_' . $this->name;
}
/**
* Should there be a way to uninstall the plugin via the administration UI.
*
*
* @return bool
*/
public function is_uninstall_allowed() {
return true;
}
}
@@ -0,0 +1,316 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\approved_userlist;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\helper;
use core_privacy\local\request\transform;
use core_privacy\local\request\userlist;
use core_privacy\local\request\writer;
/**
* Privacy class for requesting user data.
*
* @package mod_bigbluebuttonbn
* @copyright 2018 - present, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
class provider implements
// This plugin has data.
\core_privacy\local\metadata\provider,
// This plugin currently implements the original plugin\provider interface.
\core_privacy\local\request\plugin\provider,
// This plugin is capable of determining which users have data within it.
\core_privacy\local\request\core_userlist_provider {
/**
* Returns metadata.
*
* @param collection $collection The initialised collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection): collection {
// The table bigbluebuttonbn stores only the room properties.
// However, there is a chance that some personal information is stored as metadata.
// This would be done in the column 'participants' where rules can be set to define BBB roles.
// It is fair to say that only the userid is stored, which is useless if user is removed.
// But if this is a concern a refactoring on the way the rules are stored will be required.
$collection->add_database_table('bigbluebuttonbn', [
'participants' => 'privacy:metadata:bigbluebuttonbn:participants',
], 'privacy:metadata:bigbluebuttonbn');
// The table bigbluebuttonbn_logs stores events triggered by users when using the plugin.
// Some personal information along with the resource accessed is stored.
$collection->add_database_table('bigbluebuttonbn_logs', [
'userid' => 'privacy:metadata:bigbluebuttonbn_logs:userid',
'timecreated' => 'privacy:metadata:bigbluebuttonbn_logs:timecreated',
'meetingid' => 'privacy:metadata:bigbluebuttonbn_logs:meetingid',
'log' => 'privacy:metadata:bigbluebuttonbn_logs:log',
'meta' => 'privacy:metadata:bigbluebuttonbn_logs:meta',
], 'privacy:metadata:bigbluebuttonbn_logs');
$collection->add_database_table('bigbluebuttonbn_recordings', [
'userid' => 'privacy:metadata:bigbluebuttonbn_logs:userid',
], 'privacy:metadata:bigbluebuttonbn_recordings');
// Personal information has to be passed to BigBlueButton.
// This includes the user ID and fullname.
$collection->add_external_location_link('bigbluebutton', [
'userid' => 'privacy:metadata:bigbluebutton:userid',
'fullname' => 'privacy:metadata:bigbluebutton:fullname',
], 'privacy:metadata:bigbluebutton');
return $collection;
}
/**
* Get the list of contexts that contain user information for the specified user.
*
* @param int $userid The user to search.
* @return contextlist $contextlist The list of contexts used in this plugin.
*/
public static function get_contexts_for_userid(int $userid): contextlist {
// If user was already deleted, do nothing.
if (!\core_user::get_user($userid)) {
return new contextlist();
}
// Fetch all bigbluebuttonbn logs.
$sql = "SELECT c.id
FROM {context} c
INNER JOIN {course_modules} cm
ON cm.id = c.instanceid
AND c.contextlevel = :contextlevel
INNER JOIN {modules} m
ON m.id = cm.module
AND m.name = :modname
INNER JOIN {bigbluebuttonbn} bigbluebuttonbn
ON bigbluebuttonbn.id = cm.instance
INNER JOIN {bigbluebuttonbn_logs} bigbluebuttonbnlogs
ON bigbluebuttonbnlogs.bigbluebuttonbnid = bigbluebuttonbn.id
WHERE bigbluebuttonbnlogs.userid = :userid";
$params = [
'modname' => 'bigbluebuttonbn',
'contextlevel' => CONTEXT_MODULE,
'userid' => $userid,
];
$contextlist = new contextlist();
$contextlist->add_from_sql($sql, $params);
return $contextlist;
}
/**
* Export personal data for the given approved_contextlist. User and context information is contained within the contextlist.
*
* @param approved_contextlist $contextlist a list of contexts approved for export.
*/
public static function export_user_data(approved_contextlist $contextlist) {
global $DB;
// Filter out any contexts that are not related to modules.
$cmids = array_reduce($contextlist->get_contexts(), function($carry, $context) {
if ($context->contextlevel == CONTEXT_MODULE) {
$carry[] = $context->instanceid;
}
return $carry;
}, []);
if (empty($cmids)) {
return;
}
$user = $contextlist->get_user();
// Get all the bigbluebuttonbn activities associated with the above course modules.
$instanceidstocmids = self::get_instance_ids_to_cmids_from_cmids($cmids);
$instanceids = array_keys($instanceidstocmids);
list($insql, $inparams) = $DB->get_in_or_equal($instanceids, SQL_PARAMS_NAMED);
$params = array_merge($inparams, ['userid' => $user->id]);
$recordset = $DB->get_recordset_select(
'bigbluebuttonbn_logs',
"bigbluebuttonbnid $insql AND userid = :userid",
$params,
'timecreated, id'
);
self::recordset_loop_and_export($recordset, 'bigbluebuttonbnid', [],
function($carry, $record) use ($user, $instanceidstocmids) {
$carry[] = [
'timecreated' => transform::datetime($record->timecreated),
'meetingid' => $record->meetingid,
'log' => $record->log,
'meta' => $record->meta,
];
return $carry;
},
function($instanceid, $data) use ($user, $instanceidstocmids) {
$context = \context_module::instance($instanceidstocmids[$instanceid]);
$contextdata = helper::get_context_data($context, $user);
$finaldata = (object) array_merge((array) $contextdata, ['logs' => $data]);
helper::export_context_files($context, $user);
writer::with_context($context)->export_data([], $finaldata);
}
);
}
/**
* Delete all data for all users in the specified context.
*
* @param \context $context the context to delete in.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
global $DB;
if (!$context instanceof \context_module) {
return;
}
$instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST);
$DB->delete_records('bigbluebuttonbn_logs', ['bigbluebuttonbnid' => $instanceid]);
}
/**
* Delete all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist a list of contexts approved for deletion.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
global $DB;
$count = $contextlist->count();
if (empty($count)) {
return;
}
$userid = $contextlist->get_user()->id;
foreach ($contextlist->get_contexts() as $context) {
if (!$context instanceof \context_module) {
return;
}
$instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST);
$DB->delete_records('bigbluebuttonbn_logs', ['bigbluebuttonbnid' => $instanceid, 'userid' => $userid]);
}
}
/**
* Return a dict of bigbluebuttonbn IDs mapped to their course module ID.
*
* @param array $cmids The course module IDs.
* @return array In the form of [$bigbluebuttonbnid => $cmid].
*/
protected static function get_instance_ids_to_cmids_from_cmids(array $cmids) {
global $DB;
list($insql, $inparams) = $DB->get_in_or_equal($cmids, SQL_PARAMS_NAMED);
$sql = "SELECT bigbluebuttonbn.id, cm.id AS cmid
FROM {bigbluebuttonbn} bigbluebuttonbn
JOIN {modules} m
ON m.name = :bigbluebuttonbn
JOIN {course_modules} cm
ON cm.instance = bigbluebuttonbn.id
AND cm.module = m.id
WHERE cm.id $insql";
$params = array_merge($inparams, ['bigbluebuttonbn' => 'bigbluebuttonbn']);
return $DB->get_records_sql_menu($sql, $params);
}
/**
* Loop and export from a recordset.
*
* @param \moodle_recordset $recordset The recordset.
* @param string $splitkey The record key to determine when to export.
* @param mixed $initial The initial data to reduce from.
* @param callable $reducer The function to return the dataset, receives current dataset, and the current record.
* @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset.
* @return void
*/
protected static function recordset_loop_and_export(
\moodle_recordset $recordset,
$splitkey,
$initial,
callable $reducer,
callable $export
) {
$data = $initial;
$lastid = null;
foreach ($recordset as $record) {
if ($lastid && $record->{$splitkey} != $lastid) {
$export($lastid, $data);
$data = $initial;
}
$data = $reducer($data, $record);
$lastid = $record->{$splitkey};
}
$recordset->close();
if (!empty($lastid)) {
$export($lastid, $data);
}
}
/**
* Get the list of users who have data within a context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*/
public static function get_users_in_context(\core_privacy\local\request\userlist $userlist) {
$context = $userlist->get_context();
if (!$context instanceof \context_module) {
return;
}
$params = [
'instanceid' => $context->instanceid,
'modulename' => 'bigbluebuttonbn',
];
$sql = "SELECT bnl.userid
FROM {course_modules} cm
JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
JOIN {bigbluebuttonbn} bn ON bn.id = cm.instance
JOIN {bigbluebuttonbn_logs} bnl ON bnl.bigbluebuttonbnid = bn.id
WHERE cm.id = :instanceid";
$userlist->add_from_sql('userid', $sql, $params);
}
/**
* Delete multiple users within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(\core_privacy\local\request\approved_userlist $userlist) {
global $DB;
$context = $userlist->get_context();
$cm = $DB->get_record('course_modules', ['id' => $context->instanceid]);
list($userinsql, $userinparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);
$params = array_merge(['bigbluebuttonbnid' => $cm->instance], $userinparams);
$sql = "bigbluebuttonbnid = :bigbluebuttonbnid AND userid {$userinsql}";
$DB->delete_records_select('bigbluebuttonbn_logs', $sql, $params);
}
}
+852
View File
@@ -0,0 +1,852 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn;
use cache;
use context;
use context_course;
use context_module;
use core\persistent;
use mod_bigbluebuttonbn\local\proxy\recording_proxy;
use moodle_url;
use stdClass;
/**
* The recording entity.
*
* This is utility class that defines a single recording, and provides methods for their local handling locally, and
* communication with the bigbluebutton server.
*
* @package mod_bigbluebuttonbn
* @copyright 2021 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class recording extends persistent {
/** The table name. */
const TABLE = 'bigbluebuttonbn_recordings';
/** @var int Defines that the activity used to create the recording no longer exists */
public const RECORDING_HEADLESS = 1;
/** @var int Defines that the recording is not the original but an imported one */
public const RECORDING_IMPORTED = 1;
/** @var int Defines that the list should include imported recordings */
public const INCLUDE_IMPORTED_RECORDINGS = true;
/** @var int A meeting set to be recorded still awaits for a recording update */
public const RECORDING_STATUS_AWAITING = 0;
/** @var int A meeting set to be recorded was not recorded and dismissed by BBB */
public const RECORDING_STATUS_DISMISSED = 1;
/** @var int A meeting set to be recorded has a recording processed */
public const RECORDING_STATUS_PROCESSED = 2;
/** @var int A meeting set to be recorded received notification callback from BBB */
public const RECORDING_STATUS_NOTIFIED = 3;
/** @var int A meeting set to be recorded was processed and set back to an awaiting state */
public const RECORDING_STATUS_RESET = 4;
/** @var int A meeting set to be recorded was deleted from bigbluebutton */
public const RECORDING_STATUS_DELETED = 5;
/** @var bool Whether metadata been changed so the remote information needs to be updated ? */
protected $metadatachanged = false;
/** @var int A refresh period for recordings, defaults to 300s (5mins) */
public const RECORDING_REFRESH_DEFAULT_PERIOD = 300;
/** @var int A time limit for recordings to be dismissed, defaults to 30d (30days) */
public const RECORDING_TIME_LIMIT_DAYS = 30;
/** @var array A cached copy of the metadata */
protected $metadata = null;
/** @var instance A cached copy of the instance */
protected $instance;
/** @var bool imported recording status */
public $imported;
/**
* Create an instance of this class.
*
* @param int $id If set, this is the id of an existing record, used to load the data.
* @param stdClass|null $record If set will be passed to from_record
* @param null|array $metadata
*/
public function __construct($id = 0, stdClass $record = null, ?array $metadata = null) {
if ($record) {
$record->headless = $record->headless ?? false;
$record->imported = $record->imported ?? false;
$record->groupid = $record->groupid ?? 0;
$record->status = $record->status ?? self::RECORDING_STATUS_AWAITING;
}
parent::__construct($id, $record);
if ($metadata) {
$this->metadata = $metadata;
}
}
/**
* Helper function to retrieve recordings from the BigBlueButton.
*
* @param instance $instance
* @param bool $includeimported
* @param bool $onlyimported
*
* @return recording[] containing the recordings indexed by recordID, each recording is also a
* non sequential associative array itself that corresponds to the actual recording in BBB
*/
public static function get_recordings_for_instance(
instance $instance,
bool $includeimported = false,
bool $onlyimported = false
): array {
[$selects, $params] = self::get_basic_select_from_parameters(false, $includeimported, $onlyimported);
$selects[] = "bigbluebuttonbnid = :bbbid";
$params['bbbid'] = $instance->get_instance_id();
$groupmode = groups_get_activity_groupmode($instance->get_cm());
$context = $instance->get_context();
if ($groupmode) {
[$groupselects, $groupparams] = self::get_select_for_group(
$groupmode,
$context,
$instance->get_course_id(),
$instance->get_group_id(),
$instance->get_cm()->groupingid
);
if ($groupselects) {
$selects[] = $groupselects;
$params = array_merge_recursive($params, $groupparams);
}
}
$recordings = self::fetch_records($selects, $params);
foreach ($recordings as $recording) {
$recording->instance = $instance;
}
return $recordings;
}
/**
* Helper function to retrieve recordings from a given course.
*
* @param int $courseid id for a course record or null
* @param array $excludedinstanceid exclude recordings from instance ids
* @param bool $includeimported
* @param bool $onlyimported
* @param bool $includedeleted
* @param bool $onlydeleted
*
* @return recording[] containing the recordings indexed by recordID, each recording is also a
* non sequential associative array itself that corresponds to the actual recording in BBB
*/
public static function get_recordings_for_course(
int $courseid,
array $excludedinstanceid = [],
bool $includeimported = false,
bool $onlyimported = false,
bool $includedeleted = false,
bool $onlydeleted = false
): array {
global $DB;
[$selects, $params] = self::get_basic_select_from_parameters(
$includedeleted,
$includeimported,
$onlyimported,
$onlydeleted
);
if ($courseid) {
$selects[] = "courseid = :courseid";
$params['courseid'] = $courseid;
$course = $DB->get_record('course', ['id' => $courseid]);
$groupmode = groups_get_course_groupmode($course);
$context = context_course::instance($courseid);
} else {
$context = \context_system::instance();
$groupmode = NOGROUPS;
}
if ($groupmode) {
[$groupselects, $groupparams] = self::get_select_for_group($groupmode, $context, $course->id);
if ($groupselects) {
$selects[] = $groupselects;
$params = array_merge($params, $groupparams);
}
}
if ($excludedinstanceid) {
[$sqlexcluded, $paramexcluded] = $DB->get_in_or_equal($excludedinstanceid, SQL_PARAMS_NAMED, 'param', false);
$selects[] = "bigbluebuttonbnid {$sqlexcluded}";
$params = array_merge($params, $paramexcluded);
}
return self::fetch_records($selects, $params);
}
/**
* Get select for given group mode and context
*
* @param int $groupmode
* @param \context $context
* @param int $courseid
* @param int $groupid
* @param int $groupingid
* @return array
*/
protected static function get_select_for_group($groupmode, $context, $courseid, $groupid = 0, $groupingid = 0): array {
global $DB, $USER;
$selects = [];
$params = [];
if ($groupmode) {
$accessallgroups = has_capability('moodle/site:accessallgroups', $context) || $groupmode == VISIBLEGROUPS;
if ($accessallgroups) {
if ($context instanceof context_module) {
$allowedgroups = groups_get_all_groups($courseid, 0, $groupingid);
} else {
$allowedgroups = groups_get_all_groups($courseid);
}
} else {
if ($context instanceof context_module) {
$allowedgroups = groups_get_all_groups($courseid, $USER->id, $groupingid);
} else {
$allowedgroups = groups_get_all_groups($courseid, $USER->id);
}
}
$allowedgroupsid = array_map(function ($g) {
return $g->id;
}, $allowedgroups);
if ($groupid || empty($allowedgroups)) {
$selects[] = "groupid = :groupid";
$params['groupid'] = ($groupid && in_array($groupid, $allowedgroupsid)) ?
$groupid : 0;
} else {
if ($accessallgroups) {
$allowedgroupsid[] = 0;
}
list($groupselects, $groupparams) = $DB->get_in_or_equal($allowedgroupsid, SQL_PARAMS_NAMED);
$selects[] = 'groupid ' . $groupselects;
$params = array_merge_recursive($params, $groupparams);
}
}
return [
implode(" AND ", $selects),
$params,
];
}
/**
* Get basic sql select from given parameters
*
* @param bool $includedeleted
* @param bool $includeimported
* @param bool $onlyimported
* @param bool $onlydeleted
* @return array
*/
protected static function get_basic_select_from_parameters(
bool $includedeleted = false,
bool $includeimported = false,
bool $onlyimported = false,
bool $onlydeleted = false
): array {
$selects = [];
$params = [];
// Start with the filters.
if ($onlydeleted) {
// Only headless recordings when only deleted is set.
$selects[] = "headless = :headless";
$params['headless'] = self::RECORDING_HEADLESS;
} else if (!$includedeleted) {
// Exclude headless recordings unless includedeleted.
$selects[] = "headless != :headless";
$params['headless'] = self::RECORDING_HEADLESS;
}
if (!$includeimported) {
// Exclude imported recordings unless includedeleted.
$selects[] = "imported != :imported";
$params['imported'] = self::RECORDING_IMPORTED;
} else if ($onlyimported) {
// Exclude non-imported recordings.
$selects[] = "imported = :imported";
$params['imported'] = self::RECORDING_IMPORTED;
}
// Now get only recordings that have been validated by recording ready callback.
$selects[] = "status IN (:status_processed, :status_notified)";
$params['status_processed'] = self::RECORDING_STATUS_PROCESSED;
$params['status_notified'] = self::RECORDING_STATUS_NOTIFIED;
return [$selects, $params];
}
/**
* Return the definition of the properties of this model.
*
* @return array
*/
protected static function define_properties() {
return [
'courseid' => [
'type' => PARAM_INT,
],
'bigbluebuttonbnid' => [
'type' => PARAM_INT,
],
'groupid' => [
'type' => PARAM_INT,
'null' => NULL_ALLOWED,
],
'recordingid' => [
'type' => PARAM_RAW,
],
'headless' => [
'type' => PARAM_BOOL,
],
'imported' => [
'type' => PARAM_BOOL,
],
'status' => [
'type' => PARAM_INT,
],
'importeddata' => [
'type' => PARAM_RAW,
'null' => NULL_ALLOWED,
'default' => ''
],
'name' => [
'type' => PARAM_TEXT,
'null' => NULL_ALLOWED,
'default' => null
],
'description' => [
'type' => PARAM_TEXT,
'null' => NULL_ALLOWED,
'default' => 0
],
'protected' => [
'type' => PARAM_BOOL,
'null' => NULL_ALLOWED,
'default' => null
],
'starttime' => [
'type' => PARAM_INT,
'null' => NULL_ALLOWED,
'default' => null
],
'endtime' => [
'type' => PARAM_INT,
'null' => NULL_ALLOWED,
'default' => null
],
'published' => [
'type' => PARAM_BOOL,
'null' => NULL_ALLOWED,
'default' => null
],
'playbacks' => [
'type' => PARAM_RAW,
'null' => NULL_ALLOWED,
'default' => null
],
];
}
/**
* Get the instance that this recording relates to.
*
* @return instance
*/
public function get_instance(): instance {
if ($this->instance === null) {
$this->instance = instance::get_from_instanceid($this->get('bigbluebuttonbnid'));
}
return $this->instance;
}
/**
* Before doing the database update, let's check if we need to update metadata
*
* @return void
*/
protected function before_update() {
// We update if the remote metadata has been changed locally.
if ($this->metadatachanged && !$this->get('imported')) {
$metadata = $this->fetch_metadata();
if ($metadata) {
recording_proxy::update_recording(
$this->get('recordingid'),
$metadata
);
}
$this->metadatachanged = false;
}
}
/**
* Create a new imported recording from current recording
*
* @param instance $targetinstance
* @return recording
*/
public function create_imported_recording(instance $targetinstance) {
$recordingrec = $this->to_record();
$remotedata = $this->fetch_metadata();
unset($recordingrec->id);
$recordingrec->bigbluebuttonbnid = $targetinstance->get_instance_id();
$recordingrec->courseid = $targetinstance->get_course_id();
$recordingrec->groupid = 0; // The recording is available to everyone.
$recordingrec->importeddata = json_encode($remotedata);
$recordingrec->imported = true;
$recordingrec->headless = false;
$importedrecording = new self(0, $recordingrec);
$importedrecording->create();
return $importedrecording;
}
/**
* Delete the recording in the BBB button
*
* @return void
*/
protected function before_delete() {
$recordid = $this->get('recordingid');
if ($recordid && !$this->get('imported')) {
recording_proxy::delete_recording($recordid);
// Delete in cache if needed.
$cachedrecordings = cache::make('mod_bigbluebuttonbn', 'recordings');
$cachedrecordings->delete($recordid);
}
}
/**
* Set name
*
* @param string $value
*/
protected function set_name($value) {
$this->metadata_set('name', trim($value));
}
/**
* Set Description
*
* @param string $value
*/
protected function set_description($value) {
$this->metadata_set('description', trim($value));
}
/**
* Recording is protected
*
* @param bool $value
*/
protected function set_protected($value) {
$realvalue = $value ? "true" : "false";
$this->metadata_set('protected', $realvalue);
recording_proxy::protect_recording($this->get('recordingid'), $realvalue);
}
/**
* Recording starttime
*
* @param int $value
*/
protected function set_starttime($value) {
$this->metadata_set('starttime', $value);
}
/**
* Recording endtime
*
* @param int $value
*/
protected function set_endtime($value) {
$this->metadata_set('endtime', $value);
}
/**
* Recording is published
*
* @param bool $value
*/
protected function set_published($value) {
$realvalue = $value ? "true" : "false";
$this->metadata_set('published', $realvalue);
// Now set this flag onto the remote bbb server.
recording_proxy::publish_recording($this->get('recordingid'), $realvalue);
}
/**
* Update recording status
*
* @param bool $value
*/
protected function set_status($value) {
$this->raw_set('status', $value);
$this->update();
}
/**
* POSSIBLE_REMOTE_META_SOURCE match a field type and its metadataname (historical and current).
*/
const POSSIBLE_REMOTE_META_SOURCE = [
'description' => ['meta_bbb-recording-description', 'meta_contextactivitydescription'],
'name' => ['meta_bbb-recording-name', 'meta_contextactivity', 'meetingName'],
'playbacks' => ['playbacks'],
'starttime' => ['startTime'],
'endtime' => ['endTime'],
'published' => ['published'],
'protected' => ['protected'],
'tags' => ['meta_bbb-recording-tags']
];
/**
* Get the real metadata name for the possible source.
*
* @param string $sourcetype the name of the source we look for (name, description...)
* @param array $metadata current metadata
*/
protected function get_possible_meta_name_for_source($sourcetype, $metadata): string {
$possiblesource = self::POSSIBLE_REMOTE_META_SOURCE[$sourcetype];
$possiblesourcename = $possiblesource[0];
foreach ($possiblesource as $possiblesname) {
if (isset($meta[$possiblesname])) {
$possiblesourcename = $possiblesname;
}
}
return $possiblesourcename;
}
/**
* Convert string (metadata) to json object
*
* @return mixed|null
*/
protected function remote_meta_convert() {
$remotemeta = $this->raw_get('importeddata');
return json_decode($remotemeta, true);
}
/**
* Description is stored in the metadata, so we sometimes needs to do some conversion.
*/
protected function get_description() {
return trim($this->metadata_get('description'));
}
/**
* Name is stored in the metadata
*/
protected function get_name() {
return trim($this->metadata_get('name'));
}
/**
* List of playbacks for this recording.
*
* @return array[]
*/
protected function get_playbacks() {
if ($playbacks = $this->metadata_get('playbacks')) {
return array_map(function (array $playback): array {
$clone = array_merge([], $playback);
$clone['url'] = new moodle_url('/mod/bigbluebuttonbn/bbb_view.php', [
'action' => 'play',
'bn' => $this->raw_get('bigbluebuttonbnid'),
'rid' => $this->get('id'),
'rtype' => $clone['type'],
]);
return $clone;
}, $playbacks);
}
return [];
}
/**
* Get the playback URL for the specified type.
*
* @param string $type
* @return null|string
*/
public function get_remote_playback_url(string $type): ?string {
$this->refresh_metadata_if_required();
$playbacks = $this->metadata_get('playbacks');
foreach ($playbacks as $playback) {
if ($playback['type'] == $type) {
return $playback['url'];
}
}
return null;
}
/**
* Is protected. Return null if protected is not implemented.
*
* @return bool|null
*/
protected function get_protected() {
$protectedtext = $this->metadata_get('protected');
return is_null($protectedtext) ? null : $protectedtext === "true";
}
/**
* Start time
*
* @return mixed|null
*/
protected function get_starttime() {
return $this->metadata_get('starttime');
}
/**
* Start time
*
* @return mixed|null
*/
protected function get_endtime() {
return $this->metadata_get('endtime');
}
/**
* Is published
*
* @return bool
*/
protected function get_published() {
$publishedtext = $this->metadata_get('published');
return $publishedtext === "true";
}
/**
* Set locally stored metadata from this instance
*
* @param string $fieldname
* @param mixed $value
*/
protected function metadata_set($fieldname, $value) {
// Can we can change the metadata on the imported record ?
if ($this->get('imported')) {
return;
}
$this->metadatachanged = true;
$metadata = $this->fetch_metadata();
$possiblesourcename = $this->get_possible_meta_name_for_source($fieldname, $metadata);
$metadata[$possiblesourcename] = $value;
$this->metadata = $metadata;
}
/**
* Get information stored in the recording metadata such as description, name and other info
*
* @param string $fieldname
* @return mixed|null
*/
protected function metadata_get($fieldname) {
$metadata = $this->fetch_metadata();
$possiblesourcename = $this->get_possible_meta_name_for_source($fieldname, $metadata);
return $metadata[$possiblesourcename] ?? null;
}
/**
* @var string Default sort for recordings when fetching from the database.
*/
const DEFAULT_RECORDING_SORT = 'timecreated ASC';
/**
* Fetch all records which match the specified parameters, including all metadata that relates to them.
*
* @param array $selects
* @param array $params
* @return recording[]
*/
protected static function fetch_records(array $selects, array $params): array {
global $DB, $CFG;
$withindays = time() - (self::RECORDING_TIME_LIMIT_DAYS * DAYSECS);
// Sort for recordings when fetching from the database.
$recordingsort = $CFG->bigbluebuttonbn_recordings_asc_sort ? 'timecreated ASC' : 'timecreated DESC';
// Fetch the local data. Arbitrary sort by id, so we get the same result on different db engines.
$recordings = $DB->get_records_select(
static::TABLE,
implode(" AND ", $selects),
$params,
$recordingsort
);
// Grab the recording IDs.
$recordingids = array_values(array_map(function ($recording) {
return $recording->recordingid;
}, $recordings));
// Fetch all metadata for these recordings.
$metadatas = recording_proxy::fetch_recordings($recordingids);
$failedids = recording_proxy::fetch_missing_recordings($recordingids);
// Return the instances.
return array_filter(array_map(function ($recording) use ($metadatas, $withindays, $failedids) {
// Filter out if no metadata was fetched.
if (!array_key_exists($recording->recordingid, $metadatas)) {
// If the recording was successfully fetched, mark it as dismissed if it is older than 30 days.
if (!in_array($recording->recordingid, $failedids) && $withindays > $recording->timecreated) {
$recording = new self(0, $recording, null);
$recording->set_status(self::RECORDING_STATUS_DISMISSED);
}
return false;
}
$metadata = $metadatas[$recording->recordingid];
// Filter out and mark it as deleted if it was deleted in BBB.
if ($metadata['state'] == 'deleted') {
$recording = new self(0, $recording, null);
$recording->set_status(self::RECORDING_STATUS_DELETED);
return false;
}
// Include it otherwise.
return new self(0, $recording, $metadata);
}, $recordings));
}
/**
* Fetch metadata
*
* If metadata has changed locally or if it an imported recording, nothing will be done.
*
* @param bool $force
* @return array
*/
protected function fetch_metadata(bool $force = false): ?array {
if ($this->metadata !== null && !$force) {
// Metadata is already up-to-date.
return $this->metadata;
}
if ($this->get('imported')) {
$this->metadata = json_decode($this->get('importeddata'), true);
} else {
$this->metadata = recording_proxy::fetch_recording($this->get('recordingid'));
}
return $this->metadata;
}
/**
* Refresh metadata if required.
*
* If this is a protected recording which whose data was not fetched in the current request, then the metadata will
* be purged and refetched. This ensures that the url is safe for use with a protected recording.
*/
protected function refresh_metadata_if_required() {
recording_proxy::purge_protected_recording($this->get('recordingid'));
$this->fetch_metadata(true);
}
/**
* Synchronise pending recordings from the server.
*
* This function should be called by the check_pending_recordings scheduled task.
*
* @param bool $dismissedonly fetch dismissed recording only
*/
public static function sync_pending_recordings_from_server(bool $dismissedonly = false): void {
global $DB;
$params = [
'withindays' => time() - (self::RECORDING_TIME_LIMIT_DAYS * DAYSECS),
];
// Fetch the local data.
if ($dismissedonly) {
mtrace("=> Looking for any recording that has been 'dismissed' in the past " . self::RECORDING_TIME_LIMIT_DAYS
. " days.");
$select = 'status = :status_dismissed AND timemodified > :withindays';
$params['status_dismissed'] = self::RECORDING_STATUS_DISMISSED;
} else {
mtrace("=> Looking for any recording awaiting processing from the past " . self::RECORDING_TIME_LIMIT_DAYS . " days.");
$select = '(status = :status_awaiting AND timecreated > :withindays) OR status = :status_reset';
$params['status_reset'] = self::RECORDING_STATUS_RESET;
$params['status_awaiting'] = self::RECORDING_STATUS_AWAITING;
}
$recordings = $DB->get_records_select(static::TABLE, $select, $params, self::DEFAULT_RECORDING_SORT);
// Sort by DEFAULT_RECORDING_SORT we get the same result on different db engines.
$recordingcount = count($recordings);
mtrace("=> Found {$recordingcount} recordings to query");
// Grab the recording IDs.
$recordingids = array_map(function($recording) {
return $recording->recordingid;
}, $recordings);
// Fetch all metadata for these recordings.
mtrace("=> Fetching recording metadata from server");
$metadatas = recording_proxy::fetch_recordings($recordingids);
$foundcount = 0;
foreach ($metadatas as $recordingid => $metadata) {
mtrace("==> Found metadata for {$recordingid}.");
$id = array_search($recordingid, $recordingids);
if (!$id) {
// Recording was not found, skip.
mtrace("===> Skip as fetched recording was not found.");
continue;
}
// Recording was found, update status.
mtrace("===> Update local cache as fetched recording was found.");
$recording = new self(0, $recordings[$id], $metadata);
$recording->set_status(self::RECORDING_STATUS_PROCESSED);
$foundcount++;
if (array_key_exists('breakouts', $metadata)) {
// Iterate breakout recordings (if any) and update status.
foreach ($metadata['breakouts'] as $breakoutrecordingid => $breakoutmetadata) {
$breakoutrecording = self::get_record(['recordingid' => $breakoutrecordingid]);
if (!$breakoutrecording) {
$breakoutrecording = new recording(0, (object) [
'courseid' => $recording->get('courseid'),
'bigbluebuttonbnid' => $recording->get('bigbluebuttonbnid'),
'groupid' => $recording->get('groupid'),
'recordingid' => $breakoutrecordingid
], $breakoutmetadata);
$breakoutrecording->create();
}
$breakoutrecording->set_status(self::RECORDING_STATUS_PROCESSED);
$foundcount++;
}
}
}
mtrace("=> Finished processing recordings. Updated status for {$foundcount} / {$recordingcount} recordings.");
}
}
@@ -0,0 +1,36 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\search;
/**
* Search area for mod_bigbluebuttonbn activities.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class activity extends \core_search\base_activity {
/**
* Returns true if this area uses file indexing.
*
* @return bool
*/
public function uses_file_indexing() {
return true;
}
}
+117
View File
@@ -0,0 +1,117 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\search;
use stdClass;
/**
* Search area for mod_bigbluebuttonbn tags.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tags extends \core_search\base_activity {
/**
* Returns true if this area uses file indexing.
*
* @return bool
*/
public function uses_file_indexing() {
return false;
}
/**
* Overwritting get_document_recordset()
* In this search implementation, we need to re-index all instances (and not only the last modified) because we
* are working with core tags and these can be removed from "manage tags" without change the timemodified in
* BBB instances.
* @param int $modifiedfrom
* @param \context|null $context
* @return \moodle_recordset|null
*/
public function get_document_recordset($modifiedfrom = 0, \context $context = null) {
global $DB;
[$contextjoin, $contextparams] = $this->get_context_restriction_sql($context, $this->get_module_name(), 'modtable');
if ($contextjoin === null) {
return null;
}
$result = $DB->get_recordset_sql(
'SELECT modtable.* FROM {' . $this->get_module_name() . '} modtable ' . $contextjoin,
array_merge($contextparams)
);
return($result);
}
/**
* Overriding method to index tags of module as string separated by comma.
*
* @param stdClass $record
* @param array $options
* @return \core_search\document|bool
*/
public function get_document($record, $options = []) {
try {
$cm = $this->get_cm($this->get_module_name(), $record->id, $record->course);
$context = \context_module::instance($cm->id);
$tags = \core_tag_tag::get_tags_by_area_in_contexts("core", "course_modules", [$context]);
$tagsstring = "";
if (!empty($tags)) {
$res = [];
foreach ($tags as $t) {
$res[] = $t->name;
}
$tagsstring = implode(", ", $res);
}
} catch (\dml_missing_record_exception $ex) {
// Notify it as we run here as admin, we should see everything.
debugging('Error retrieving ' . $this->areaid . ' ' . $record->id . ' document, not all required data is available: ' .
$ex->getMessage(), DEBUG_DEVELOPER);
return false;
} catch (\dml_exception $ex) {
// Notify it as we run here as admin, we should see everything.
debugging('Error retrieving ' . $this->areaid . ' ' . $record->id . ' document: ' . $ex->getMessage(), DEBUG_DEVELOPER);
return false;
}
// Prepare array with data from DB.
$doc = \core_search\document_factory::instance($record->id, $this->componentname, $this->areaname);
$doc->set('title', content_to_text($record->name, false));
$doc->set('content', $tagsstring);
$doc->set('contextid', $context->id);
$doc->set('courseid', $record->course);
$doc->set('owneruserid', \core_search\manager::NO_OWNER_ID);
$doc->set('modified', $record->{static::MODIFIED_FIELD_NAME});
// Check if this document should be considered new.
if (isset($options['lastindexedtime'])) {
$createdfield = static::CREATED_FIELD_NAME;
if (!empty($createdfield) && ($options['lastindexedtime'] < $record->{$createdfield})) {
// If the document was created after the last index time, it must be new.
$doc->set_is_new(true);
}
}
return $doc;
}
}
@@ -0,0 +1,255 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir.'/adminlib.php');
/**
* Helper class for validating settings used HTML for settings.php.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class setting_validator {
/**
* Validate if general section will be shown.
*
* @return bool
*/
public static function section_general_shown() {
global $CFG;
return (!isset($CFG->bigbluebuttonbn['server_url']) ||
!isset($CFG->bigbluebuttonbn['shared_secret']) ||
!isset($CFG->bigbluebuttonbn['checksum_algorithm'])
);
}
/**
* Validate if default messages section will be shown.
*
* @return bool
*/
public static function section_default_messages_shown() {
global $CFG;
return (!isset($CFG->bigbluebuttonbn['welcome_default']) ||
!isset($CFG->bigbluebuttonbn['welcome_editable']));
}
/**
* Validate if record meeting section will be shown.
*
* @return bool
*/
public static function section_record_meeting_shown() {
global $CFG;
return (!isset($CFG->bigbluebuttonbn['recording_default']) ||
!isset($CFG->bigbluebuttonbn['recording_editable']) ||
!isset($CFG->bigbluebuttonbn['recording_all_from_start_default']) ||
!isset($CFG->bigbluebuttonbn['recording_all_from_start_editable']) ||
!isset($CFG->bigbluebuttonbn['recording_hide_button_default']) ||
!isset($CFG->bigbluebuttonbn['recording_hide_button_editable'])
);
}
/**
* Validate if import recording section will be shown.
*
* @return bool
*/
public static function section_import_recordings_shown() {
global $CFG;
return (!isset($CFG->bigbluebuttonbn['importrecordings_enabled']) ||
!isset($CFG->bigbluebuttonbn['importrecordings_from_deleted_enabled']));
}
/**
* Validate if show recording section will be shown.
*
* @return bool
*/
public static function section_show_recordings_shown() {
global $CFG;
return (!isset($CFG->bigbluebuttonbn['recordings_deleted_default']) ||
!isset($CFG->bigbluebuttonbn['recordings_deleted_editable']) ||
!isset($CFG->bigbluebuttonbn['recordings_imported_default']) ||
!isset($CFG->bigbluebuttonbn['recordings_imported_editable']) ||
!isset($CFG->bigbluebuttonbn['recordings_preview_default']) ||
!isset($CFG->bigbluebuttonbn['recordings_preview_editable']) ||
!isset($CFG->bigbluebuttonbn['recording_protect_editable'])
);
}
/**
* Validate if wait moderator section will be shown.
*
* @return bool
*/
public static function section_wait_moderator_shown() {
global $CFG;
return (!isset($CFG->bigbluebuttonbn['waitformoderator_default']) ||
!isset($CFG->bigbluebuttonbn['waitformoderator_editable']) ||
!isset($CFG->bigbluebuttonbn['waitformoderator_ping_interval']) ||
!isset($CFG->bigbluebuttonbn['waitformoderator_cache_ttl']));
}
/**
* Validate if static voice bridge section will be shown.
*
* @return bool
*/
public static function section_static_voice_bridge_shown() {
global $CFG;
return (!isset($CFG->bigbluebuttonbn['voicebridge_editable']));
}
/**
* Validate if preupload presentation section will be shown.
*
* @return bool
*/
public static function section_preupload_presentation_shown() {
global $CFG;
return (!isset($CFG->bigbluebuttonbn['preuploadpresentation_editable']));
}
/**
* Validate if user limit section will be shown.
*
* @return bool
*/
public static function section_user_limit_shown() {
global $CFG;
return (!isset($CFG->bigbluebuttonbn['userlimit_default']) ||
!isset($CFG->bigbluebuttonbn['userlimit_editable']));
}
/**
* Validate if moderator default section will be shown.
*
* @return bool
*/
public static function section_moderator_default_shown() {
global $CFG;
return (!isset($CFG->bigbluebuttonbn['participant_moderator_default']));
}
/**
* Validate if settings extended section will be shown.
*
* @return bool
*/
public static function section_settings_extended_shown() {
global $CFG;
return (!isset($CFG->bigbluebuttonbn['recordingready_enabled']) ||
!isset($CFG->bigbluebuttonbn['meetingevents_enabled']));
}
/**
* Validate if muteonstart section will be shown.
*
* @return bool
*/
public static function section_muteonstart_shown() {
global $CFG;
return (!isset($CFG->bigbluebuttonbn['muteonstart_default']) ||
!isset($CFG->bigbluebuttonbn['muteonstart_editable']));
}
/**
* Validate if disablecam section will be shown.
*
* @return bool
*/
public static function section_disablecam_shown() {
global $CFG;
return (!isset($CFG->bigbluebuttonbn['disablecam_default']) ||
!isset($CFG->bigbluebuttonbn['disablecam_editable']));
}
/**
* Validate if disablemic section will be shown.
*
* @return bool
*/
public static function section_disablemic_shown() {
global $CFG;
return (!isset($CFG->bigbluebuttonbn['disablemic_default']) ||
!isset($CFG->bigbluebuttonbn['disablemic_editable']));
}
/**
* Validate if disableprivatechat section will be shown.
*
* @return bool
*/
public static function section_disableprivatechat_shown() {
global $CFG;
return (!isset($CFG->bigbluebuttonbn['disableprivatechat_default']) ||
!isset($CFG->bigbluebuttonbn['disableprivatechat_editable']));
}
/**
* Validate if disablepublicchat section will be shown.
*
* @return bool
*/
public static function section_disablepublicchat_shown() {
global $CFG;
return (!isset($CFG->bigbluebuttonbn['disablepublicchat_default']) ||
!isset($CFG->bigbluebuttonbn['disablepublicchat_editable']));
}
/**
* Validate if disablenote section will be shown.
*
* @return bool
*/
public static function section_disablenote_shown() {
global $CFG;
return (!isset($CFG->bigbluebuttonbn['disablenote_default']) ||
!isset($CFG->bigbluebuttonbn['disablenote_editable']));
}
/**
* Validate if hideuserlist section will be shown.
*
* @return bool
*/
public static function section_hideuserlist_shown() {
global $CFG;
return (!isset($CFG->bigbluebuttonbn['hideuserlist_default']) ||
!isset($CFG->bigbluebuttonbn['hideuserlist_editable']));
}
/**
* Validate that session lock settings is shown or not
* @return bool
*/
public static function section_lock_shown() {
return self::section_disablecam_shown() ||
self::section_disablemic_shown() ||
self::section_disablenote_shown() ||
self::section_disableprivatechat_shown() ||
self::section_disablepublicchat_shown() ||
self::section_disablenote_shown() ||
self::section_hideuserlist_shown();
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,236 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\task;
use core\message\message;
use core\task\adhoc_task;
use mod_bigbluebuttonbn\instance;
use moodle_exception;
use stdClass;
/**
* Class containing the abstract class for notification processes in BBB.
*
* @package mod_bigbluebuttonbn
* @copyright 2023 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class base_send_notification extends adhoc_task {
/** @var instance */
protected $instance = null;
/** @var object */
protected $coursecontact = null;
/**
* Execute the task.
*/
public function execute() {
$this->send_all_notifications();
}
/**
* Append additional elements of custom data
*
* @param array $newdata
*/
protected function append_custom_data(array $newdata): void {
if ($currentdata = (array) $this->get_custom_data()) {
$newdata = array_merge($currentdata, $newdata);
}
$this->set_custom_data($newdata);
}
/**
* Set the instanceid in the custom data.
*
* @param int $instanceid
*/
public function set_instance_id(int $instanceid): void {
$this->append_custom_data(['instanceid' => $instanceid]);
}
/**
* Get the bigbluebutton instance that this notification is for.
*
* @return instance|null null if the instance could not be loaded.
*/
protected function get_instance(): ?instance {
// This means the customdata is broken, and needs to be fixed.
if (empty($this->get_custom_data()->instanceid)) {
throw new \coding_exception("Task custom data was missing instanceid");
}
if ($this->instance === null) {
$this->instance = instance::get_from_instanceid($this->get_custom_data()->instanceid);
}
return $this->instance;
}
/**
* Get the preferred course contact for this notification.
*
* @return stdClass
*/
protected function get_course_contact(): stdClass {
global $DB;
if ($this->coursecontact === null) {
// Get course managers so they can be highlighted in the list.
$coursecontext = $this->get_instance()->get_course_context();
if ($managerroles = get_config('', 'coursecontact')) {
$coursecontactroles = explode(',', $managerroles);
foreach ($coursecontactroles as $roleid) {
$contacts = get_role_users($roleid, $coursecontext, true, 'u.id', 'u.id ASC');
foreach ($contacts as $contact) {
$this->coursecontact = $contact;
break;
}
}
}
if ($this->coursecontact === null) {
$this->coursecontact = \core_user::get_noreply_user();
}
}
return $this->coursecontact;
}
/**
* Get the list of recipients for the notification.
*
* @return stdClass[]
*/
protected function get_recipients(): array {
// Potential users should be active users only.
return get_enrolled_users(
$this->get_instance()->get_course_context(),
'mod/bigbluebuttonbn:view',
0,
'u.*',
null,
0,
0,
true
);
}
/**
* Get the HTML message content.
*
* @return string
*/
abstract protected function get_html_message(): string;
/**
* Get the plain text message content.
*
* @return string
*/
protected function get_message(): string {
return html_to_text($this->get_html_message());
}
/**
* Get the short summary message.
*
* @return string
*/
abstract protected function get_small_message(): string;
/**
* Get the preferred message format
*
* @return string
*/
protected function get_message_format(): string {
return FORMAT_HTML;
}
/**
* Get the notification type.
*
* @return string
*/
abstract protected function get_notification_type(): string;
/**
* Get the subject of the notification.
*
* @return string
*/
abstract protected function get_subject(): string;
/**
* Send all of the notifications
*/
protected function send_all_notifications(): void {
$instance = $this->get_instance();
// Cannot do anything without a valid instance.
if (empty($instance)) {
mtrace("Instance was empty, skipping");
return;
}
foreach ($this->get_recipients() as $recipient) {
try {
\core_user::require_active_user($recipient, true, true);
\core\cron::setup_user($recipient);
} catch (moodle_exception $e) {
// Skip sending.
continue;
}
$this->send_notification_to_current_user();
}
\core\cron::setup_user();
}
/**
* Send the notificiation to the current user.
*/
protected function send_notification_to_current_user(): void {
global $USER;
$instance = $this->get_instance();
$eventdata = new message();
$eventdata->courseid = $instance->get_course_id();
$eventdata->component = 'mod_bigbluebuttonbn';
$eventdata->name = $this->get_notification_type();
$eventdata->userfrom = $this->get_course_contact();
$eventdata->userto = $USER;
$eventdata->subject = $this->get_subject();
$eventdata->smallmessage = $this->get_small_message();
$eventdata->fullmessage = $this->get_message();
$eventdata->fullmessageformat = $this->get_message_format();
$eventdata->fullmessagehtml = $this->get_html_message();
$eventdata->notification = 1;
$eventdata->contexturl = $this->get_instance()->get_view_url();
$eventdata->contexturlname = $this->get_instance()->get_meeting_name();
message_send($eventdata);
}
}
@@ -0,0 +1,45 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\task;
use core\task\scheduled_task;
use mod_bigbluebuttonbn\recording;
/**
* Synchronise pending and dismissed recordings from the server.
*
* @package mod_bigbluebuttonbn
* @copyright 2022 Laurent David Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class check_dismissed_recordings extends scheduled_task {
/**
* Run the migration task.
*/
public function execute() {
recording::sync_pending_recordings_from_server(true);
}
/**
* Get the name of the task for use in the interface.
*
* @return string
*/
public function get_name(): string {
return get_string('taskname:check_dismissed_recordings', 'mod_bigbluebuttonbn');
}
}
@@ -0,0 +1,45 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\task;
use core\task\scheduled_task;
use mod_bigbluebuttonbn\recording;
/**
* Synchronise pending recordings from the server.
*
* @package mod_bigbluebuttonbn
* @copyright 2021 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class check_pending_recordings extends scheduled_task {
/**
* Run the migration task.
*/
public function execute() {
recording::sync_pending_recordings_from_server();
}
/**
* Get the name of the task for use in the interface.
*
* @return string
*/
public function get_name(): string {
return get_string('taskname:check_pending_recordings', 'mod_bigbluebuttonbn');
}
}
@@ -0,0 +1,60 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\task;
use core\task\adhoc_task;
use core_user;
use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy;
/**
* Class containing the scheduled task for updating the completion state.
*
* @package mod_bigbluebuttonbn
* @copyright 2019 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class completion_update_state extends adhoc_task {
/**
* Run bigbluebuttonbn cron.
*/
public function execute() {
// Get the custom data.
$data = $this->get_custom_data();
// Ensure the customdata structure is corect.
if (empty($data->bigbluebuttonbn->id) || empty($data->userid)) {
throw new \coding_exception("Task customdata was missing bigbluebuttonbn->id or userid");
}
// If coursemodule does not exist, ignore (likely has been deleted).
if (get_coursemodule_from_instance('bigbluebuttonbn', $data->bigbluebuttonbn->id) === false) {
mtrace("Course module does not exist, ignoring.");
return;
}
// If user does not exist, ignore (likely has been deleted).
if (core_user::get_user($data->userid) === false) {
mtrace("User does not exist, ignoring.");
return;
}
mtrace("Task completion_update_state running for user {$data->userid}");
// Process the completion.
bigbluebutton_proxy::update_completion_state($data->bigbluebuttonbn, $data->userid);
}
}
@@ -0,0 +1,96 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\task;
use core\task\adhoc_task;
use mod_bigbluebuttonbn\recording;
/**
* Class containing the scheduled task for converting recordings for the BigBlueButton version 2.5 in Moodle 4.0.
*
* @package mod_bigbluebuttonbn
* @copyright 2021 Jesus Federico, Blindside Networks Inc <jesus at blindsidenetworks dot com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class reset_recordings extends adhoc_task {
/** @var int Chunk size to use when resetting recordings */
protected static $chunksize = 100;
/**
* Run the migration task.
*/
public function execute() {
if ($this->process_reset_recordings()) {
\core\task\manager::queue_adhoc_task(new static());
}
}
/**
* Process all bigbluebuttonbn_recordings looking for entries which should be reset to be fetched again.
*
* @return bool Whether any more recodgins are waiting to be processed
*/
protected function process_reset_recordings(): bool {
global $DB;
$classname = static::class;
mtrace("Executing {$classname}...");
// Read a block of recordings to be updated.
$recs = $this->get_recordngs_to_reset();
if (empty($recs)) {
mtrace("No recordings were found for reset...");
// No more logs. Stop queueing.
return false;
}
// Reset status of {chunksize} recordings.
mtrace("Reset status of " . self::$chunksize . " recordings...");
$sql = "UPDATE {bigbluebuttonbn_recordings}
SET status = :status_reset
WHERE id = " . implode(' OR id = ', array_keys($recs));
$DB->execute($sql,
['status_reset' => recording::RECORDING_STATUS_RESET]
);
return true;
}
/**
* Get the list of recordings to be reset.
*
* @return array
*/
protected function get_recordngs_to_reset(): array {
global $DB;
return $DB->get_records_sql(
'SELECT * FROM {bigbluebuttonbn_recordings}
WHERE status = :status_processed OR status = :status_notified
ORDER BY timecreated DESC', [
'status_processed' => recording::RECORDING_STATUS_PROCESSED,
'status_notified' => recording::RECORDING_STATUS_NOTIFIED
],
0,
self::$chunksize
);
}
}
@@ -0,0 +1,45 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
declare(strict_types=1);
namespace mod_bigbluebuttonbn\task;
use core\task\adhoc_task;
use core\message\message;
use mod_bigbluebuttonbn\local\config;
/**
* Deprecated Ad-hoc task to send a notification related to the disabling of the BigBlueButton activity module.
*
* The ad-hoc tasks sends a notification to the administrator informing that the BigBlueButton activity module has
* been disabled and they are required to confirm their acceptance of the data processing agreement prior to
* re-enabling it.
*
* @package mod_bigbluebuttonbn
* @copyright 2022 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class send_bigbluebutton_module_disabled_notification extends adhoc_task {
/**
* Execute the task.
*/
public function execute() {
// Log the debug message.
$message = "Attempted to run deprecated send_bigbluebutton_module_disabled_notification task.";
debugging($message, DEBUG_DEVELOPER);
}
}
@@ -0,0 +1,109 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\task;
use core_user;
use html_writer;
/**
* This adhoc task will send emails to guest users with the meeting's details
*
* @package mod_bigbluebuttonbn
* @copyright 2022 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
*/
class send_guest_emails extends base_send_notification {
/**
* Get the notification type.
*
* @return string
*/
protected function get_notification_type(): string {
return 'guest_invited';
}
/**
* Send all the emails
*/
protected function send_all_notifications(): void {
$customdata = $this->get_custom_data();
if (!empty($customdata->emails)) {
foreach ($customdata->emails as $email) {
$user = core_user::get_noreply_user();
$user->email = $email;
$user->mailformat = 1; // HTML format.
email_to_user(
$user,
core_user::get_noreply_user(),
$this->get_subject(),
$this->get_small_message(),
$this->get_html_message()
);
}
}
}
/**
* Get the subject of the notification.
*
* @return string
*/
protected function get_subject(): string {
return get_string('guest_invitation_subject', 'mod_bigbluebuttonbn', $this->get_string_vars());
}
/**
* Get variables to make available to strings.
*
* @return array
*/
protected function get_string_vars(): array {
$customdata = $this->get_custom_data();
$sender = core_user::get_user($customdata->useridfrom);
return [
'course_fullname' => $this->get_instance()->get_course()->fullname,
'course_shortname' => $this->get_instance()->get_course()->shortname,
'name' => $this->get_instance()->get_cm()->name,
'guestjoinurl' => $this->get_instance()->get_guest_access_url()->out(false),
'guestpassword' => $this->get_instance()->get_guest_access_password(),
'sender' => fullname($sender)
];
}
/**
* Get the short summary message.
*
* @return string
*/
protected function get_small_message(): string {
return get_string('guest_invitation_small_message', 'mod_bigbluebuttonbn', $this->get_string_vars());
}
/**
* Get the HTML message content.
*
* @return string
*/
protected function get_html_message(): string {
return html_writer::tag(
'p',
get_string('guest_invitation_full_message', 'mod_bigbluebuttonbn', $this->get_string_vars())
);
}
}
@@ -0,0 +1,45 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\task;
use core\task\adhoc_task;
/**
* Class containing the deprecated class for send_notification event in BBB.
*
* @package mod_bigbluebuttonbn
* @copyright 2021 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class send_notification extends adhoc_task {
/**
* Execute the task.
*/
public function execute() {
// Log the debug message.
$message = $this->generate_message();
debugging($message, DEBUG_DEVELOPER);
}
/**
* Output the debug log message.
*
* @return string The debug log message.
*/
public function generate_message() {
return "Attempted to run deprecated implementation of send_notification task.";
}
}
@@ -0,0 +1,83 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\task;
use html_writer;
/**
* Class containing the adhoc task to send a recording ready notification.
*
* @package mod_bigbluebuttonbn
* @copyright 2021 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class send_recording_ready_notification extends base_send_notification {
/**
* Get the notification type.
*
* @return string
*/
protected function get_notification_type(): string {
return 'recording_ready';
}
/**
* Get the subject of the notification.
*
* @return string
*/
protected function get_subject(): string {
$instance = $this->get_instance();
return get_string('notification_recording_ready_subject', 'mod_bigbluebuttonbn', $this->get_string_vars());
}
/**
* Get the short summary message.
*
* @return string
*/
protected function get_small_message(): string {
return get_string('notification_recording_ready_small', 'mod_bigbluebuttonbn', $this->get_string_vars());
}
/**
* Get the HTML message content.
*
* @return string
*/
protected function get_html_message(): string {
return html_writer::tag(
'p',
get_string('notification_recording_ready_html', 'mod_bigbluebuttonbn', $this->get_string_vars())
);
}
/**
* Get variables to make available to strings.
*
* @return array
*/
protected function get_string_vars(): array {
return [
'course_fullname' => $this->instance->get_course()->fullname,
'course_shortname' => $this->instance->get_course()->shortname,
'name' => $this->instance->get_cm()->name,
'link' => $this->instance->get_view_url()->out(),
];
}
}
@@ -0,0 +1,203 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace mod_bigbluebuttonbn\task;
use core\task\adhoc_task;
use core\task\manager;
use Matrix\Exception;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\proxy\recording_proxy;
use mod_bigbluebuttonbn\logger;
use mod_bigbluebuttonbn\recording;
use moodle_exception;
/**
* Class containing the scheduled task for converting recordings for the BigBlueButton version 2.5 in Moodle 4.0.
*
* @package mod_bigbluebuttonbn
* @copyright 2021 Jesus Federico, Blindside Networks Inc <jesus at blindsidenetworks dot com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class upgrade_recordings_task extends adhoc_task {
/**
* Run the migration task.
*/
public function execute() {
$info = $this->get_custom_data();
$meetingid = $info->meetingid;
$isimported = $info->isimported ?? 0;
$this->process_bigbluebuttonbn_logs($meetingid, $isimported);
}
/**
* Process all bigbluebuttonbn logs looking for entries which should be converted to meetings.
*
* @param string $meetingid
* @param bool $isimported
* @return bool Whether any more logs are waiting to be processed
* @throws \dml_exception
* @throws moodle_exception
*/
protected function process_bigbluebuttonbn_logs(string $meetingid, bool $isimported): bool {
global $DB;
$classname = static::class;
mtrace("Executing {$classname} for meeting {$meetingid}...");
// Fetch the logs queued for upgrade.
mtrace("Fetching logs for conversion");
// Each log is ordered by timecreated.
[$select, $params] = $this->get_sql_query_for_logs($meetingid, $isimported);
$logsrs = $DB->get_recordset_select('bigbluebuttonbn_logs',
$select,
$params,
'timecreated DESC',
'id, meetingid, timecreated, log');
if (!$logsrs->valid()) {
mtrace("No logs were found for conversion.");
// No more logs. Stop queueing.
return false;
}
// Retrieve recordings from the servers for this meeting.
$recordings = recording_proxy::fetch_recording_by_meeting_id([$meetingid]);
// Sort recordings by meetingId, then startTime.
uasort($recordings, function($a, $b) {
return $b['startTime'] - $a['startTime'];
});
// Create an instance of bigbluebuttonbn_recording per valid recording.
mtrace("Creating new recording records...");
$recordingcount = 0;
foreach ($recordings as $recordingid => $recording) {
$importeddata = $isimported ? '' : json_encode($recording);
try {
$instance = instance::get_from_meetingid($recording['meetingID']);
} catch (Exception $e) {
mtrace("Unable to parse meetingID " . $e->getMessage());
continue;
}
if ($instance) {
$newrecording = [
'courseid' => $instance->get_course_id(),
'bigbluebuttonbnid' => $instance->get_instance_id(),
'groupid' => $instance->get_group_id(), // The groupid should be taken from the meetingID.
'recordingid' => $recordingid,
'status' => recording::RECORDING_STATUS_PROCESSED,
];
} else {
mtrace("Unable to find an activity for {$recording['meetingID']}. This recording is headless.");
// This instance does not exist any more.
// Use the data in the log instead of the instance.
$meetingdata = instance::parse_meetingid($recording['meetingID']);
$newrecording = [
'courseid' => $meetingdata['courseid'],
'bigbluebuttonbnid' => $meetingdata['instanceid'],
'groupid' => 0,
'recordingid' => $recordingid,
'status' => recording::RECORDING_STATUS_PROCESSED,
];
if (array_key_exists('groupid', $meetingdata)) {
$newrecording['groupid'] = $meetingdata['groupid'];
}
}
if ($DB->record_exists('bigbluebuttonbn_recordings', $newrecording)) {
mtrace("A recording already exists for {$recording['recordID']}. Skipping.");
// A recording matching these characteristics alreay exists.
continue;
}
// Recording has not been imported, check if we still have more logs.
// We try to guess which logs matches which recordings are they are classed in the same order.
// But this is just an attempt.
$log = null;
if ($logsrs->valid()) {
$log = $logsrs->current();
$logsrs->next();
}
$timecreated = empty($log) ? time() : $log->timecreated;
$newrecording['imported'] = $isimported;
$newrecording['headless'] = 0;
$newrecording['importeddata'] = $importeddata;
$newrecording['timecreated'] = $newrecording['timemodified'] = $timecreated;
// If we could not match with a log, we still create the recording.
$DB->insert_record('bigbluebuttonbn_recordings', $newrecording);
$recordingcount++;
}
mtrace("Migrated {$recordingcount} recordings.");
// Now deactivate logs by marking all of them as migrated.
// Reason for this is that we don't want to run another migration here and we don't know
// which logs matches which recordings.
$DB->set_field_select('bigbluebuttonbn_logs', 'log',
$isimported ? logger::EVENT_IMPORT_MIGRATED : logger::EVENT_CREATE_MIGRATED,
$select,
$params
);
$logsrs->close();
return true;
}
/**
* Get the query (records_select) for the logs to convert.
*
* Each log is ordered by timecreated.
*
* @param string $meetingid
* @param bool $isimported
* @return array
*/
protected function get_sql_query_for_logs(string $meetingid, bool $isimported): array {
global $DB;
if ($isimported) {
return [
'log = :logmatch AND meetingid = :meetingid',
['logmatch' => logger::EVENT_IMPORT, 'meetingid' => $meetingid],
];
}
return [
'log = :logmatch AND meetingid = :meetingid AND ' . $DB->sql_like('meta', ':match'),
[
'logmatch' => logger::EVENT_CREATE,
'match' => '%true%',
'meetingid' => $meetingid
],
];
}
/**
* Schedule all upgrading tasks.
*
* @param bool $importedrecordings
* @return void
* @throws \dml_exception
*/
public static function schedule_upgrade_per_meeting($importedrecordings = false) {
global $DB;
$meetingids = $DB->get_fieldset_sql(
'SELECT DISTINCT meetingid FROM {bigbluebuttonbn_logs} WHERE log = :createorimport',
['createorimport' => $importedrecordings ? logger::EVENT_IMPORT : logger::EVENT_CREATE]
);
foreach ($meetingids as $mid) {
$createdrecordingtask = new static();
$createdrecordingtask->set_custom_data((object) ['meetingid' => $mid, 'isimported' => $importedrecordings]);
manager::queue_adhoc_task($createdrecordingtask);
}
}
}
@@ -0,0 +1,163 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Subplugin test helper trait
*
* @package mod_bigbluebuttonbn
* @copyright 2023 - present, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent@call-learning.fr)
*/
namespace mod_bigbluebuttonbn\test;
use core_component;
use core_h5p\core;
use core_plugin_manager;
use mod_bigbluebuttonbn\extension;
use ReflectionClass;
trait subplugins_test_helper_trait {
/**
* Setup a fake extension plugin
*
* This is intended to behave in most case like a real subplugina and will
* allow most functionalities to be tested.
*
* @param string $pluginname plugin name
* @return void
*/
protected function setup_fake_plugin(string $pluginname): void {
global $CFG;
require_once("$CFG->libdir/upgradelib.php");
$bbbextpath = "{$CFG->dirroot}/mod/bigbluebuttonbn/tests/fixtures/extension";
// This is similar to accesslib_test::setup_fake_plugin.
$mockedcomponent = new ReflectionClass(core_component::class);
$mockedplugins = $mockedcomponent->getProperty('plugins');
$plugins = $mockedplugins->getValue();
$plugins[extension::BBB_EXTENSION_PLUGIN_NAME] = [$pluginname => $bbbextpath . "/$pluginname"];
$mockedplugins->setValue(null, $plugins);
$mockedplugintypes = $mockedcomponent->getProperty('plugintypes');
$pluginstypes = $mockedplugintypes->getValue();
$pluginstypes[extension::BBB_EXTENSION_PLUGIN_NAME] = $bbbextpath;
$mockedplugintypes->setValue(null, $pluginstypes);
$fillclassmap = $mockedcomponent->getMethod('fill_classmap_cache');
$fillclassmap->invoke(null);
$fillfilemap = $mockedcomponent->getMethod('fill_filemap_cache');
$fillfilemap->invoke(null);
$mockedsubplugins = $mockedcomponent->getProperty('subplugins');
$subplugins = $mockedsubplugins->getValue();
$subplugins['mod_bigbluebuttonbn'][extension::BBB_EXTENSION_PLUGIN_NAME][] = $pluginname;
$mockedsubplugins->setValue(null, $subplugins);
// Now write the content of the cache in a file so we can use it later.
$content = core_component::get_cache_content();
self::write_fake_component_cache($content);
// Make sure the plugin is installed.
ob_start();
upgrade_noncore(false);
upgrade_finished();
ob_end_clean();
// Cache has been cleared so let's write it again.
self::write_fake_component_cache($content);
}
/**
* Write the content of the cache in a file for later use.
*
* This is used exclusively in behat test as the cache is filled with new values at each session/page load.
*
* @param string $content content of the cache
* @return void
*/
protected function write_fake_component_cache($content) {
global $CFG;
$cachefile = "$CFG->cachedir/core_component.php";
if (file_exists($cachefile)) {
// Stale cache detected!
unlink($cachefile);
}
// Permissions might not be setup properly in installers.
$dirpermissions = !isset($CFG->directorypermissions) ? 02777 : $CFG->directorypermissions;
$filepermissions = !isset($CFG->filepermissions) ? ($dirpermissions & 0666) : $CFG->filepermissions;
clearstatcache();
$cachedir = dirname($cachefile);
if (!is_dir($cachedir)) {
mkdir($cachedir, $dirpermissions, true);
}
if ($fp = @fopen($cachefile . '.tmp', 'xb')) {
fwrite($fp, $content);
fclose($fp);
@rename($cachefile . '.tmp', $cachefile);
@chmod($cachefile, $filepermissions);
}
@unlink($cachefile . '.tmp'); // Just in case anything fails (race condition).
core_component::invalidate_opcode_php_cache($cachefile);
}
/**
* Uninstall a fake extension plugin
*
* This is intended to behave in most case like a real subplugina and will
* allow most functionalities to be tested.
*
* @param string $pluginname plugin name
* @return void
*/
protected function uninstall_fake_plugin(string $pluginname): void {
global $CFG;
require_once("$CFG->libdir/adminlib.php");
// We just need access to fill_all_caches so everything goes back to normal.
// If we don't do this, there are some side effects that will make other test fails
// (such as mod_bigbluebuttonbn\task\upgrade_recordings_task_test::test_upgrade_recordings_imported_basic).
$cachefile = "$CFG->cachedir/core_component.php";
if (file_exists($cachefile)) {
// Stale cache detected!
unlink($cachefile);
}
$mockedcomponent = new ReflectionClass(core_component::class);
// Here we reset the plugin caches.
$mockedplugintypes = $mockedcomponent->getProperty('plugintypes');
$mockedplugintypes->setValue(null, null);
$fillclassmap = $mockedcomponent->getMethod('init');
$fillclassmap->invoke(null);
// Now uninstall the plugin and clean everything up for other tests.
$pluginman = core_plugin_manager::instance();
$plugininfo = $pluginman->get_plugins();
foreach ($plugininfo as $type => $plugins) {
foreach ($plugins as $name => $plugin) {
if ($name === $pluginname) {
ob_start();
uninstall_plugin($type, $name);
ob_end_clean();
}
}
}
}
}
@@ -0,0 +1,345 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* BBB Library tests class trait.
*
* @package mod_bigbluebuttonbn
* @copyright 2018 - present, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent@call-learning.fr)
*/
namespace mod_bigbluebuttonbn\test;
use context_module;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\config;
use mod_bigbluebuttonbn\local\proxy\recording_proxy;
use mod_bigbluebuttonbn\meeting;
use stdClass;
use testing_data_generator;
use core\plugininfo\mod;
trait testcase_helper_trait {
/** @var testing_data_generator|null */
protected $generator = null;
/** @var object|null */
protected $course = null;
/**
* Convenience function to create an instance of a bigbluebuttonactivty.
*
* @param stdClass|null $course course to add the module to
* @param array $params Array of parameters to pass to the generator
* @param array $options Array of options to pass to the generator
* @return array($context, $cm, $instance) Testable wrapper around the assign class.
*/
protected function create_instance(?stdClass $course = null, array $params = [], array $options = []): array {
// Prior to creating the instance, make sure that the BigBlueButton module is enabled.
$modules = \core_plugin_manager::instance()->get_plugins_of_type('mod');
if (!$modules['bigbluebuttonbn']->is_enabled()) {
mod::enable_plugin('bigbluebuttonbn', true);
}
if (!$course) {
$course = $this->get_course();
}
$params['course'] = $course->id;
$options['visible'] = 1;
$instance = $this->getDataGenerator()->create_module('bigbluebuttonbn', $params, $options);
list($course, $cm) = get_course_and_cm_from_instance($instance, 'bigbluebuttonbn');
$context = context_module::instance($cm->id);
return [$context, $cm, $instance];
}
/**
* Get the matching form data
*
* @param stdClass $bbactivity the current bigbluebutton activity
* @param stdClass|null $course the course or null (taken from $this->get_course() if null)
* @return mixed
*/
protected function get_form_data_from_instance(stdClass $bbactivity, ?stdClass $course = null): object {
global $USER;
if (!$course) {
$course = $this->get_course();
}
$this->setAdminUser();
$bbactivitycm = get_coursemodule_from_instance('bigbluebuttonbn', $bbactivity->id);
list($cm, $context, $module, $data, $cw) = get_moduleinfo_data($bbactivitycm, $course);
$this->setUser($USER);
return $data;
}
/**
* Get or create course if it does not exist
*
* @return stdClass|null
*/
protected function get_course(): stdClass {
if (!$this->course) {
$this->course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
}
return $this->course;
}
/**
* Generate a course, several students and several groups
*
* @param stdClass $courserecord
* @param int $numstudents
* @param int $numteachers
* @param int $groupsnum
* @return array
*/
protected function setup_course_students_teachers(stdClass $courserecord, int $numstudents, int $numteachers,
int $groupsnum): array {
global $DB;
$generator = $this->getDataGenerator();
$course = $generator->create_course($courserecord);
$groups = [];
for ($i = 0; $i < $groupsnum; $i++) {
$groups[] = $generator->create_group(['courseid' => $course->id]);
}
$generator->create_group(['courseid' => $course->id]);
$generator->create_group(['courseid' => $course->id]);
$roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
$students = [];
for ($i = 0; $i < $numstudents; $i++) {
$student = $generator->create_user();
$generator->enrol_user($student->id, $course->id, $roleids['student']);
$groupid = $groups[$i % $groupsnum]->id;
groups_add_member($groupid, $student->id);
$students[] = $student;
}
$teachers = [];
for ($i = 0; $i < $numteachers; $i++) {
$teacher = $generator->create_user();
$generator->enrol_user($teacher->id, $course->id, $roleids['teacher']);
$groupid = $groups[$i % $groupsnum]->id;
groups_add_member($groupid, $teacher->id);
$teachers[] = $teacher;
}
$bbactivity = $generator->create_module(
'bigbluebuttonbn',
['course' => $course->id],
['visible' => true]);
get_fast_modinfo(0, 0, true);
return [$course, $groups, $students, $teachers, $bbactivity, $roleids];
}
/**
* This test requires mock server to be present.
*/
protected function initialise_mock_server(): void {
if (!defined('TEST_MOD_BIGBLUEBUTTONBN_MOCK_SERVER')) {
$this->markTestSkipped(
'The TEST_MOD_BIGBLUEBUTTONBN_MOCK_SERVER constant must be defined to run mod_bigbluebuttonbn tests'
);
}
try {
$this->getDataGenerator()->get_plugin_generator('mod_bigbluebuttonbn')->reset_mock();
// Mock server expects a value. By default this field is empty.
set_config('bigbluebuttonbn_shared_secret', config::DEFAULT_SHARED_SECRET);
} catch (\moodle_exception $e) {
$this->markTestSkipped(
'Cannot connect to the mock server for this test. Make sure that TEST_MOD_BIGBLUEBUTTONBN_MOCK_SERVER points
to an active Mock server'
);
}
}
/**
* Create an return an array of recordings
*
* @param instance $instance
* @param array $recordingdata array of recording information
* @param array $additionalmeetingdata
* @return array
*/
protected function create_recordings_for_instance(instance $instance, array $recordingdata = [],
$additionalmeetingdata = []): array {
$recordings = [];
$bbbgenerator = $this->getDataGenerator()->get_plugin_generator('mod_bigbluebuttonbn');
// Create the meetings on the mock server, so like this we can find the recordings.
$meeting = new meeting($instance);
$meeting->update_cache(); // The meeting has just been created but we need to force fetch info from the server.
if (!$meeting->is_running()) {
$additionalmeetingdata = array_merge([
'instanceid' => $instance->get_instance_id(),
'groupid' => $instance->get_group_id()
], $additionalmeetingdata);
$bbbgenerator->create_meeting($additionalmeetingdata);
}
foreach ($recordingdata as $rindex => $data) {
$recordings[] = $bbbgenerator->create_recording(
array_merge([
'bigbluebuttonbnid' => $instance->get_instance_id(),
'groupid' => $instance->get_group_id()
], $data)
);
}
return $recordings;
}
/**
* Create an activity which includes a set of recordings.
*
* @param stdClass $course
* @param int $type
* @param array $recordingdata array of recording information
* @param int $groupid
* @return array
*/
protected function create_activity_with_recordings(stdClass $course, int $type, array $recordingdata, int $groupid = 0): array {
$generator = $this->getDataGenerator()->get_plugin_generator('mod_bigbluebuttonbn');
$activity = $generator->create_instance([
'course' => $course->id,
'type' => $type
]);
$instance = instance::get_from_instanceid($activity->id);
$instance->set_group_id($groupid);
$recordings = $this->create_recordings_for_instance($instance, $recordingdata);
return [
'course' => $course,
'activity' => $activity,
'recordings' => $recordings,
];
}
/**
* Create a course, users and recording from dataset given in an array form
*
* @param array $dataset
* @return mixed
*/
protected function create_from_dataset(array $dataset) {
list('type' => $type, 'recordingsdata' => $recordingsdata, 'groups' => $groups,
'users' => $users) = $dataset;
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_bigbluebuttonbn');
$coursedata = empty($groups) ? [] : ['groupmodeforce' => true, 'groupmode' => $dataset['coursemode'] ?? VISIBLEGROUPS];
$this->course = $this->getDataGenerator()->create_course($coursedata);
foreach ($users as $userdata) {
$this->getDataGenerator()->create_and_enrol($this->course, $userdata['role'], ['username' => $userdata['username']]);
}
if ($groups) {
foreach ($groups as $groupname => $students) {
$group = $this->getDataGenerator()->create_group(['name' => $groupname, 'courseid' => $this->course->id]);
foreach ($students as $username) {
$user = \core_user::get_user_by_username($username);
$this->getDataGenerator()->create_group_member(['userid' => $user->id, 'groupid' => $group->id]);
}
}
}
$instancesettings = [
'course' => $this->course->id,
'type' => $type,
'name' => 'Example',
];
if (!empty($dataset['additionalsettings'])) {
$instancesettings = array_merge($instancesettings, $dataset['additionalsettings']);
}
$activity = $plugingenerator->create_instance($instancesettings);
$instance = instance::get_from_instanceid($activity->id);
foreach ($recordingsdata as $groupname => $recordings) {
if ($groups) {
$groupid = groups_get_group_by_name($this->course->id, $groupname);
$instance->set_group_id($groupid);
}
$this->create_recordings_for_instance($instance, $recordings);
}
return $activity->id;
}
/**
* Create the legacy log entries for this task.
*
* @param instance $instance
* @param int $userid
* @param int $count
* @param bool $importedrecordings
* @param bool $withremoterecordings create recording on the mock server ?
* @return array
*/
protected function create_log_entries(
instance $instance,
int $userid,
int $count = 30,
bool $importedrecordings = false,
bool $withremoterecordings = true
): array {
// Create log entries for each (30 for the ungrouped, 30 for the grouped).
$baselogdata = [
'courseid' => $instance->get_course_id(),
'userid' => $userid,
'log' => $importedrecordings ? 'Import' : 'Create',
'meta' => json_encode(['record' => true]),
'imported' => $importedrecordings,
];
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_bigbluebuttonbn');
for ($i = 0; $i < $count; $i++) {
if ($withremoterecordings) {
// Create a recording.
$starttime = time() - random_int(HOURSECS, WEEKSECS);
$recording = $plugingenerator->create_recording([
'bigbluebuttonbnid' => $instance->get_instance_id(),
'groupid' => $instance->get_group_id(),
'starttime' => $starttime,
'endtime' => $starttime + HOURSECS,
], true); // Create them on the server only.
$baselogdata['meetingid'] = $instance->get_meeting_id();
if ($importedrecordings) {
// Fetch the data.
$data = recording_proxy::fetch_recordings([$recording->recordingid]);
$data = end($data);
if ($data) {
$metaonly = array_filter($data, function($key) {
return strstr($key, 'meta_');
}, ARRAY_FILTER_USE_KEY);
} else {
$data = [];
}
$baselogdata['meta'] = json_encode(array_merge([
'recording' => array_diff_key($data, $metaonly),
], $metaonly));
} else {
$baselogdata['meta'] = json_encode((object) ['record' => true]);
}
}
// Insert the legacy log entry.
$logs[] = $plugingenerator->create_log(array_merge($baselogdata, [
'bigbluebuttonbnid' => $instance->get_instance_id(),
'timecreated' => time() - random_int(HOURSECS, WEEKSECS) + (HOURSECS * $i),
]));
}
return $logs;
}
}