. /** * Library of interface functions and constants for module zoom * * All the core Moodle functions, neeeded to allow the module to work * integrated in Moodle should be placed here. * * All the zoom specific functions, needed to implement all the module * logic, should go to locallib.php. This will help to save some memory when * Moodle is performing actions across all modules. * * @package mod_zoom * @copyright 2015 UC Regents * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /* Moodle core API */ /** * Returns the information on whether the module supports a feature. * * @param string $feature FEATURE_xx constant for requested feature * @return mixed true if the feature is supported, null if unknown */ function zoom_supports($feature) { // Adding support for FEATURE_MOD_PURPOSE (MDL-71457) and providing backward compatibility (pre-v4.0). if (defined('FEATURE_MOD_PURPOSE') && $feature === FEATURE_MOD_PURPOSE) { return MOD_PURPOSE_COMMUNICATION; } switch ($feature) { case FEATURE_BACKUP_MOODLE2: case FEATURE_COMPLETION_TRACKS_VIEWS: case FEATURE_GRADE_HAS_GRADE: case FEATURE_GROUPINGS: case FEATURE_GROUPMEMBERSONLY: case FEATURE_MOD_INTRO: case FEATURE_SHOW_DESCRIPTION: return true; default: return null; } } /** * Saves a new instance of the zoom object into the database. * * Given an object containing all the necessary data (defined by the form in mod_form.php), this function * will create a new instance and return the id number of the new instance. * * @param stdClass $zoom Submitted data from the form in mod_form.php * @param mod_zoom_mod_form $mform The form instance (included because the function is used as a callback) * @return int The id of the newly inserted zoom record */ function zoom_add_instance(stdClass $zoom, mod_zoom_mod_form $mform = null) { global $CFG, $DB; require_once($CFG->dirroot . '/mod/zoom/locallib.php'); if (defined('PHPUNIT_TEST') && PHPUNIT_TEST) { $zoom->id = $DB->insert_record('zoom', $zoom); zoom_grade_item_update($zoom); zoom_calendar_item_update($zoom); return $zoom->id; } // Deals with password manager issues. $zoom->password = $zoom->meetingcode; unset($zoom->meetingcode); if (empty($zoom->requirepasscode)) { $zoom->password = ''; } // Handle weekdays if weekly recurring meeting selected. if ($zoom->recurring && $zoom->recurrence_type == ZOOM_RECURRINGTYPE_WEEKLY) { $zoom->weekly_days = zoom_handle_weekly_days($zoom); } $zoom->course = (int) $zoom->course; $zoom->breakoutrooms = []; if (!empty($zoom->rooms)) { $breakoutrooms = zoom_build_instance_breakout_rooms_array_for_api($zoom); $zoom->breakoutrooms = $breakoutrooms['zoom']; } $response = zoom_webservice()->create_meeting($zoom); $zoom = populate_zoom_from_response($zoom, $response); $zoom->timemodified = time(); if (!empty($zoom->schedule_for)) { // Wait until after receiving a successful response from zoom to update the host // based on the schedule_for field. Zoom handles the schedule for on their // end, but returns the host as the person who created the meeting, not the person // that it was scheduled for. $correcthostzoomuser = zoom_get_user($zoom->schedule_for); $zoom->host_id = $correcthostzoomuser->id; } if (isset($zoom->recurring) && isset($response->occurrences) && empty($response->occurrences)) { // Recurring meetings did not create any occurrencces. // This means invalid options selected. // Need to rollback created meeting. zoom_webservice()->delete_meeting($zoom->meeting_id, $zoom->webinar); $redirecturl = new moodle_url('/course/view.php', ['id' => $zoom->course]); throw new moodle_exception('erroraddinstance', 'zoom', $redirecturl->out()); } $zoom->id = $DB->insert_record('zoom', $zoom); if (!empty($zoom->breakoutrooms)) { // We ignore the API response and save the local data for breakout rooms to support dynamic users and groups. zoom_insert_instance_breakout_rooms($zoom->id, $breakoutrooms['db']); } // Store tracking field data for meeting. zoom_sync_meeting_tracking_fields($zoom->id, $response->tracking_fields ?? []); zoom_calendar_item_update($zoom); zoom_grade_item_update($zoom); return $zoom->id; } /** * Updates an instance of the zoom in the database and on Zoom servers. * * Given an object containing all the necessary data (defined by the form in mod_form.php), this function * will update an existing instance with new data. * * @param stdClass $zoom An object from the form in mod_form.php * @param mod_zoom_mod_form $mform The form instance (included because the function is used as a callback) * @return boolean Success/Failure */ function zoom_update_instance(stdClass $zoom, mod_zoom_mod_form $mform = null) { global $CFG, $DB; require_once($CFG->dirroot . '/mod/zoom/locallib.php'); // The object received from mod_form.php returns instance instead of id for some reason. if (isset($zoom->instance)) { $zoom->id = $zoom->instance; } $zoom->timemodified = time(); // Deals with password manager issues. if (isset($zoom->meetingcode)) { $zoom->password = $zoom->meetingcode; unset($zoom->meetingcode); } if (property_exists($zoom, 'requirepasscode') && empty($zoom->requirepasscode)) { $zoom->password = ''; } // Handle weekdays if weekly recurring meeting selected. if ($zoom->recurring && $zoom->recurrence_type == ZOOM_RECURRINGTYPE_WEEKLY) { $zoom->weekly_days = zoom_handle_weekly_days($zoom); } $DB->update_record('zoom', $zoom); $zoom->breakoutrooms = []; if (!empty($zoom->rooms)) { $breakoutrooms = zoom_build_instance_breakout_rooms_array_for_api($zoom); zoom_update_instance_breakout_rooms($zoom->id, $breakoutrooms['db']); $zoom->breakoutrooms = $breakoutrooms['zoom']; } $updatedzoomrecord = $DB->get_record('zoom', ['id' => $zoom->id]); $zoom->meeting_id = $updatedzoomrecord->meeting_id; $zoom->webinar = $updatedzoomrecord->webinar; // Update meeting on Zoom. try { zoom_webservice()->update_meeting($zoom); if (!empty($zoom->schedule_for)) { // Only update this if we actually get a valid user. if ($correcthostzoomuser = zoom_get_user($zoom->schedule_for)) { $zoom->host_id = $correcthostzoomuser->id; $DB->update_record('zoom', $zoom); } } } catch (moodle_exception $error) { return false; } // Get the updated meeting info from zoom, before updating calendar events. $response = zoom_webservice()->get_meeting_webinar_info($zoom->meeting_id, $zoom->webinar); $zoom = populate_zoom_from_response($zoom, $response); // Update tracking field data for meeting. zoom_sync_meeting_tracking_fields($zoom->id, $response->tracking_fields ?? []); zoom_calendar_item_update($zoom); zoom_grade_item_update($zoom); return true; } /** * Function to handle selected weekdays, for recurring weekly meeting. * * @param stdClass $zoom The zoom instance * @return string The comma separated string for selected weekdays */ function zoom_handle_weekly_days($zoom) { $weekdaynumbers = []; for ($i = 1; $i <= 7; $i++) { $key = 'weekly_days_' . $i; if (!empty($zoom->$key)) { $weekdaynumbers[] = $i; } } return implode(',', $weekdaynumbers); } /** * Function to unset the weekly options in postprocessing. * * @param stdClass $data The form data object * @return stdClass $data The form data object minus weekly options. */ function zoom_remove_weekly_options($data) { // Unset the weekly_days options. for ($i = 1; $i <= 7; $i++) { $key = 'weekly_days_' . $i; unset($data->$key); } return $data; } /** * Function to unset the monthly options in postprocessing. * * @param stdClass $data The form data object * @return stdClass $data The form data object minus monthly options. */ function zoom_remove_monthly_options($data) { // Unset the monthly options. unset($data->monthly_repeat_option); unset($data->monthly_day); unset($data->monthly_week); unset($data->monthly_week_day); return $data; } /** * Populates a zoom meeting or webinar from a response object. * * Given a zoom meeting object from mod_form.php, this function uses the response to repopulate some of the object properties. * * @param stdClass $zoom An object from the form in mod_form.php * @param stdClass $response A response from an API call like 'create meeting' or 'update meeting' * @return stdClass A $zoom object ready to be added to the database. */ function populate_zoom_from_response(stdClass $zoom, stdClass $response) { global $CFG; // Inlcuded for constants. require_once($CFG->dirroot . '/mod/zoom/locallib.php'); $newzoom = clone $zoom; $samefields = ['start_url', 'join_url', 'created_at', 'timezone']; foreach ($samefields as $field) { if (isset($response->$field)) { $newzoom->$field = $response->$field; } } if (isset($response->duration)) { $newzoom->duration = $response->duration * 60; } $newzoom->meeting_id = $response->id; $newzoom->name = $response->topic; if (isset($response->start_time)) { $newzoom->start_time = strtotime($response->start_time); } $recurringtypes = [ ZOOM_RECURRING_MEETING, ZOOM_RECURRING_FIXED_MEETING, ZOOM_RECURRING_WEBINAR, ZOOM_RECURRING_FIXED_WEBINAR, ]; $newzoom->recurring = in_array($response->type, $recurringtypes); if (!empty($response->occurrences)) { $newzoom->occurrences = []; // Normalise the occurrence times. foreach ($response->occurrences as $occurrence) { $occurrence->start_time = strtotime($occurrence->start_time); $occurrence->duration = $occurrence->duration * 60; $newzoom->occurrences[] = $occurrence; } } if (isset($response->password)) { $newzoom->password = $response->password; } if (isset($response->settings->encryption_type)) { $newzoom->option_encryption_type = $response->settings->encryption_type; } if (isset($response->settings->join_before_host)) { $newzoom->option_jbh = $response->settings->join_before_host; } if (isset($response->settings->participant_video)) { $newzoom->option_participants_video = $response->settings->participant_video; } if (isset($response->settings->alternative_hosts)) { $newzoom->alternative_hosts = $response->settings->alternative_hosts; } if (isset($response->settings->mute_upon_entry)) { $newzoom->option_mute_upon_entry = $response->settings->mute_upon_entry; } if (isset($response->settings->meeting_authentication)) { $newzoom->option_authenticated_users = $response->settings->meeting_authentication; } if (isset($response->settings->waiting_room)) { $newzoom->option_waiting_room = $response->settings->waiting_room; } if (isset($response->settings->auto_recording)) { $newzoom->option_auto_recording = $response->settings->auto_recording; } if (!isset($newzoom->option_auto_recording)) { $newzoom->option_auto_recording = 'none'; } return $newzoom; } /** * Removes an instance of the zoom from the database * * Given an ID of an instance of this module, this function will permanently delete the instance and any data that depends on it. * * @param int $id Id of the module instance * @return boolean Success/Failure * @throws moodle_exception if failed to delete and zoom did not issue a not found error */ function zoom_delete_instance($id) { global $CFG, $DB; require_once($CFG->dirroot . '/mod/zoom/locallib.php'); if (!$zoom = $DB->get_record('zoom', ['id' => $id])) { // For some reason already deleted, so let Moodle take care of the rest. return true; } // If the meeting is missing from zoom, don't bother with the webservice. if ($zoom->exists_on_zoom == ZOOM_MEETING_EXISTS) { try { zoom_webservice()->delete_meeting($zoom->meeting_id, $zoom->webinar); } catch (zoom_not_found_exception $error) { // Meeting not on Zoom, so continue. mtrace('Meeting not on Zoom; continuing'); } } // If we delete a meeting instance, do we want to delete the participants? $meetinginstances = $DB->get_records('zoom_meeting_details', ['zoomid' => $zoom->id]); foreach ($meetinginstances as $meetinginstance) { $DB->delete_records('zoom_meeting_participants', ['detailsid' => $meetinginstance->id]); } $DB->delete_records('zoom_meeting_details', ['zoomid' => $zoom->id]); // Delete tracking field data for deleted meetings. $DB->delete_records('zoom_meeting_tracking_fields', ['meeting_id' => $zoom->id]); // Delete any dependent records here. zoom_calendar_item_delete($zoom); zoom_grade_item_delete($zoom); $DB->delete_records('zoom', ['id' => $zoom->id]); // Delete breakout rooms. zoom_delete_instance_breakout_rooms($zoom->id); return true; } /** * Callback function to update the Zoom event in the database and on Zoom servers. * * The function is triggered when the course module name is set via quick edit. * * @param int $courseid * @param stdClass $zoom Zoom Module instance object. * @param stdClass $cm Course Module object. * @return bool */ function zoom_refresh_events($courseid, $zoom, $cm) { global $CFG; require_once($CFG->dirroot . '/mod/zoom/locallib.php'); try { // Get the updated meeting info from zoom, before updating calendar events. $response = zoom_webservice()->get_meeting_webinar_info($zoom->meeting_id, $zoom->webinar); $fullzoom = populate_zoom_from_response($zoom, $response); // Only if the name has changed, update meeting on Zoom. if ($zoom->name !== $fullzoom->name) { $fullzoom->name = $zoom->name; zoom_webservice()->update_meeting($zoom); } zoom_calendar_item_update($fullzoom); zoom_grade_item_update($fullzoom); } catch (moodle_exception $error) { return false; } return true; } /** * Given a course and a time, this module should find recent activity that has occurred in zoom activities and print it out. * * @param stdClass $course The course record * @param bool $viewfullnames Should we display full names * @param int $timestart Print activity since this timestamp * @return boolean True if anything was printed, otherwise false * @todo implement this function */ function zoom_print_recent_activity($course, $viewfullnames, $timestart) { return false; } /** * Prepares the recent activity data * * This callback function is supposed to populate the passed array with * custom activity records. These records are then rendered into HTML * zoom_print_recent_mod_activity(). * * Returns void, it adds items into $activities and increases $index. * * @param array $activities sequentially indexed array of objects with added 'cmid' property * @param int $index the index in the $activities to use for the next record * @param int $timestart append activity since this time * @param int $courseid the id of the course we produce the report for * @param int $cmid course module id * @param int $userid check for a particular user's activity only, defaults to 0 (all users) * @param int $groupid check for a particular group's activity only, defaults to 0 (all groups) * @todo implement this function */ function zoom_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid = 0, $groupid = 0) { } /** * Prints single activity item prepared by zoom_get_recent_mod_activity() * * @param stdClass $activity activity record with added 'cmid' property * @param int $courseid the id of the course we produce the report for * @param bool $detail print detailed report * @param array $modnames as returned by get_module_types_names() * @param bool $viewfullnames display users' full names * @todo implement this function */ function zoom_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) { } /** * Returns all other caps used in the module * * For example, this could be array('moodle/site:accessallgroups') if the * module uses that capability. * * @return array * @todo implement this function */ function zoom_get_extra_capabilities() { return []; } /** * Create or update Moodle calendar event of the Zoom instance. * * @param stdClass $zoom */ function zoom_calendar_item_update(stdClass $zoom) { global $CFG, $DB; require_once($CFG->dirroot . '/calendar/lib.php'); // Based on data passed back from zoom, create/update/delete events based on data. $newevents = []; if (!$zoom->recurring) { $newevents[''] = zoom_populate_calender_item($zoom); } else if (!empty($zoom->occurrences)) { foreach ($zoom->occurrences as $occurrence) { $uuid = $occurrence->occurrence_id; $newevents[$uuid] = zoom_populate_calender_item($zoom, $occurrence); } } // Fetch all the events related to this zoom instance. $conditions = [ 'modulename' => 'zoom', 'instance' => $zoom->id, ]; $events = $DB->get_records('event', $conditions); $eventfields = ['name', 'timestart', 'timeduration']; foreach ($events as $event) { $uuid = $event->uuid; if (isset($newevents[$uuid])) { // This event already exists in Moodle. $changed = false; $newevent = $newevents[$uuid]; // Check if the important fields have actually changed. foreach ($eventfields as $field) { if ($newevent->$field !== $event->$field) { $changed = true; } } if ($changed) { calendar_event::load($event)->update($newevent); } // Event has been updated, remove from the list. unset($newevents[$uuid]); } else { // Event does not exist in Zoom, so delete from Moodle. calendar_event::load($event)->delete(); } } // Any remaining events in the array don't exist on Moodle, so create a new event. foreach ($newevents as $uuid => $newevent) { calendar_event::create($newevent); } } /** * Return an array with the days of the week. * * @return array */ function zoom_get_weekday_options() { return [ 1 => get_string('sunday', 'calendar'), 2 => get_string('monday', 'calendar'), 3 => get_string('tuesday', 'calendar'), 4 => get_string('wednesday', 'calendar'), 5 => get_string('thursday', 'calendar'), 6 => get_string('friday', 'calendar'), 7 => get_string('saturday', 'calendar'), ]; } /** * Return an array with the weeks of the month. * * @return array */ function zoom_get_monthweek_options() { return [ 1 => get_string('weekoption_first', 'zoom'), 2 => get_string('weekoption_second', 'zoom'), 3 => get_string('weekoption_third', 'zoom'), 4 => get_string('weekoption_fourth', 'zoom'), -1 => get_string('weekoption_last', 'zoom'), ]; } /** * Populate the calendar event object, based on the zoom instance * * @param stdClass $zoom The zoom instance. * @param stdClass $occurrence The occurrence object passed from the zoom api. * @return stdClass The calendar event object. */ function zoom_populate_calender_item(stdClass $zoom, stdClass $occurrence = null) { $event = new stdClass(); $event->type = CALENDAR_EVENT_TYPE_ACTION; $event->modulename = 'zoom'; $event->eventtype = 'zoom'; $event->courseid = $zoom->course; $event->instance = $zoom->id; $event->visible = true; $event->name = $zoom->name; if ($zoom->intro) { $event->description = $zoom->intro; $event->format = $zoom->introformat; } if (!$occurrence) { $event->timesort = $zoom->start_time; $event->timestart = $zoom->start_time; $event->timeduration = $zoom->duration; } else { $event->timesort = $occurrence->start_time; $event->timestart = $occurrence->start_time; $event->timeduration = $occurrence->duration; $event->uuid = $occurrence->occurrence_id; } // Recurring meetings/webinars with no fixed time are created as invisible events. // For recurring meetings/webinars with a fixed time, we want to see the events on the calendar. if ($zoom->recurring && $zoom->recurrence_type == ZOOM_RECURRINGTYPE_NOTIME) { $event->visible = false; } return $event; } /** * Delete Moodle calendar events of the Zoom instance. * * @param stdClass $zoom */ function zoom_calendar_item_delete(stdClass $zoom) { global $CFG, $DB; require_once($CFG->dirroot . '/calendar/lib.php'); $events = $DB->get_records('event', [ 'modulename' => 'zoom', 'instance' => $zoom->id, ]); foreach ($events as $event) { calendar_event::load($event)->delete(); } } /** * This function receives a calendar event and returns the action associated with it, or null if there is none. * * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event * is not displayed on the block. * * @param calendar_event $event * @param \core_calendar\action_factory $factory * @param int $userid User id override * @return \core_calendar\local\event\entities\action_interface|null */ function mod_zoom_core_calendar_provide_event_action(calendar_event $event, \core_calendar\action_factory $factory, $userid = null) { global $CFG, $DB, $USER; require_once($CFG->dirroot . '/mod/zoom/locallib.php'); if (empty($userid)) { $userid = $USER->id; } $cm = get_fast_modinfo($event->courseid, $userid)->instances['zoom'][$event->instance]; $zoom = $DB->get_record('zoom', ['id' => $cm->instance], '*'); list($inprogress, $available, $finished) = zoom_get_state($zoom); if ($finished) { return null; // No point to showing finished meetings in overview. } else { return $factory->create_instance( get_string('join_meeting', 'zoom'), new \moodle_url('/mod/zoom/view.php', ['id' => $cm->id]), 1, $available ); } } /* Gradebook API */ /** * Checks if scale is being used by any instance of zoom. * * This is used to find out if scale used anywhere. * * @param int $scaleid ID of the scale * @return boolean true if the scale is used by any zoom instance */ function zoom_scale_used_anywhere($scaleid) { global $DB; if ($scaleid && $DB->record_exists('zoom', ['grade' => -$scaleid])) { return true; } else { return false; } } /** * Creates or updates grade item for the given zoom instance * * Needed by grade_update_mod_grades(). * * @param stdClass $zoom instance object with extra cmidnumber and modname property * @param array $grades optional array/object of grade(s); 'reset' means reset grades in gradebook * @return void */ function zoom_grade_item_update(stdClass $zoom, $grades = null) { global $CFG; require_once($CFG->libdir . '/gradelib.php'); $item = []; $item['itemname'] = clean_param($zoom->name, PARAM_NOTAGS); $item['gradetype'] = GRADE_TYPE_VALUE; if ($zoom->grade > 0) { $item['gradetype'] = GRADE_TYPE_VALUE; $item['grademax'] = $zoom->grade; $item['grademin'] = 0; } else if ($zoom->grade < 0) { $item['gradetype'] = GRADE_TYPE_SCALE; $item['scaleid'] = -$zoom->grade; } else { $gradebook = grade_get_grades($zoom->course, 'mod', 'zoom', $zoom->id); // Prevent the gradetype from switching to None if grades exist. if (empty($gradebook->items[0]->grades)) { $item['gradetype'] = GRADE_TYPE_NONE; } else { return; } } if ($grades === 'reset') { $item['reset'] = true; $grades = null; } grade_update('mod/zoom', $zoom->course, 'mod', 'zoom', $zoom->id, 0, $grades, $item); } /** * Delete grade item for given zoom instance * * @param stdClass $zoom instance object * @return grade_item */ function zoom_grade_item_delete($zoom) { global $CFG; require_once($CFG->libdir . '/gradelib.php'); return grade_update('mod/zoom', $zoom->course, 'mod', 'zoom', $zoom->id, 0, null, ['deleted' => 1]); } /** * Update zoom grades in the gradebook * * Needed by grade_update_mod_grades(). * * @param stdClass $zoom instance object with extra cmidnumber and modname property * @param int $userid update grade of specific user only, 0 means all participants */ function zoom_update_grades(stdClass $zoom, $userid = 0) { global $CFG; require_once($CFG->libdir . '/gradelib.php'); // Populate array of grade objects indexed by userid. if ($zoom->grade == 0) { zoom_grade_item_update($zoom); } else if ($userid != 0) { $grade = grade_get_grades($zoom->course, 'mod', 'zoom', $zoom->id, $userid)->items[0]->grades[$userid]; $grade->userid = $userid; if ($grade->grade == -1) { $grade->grade = null; } zoom_grade_item_update($zoom, $grade); } else if ($userid == 0) { $context = context_course::instance($zoom->course); $enrollusersid = array_keys(get_enrolled_users($context)); $grades = grade_get_grades($zoom->course, 'mod', 'zoom', $zoom->id, $enrollusersid)->items[0]->grades; foreach ($grades as $k => $v) { $grades[$k]->userid = $k; if ($v->grade == -1) { $grades[$k]->grade = null; } } zoom_grade_item_update($zoom, $grades); } else { zoom_grade_item_update($zoom); } } /** * Removes all zoom grades from gradebook by course id * * @param int $courseid */ function zoom_reset_gradebook($courseid) { global $DB; $params = [$courseid]; $sql = "SELECT z.*, cm.idnumber as cmidnumber, z.course as courseid FROM {zoom} z JOIN {course_modules} cm ON cm.instance = z.id JOIN {modules} m ON m.id = cm.module AND m.name = 'zoom' WHERE z.course = ?"; if ($zooms = $DB->get_records_sql($sql, $params)) { foreach ($zooms as $zoom) { zoom_grade_item_update($zoom, 'reset'); } } } /** * This function is used by the reset_course_userdata function in moodlelib. * This function will remove all user data from zoom activites * and clean up any related data. * * @param object $data the data submitted from the reset course. * @return array status array */ function zoom_reset_userdata($data) { global $CFG, $DB; $componentstr = get_string('modulenameplural', 'zoom'); $status = []; if (!empty($data->reset_zoom_all)) { // Reset tables that record user data. $DB->delete_records_select('zoom_meeting_participants', 'detailsid IN (SELECT zmd.id FROM {zoom_meeting_details} zmd JOIN {zoom} z ON z.id = zmd.zoomid WHERE z.course = ?)', [$data->courseid]); $status[] = [ 'component' => $componentstr, 'item' => get_string('meetingparticipantsdeleted', 'zoom'), 'error' => false, ]; $DB->delete_records_select('zoom_meeting_recordings_view', 'recordingsid IN (SELECT zmr.id FROM {zoom_meeting_recordings} zmr JOIN {zoom} z ON z.id = zmr.zoomid WHERE z.course = ?)', [$data->courseid]); $status[] = [ 'component' => $componentstr, 'item' => get_string('meetingrecordingviewsdeleted', 'zoom'), 'error' => false, ]; } return $status; } /** * Called by course/reset.php * * @param object $mform the course reset form that is being built. */ function zoom_reset_course_form_definition(&$mform) { $mform->addElement('header', 'zoomheader', get_string('modulenameplural', 'zoom')); $mform->addElement('checkbox', 'reset_zoom_all', get_string('resetzoomsall', 'zoom')); } /** * Course reset form defaults. * * @param object $course data passed by the form. * @return array the defaults. */ function zoom_reset_course_form_defaults($course) { return ['reset_zoom_all' => 1]; } /* File API */ /** * Returns the lists of all browsable file areas within the given module context * * The file area 'intro' for the activity introduction field is added automatically * by file_browser::get_file_info_context_module() * * @param stdClass $course * @param stdClass $cm * @param stdClass $context * @return array of [(string)filearea] => (string)description * @todo implement this function */ function zoom_get_file_areas($course, $cm, $context) { return []; } /** * File browsing support for zoom file areas * * @package mod_zoom * @category files * * @param file_browser $browser * @param array $areas * @param stdClass $course * @param stdClass $cm * @param stdClass $context * @param string $filearea * @param int $itemid * @param string $filepath * @param string $filename * @return file_info instance or null if not found * @todo implement this function */ function zoom_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) { return null; } /** * Serves the files from the zoom file areas * * @package mod_zoom * @category files * * @param stdClass $course the course object * @param stdClass $cm the course module object * @param stdClass $context the zoom's context * @param string $filearea the name of the file area * @param array $args extra arguments (itemid, path) * @param bool $forcedownload whether or not force download * @param array $options additional options affecting the file serving */ function zoom_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload, array $options = []) { if ($context->contextlevel != CONTEXT_MODULE) { send_file_not_found(); } require_login($course, true, $cm); send_file_not_found(); } /* Navigation API */ /** * Extends the global navigation tree by adding zoom nodes if there is a relevant content * * This can be called by an AJAX request so do not rely on $PAGE as it might not be set up properly. * * @param navigation_node $navref An object representing the navigation tree node of the zoom module instance * @param stdClass $course current course record * @param stdClass $module current zoom instance record * @param cm_info $cm course module information * @todo implement this function */ function zoom_extend_navigation(navigation_node $navref, stdClass $course, stdClass $module, cm_info $cm) { } /** * Extends the settings navigation with the zoom settings * * This function is called when the context for the page is a zoom module. This is not called by AJAX * so it is safe to rely on the $PAGE. * * @param settings_navigation $settingsnav complete settings navigation tree * @param navigation_node $zoomnode zoom administration node * @todo implement this function */ function zoom_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $zoomnode = null) { } /** * Get icon mapping for font-awesome. * * @see https://docs.moodle.org/dev/Moodle_icons */ function mod_zoom_get_fontawesome_icon_map() { return [ 'mod_zoom:i/calendar' => 'fa-calendar', ]; } /** * This function updates the tracking field settings in config_plugins. */ function mod_zoom_update_tracking_fields() { global $DB; try { $defaulttrackingfields = zoom_clean_tracking_fields(); $zoomprops = ['id', 'field', 'required', 'visible', 'recommended_values']; $confignames = []; if (!empty($defaulttrackingfields)) { $zoomtrackingfields = zoom_list_tracking_fields(); foreach ($zoomtrackingfields as $field => $zoomtrackingfield) { if (isset($defaulttrackingfields[$field])) { foreach ($zoomprops as $zoomprop) { $configname = 'tf_' . $field . '_' . $zoomprop; $confignames[] = $configname; if ($zoomprop === 'recommended_values') { $configvalue = implode(', ', $zoomtrackingfield[$zoomprop]); } else { $configvalue = $zoomtrackingfield[$zoomprop]; } set_config($configname, $configvalue, 'zoom'); } } } } $config = get_config('zoom'); $proparray = get_object_vars($config); $properties = array_keys($proparray); $oldconfigs = array_diff($properties, $confignames); $pattern = '/^tf_(?P.*)_(' . implode('|', $zoomprops) . ')$/'; foreach ($oldconfigs as $oldconfig) { if (preg_match($pattern, $oldconfig, $matches)) { set_config($oldconfig, null, 'zoom'); $DB->delete_records('zoom_meeting_tracking_fields', ['tracking_field' => $matches['oldfield']]); } } } catch (Exception $e) { // Fail gracefully because the callback function might be called directly. return false; } return true; } /** * Insert zoom instance breakout rooms * * @param int $zoomid * @param array $breakoutrooms zoom breakout rooms */ function zoom_insert_instance_breakout_rooms($zoomid, $breakoutrooms) { global $DB; foreach ($breakoutrooms as $breakoutroom) { $item = new stdClass(); $item->name = $breakoutroom['name']; $item->zoomid = $zoomid; $breakoutroomid = $DB->insert_record('zoom_meeting_breakout_rooms', $item); foreach ($breakoutroom['participants'] as $participant) { $item = new stdClass(); $item->userid = $participant; $item->breakoutroomid = $breakoutroomid; $DB->insert_record('zoom_breakout_participants', $item); } foreach ($breakoutroom['groups'] as $group) { $item = new stdClass(); $item->groupid = $group; $item->breakoutroomid = $breakoutroomid; $DB->insert_record('zoom_breakout_groups', $item); } } } /** * Update zoom instance breakout rooms * * @param int $zoomid * @param array $breakoutrooms */ function zoom_update_instance_breakout_rooms($zoomid, $breakoutrooms) { global $DB; zoom_delete_instance_breakout_rooms($zoomid); zoom_insert_instance_breakout_rooms($zoomid, $breakoutrooms); } /** * Delete zoom instance breakout rooms * * @param int $zoomid */ function zoom_delete_instance_breakout_rooms($zoomid) { global $DB; $zoomcurrentbreakoutroomsids = $DB->get_fieldset_select('zoom_meeting_breakout_rooms', 'id', "zoomid = {$zoomid}"); foreach ($zoomcurrentbreakoutroomsids as $id) { $DB->delete_records('zoom_breakout_participants', ['breakoutroomid' => $id]); $DB->delete_records('zoom_breakout_groups', ['breakoutroomid' => $id]); } $DB->delete_records('zoom_meeting_breakout_rooms', ['zoomid' => $zoomid]); } /** * Build zoom instance breakout rooms array for api * * @param stdClass $zoom Submitted data from the form in mod_form.php. * @return array The meeting breakout rooms array. */ function zoom_build_instance_breakout_rooms_array_for_api($zoom) { $context = context_course::instance($zoom->course); $users = get_enrolled_users($context); $groups = groups_get_all_groups($zoom->course); // Building meeting breakout rooms array. $breakoutrooms = []; if (!empty($zoom->rooms)) { foreach ($zoom->rooms as $roomid => $roomname) { // Getting meeting rooms participants. $roomparticipants = []; $dbroomparticipants = []; if (!empty($zoom->roomsparticipants[$roomid])) { foreach ($zoom->roomsparticipants[$roomid] as $participantid) { if (isset($users[$participantid])) { $roomparticipants[] = $users[$participantid]->email; $dbroomparticipants[] = $participantid; } } } // Getting meeting rooms groups members. $roomgroupsmembers = []; $dbroomgroupsmembers = []; if (!empty($zoom->roomsgroups[$roomid])) { foreach ($zoom->roomsgroups[$roomid] as $groupid) { if (isset($groups[$groupid])) { $groupmembers = groups_get_members($groupid); $roomgroupsmembers[] = array_column(array_values($groupmembers), 'email'); $dbroomgroupsmembers[] = $groupid; } } $roomgroupsmembers = array_merge(...$roomgroupsmembers); } $zoomdata = [ 'name' => $roomname, 'participants' => array_values(array_unique(array_merge($roomparticipants, $roomgroupsmembers))), ]; $dbdata = [ 'name' => $roomname, 'participants' => $dbroomparticipants, 'groups' => $dbroomgroupsmembers, ]; $breakoutrooms['zoom'][] = $zoomdata; $breakoutrooms['db'][] = $dbdata; } } return $breakoutrooms; } /** * Build zoom instance breakout rooms array for view. * * @param int $zoomid * @param array $courseparticipants * @param array $coursegroups * @return array The meeting breakout rooms array. */ function zoom_build_instance_breakout_rooms_array_for_view($zoomid, $courseparticipants, $coursegroups) { $breakoutrooms = zoom_get_instance_breakout_rooms($zoomid); $rooms = []; if (!empty($breakoutrooms)) { foreach ($breakoutrooms as $key => $breakoutroom) { $roomparticipants = $courseparticipants; if (!empty($breakoutroom['participants'])) { $participants = $breakoutroom['participants']; $roomparticipants = array_map(function($roomparticipant) use ($participants) { if (isset($participants[$roomparticipant['participantid']])) { $roomparticipant['selected'] = true; } return $roomparticipant; }, $courseparticipants); } $roomgroups = $coursegroups; if (!empty($breakoutroom['groups'])) { $groups = $breakoutroom['groups']; $roomgroups = array_map(function($roomgroup) use ($groups) { if (isset($groups[$roomgroup['groupid']])) { $roomgroup['selected'] = true; } return $roomgroup; }, $coursegroups); } $rooms[] = [ 'roomid' => $breakoutroom['roomid'], 'roomname' => $breakoutroom['roomname'], 'courseparticipants' => $roomparticipants, 'coursegroups' => $roomgroups, ]; } $rooms[0]['roomactive'] = true; } return $rooms; } /** * Get zoom instance breakout rooms. * * @param int $zoomid * @return array */ function zoom_get_instance_breakout_rooms($zoomid) { global $DB; $breakoutrooms = []; $params = [$zoomid]; $sql = "SELECT id, name FROM {zoom_meeting_breakout_rooms} WHERE zoomid = ?"; $rooms = $DB->get_records_sql($sql, $params); foreach ($rooms as $room) { $breakoutrooms[$room->id] = [ 'roomid' => $room->id, 'roomname' => $room->name, 'participants' => [], 'groups' => [], ]; // Get breakout room participants. $params = [$room->id]; $sql = "SELECT userid FROM {zoom_breakout_participants} WHERE breakoutroomid = ?"; $participants = $DB->get_records_sql($sql, $params); if (!empty($participants)) { foreach ($participants as $participant) { $breakoutrooms[$room->id]['participants'][$participant->userid] = $participant->userid; } } // Get breakout room groups. $sql = "SELECT groupid FROM {zoom_breakout_groups} WHERE breakoutroomid = ?"; $groups = $DB->get_records_sql($sql, $params); if (!empty($groups)) { foreach ($groups as $group) { $breakoutrooms[$room->id]['groups'][$group->groupid] = $group->groupid; } } } return $breakoutrooms; }