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