Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 400 and 403] [Versions 401 and 403] [Versions 402 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  /**
  18   * The mod_bigbluebuttonbn files helper
  19   *
  20   * @package   mod_bigbluebuttonbn
  21   * @copyright 2021 onwards, Blindside Networks Inc
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   * @author    Laurent David  (laurent [at] call-learning [dt] fr)
  24   */
  25  
  26  namespace mod_bigbluebuttonbn\local\helpers;
  27  
  28  use cache;
  29  use cache_store;
  30  use context;
  31  use context_module;
  32  use context_system;
  33  use mod_bigbluebuttonbn\instance;
  34  use moodle_url;
  35  use stdClass;
  36  
  37  /**
  38   * Utility class for all files routines helper
  39   *
  40   * @package mod_bigbluebuttonbn
  41   * @copyright 2021 onwards, Blindside Networks Inc
  42   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  43   */
  44  class files {
  45  
  46      /**
  47       * Helper for validating pluginfile.
  48       *
  49       * @param stdClass $context context object
  50       * @param string $filearea file area
  51       *
  52       * @return bool|null false if file not valid
  53       */
  54      public static function pluginfile_valid(stdClass $context, string $filearea): ?bool {
  55  
  56          // Can be in context module or in context_system (if is the presentation by default).
  57          if (!in_array($context->contextlevel, [CONTEXT_MODULE, CONTEXT_SYSTEM])) {
  58              return false;
  59          }
  60  
  61          if (!array_key_exists($filearea, self::get_file_areas())) {
  62              return false;
  63          }
  64  
  65          return true;
  66      }
  67  
  68      /**
  69       * Helper for getting pluginfile.
  70       *
  71       * @param stdClass|null $course course object
  72       * @param stdClass|null $cm course module object
  73       * @param context $context context object
  74       * @param string $filearea file area
  75       * @param array $args extra arguments
  76       *
  77       * @return \stored_file|bool
  78       */
  79      public static function pluginfile_file(?stdClass $course, ?stdClass $cm, context $context, string $filearea, array $args) {
  80          $filename = self::get_plugin_filename($course, $cm, $context, $args);
  81          if (!$filename) {
  82              return false;
  83          }
  84          $fullpath = "/$context->id/mod_bigbluebuttonbn/$filearea/0/" . $filename;
  85          $fs = get_file_storage();
  86          $file = $fs->get_file_by_hash(sha1($fullpath));
  87          if (!$file || $file->is_directory()) {
  88              return false;
  89          }
  90          return $file;
  91      }
  92  
  93      /**
  94       * Get a full path to the file attached as a preuploaded presentation
  95       * or if there is none, set the presentation field will be set to blank.
  96       *
  97       * @param stdClass $bigbluebuttonformdata BigBlueButtonBN form data
  98       * Note that $bigbluebuttonformdata->presentation is the id of the filearea whereas the bbb instance table
  99       * stores the file name/path
 100       * @return string
 101       */
 102      public static function save_media_file(stdClass &$bigbluebuttonformdata): string {
 103          if (!isset($bigbluebuttonformdata->presentation) || $bigbluebuttonformdata->presentation == '') {
 104              return '';
 105          }
 106          $context = context_module::instance($bigbluebuttonformdata->coursemodule);
 107          // Set the filestorage object.
 108          $fs = get_file_storage();
 109          // Save the file if it exists that is currently in the draft area.
 110          file_save_draft_area_files($bigbluebuttonformdata->presentation, $context->id, 'mod_bigbluebuttonbn', 'presentation', 0);
 111          // Get the file if it exists.
 112          $files = $fs->get_area_files(
 113              $context->id,
 114              'mod_bigbluebuttonbn',
 115              'presentation',
 116              0,
 117              'itemid, filepath, filename',
 118              false
 119          );
 120          // Check that there is a file to process.
 121          $filesrc = '';
 122          if (count($files) == 1) {
 123              // Get the first (and only) file.
 124              $file = reset($files);
 125              $filesrc = '/' . $file->get_filename();
 126          }
 127          return $filesrc;
 128      }
 129  
 130      /**
 131       * Helper return array containing the file descriptor for a preuploaded presentation.
 132       *
 133       * @param context $context
 134       * @param string $presentation matching presentation file name
 135       * @param int $id bigbluebutton instance id
 136       * @param bool $withnonce add nonce to the url
 137       * @return array|null the representation of the presentation as an associative array
 138       */
 139      public static function get_presentation(context $context, string $presentation, $id = null, $withnonce = false): ?array {
 140          global $CFG;
 141          $fs = get_file_storage();
 142          $files = [];
 143          $defaultpresentation = $fs->get_area_files(
 144              context_system::instance()->id,
 145              'mod_bigbluebuttonbn',
 146              'presentationdefault',
 147              0,
 148              "filename",
 149              false
 150          );
 151          $activitypresentation = $files = $fs->get_area_files(
 152              $context->id,
 153              'mod_bigbluebuttonbn',
 154              'presentation',
 155              false,
 156              'itemid, filepath, filename',
 157              false
 158          );
 159          // Presentation upload logic based on config settings.
 160          if (empty($defaultpresentation)) {
 161              if (empty($activitypresentation) || !\mod_bigbluebuttonbn\local\config::get('preuploadpresentation_editable')) {
 162                  return null;
 163              }
 164              $files = $activitypresentation;
 165  
 166          } else {
 167              if (empty($activitypresentation) || !\mod_bigbluebuttonbn\local\config::get('preuploadpresentation_editable')) {
 168                  $files = $defaultpresentation;
 169                  $id = null;
 170              } else {
 171                  $files = $activitypresentation;
 172              }
 173          }
 174          $pnoncevalue = 0;
 175          if ($withnonce) {
 176              $nonceid = 0;
 177              if (!is_null($id)) {
 178                  $instance = instance::get_from_instanceid($id);
 179                  $nonceid = $instance->get_instance_id();
 180              }
 181              $pnoncevalue = self::generate_nonce($nonceid);
 182          }
 183  
 184          $file = null;
 185          foreach ($files as $f) {
 186              if (basename($f->get_filename()) == basename($presentation)) {
 187                  $file = $f;
 188              }
 189          }
 190          if (!$file && !empty($files)) {
 191              $file = reset($files);
 192          }
 193          if (empty($file)) {
 194              return null; // File was not found.
 195          }
 196  
 197          // Note: $pnoncevalue is an int.
 198          $url = moodle_url::make_pluginfile_url(
 199              $file->get_contextid(),
 200              $file->get_component(),
 201              $file->get_filearea(),
 202              $withnonce ? $pnoncevalue : null, // Hack: item id as a nonce.
 203              $file->get_filepath(),
 204              $file->get_filename()
 205          );
 206          return [
 207              'icondesc' => get_mimetype_description($file),
 208              'iconname' => file_file_icon($file),
 209              'name' => $file->get_filename(),
 210              'url' => $url->out(false),
 211          ];
 212      }
 213  
 214      /**
 215       * Helper for getting pluginfile name.
 216       *
 217       * @param stdClass|null $course course object
 218       * @param stdClass|null $cm course module object
 219       * @param context $context context object
 220       * @param array $args extra arguments
 221       *
 222       * @return string|null
 223       */
 224      public static function get_plugin_filename(?stdClass $course, ?stdClass $cm, context $context, array $args): ?string {
 225          global $DB;
 226          if ($context->contextlevel != CONTEXT_SYSTEM) {
 227              // Plugin has a file to use as default in general setting.
 228              // The difference with the standard bigbluebuttonbn_pluginfile_filename() are.
 229              // - Context is system, so we don't need to check the cmid in this case.
 230              // - The area is "presentationdefault_cache".
 231              if (!$DB->get_record('bigbluebuttonbn', ['id' => $cm->instance])) {
 232                  return null;
 233              }
 234          }
 235          // Plugin has a file to use as default in general setting.
 236          // The difference with the standard bigbluebuttonbn_pluginfile_filename() are.
 237          // - Context is system, so we don't need to check the cmid in this case.
 238          // - The area is "presentationdefault_cache".
 239          if (count($args) > 1) {
 240              $id = 0;
 241              if ($cm) {
 242                  $instance = instance::get_from_cmid($cm->id);
 243                  $id = $instance->get_instance_id();
 244              }
 245              $actualnonce = self::get_nonce($id);
 246              return ($args['0'] == $actualnonce) ? $args['1'] : null;
 247  
 248          }
 249          if (!empty($course)) {
 250              require_course_login($course, true, $cm, true, true);
 251          } else {
 252              require_login(null, true, $cm, true, true);
 253          }
 254          if (!has_capability('mod/bigbluebuttonbn:join', $context)) {
 255              return null;
 256          }
 257          return implode('/', $args);
 258      }
 259  
 260      /**
 261       * Helper generates a salt used for the preuploaded presentation callback url.
 262       *
 263       * @param int $id
 264       * @return int
 265       */
 266      protected static function get_nonce(int $id): int {
 267          $cache = static::get_nonce_cache();
 268          $pnoncekey = sha1($id);
 269          $existingnoncedata = $cache->get($pnoncekey);
 270          if ($existingnoncedata) {
 271              if ($existingnoncedata->counter > 0) {
 272                  $existingnoncedata->counter--;
 273                  $cache->set($pnoncekey, $existingnoncedata);
 274                  return $existingnoncedata->nonce;
 275              }
 276          }
 277          // The item id was adapted for granting public access to the presentation once in order to allow BigBlueButton to gather
 278          // the file once.
 279          return static::generate_nonce($id);
 280      }
 281  
 282      /**
 283       * Generate a nonce and store it in the cache
 284       *
 285       * @param int $id
 286       * @return int
 287       */
 288      protected static function generate_nonce($id): int {
 289          $cache = static::get_nonce_cache();
 290          $pnoncekey = sha1($id);
 291          // The item id was adapted for granting public access to the presentation once in order to allow BigBlueButton to gather
 292          // the file once.
 293          $pnoncevalue = ((int) microtime()) + mt_rand();
 294          $cache->set($pnoncekey, (object) ['nonce' => $pnoncevalue, 'counter' => 2]);
 295          return $pnoncevalue;
 296      }
 297  
 298      /**
 299       * Get cache for nonce
 300       *
 301       * @return \cache_application|\cache_session|cache_store
 302       */
 303      private static function get_nonce_cache() {
 304          return cache::make_from_params(
 305              cache_store::MODE_APPLICATION,
 306              'mod_bigbluebuttonbn',
 307              'presentation_cache'
 308          );
 309      }
 310  
 311      /**
 312       * Returns an array of file areas.
 313       *
 314       * @return array a list of available file areas
 315       *
 316       */
 317      protected static function get_file_areas(): array {
 318          $areas = [];
 319          $areas['presentation'] = get_string('mod_form_block_presentation', 'bigbluebuttonbn');
 320          $areas['presentationdefault'] = get_string('mod_form_block_presentation_default', 'bigbluebuttonbn');
 321          return $areas;
 322      }
 323  
 324  }