Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  namespace mod_bigbluebuttonbn\task;
  18  
  19  use core\task\adhoc_task;
  20  use core\task\manager;
  21  use Matrix\Exception;
  22  use mod_bigbluebuttonbn\instance;
  23  use mod_bigbluebuttonbn\local\proxy\recording_proxy;
  24  use mod_bigbluebuttonbn\logger;
  25  use mod_bigbluebuttonbn\recording;
  26  use moodle_exception;
  27  
  28  /**
  29   * Class containing the scheduled task for converting recordings for the BigBlueButton version 2.5 in Moodle 4.0.
  30   *
  31   * @package   mod_bigbluebuttonbn
  32   * @copyright 2021 Jesus Federico, Blindside Networks Inc <jesus at blindsidenetworks dot com>
  33   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  34   */
  35  class upgrade_recordings_task extends adhoc_task {
  36      /**
  37       * Run the migration task.
  38       */
  39      public function execute() {
  40          $info = $this->get_custom_data();
  41          $meetingid = $info->meetingid;
  42          $isimported = $info->isimported ?? 0;
  43          $this->process_bigbluebuttonbn_logs($meetingid, $isimported);
  44      }
  45  
  46      /**
  47       * Process all bigbluebuttonbn logs looking for entries which should be converted to meetings.
  48       *
  49       * @param string $meetingid
  50       * @param bool $isimported
  51       * @return bool Whether any more logs are waiting to be processed
  52       * @throws \dml_exception
  53       * @throws moodle_exception
  54       */
  55      protected function process_bigbluebuttonbn_logs(string $meetingid, bool $isimported): bool {
  56          global $DB;
  57  
  58          $classname = static::class;
  59          mtrace("Executing {$classname} for meeting {$meetingid}...");
  60  
  61          // Fetch the logs queued for upgrade.
  62          mtrace("Fetching logs for conversion");
  63          // Each log is ordered by timecreated.
  64          [$select, $params] = $this->get_sql_query_for_logs($meetingid, $isimported);
  65          $logsrs = $DB->get_recordset_select('bigbluebuttonbn_logs',
  66              $select,
  67              $params,
  68              'timecreated DESC',
  69              'id, meetingid, timecreated, log');
  70  
  71          if (!$logsrs->valid()) {
  72              mtrace("No logs were found for conversion.");
  73              // No more logs. Stop queueing.
  74              return false;
  75          }
  76          // Retrieve recordings from the servers for this meeting.
  77          $recordings = recording_proxy::fetch_recording_by_meeting_id([$meetingid]);
  78          // Sort recordings by meetingId, then startTime.
  79          uasort($recordings, function($a, $b) {
  80              return $b['startTime'] - $a['startTime'];
  81          });
  82  
  83          // Create an instance of bigbluebuttonbn_recording per valid recording.
  84          mtrace("Creating new recording records...");
  85          $recordingcount = 0;
  86          foreach ($recordings as $recordingid => $recording) {
  87              $importeddata = $isimported ? '' : json_encode($recording);
  88              try {
  89                  $instance = instance::get_from_meetingid($recording['meetingID']);
  90              } catch (Exception $e) {
  91                  mtrace("Unable to parse meetingID " . $e->getMessage());
  92                  continue;
  93              }
  94  
  95              if ($instance) {
  96                  $newrecording = [
  97                      'courseid' => $instance->get_course_id(),
  98                      'bigbluebuttonbnid' => $instance->get_instance_id(),
  99                      'groupid' => $instance->get_group_id(), // The groupid should be taken from the meetingID.
 100                      'recordingid' => $recordingid,
 101                      'status' => recording::RECORDING_STATUS_PROCESSED,
 102                  ];
 103              } else {
 104                  mtrace("Unable to find an activity for {$recording['meetingID']}. This recording is headless.");
 105                  // This instance does not exist any more.
 106                  // Use the data in the log instead of the instance.
 107                  $meetingdata = instance::parse_meetingid($recording['meetingID']);
 108                  $newrecording = [
 109                      'courseid' => $meetingdata['courseid'],
 110                      'bigbluebuttonbnid' => $meetingdata['instanceid'],
 111                      'groupid' => 0,
 112                      'recordingid' => $recordingid,
 113                      'status' => recording::RECORDING_STATUS_PROCESSED,
 114                  ];
 115  
 116                  if (array_key_exists('groupid', $meetingdata)) {
 117                      $newrecording['groupid'] = $meetingdata['groupid'];
 118                  }
 119              }
 120  
 121              if ($DB->record_exists('bigbluebuttonbn_recordings', $newrecording)) {
 122                  mtrace("A recording already exists for {$recording['recordID']}. Skipping.");
 123                  // A recording matching these characteristics alreay exists.
 124                  continue;
 125              }
 126              // Recording has not been imported, check if we still have more logs.
 127              // We try to guess which logs matches which recordings are they are classed in the same order.
 128              // But  this is just an attempt.
 129              $log = null;
 130              if ($logsrs->valid()) {
 131                  $log = $logsrs->current();
 132                  $logsrs->next();
 133              }
 134              $timecreated = empty($log) ? time() : $log->timecreated;
 135              $newrecording['imported'] = $isimported;
 136              $newrecording['headless'] = 0;
 137              $newrecording['importeddata'] = $importeddata;
 138              $newrecording['timecreated'] = $newrecording['timemodified'] = $timecreated;
 139  
 140              // If we could not match with a log, we still create the recording.
 141              $DB->insert_record('bigbluebuttonbn_recordings', $newrecording);
 142              $recordingcount++;
 143          }
 144          mtrace("Migrated {$recordingcount} recordings.");
 145          // Now deactivate logs by marking all of them as migrated.
 146          // Reason for this is that we don't want to run another migration here and we don't know
 147          // which logs matches which recordings.
 148          $DB->set_field_select('bigbluebuttonbn_logs', 'log',
 149              $isimported ? logger::EVENT_IMPORT_MIGRATED : logger::EVENT_CREATE_MIGRATED,
 150              $select,
 151              $params
 152          );
 153          $logsrs->close();
 154          return true;
 155      }
 156  
 157      /**
 158       * Get the query (records_select) for the logs to convert.
 159       *
 160       * Each log is ordered by timecreated.
 161       *
 162       * @param string $meetingid
 163       * @param bool $isimported
 164       * @return array
 165       */
 166      protected function get_sql_query_for_logs(string $meetingid, bool $isimported): array {
 167          global $DB;
 168          if ($isimported) {
 169              return [
 170                  'log = :logmatch AND meetingid = :meetingid',
 171                  ['logmatch' => logger::EVENT_IMPORT, 'meetingid' => $meetingid],
 172              ];
 173          }
 174          return [
 175              'log = :logmatch AND meetingid = :meetingid AND ' . $DB->sql_like('meta', ':match'),
 176              [
 177                  'logmatch' => logger::EVENT_CREATE,
 178                  'match' => '%true%',
 179                  'meetingid' => $meetingid
 180              ],
 181          ];
 182      }
 183  
 184      /**
 185       * Schedule all upgrading tasks.
 186       *
 187       * @param bool $importedrecordings
 188       * @return void
 189       * @throws \dml_exception
 190       */
 191      public static function schedule_upgrade_per_meeting($importedrecordings = false) {
 192          global $DB;
 193          $meetingids = $DB->get_fieldset_sql(
 194              'SELECT DISTINCT meetingid FROM {bigbluebuttonbn_logs} WHERE log = :createorimport',
 195              ['createorimport' => $importedrecordings ? logger::EVENT_IMPORT : logger::EVENT_CREATE]
 196          );
 197          foreach ($meetingids as $mid) {
 198              $createdrecordingtask = new static();
 199              $createdrecordingtask->set_custom_data((object) ['meetingid' => $mid, 'isimported' => $importedrecordings]);
 200              manager::queue_adhoc_task($createdrecordingtask);
 201          }
 202      }
 203  }