Proyectos de Subversion Moodle

Rev

Autoría | Ultima modificación | Ver Log |

<?php
// This file is part of the Zoom plugin for 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/>.

/**
 * 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|null $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|null $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);
    $DB->update_record('zoom', $zoom);

    // 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 = ['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;
    }

    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 (\mod_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
 */
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)
 */
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
 */
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
 */
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, false);
    }
}

/**
 * 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|null $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], '*');
    [$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 int
 */
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
 */
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
 */
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
 */
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|null $zoomnode zoom administration node
 */
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<oldfield>.*)_(' . 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;
}

/**
 * Print zoom meeting date and time in the course listing page
 *
 * Given a course_module object, this function returns any "extra" information that may be needed
 * when printing this activity in a course listing. See get_array_of_activities() in course/lib.php.
 *
 * @param stdClass $coursemodule The coursemodule object
 * @return cached_cm_info An object on information that the courses will know about
 */
function zoom_get_coursemodule_info($coursemodule) {
    global $DB;

    $dbparams = ['id' => $coursemodule->instance];
    $fields = 'id, intro, introformat, start_time, recurring, recurrence_type, duration';
    if (!$zoom = $DB->get_record('zoom', $dbparams, $fields)) {
        return false;
    }

    $result = new cached_cm_info();

    if ($coursemodule->showdescription) {
        // Convert intro to html. Do not filter cached version, filters run at display time.
        $result->content = format_module_intro('zoom', $zoom, $coursemodule->id, false);
    }

    // Populate some other values that can be used in calendar or on dashboard.
    if ($zoom->start_time) {
        $result->customdata['start_time'] = $zoom->start_time;
    }

    if ($zoom->duration) {
        $result->customdata['duration'] = $zoom->duration;
    }

    // Skip the if condition for recurring and recurrence_type, the values of NULL and 0 are needed in other functions.
    $result->customdata['recurring'] = $zoom->recurring;
    $result->customdata['recurrence_type'] = $zoom->recurrence_type;

    return $result;
}

/**
 * Sets dynamic information about a course module
 *
 * This function is called from cm_info when displaying the module
 *
 * @param cm_info $cm
 */
function zoom_cm_info_dynamic(cm_info $cm) {
    global $CFG, $DB;

    require_once($CFG->dirroot . '/mod/zoom/locallib.php');

    if (method_exists($cm, 'override_customdata')) {
        $moduleinstance = $DB->get_record('zoom', ['id' => $cm->instance], '*', MUST_EXIST);

        // Get meeting state from Zoom.
        [$inprogress, $available, $finished] = zoom_get_state($moduleinstance);

        // For unfinished meetings, override start_time with the next occurrence.
        // If this is a recurring meeting without fixed time, do not override - it will set start_time = 0.
        if (!$finished && $moduleinstance->recurrence_type != ZOOM_RECURRINGTYPE_NOTIME) {
            $cm->override_customdata('start_time', zoom_get_next_occurrence($moduleinstance));
        }
    }
}