See Release Notes
Long Term Support Release
Differences Between: [Versions 401 and 402] [Versions 401 and 403]
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; 18 19 use cache; 20 use context; 21 use context_course; 22 use context_module; 23 use core\persistent; 24 use mod_bigbluebuttonbn\local\proxy\recording_proxy; 25 use moodle_url; 26 use stdClass; 27 28 /** 29 * The recording entity. 30 * 31 * This is utility class that defines a single recording, and provides methods for their local handling locally, and 32 * communication with the bigbluebutton server. 33 * 34 * @package mod_bigbluebuttonbn 35 * @copyright 2021 onwards, Blindside Networks Inc 36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 */ 38 class recording extends persistent { 39 /** The table name. */ 40 const TABLE = 'bigbluebuttonbn_recordings'; 41 42 /** @var int Defines that the activity used to create the recording no longer exists */ 43 public const RECORDING_HEADLESS = 1; 44 45 /** @var int Defines that the recording is not the original but an imported one */ 46 public const RECORDING_IMPORTED = 1; 47 48 /** @var int Defines that the list should include imported recordings */ 49 public const INCLUDE_IMPORTED_RECORDINGS = true; 50 51 /** @var int A meeting set to be recorded still awaits for a recording update */ 52 public const RECORDING_STATUS_AWAITING = 0; 53 54 /** @var int A meeting set to be recorded was not recorded and dismissed by BBB */ 55 public const RECORDING_STATUS_DISMISSED = 1; 56 57 /** @var int A meeting set to be recorded has a recording processed */ 58 public const RECORDING_STATUS_PROCESSED = 2; 59 60 /** @var int A meeting set to be recorded received notification callback from BBB */ 61 public const RECORDING_STATUS_NOTIFIED = 3; 62 63 /** @var int A meeting set to be recorded was processed and set back to an awaiting state */ 64 public const RECORDING_STATUS_RESET = 4; 65 66 /** @var int A meeting set to be recorded was deleted from bigbluebutton */ 67 public const RECORDING_STATUS_DELETED = 5; 68 69 /** @var bool Whether metadata been changed so the remote information needs to be updated ? */ 70 protected $metadatachanged = false; 71 72 /** @var int A refresh period for recordings, defaults to 300s (5mins) */ 73 public const RECORDING_REFRESH_DEFAULT_PERIOD = 300; 74 75 /** @var int A time limit for recordings to be dismissed, defaults to 30d (30days) */ 76 public const RECORDING_TIME_LIMIT_DAYS = 30; 77 78 /** @var array A cached copy of the metadata */ 79 protected $metadata = null; 80 81 /** @var instance A cached copy of the instance */ 82 protected $instance; 83 84 /** 85 * Create an instance of this class. 86 * 87 * @param int $id If set, this is the id of an existing record, used to load the data. 88 * @param stdClass|null $record If set will be passed to from_record 89 * @param null|array $metadata 90 */ 91 public function __construct($id = 0, stdClass $record = null, ?array $metadata = null) { 92 if ($record) { 93 $record->headless = $record->headless ?? false; 94 $record->imported = $record->imported ?? false; 95 $record->groupid = $record->groupid ?? 0; 96 $record->status = $record->status ?? self::RECORDING_STATUS_AWAITING; 97 } 98 parent::__construct($id, $record); 99 100 if ($metadata) { 101 $this->metadata = $metadata; 102 } 103 } 104 105 /** 106 * Helper function to retrieve recordings from the BigBlueButton. 107 * 108 * @param instance $instance 109 * @param bool $includeimported 110 * @param bool $onlyimported 111 * 112 * @return recording[] containing the recordings indexed by recordID, each recording is also a 113 * non sequential associative array itself that corresponds to the actual recording in BBB 114 */ 115 public static function get_recordings_for_instance( 116 instance $instance, 117 bool $includeimported = false, 118 bool $onlyimported = false 119 ): array { 120 [$selects, $params] = self::get_basic_select_from_parameters(false, $includeimported, $onlyimported); 121 $selects[] = "bigbluebuttonbnid = :bbbid"; 122 $params['bbbid'] = $instance->get_instance_id(); 123 $groupmode = groups_get_activity_groupmode($instance->get_cm()); 124 $context = $instance->get_context(); 125 if ($groupmode) { 126 [$groupselects, $groupparams] = self::get_select_for_group( 127 $groupmode, 128 $context, 129 $instance->get_course_id(), 130 $instance->get_group_id(), 131 $instance->get_cm()->groupingid 132 ); 133 if ($groupselects) { 134 $selects[] = $groupselects; 135 $params = array_merge_recursive($params, $groupparams); 136 } 137 } 138 139 $recordings = self::fetch_records($selects, $params); 140 foreach ($recordings as $recording) { 141 $recording->instance = $instance; 142 } 143 144 return $recordings; 145 } 146 147 /** 148 * Helper function to retrieve recordings from a given course. 149 * 150 * @param int $courseid id for a course record or null 151 * @param array $excludedinstanceid exclude recordings from instance ids 152 * @param bool $includeimported 153 * @param bool $onlyimported 154 * @param bool $includedeleted 155 * @param bool $onlydeleted 156 * 157 * @return recording[] containing the recordings indexed by recordID, each recording is also a 158 * non sequential associative array itself that corresponds to the actual recording in BBB 159 */ 160 public static function get_recordings_for_course( 161 int $courseid, 162 array $excludedinstanceid = [], 163 bool $includeimported = false, 164 bool $onlyimported = false, 165 bool $includedeleted = false, 166 bool $onlydeleted = false 167 ): array { 168 global $DB; 169 170 [$selects, $params] = self::get_basic_select_from_parameters( 171 $includedeleted, 172 $includeimported, 173 $onlyimported, 174 $onlydeleted 175 ); 176 if ($courseid) { 177 $selects[] = "courseid = :courseid"; 178 $params['courseid'] = $courseid; 179 $course = $DB->get_record('course', ['id' => $courseid]); 180 $groupmode = groups_get_course_groupmode($course); 181 $context = context_course::instance($courseid); 182 } else { 183 $context = \context_system::instance(); 184 $groupmode = NOGROUPS; 185 } 186 187 if ($groupmode) { 188 [$groupselects, $groupparams] = self::get_select_for_group($groupmode, $context, $course->id); 189 if ($groupselects) { 190 $selects[] = $groupselects; 191 $params = array_merge($params, $groupparams); 192 } 193 } 194 195 if ($excludedinstanceid) { 196 [$sqlexcluded, $paramexcluded] = $DB->get_in_or_equal($excludedinstanceid, SQL_PARAMS_NAMED, 'param', false); 197 $selects[] = "bigbluebuttonbnid {$sqlexcluded}"; 198 $params = array_merge($params, $paramexcluded); 199 } 200 201 return self::fetch_records($selects, $params); 202 } 203 204 /** 205 * Get select for given group mode and context 206 * 207 * @param int $groupmode 208 * @param context $context 209 * @param int $courseid 210 * @param int $groupid 211 * @param int $groupingid 212 * @return array 213 */ 214 protected static function get_select_for_group($groupmode, $context, $courseid, $groupid = 0, $groupingid = 0): array { 215 global $DB, $USER; 216 217 $selects = []; 218 $params = []; 219 if ($groupmode) { 220 $accessallgroups = has_capability('moodle/site:accessallgroups', $context) || $groupmode == VISIBLEGROUPS; 221 if ($accessallgroups) { 222 if ($context instanceof context_module) { 223 $allowedgroups = groups_get_all_groups($courseid, 0, $groupingid); 224 } else { 225 $allowedgroups = groups_get_all_groups($courseid); 226 } 227 } else { 228 if ($context instanceof context_module) { 229 $allowedgroups = groups_get_all_groups($courseid, $USER->id, $groupingid); 230 } else { 231 $allowedgroups = groups_get_all_groups($courseid, $USER->id); 232 } 233 } 234 $allowedgroupsid = array_map(function ($g) { 235 return $g->id; 236 }, $allowedgroups); 237 if ($groupid || empty($allowedgroups)) { 238 $selects[] = "groupid = :groupid"; 239 $params['groupid'] = ($groupid && in_array($groupid, $allowedgroupsid)) ? 240 $groupid : 0; 241 } else { 242 if ($accessallgroups) { 243 $allowedgroupsid[] = 0; 244 } 245 list($groupselects, $groupparams) = $DB->get_in_or_equal($allowedgroupsid, SQL_PARAMS_NAMED); 246 $selects[] = 'groupid ' . $groupselects; 247 $params = array_merge_recursive($params, $groupparams); 248 } 249 } 250 return [ 251 implode(" AND ", $selects), 252 $params, 253 ]; 254 } 255 256 /** 257 * Get basic sql select from given parameters 258 * 259 * @param bool $includedeleted 260 * @param bool $includeimported 261 * @param bool $onlyimported 262 * @param bool $onlydeleted 263 * @return array 264 */ 265 protected static function get_basic_select_from_parameters( 266 bool $includedeleted = false, 267 bool $includeimported = false, 268 bool $onlyimported = false, 269 bool $onlydeleted = false 270 ): array { 271 $selects = []; 272 $params = []; 273 274 // Start with the filters. 275 if ($onlydeleted) { 276 // Only headless recordings when only deleted is set. 277 $selects[] = "headless = :headless"; 278 $params['headless'] = self::RECORDING_HEADLESS; 279 } else if (!$includedeleted) { 280 // Exclude headless recordings unless includedeleted. 281 $selects[] = "headless != :headless"; 282 $params['headless'] = self::RECORDING_HEADLESS; 283 } 284 285 if (!$includeimported) { 286 // Exclude imported recordings unless includedeleted. 287 $selects[] = "imported != :imported"; 288 $params['imported'] = self::RECORDING_IMPORTED; 289 } else if ($onlyimported) { 290 // Exclude non-imported recordings. 291 $selects[] = "imported = :imported"; 292 $params['imported'] = self::RECORDING_IMPORTED; 293 } 294 295 // Now get only recordings that have been validated by recording ready callback. 296 $selects[] = "status IN (:status_processed, :status_notified)"; 297 $params['status_processed'] = self::RECORDING_STATUS_PROCESSED; 298 $params['status_notified'] = self::RECORDING_STATUS_NOTIFIED; 299 return [$selects, $params]; 300 } 301 302 /** 303 * Return the definition of the properties of this model. 304 * 305 * @return array 306 */ 307 protected static function define_properties() { 308 return [ 309 'courseid' => [ 310 'type' => PARAM_INT, 311 ], 312 'bigbluebuttonbnid' => [ 313 'type' => PARAM_INT, 314 ], 315 'groupid' => [ 316 'type' => PARAM_INT, 317 'null' => NULL_ALLOWED, 318 ], 319 'recordingid' => [ 320 'type' => PARAM_RAW, 321 ], 322 'headless' => [ 323 'type' => PARAM_BOOL, 324 ], 325 'imported' => [ 326 'type' => PARAM_BOOL, 327 ], 328 'status' => [ 329 'type' => PARAM_INT, 330 ], 331 'importeddata' => [ 332 'type' => PARAM_RAW, 333 'null' => NULL_ALLOWED, 334 'default' => '' 335 ], 336 'name' => [ 337 'type' => PARAM_TEXT, 338 'null' => NULL_ALLOWED, 339 'default' => null 340 ], 341 'description' => [ 342 'type' => PARAM_TEXT, 343 'null' => NULL_ALLOWED, 344 'default' => 0 345 ], 346 'protected' => [ 347 'type' => PARAM_BOOL, 348 'null' => NULL_ALLOWED, 349 'default' => null 350 ], 351 'starttime' => [ 352 'type' => PARAM_INT, 353 'null' => NULL_ALLOWED, 354 'default' => null 355 ], 356 'endtime' => [ 357 'type' => PARAM_INT, 358 'null' => NULL_ALLOWED, 359 'default' => null 360 ], 361 'published' => [ 362 'type' => PARAM_BOOL, 363 'null' => NULL_ALLOWED, 364 'default' => null 365 ], 366 'playbacks' => [ 367 'type' => PARAM_RAW, 368 'null' => NULL_ALLOWED, 369 'default' => null 370 ], 371 ]; 372 } 373 374 /** 375 * Get the instance that this recording relates to. 376 * 377 * @return instance 378 */ 379 public function get_instance(): instance { 380 if ($this->instance === null) { 381 $this->instance = instance::get_from_instanceid($this->get('bigbluebuttonbnid')); 382 } 383 384 return $this->instance; 385 } 386 387 /** 388 * Before doing the database update, let's check if we need to update metadata 389 * 390 * @return void 391 */ 392 protected function before_update() { 393 // We update if the remote metadata has been changed locally. 394 if ($this->metadatachanged && !$this->get('imported')) { 395 $metadata = $this->fetch_metadata(); 396 if ($metadata) { 397 recording_proxy::update_recording( 398 $this->get('recordingid'), 399 $metadata 400 ); 401 } 402 $this->metadatachanged = false; 403 } 404 } 405 406 /** 407 * Create a new imported recording from current recording 408 * 409 * @param instance $targetinstance 410 * @return recording 411 */ 412 public function create_imported_recording(instance $targetinstance) { 413 $recordingrec = $this->to_record(); 414 $remotedata = $this->fetch_metadata(); 415 unset($recordingrec->id); 416 $recordingrec->bigbluebuttonbnid = $targetinstance->get_instance_id(); 417 $recordingrec->courseid = $targetinstance->get_course_id(); 418 $recordingrec->groupid = 0; // The recording is available to everyone. 419 $recordingrec->importeddata = json_encode($remotedata); 420 $recordingrec->imported = true; 421 $recordingrec->headless = false; 422 $importedrecording = new self(0, $recordingrec); 423 $importedrecording->create(); 424 return $importedrecording; 425 } 426 427 /** 428 * Delete the recording in the BBB button 429 * 430 * @return void 431 */ 432 protected function before_delete() { 433 $recordid = $this->get('recordingid'); 434 if ($recordid && !$this->get('imported')) { 435 recording_proxy::delete_recording($recordid); 436 // Delete in cache if needed. 437 $cachedrecordings = cache::make('mod_bigbluebuttonbn', 'recordings'); 438 $cachedrecordings->delete($recordid); 439 } 440 } 441 442 /** 443 * Set name 444 * 445 * @param string $value 446 */ 447 protected function set_name($value) { 448 $this->metadata_set('name', trim($value)); 449 } 450 451 /** 452 * Set Description 453 * 454 * @param string $value 455 */ 456 protected function set_description($value) { 457 $this->metadata_set('description', trim($value)); 458 } 459 460 /** 461 * Recording is protected 462 * 463 * @param bool $value 464 */ 465 protected function set_protected($value) { 466 $realvalue = $value ? "true" : "false"; 467 $this->metadata_set('protected', $realvalue); 468 recording_proxy::protect_recording($this->get('recordingid'), $realvalue); 469 } 470 471 /** 472 * Recording starttime 473 * 474 * @param int $value 475 */ 476 protected function set_starttime($value) { 477 $this->metadata_set('starttime', $value); 478 } 479 480 /** 481 * Recording endtime 482 * 483 * @param int $value 484 */ 485 protected function set_endtime($value) { 486 $this->metadata_set('endtime', $value); 487 } 488 489 /** 490 * Recording is published 491 * 492 * @param bool $value 493 */ 494 protected function set_published($value) { 495 $realvalue = $value ? "true" : "false"; 496 $this->metadata_set('published', $realvalue); 497 // Now set this flag onto the remote bbb server. 498 recording_proxy::publish_recording($this->get('recordingid'), $realvalue); 499 } 500 501 /** 502 * Update recording status 503 * 504 * @param bool $value 505 */ 506 protected function set_status($value) { 507 $this->raw_set('status', $value); 508 $this->update(); 509 } 510 511 /** 512 * POSSIBLE_REMOTE_META_SOURCE match a field type and its metadataname (historical and current). 513 */ 514 const POSSIBLE_REMOTE_META_SOURCE = [ 515 'description' => ['meta_bbb-recording-description', 'meta_contextactivitydescription'], 516 'name' => ['meta_bbb-recording-name', 'meta_contextactivity', 'meetingName'], 517 'playbacks' => ['playbacks'], 518 'starttime' => ['startTime'], 519 'endtime' => ['endTime'], 520 'published' => ['published'], 521 'protected' => ['protected'], 522 'tags' => ['meta_bbb-recording-tags'] 523 ]; 524 525 /** 526 * Get the real metadata name for the possible source. 527 * 528 * @param string $sourcetype the name of the source we look for (name, description...) 529 * @param array $metadata current metadata 530 */ 531 protected function get_possible_meta_name_for_source($sourcetype, $metadata): string { 532 $possiblesource = self::POSSIBLE_REMOTE_META_SOURCE[$sourcetype]; 533 $possiblesourcename = $possiblesource[0]; 534 foreach ($possiblesource as $possiblesname) { 535 if (isset($meta[$possiblesname])) { 536 $possiblesourcename = $possiblesname; 537 } 538 } 539 return $possiblesourcename; 540 } 541 542 /** 543 * Convert string (metadata) to json object 544 * 545 * @return mixed|null 546 */ 547 protected function remote_meta_convert() { 548 $remotemeta = $this->raw_get('importeddata'); 549 return json_decode($remotemeta, true); 550 } 551 552 /** 553 * Description is stored in the metadata, so we sometimes needs to do some conversion. 554 */ 555 protected function get_description() { 556 return trim($this->metadata_get('description')); 557 } 558 559 /** 560 * Name is stored in the metadata 561 */ 562 protected function get_name() { 563 return trim($this->metadata_get('name')); 564 } 565 566 /** 567 * List of playbacks for this recording. 568 * 569 * @return array[] 570 */ 571 protected function get_playbacks() { 572 if ($playbacks = $this->metadata_get('playbacks')) { 573 return array_map(function (array $playback): array { 574 $clone = array_merge([], $playback); 575 $clone['url'] = new moodle_url('/mod/bigbluebuttonbn/bbb_view.php', [ 576 'action' => 'play', 577 'bn' => $this->raw_get('bigbluebuttonbnid'), 578 'rid' => $this->get('id'), 579 'rtype' => $clone['type'], 580 ]); 581 582 return $clone; 583 }, $playbacks); 584 } 585 586 return []; 587 } 588 589 /** 590 * Get the playback URL for the specified type. 591 * 592 * @param string $type 593 * @return null|string 594 */ 595 public function get_remote_playback_url(string $type): ?string { 596 $this->refresh_metadata_if_required(); 597 598 $playbacks = $this->metadata_get('playbacks'); 599 foreach ($playbacks as $playback) { 600 if ($playback['type'] == $type) { 601 return $playback['url']; 602 } 603 } 604 605 return null; 606 } 607 608 /** 609 * Is protected. Return null if protected is not implemented. 610 * 611 * @return bool|null 612 */ 613 protected function get_protected() { 614 $protectedtext = $this->metadata_get('protected'); 615 return is_null($protectedtext) ? null : $protectedtext === "true"; 616 } 617 618 /** 619 * Start time 620 * 621 * @return mixed|null 622 */ 623 protected function get_starttime() { 624 return $this->metadata_get('starttime'); 625 } 626 627 /** 628 * Start time 629 * 630 * @return mixed|null 631 */ 632 protected function get_endtime() { 633 return $this->metadata_get('endtime'); 634 } 635 636 /** 637 * Is published 638 * 639 * @return bool 640 */ 641 protected function get_published() { 642 $publishedtext = $this->metadata_get('published'); 643 return $publishedtext === "true"; 644 } 645 646 /** 647 * Set locally stored metadata from this instance 648 * 649 * @param string $fieldname 650 * @param mixed $value 651 */ 652 protected function metadata_set($fieldname, $value) { 653 // Can we can change the metadata on the imported record ? 654 if ($this->get('imported')) { 655 return; 656 } 657 658 $this->metadatachanged = true; 659 660 $metadata = $this->fetch_metadata(); 661 $possiblesourcename = $this->get_possible_meta_name_for_source($fieldname, $metadata); 662 $metadata[$possiblesourcename] = $value; 663 664 $this->metadata = $metadata; 665 } 666 667 /** 668 * Get information stored in the recording metadata such as description, name and other info 669 * 670 * @param string $fieldname 671 * @return mixed|null 672 */ 673 protected function metadata_get($fieldname) { 674 $metadata = $this->fetch_metadata(); 675 676 $possiblesourcename = $this->get_possible_meta_name_for_source($fieldname, $metadata); 677 return $metadata[$possiblesourcename] ?? null; 678 } 679 680 /** 681 * @var string Default sort for recordings when fetching from the database. 682 */ 683 const DEFAULT_RECORDING_SORT = 'timecreated ASC'; 684 685 /** 686 * Fetch all records which match the specified parameters, including all metadata that relates to them. 687 * 688 * @param array $selects 689 * @param array $params 690 * @return recording[] 691 */ 692 protected static function fetch_records(array $selects, array $params): array { 693 global $DB, $CFG; 694 695 $withindays = time() - (self::RECORDING_TIME_LIMIT_DAYS * DAYSECS); 696 // Sort for recordings when fetching from the database. 697 $recordingsort = $CFG->bigbluebuttonbn_recordings_asc_sort ? 'timecreated ASC' : 'timecreated DESC'; 698 699 // Fetch the local data. Arbitrary sort by id, so we get the same result on different db engines. 700 $recordings = $DB->get_records_select( 701 static::TABLE, 702 implode(" AND ", $selects), 703 $params, 704 $recordingsort 705 ); 706 707 // Grab the recording IDs. 708 $recordingids = array_values(array_map(function ($recording) { 709 return $recording->recordingid; 710 }, $recordings)); 711 712 // Fetch all metadata for these recordings. 713 $metadatas = recording_proxy::fetch_recordings($recordingids); 714 715 // Return the instances. 716 return array_filter(array_map(function ($recording) use ($metadatas, $withindays) { 717 // Filter out if no metadata was fetched. 718 if (!array_key_exists($recording->recordingid, $metadatas)) { 719 // Mark it as dismissed if it is older than 30 days. 720 if ($withindays > $recording->timecreated) { 721 $recording = new self(0, $recording, null); 722 $recording->set_status(self::RECORDING_STATUS_DISMISSED); 723 } 724 return false; 725 } 726 $metadata = $metadatas[$recording->recordingid]; 727 // Filter out and mark it as deleted if it was deleted in BBB. 728 if ($metadata['state'] == 'deleted') { 729 $recording = new self(0, $recording, null); 730 $recording->set_status(self::RECORDING_STATUS_DELETED); 731 return false; 732 } 733 // Include it otherwise. 734 return new self(0, $recording, $metadata); 735 }, $recordings)); 736 } 737 738 /** 739 * Fetch metadata 740 * 741 * If metadata has changed locally or if it an imported recording, nothing will be done. 742 * 743 * @param bool $force 744 * @return array 745 */ 746 protected function fetch_metadata(bool $force = false): ?array { 747 if ($this->metadata !== null && !$force) { 748 // Metadata is already up-to-date. 749 return $this->metadata; 750 } 751 752 if ($this->get('imported')) { 753 $this->metadata = json_decode($this->get('importeddata'), true); 754 } else { 755 $this->metadata = recording_proxy::fetch_recording($this->get('recordingid')); 756 } 757 758 return $this->metadata; 759 } 760 761 /** 762 * Refresh metadata if required. 763 * 764 * If this is a protected recording which whose data was not fetched in the current request, then the metadata will 765 * be purged and refetched. This ensures that the url is safe for use with a protected recording. 766 */ 767 protected function refresh_metadata_if_required() { 768 recording_proxy::purge_protected_recording($this->get('recordingid')); 769 $this->fetch_metadata(true); 770 } 771 772 /** 773 * Synchronise pending recordings from the server. 774 * 775 * This function should be called by the check_pending_recordings scheduled task. 776 * 777 * @param bool $dismissedonly fetch dismissed recording only 778 */ 779 public static function sync_pending_recordings_from_server(bool $dismissedonly = false): void { 780 global $DB; 781 $params = [ 782 'withindays' => time() - (self::RECORDING_TIME_LIMIT_DAYS * DAYSECS), 783 ]; 784 // Fetch the local data. 785 if ($dismissedonly) { 786 mtrace("=> Looking for any recording that has been 'dismissed' in the past " . self::RECORDING_TIME_LIMIT_DAYS 787 . " days."); 788 $select = 'status = :status_dismissed AND timecreated > :withindays'; 789 $params['status_dismissed'] = self::RECORDING_STATUS_DISMISSED; 790 } else { 791 mtrace("=> Looking for any recording awaiting processing from the past " . self::RECORDING_TIME_LIMIT_DAYS . " days."); 792 $select = '(status = :status_awaiting AND timecreated > :withindays) OR status = :status_reset'; 793 $params['status_reset'] = self::RECORDING_STATUS_RESET; 794 $params['status_awaiting'] = self::RECORDING_STATUS_AWAITING; 795 } 796 797 $recordings = $DB->get_records_select(static::TABLE, $select, $params, self::DEFAULT_RECORDING_SORT); 798 // Sort by DEFAULT_RECORDING_SORT we get the same result on different db engines. 799 800 $recordingcount = count($recordings); 801 mtrace("=> Found {$recordingcount} recordings to query"); 802 803 // Grab the recording IDs. 804 $recordingids = array_map(function($recording) { 805 return $recording->recordingid; 806 }, $recordings); 807 808 // Fetch all metadata for these recordings. 809 mtrace("=> Fetching recording metadata from server"); 810 $metadatas = recording_proxy::fetch_recordings($recordingids); 811 812 $foundcount = 0; 813 foreach ($metadatas as $recordingid => $metadata) { 814 mtrace("==> Found metadata for {$recordingid}."); 815 $id = array_search($recordingid, $recordingids); 816 if (!$id) { 817 // Recording was not found, skip. 818 mtrace("===> Skip as fetched recording was not found."); 819 continue; 820 } 821 // Recording was found, update status. 822 mtrace("===> Update local cache as fetched recording was found."); 823 $recording = new self(0, $recordings[$id], $metadata); 824 $recording->set_status(self::RECORDING_STATUS_PROCESSED); 825 $foundcount++; 826 827 if (array_key_exists('breakouts', $metadata)) { 828 // Iterate breakout recordings (if any) and update status. 829 foreach ($metadata['breakouts'] as $breakoutrecordingid => $breakoutmetadata) { 830 $breakoutrecording = self::get_record(['recordingid' => $breakoutrecordingid]); 831 if (!$breakoutrecording) { 832 $breakoutrecording = new recording(0, (object) [ 833 'courseid' => $recording->get('courseid'), 834 'bigbluebuttonbnid' => $recording->get('bigbluebuttonbnid'), 835 'groupid' => $recording->get('groupid'), 836 'recordingid' => $breakoutrecordingid 837 ], $breakoutmetadata); 838 $breakoutrecording->create(); 839 } 840 $breakoutrecording->set_status(self::RECORDING_STATUS_PROCESSED); 841 $foundcount++; 842 } 843 } 844 } 845 846 mtrace("=> Finished processing recordings. Updated status for {$foundcount} / {$recordingcount} recordings."); 847 } 848 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body