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 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [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   * This file contains the definition for the library class for file submission plugin
  19   *
  20   * This class provides all the functionality for the new assign module.
  21   *
  22   * @package assignsubmission_file
  23   * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
  24   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  
  27  use core_external\external_value;
  28  
  29  // File areas for file submission assignment.
  30  define('ASSIGNSUBMISSION_FILE_MAXSUMMARYFILES', 5);
  31  define('ASSIGNSUBMISSION_FILE_FILEAREA', 'submission_files');
  32  
  33  /**
  34   * Library class for file submission plugin extending submission plugin base class
  35   *
  36   * @package   assignsubmission_file
  37   * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
  38   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  39   */
  40  class assign_submission_file extends assign_submission_plugin {
  41  
  42      /**
  43       * Get the name of the file submission plugin
  44       * @return string
  45       */
  46      public function get_name() {
  47          return get_string('file', 'assignsubmission_file');
  48      }
  49  
  50      /**
  51       * Get file submission information from the database
  52       *
  53       * @param int $submissionid
  54       * @return mixed
  55       */
  56      private function get_file_submission($submissionid) {
  57          global $DB;
  58          return $DB->get_record('assignsubmission_file', array('submission'=>$submissionid));
  59      }
  60  
  61      /**
  62       * Get the default setting for file submission plugin
  63       *
  64       * @param MoodleQuickForm $mform The form to add elements to
  65       * @return void
  66       */
  67      public function get_settings(MoodleQuickForm $mform) {
  68          global $CFG, $COURSE;
  69  
  70          if ($this->assignment->has_instance()) {
  71              $defaultmaxfilesubmissions = $this->get_config('maxfilesubmissions');
  72              $defaultmaxsubmissionsizebytes = $this->get_config('maxsubmissionsizebytes');
  73              $defaultfiletypes = $this->get_config('filetypeslist');
  74          } else {
  75              $defaultmaxfilesubmissions = get_config('assignsubmission_file', 'maxfiles');
  76              $defaultmaxsubmissionsizebytes = get_config('assignsubmission_file', 'maxbytes');
  77              $defaultfiletypes = get_config('assignsubmission_file', 'filetypes');
  78          }
  79          $defaultfiletypes = (string)$defaultfiletypes;
  80  
  81          $settings = array();
  82          $options = array();
  83          for ($i = 1; $i <= get_config('assignsubmission_file', 'maxfiles'); $i++) {
  84              $options[$i] = $i;
  85          }
  86  
  87          $name = get_string('maxfilessubmission', 'assignsubmission_file');
  88          $mform->addElement('select', 'assignsubmission_file_maxfiles', $name, $options);
  89          $mform->addHelpButton('assignsubmission_file_maxfiles',
  90                                'maxfilessubmission',
  91                                'assignsubmission_file');
  92          $mform->setDefault('assignsubmission_file_maxfiles', $defaultmaxfilesubmissions);
  93          $mform->hideIf('assignsubmission_file_maxfiles', 'assignsubmission_file_enabled', 'notchecked');
  94  
  95          $choices = get_max_upload_sizes($CFG->maxbytes,
  96                                          $COURSE->maxbytes,
  97                                          get_config('assignsubmission_file', 'maxbytes'));
  98  
  99          $settings[] = array('type' => 'select',
 100                              'name' => 'maxsubmissionsizebytes',
 101                              'description' => get_string('maximumsubmissionsize', 'assignsubmission_file'),
 102                              'options'=> $choices,
 103                              'default'=> $defaultmaxsubmissionsizebytes);
 104  
 105          $name = get_string('maximumsubmissionsize', 'assignsubmission_file');
 106          $mform->addElement('select', 'assignsubmission_file_maxsizebytes', $name, $choices);
 107          $mform->addHelpButton('assignsubmission_file_maxsizebytes',
 108                                'maximumsubmissionsize',
 109                                'assignsubmission_file');
 110          $mform->setDefault('assignsubmission_file_maxsizebytes', $defaultmaxsubmissionsizebytes);
 111          $mform->hideIf('assignsubmission_file_maxsizebytes',
 112                             'assignsubmission_file_enabled',
 113                             'notchecked');
 114  
 115          $name = get_string('acceptedfiletypes', 'assignsubmission_file');
 116          $mform->addElement('filetypes', 'assignsubmission_file_filetypes', $name);
 117          $mform->addHelpButton('assignsubmission_file_filetypes', 'acceptedfiletypes', 'assignsubmission_file');
 118          $mform->setDefault('assignsubmission_file_filetypes', $defaultfiletypes);
 119          $mform->hideIf('assignsubmission_file_filetypes', 'assignsubmission_file_enabled', 'notchecked');
 120      }
 121  
 122      /**
 123       * Save the settings for file submission plugin
 124       *
 125       * @param stdClass $data
 126       * @return bool
 127       */
 128      public function save_settings(stdClass $data) {
 129          $this->set_config('maxfilesubmissions', $data->assignsubmission_file_maxfiles);
 130          $this->set_config('maxsubmissionsizebytes', $data->assignsubmission_file_maxsizebytes);
 131  
 132          if (!empty($data->assignsubmission_file_filetypes)) {
 133              $this->set_config('filetypeslist', $data->assignsubmission_file_filetypes);
 134          } else {
 135              $this->set_config('filetypeslist', '');
 136          }
 137  
 138          return true;
 139      }
 140  
 141      /**
 142       * File format options
 143       *
 144       * @return array
 145       */
 146      private function get_file_options() {
 147          $fileoptions = array('subdirs' => 1,
 148                                  'maxbytes' => $this->get_config('maxsubmissionsizebytes'),
 149                                  'maxfiles' => $this->get_config('maxfilesubmissions'),
 150                                  'accepted_types' => $this->get_configured_typesets(),
 151                                  'return_types' => (FILE_INTERNAL | FILE_CONTROLLED_LINK));
 152          if ($fileoptions['maxbytes'] == 0) {
 153              // Use module default.
 154              $fileoptions['maxbytes'] = get_config('assignsubmission_file', 'maxbytes');
 155          }
 156          return $fileoptions;
 157      }
 158  
 159      /**
 160       * Add elements to submission form
 161       *
 162       * @param mixed $submission stdClass|null
 163       * @param MoodleQuickForm $mform
 164       * @param stdClass $data
 165       * @return bool
 166       */
 167      public function get_form_elements($submission, MoodleQuickForm $mform, stdClass $data) {
 168          global $OUTPUT;
 169  
 170          if ($this->get_config('maxfilesubmissions') <= 0) {
 171              return false;
 172          }
 173  
 174          $fileoptions = $this->get_file_options();
 175          $submissionid = $submission ? $submission->id : 0;
 176  
 177          $data = file_prepare_standard_filemanager($data,
 178                                                    'files',
 179                                                    $fileoptions,
 180                                                    $this->assignment->get_context(),
 181                                                    'assignsubmission_file',
 182                                                    ASSIGNSUBMISSION_FILE_FILEAREA,
 183                                                    $submissionid);
 184          $mform->addElement('filemanager', 'files_filemanager', $this->get_name(), null, $fileoptions);
 185  
 186          return true;
 187      }
 188  
 189      /**
 190       * Count the number of files
 191       *
 192       * @param int $submissionid
 193       * @param string $area
 194       * @return int
 195       */
 196      private function count_files($submissionid, $area) {
 197          $fs = get_file_storage();
 198          $files = $fs->get_area_files($this->assignment->get_context()->id,
 199                                       'assignsubmission_file',
 200                                       $area,
 201                                       $submissionid,
 202                                       'id',
 203                                       false);
 204  
 205          return count($files);
 206      }
 207  
 208      /**
 209       * Save the files and trigger plagiarism plugin, if enabled,
 210       * to scan the uploaded files via events trigger
 211       *
 212       * @param stdClass $submission
 213       * @param stdClass $data
 214       * @return bool
 215       */
 216      public function save(stdClass $submission, stdClass $data) {
 217          global $USER, $DB;
 218  
 219          $fileoptions = $this->get_file_options();
 220  
 221          $data = file_postupdate_standard_filemanager($data,
 222                                                       'files',
 223                                                       $fileoptions,
 224                                                       $this->assignment->get_context(),
 225                                                       'assignsubmission_file',
 226                                                       ASSIGNSUBMISSION_FILE_FILEAREA,
 227                                                       $submission->id);
 228  
 229          $filesubmission = $this->get_file_submission($submission->id);
 230  
 231          // Plagiarism code event trigger when files are uploaded.
 232  
 233          $fs = get_file_storage();
 234          $files = $fs->get_area_files($this->assignment->get_context()->id,
 235                                       'assignsubmission_file',
 236                                       ASSIGNSUBMISSION_FILE_FILEAREA,
 237                                       $submission->id,
 238                                       'id',
 239                                       false);
 240  
 241          $count = $this->count_files($submission->id, ASSIGNSUBMISSION_FILE_FILEAREA);
 242  
 243          $params = array(
 244              'context' => context_module::instance($this->assignment->get_course_module()->id),
 245              'courseid' => $this->assignment->get_course()->id,
 246              'objectid' => $submission->id,
 247              'other' => array(
 248                  'content' => '',
 249                  'pathnamehashes' => array_keys($files)
 250              )
 251          );
 252          if (!empty($submission->userid) && ($submission->userid != $USER->id)) {
 253              $params['relateduserid'] = $submission->userid;
 254          }
 255          if ($this->assignment->is_blind_marking()) {
 256              $params['anonymous'] = 1;
 257          }
 258          $event = \assignsubmission_file\event\assessable_uploaded::create($params);
 259          $event->set_legacy_files($files);
 260          $event->trigger();
 261  
 262          $groupname = null;
 263          $groupid = 0;
 264          // Get the group name as other fields are not transcribed in the logs and this information is important.
 265          if (empty($submission->userid) && !empty($submission->groupid)) {
 266              $groupname = $DB->get_field('groups', 'name', array('id' => $submission->groupid), MUST_EXIST);
 267              $groupid = $submission->groupid;
 268          } else {
 269              $params['relateduserid'] = $submission->userid;
 270          }
 271  
 272          // Unset the objectid and other field from params for use in submission events.
 273          unset($params['objectid']);
 274          unset($params['other']);
 275          $params['other'] = array(
 276              'submissionid' => $submission->id,
 277              'submissionattempt' => $submission->attemptnumber,
 278              'submissionstatus' => $submission->status,
 279              'filesubmissioncount' => $count,
 280              'groupid' => $groupid,
 281              'groupname' => $groupname
 282          );
 283  
 284          if ($filesubmission) {
 285              $filesubmission->numfiles = $this->count_files($submission->id,
 286                                                             ASSIGNSUBMISSION_FILE_FILEAREA);
 287              $updatestatus = $DB->update_record('assignsubmission_file', $filesubmission);
 288              $params['objectid'] = $filesubmission->id;
 289  
 290              $event = \assignsubmission_file\event\submission_updated::create($params);
 291              $event->set_assign($this->assignment);
 292              $event->trigger();
 293              return $updatestatus;
 294          } else {
 295              $filesubmission = new stdClass();
 296              $filesubmission->numfiles = $this->count_files($submission->id,
 297                                                             ASSIGNSUBMISSION_FILE_FILEAREA);
 298              $filesubmission->submission = $submission->id;
 299              $filesubmission->assignment = $this->assignment->get_instance()->id;
 300              $filesubmission->id = $DB->insert_record('assignsubmission_file', $filesubmission);
 301              $params['objectid'] = $filesubmission->id;
 302  
 303              $event = \assignsubmission_file\event\submission_created::create($params);
 304              $event->set_assign($this->assignment);
 305              $event->trigger();
 306              return $filesubmission->id > 0;
 307          }
 308      }
 309  
 310      /**
 311       * Remove files from this submission.
 312       *
 313       * @param stdClass $submission The submission
 314       * @return boolean
 315       */
 316      public function remove(stdClass $submission) {
 317          global $DB;
 318          $fs = get_file_storage();
 319  
 320          $fs->delete_area_files($this->assignment->get_context()->id,
 321                                 'assignsubmission_file',
 322                                 ASSIGNSUBMISSION_FILE_FILEAREA,
 323                                 $submission->id);
 324  
 325          $currentsubmission = $this->get_file_submission($submission->id);
 326          if ($currentsubmission) {
 327              $currentsubmission->numfiles = 0;
 328              $DB->update_record('assignsubmission_file', $currentsubmission);
 329          }
 330  
 331          return true;
 332      }
 333  
 334      /**
 335       * Produce a list of files suitable for export that represent this feedback or submission
 336       *
 337       * @param stdClass $submission The submission
 338       * @param stdClass $user The user record - unused
 339       * @return array - return an array of files indexed by filename
 340       */
 341      public function get_files(stdClass $submission, stdClass $user) {
 342          $result = array();
 343          $fs = get_file_storage();
 344  
 345          $files = $fs->get_area_files($this->assignment->get_context()->id,
 346                                       'assignsubmission_file',
 347                                       ASSIGNSUBMISSION_FILE_FILEAREA,
 348                                       $submission->id,
 349                                       'timemodified',
 350                                       false);
 351  
 352          foreach ($files as $file) {
 353              // Do we return the full folder path or just the file name?
 354              if (isset($submission->exportfullpath) && $submission->exportfullpath == false) {
 355                  $result[$file->get_filename()] = $file;
 356              } else {
 357                  $result[$file->get_filepath().$file->get_filename()] = $file;
 358              }
 359          }
 360          return $result;
 361      }
 362  
 363      /**
 364       * Display the list of files  in the submission status table
 365       *
 366       * @param stdClass $submission
 367       * @param bool $showviewlink Set this to true if the list of files is long
 368       * @return string
 369       */
 370      public function view_summary(stdClass $submission, & $showviewlink) {
 371          $count = $this->count_files($submission->id, ASSIGNSUBMISSION_FILE_FILEAREA);
 372  
 373          // Show we show a link to view all files for this plugin?
 374          $showviewlink = $count > ASSIGNSUBMISSION_FILE_MAXSUMMARYFILES;
 375          if ($count <= ASSIGNSUBMISSION_FILE_MAXSUMMARYFILES) {
 376              return $this->assignment->render_area_files('assignsubmission_file',
 377                                                          ASSIGNSUBMISSION_FILE_FILEAREA,
 378                                                          $submission->id);
 379          } else {
 380              return get_string('countfiles', 'assignsubmission_file', $count);
 381          }
 382      }
 383  
 384      /**
 385       * No full submission view - the summary contains the list of files and that is the whole submission
 386       *
 387       * @param stdClass $submission
 388       * @return string
 389       */
 390      public function view(stdClass $submission) {
 391          return $this->assignment->render_area_files('assignsubmission_file',
 392                                                      ASSIGNSUBMISSION_FILE_FILEAREA,
 393                                                      $submission->id);
 394      }
 395  
 396  
 397  
 398      /**
 399       * Return true if this plugin can upgrade an old Moodle 2.2 assignment of this type
 400       * and version.
 401       *
 402       * @param string $type
 403       * @param int $version
 404       * @return bool True if upgrade is possible
 405       */
 406      public function can_upgrade($type, $version) {
 407  
 408          $uploadsingletype ='uploadsingle';
 409          $uploadtype ='upload';
 410  
 411          if (($type == $uploadsingletype || $type == $uploadtype) && $version >= 2011112900) {
 412              return true;
 413          }
 414          return false;
 415      }
 416  
 417  
 418      /**
 419       * Upgrade the settings from the old assignment
 420       * to the new plugin based one
 421       *
 422       * @param context $oldcontext - the old assignment context
 423       * @param stdClass $oldassignment - the old assignment data record
 424       * @param string $log record log events here
 425       * @return bool Was it a success? (false will trigger rollback)
 426       */
 427      public function upgrade_settings(context $oldcontext, stdClass $oldassignment, & $log) {
 428          global $DB;
 429  
 430          if ($oldassignment->assignmenttype == 'uploadsingle') {
 431              $this->set_config('maxfilesubmissions', 1);
 432              $this->set_config('maxsubmissionsizebytes', $oldassignment->maxbytes);
 433              return true;
 434          } else if ($oldassignment->assignmenttype == 'upload') {
 435              $this->set_config('maxfilesubmissions', $oldassignment->var1);
 436              $this->set_config('maxsubmissionsizebytes', $oldassignment->maxbytes);
 437  
 438              // Advanced file upload uses a different setting to do the same thing.
 439              $DB->set_field('assign',
 440                             'submissiondrafts',
 441                             $oldassignment->var4,
 442                             array('id'=>$this->assignment->get_instance()->id));
 443  
 444              // Convert advanced file upload "hide description before due date" setting.
 445              $alwaysshow = 0;
 446              if (!$oldassignment->var3) {
 447                  $alwaysshow = 1;
 448              }
 449              $DB->set_field('assign',
 450                             'alwaysshowdescription',
 451                             $alwaysshow,
 452                             array('id'=>$this->assignment->get_instance()->id));
 453              return true;
 454          }
 455      }
 456  
 457      /**
 458       * Upgrade the submission from the old assignment to the new one
 459       *
 460       * @param context $oldcontext The context of the old assignment
 461       * @param stdClass $oldassignment The data record for the old oldassignment
 462       * @param stdClass $oldsubmission The data record for the old submission
 463       * @param stdClass $submission The data record for the new submission
 464       * @param string $log Record upgrade messages in the log
 465       * @return bool true or false - false will trigger a rollback
 466       */
 467      public function upgrade(context $oldcontext,
 468                              stdClass $oldassignment,
 469                              stdClass $oldsubmission,
 470                              stdClass $submission,
 471                              & $log) {
 472          global $DB;
 473  
 474          $filesubmission = new stdClass();
 475  
 476          $filesubmission->numfiles = $oldsubmission->numfiles;
 477          $filesubmission->submission = $submission->id;
 478          $filesubmission->assignment = $this->assignment->get_instance()->id;
 479  
 480          if (!$DB->insert_record('assignsubmission_file', $filesubmission) > 0) {
 481              $log .= get_string('couldnotconvertsubmission', 'mod_assign', $submission->userid);
 482              return false;
 483          }
 484  
 485          // Now copy the area files.
 486          $this->assignment->copy_area_files_for_upgrade($oldcontext->id,
 487                                                          'mod_assignment',
 488                                                          'submission',
 489                                                          $oldsubmission->id,
 490                                                          $this->assignment->get_context()->id,
 491                                                          'assignsubmission_file',
 492                                                          ASSIGNSUBMISSION_FILE_FILEAREA,
 493                                                          $submission->id);
 494  
 495          return true;
 496      }
 497  
 498      /**
 499       * The assignment has been deleted - cleanup
 500       *
 501       * @return bool
 502       */
 503      public function delete_instance() {
 504          global $DB;
 505          // Will throw exception on failure.
 506          $DB->delete_records('assignsubmission_file',
 507                              array('assignment'=>$this->assignment->get_instance()->id));
 508  
 509          return true;
 510      }
 511  
 512      /**
 513       * Return true if there are no submission files
 514       * @param stdClass $submission
 515       */
 516      public function is_empty(stdClass $submission) {
 517          return $this->count_files($submission->id, ASSIGNSUBMISSION_FILE_FILEAREA) == 0;
 518      }
 519  
 520      /**
 521       * Determine if a submission is empty
 522       *
 523       * This is distinct from is_empty in that it is intended to be used to
 524       * determine if a submission made before saving is empty.
 525       *
 526       * @param stdClass $data The submission data
 527       * @return bool
 528       */
 529      public function submission_is_empty(stdClass $data) {
 530          global $USER;
 531          $fs = get_file_storage();
 532          // Get a count of all the draft files, excluding any directories.
 533          $files = $fs->get_area_files(context_user::instance($USER->id)->id,
 534                                       'user',
 535                                       'draft',
 536                                       $data->files_filemanager,
 537                                       'id',
 538                                       false);
 539          return count($files) == 0;
 540      }
 541  
 542      /**
 543       * Get file areas returns a list of areas this plugin stores files
 544       * @return array - An array of fileareas (keys) and descriptions (values)
 545       */
 546      public function get_file_areas() {
 547          return array(ASSIGNSUBMISSION_FILE_FILEAREA=>$this->get_name());
 548      }
 549  
 550      /**
 551       * Copy the student's submission from a previous submission. Used when a student opts to base their resubmission
 552       * on the last submission.
 553       * @param stdClass $sourcesubmission
 554       * @param stdClass $destsubmission
 555       */
 556      public function copy_submission(stdClass $sourcesubmission, stdClass $destsubmission) {
 557          global $DB;
 558  
 559          // Copy the files across.
 560          $contextid = $this->assignment->get_context()->id;
 561          $fs = get_file_storage();
 562          $files = $fs->get_area_files($contextid,
 563                                       'assignsubmission_file',
 564                                       ASSIGNSUBMISSION_FILE_FILEAREA,
 565                                       $sourcesubmission->id,
 566                                       'id',
 567                                       false);
 568          foreach ($files as $file) {
 569              $fieldupdates = array('itemid' => $destsubmission->id);
 570              $fs->create_file_from_storedfile($fieldupdates, $file);
 571          }
 572  
 573          // Copy the assignsubmission_file record.
 574          if ($filesubmission = $this->get_file_submission($sourcesubmission->id)) {
 575              unset($filesubmission->id);
 576              $filesubmission->submission = $destsubmission->id;
 577              $DB->insert_record('assignsubmission_file', $filesubmission);
 578          }
 579          return true;
 580      }
 581  
 582      /**
 583       * Return a description of external params suitable for uploading a file submission from a webservice.
 584       *
 585       * @return \core_external\external_description|null
 586       */
 587      public function get_external_parameters() {
 588          return array(
 589              'files_filemanager' => new external_value(
 590                  PARAM_INT,
 591                  'The id of a draft area containing files for this submission.',
 592                  VALUE_OPTIONAL
 593              )
 594          );
 595      }
 596  
 597      /**
 598       * Return the plugin configs for external functions.
 599       *
 600       * @return array the list of settings
 601       * @since Moodle 3.2
 602       */
 603      public function get_config_for_external() {
 604          global $CFG;
 605  
 606          $configs = $this->get_config();
 607  
 608          // Get a size in bytes.
 609          if ($configs->maxsubmissionsizebytes == 0) {
 610              $configs->maxsubmissionsizebytes = get_max_upload_file_size($CFG->maxbytes, $this->assignment->get_course()->maxbytes,
 611                                                                          get_config('assignsubmission_file', 'maxbytes'));
 612          }
 613          return (array) $configs;
 614      }
 615  
 616      /**
 617       * Get the type sets configured for this assignment.
 618       *
 619       * @return array('groupname', 'mime/type', ...)
 620       */
 621      private function get_configured_typesets() {
 622          $typeslist = (string)$this->get_config('filetypeslist');
 623  
 624          $util = new \core_form\filetypes_util();
 625          $sets = $util->normalize_file_types($typeslist);
 626  
 627          return $sets;
 628      }
 629  
 630      /**
 631       * Determine if the plugin allows image file conversion
 632       * @return bool
 633       */
 634      public function allow_image_conversion() {
 635          return true;
 636      }
 637  }