[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/lib/ -> uploadlib.php (source)

   1  <?php
   2  
   3  // This file is part of Moodle - http://moodle.org/
   4  //
   5  // Moodle is free software: you can redistribute it and/or modify
   6  // it under the terms of the GNU General Public License as published by
   7  // the Free Software Foundation, either version 3 of the License, or
   8  // (at your option) any later version.
   9  //
  10  // Moodle is distributed in the hope that it will be useful,
  11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13  // GNU General Public License for more details.
  14  //
  15  // You should have received a copy of the GNU General Public License
  16  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  17  
  18  /**
  19   * uploadlib.php - This class handles all aspects of fileuploading
  20   *
  21   * @package    core
  22   * @subpackage file
  23   * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
  24   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  /**
  30   * This class handles all aspects of fileuploading
  31   *
  32   * @deprecated since 2.7 - use new file pickers instead
  33   *
  34   * @package   moodlecore
  35   * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
  36   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  class upload_manager {
  39  
  40     /**
  41      * Array to hold local copies of stuff in $_FILES
  42      * @var array $files
  43      */
  44      var $files;
  45     /**
  46      * Holds all configuration stuff
  47      * @var array $config
  48      */
  49      var $config;
  50     /**
  51      * Keep track of if we're ok
  52      * (errors for each file are kept in $files['whatever']['uploadlog']
  53      * @var boolean $status
  54      */
  55      var $status;
  56     /**
  57      * The course this file has been uploaded for. {@link $COURSE}
  58      * (for logging and virus notifications)
  59      * @var course $course
  60      */
  61      var $course;
  62     /**
  63      * If we're only getting one file.
  64      * (for logging and virus notifications)
  65      * @var string $inputname
  66      */
  67      var $inputname;
  68     /**
  69      * If we're given silent=true in the constructor, this gets built
  70      * up to hold info about the process.
  71      * @var string $notify
  72      */
  73      var $notify;
  74  
  75      /**
  76       * Constructor, sets up configuration stuff so we know how to act.
  77       *
  78       * Note: destination not taken as parameter as some modules want to use the insertid in the path and we need to check the other stuff first.
  79       *
  80       * @uses $CFG
  81       * @param string $inputname If this is given the upload manager will only process the file in $_FILES with this name.
  82       * @param boolean $deleteothers Whether to delete other files in the destination directory (optional, defaults to false)
  83       * @param boolean $handlecollisions Whether to use {@link handle_filename_collision()} or not. (optional, defaults to false)
  84       * @param course $course The course the files are being uploaded for (for logging and virus notifications) {@link $COURSE}
  85       * @param boolean $recoverifmultiple If we come across a virus, or if a file doesn't validate or whatever, do we continue? optional, defaults to true.
  86       * @param int $modbytes Max bytes for this module - this and $course->maxbytes are used to get the maxbytes from {@link get_max_upload_file_size()}.
  87       * @param boolean $silent Whether to notify errors or not.
  88       * @param boolean $allownull Whether we care if there's no file when we've set the input name.
  89       * @param boolean $allownullmultiple Whether we care if there's no files AT ALL  when we've got multiples. This won't complain if we have file 1 and file 3 but not file 2, only for NO FILES AT ALL.
  90       */
  91      function __construct($inputname='', $deleteothers=false, $handlecollisions=false, $course=null, $recoverifmultiple=false, $modbytes=0, $silent=false, $allownull=false, $allownullmultiple=true) {
  92          global $CFG, $SITE;
  93  
  94          debugging('upload_manager class is deprecated, use new file picker instead', DEBUG_DEVELOPER);
  95  
  96          if (empty($course->id)) {
  97              $course = $SITE;
  98          }
  99  
 100          $this->config = new stdClass();
 101          $this->config->deleteothers = $deleteothers;
 102          $this->config->handlecollisions = $handlecollisions;
 103          $this->config->recoverifmultiple = $recoverifmultiple;
 104          $this->config->maxbytes = get_max_upload_file_size($CFG->maxbytes, $course->maxbytes, $modbytes);
 105          $this->config->silent = $silent;
 106          $this->config->allownull = $allownull;
 107          $this->files = array();
 108          $this->status = false;
 109          $this->course = $course;
 110          $this->inputname = $inputname;
 111          if (empty($this->inputname)) {
 112              $this->config->allownull = $allownullmultiple;
 113          }
 114      }
 115  
 116      /**
 117       * Gets all entries out of $_FILES and stores them locally in $files and then
 118       * checks each one against {@link get_max_upload_file_size()} and calls {@link cleanfilename()}
 119       * and scans them for viruses etc.
 120       * @uses $CFG
 121       * @uses $_FILES
 122       * @return boolean
 123       */
 124      function preprocess_files() {
 125          global $CFG, $OUTPUT;
 126  
 127          foreach ($_FILES as $name => $file) {
 128              $this->status = true; // only set it to true here so that we can check if this function has been called.
 129              if (empty($this->inputname) || $name == $this->inputname) { // if we have input name, only process if it matches.
 130                  $file['originalname'] = $file['name']; // do this first for the log.
 131                  $this->files[$name] = $file; // put it in first so we can get uploadlog out in print_upload_log.
 132                  $this->files[$name]['uploadlog'] = ''; // initialize error log
 133                  $this->status = $this->validate_file($this->files[$name]); // default to only allowing empty on multiple uploads.
 134                  if (!$this->status && ($this->files[$name]['error'] == 0 || $this->files[$name]['error'] == 4) && ($this->config->allownull || empty($this->inputname))) {
 135                      // this shouldn't cause everything to stop.. modules should be responsible for knowing which if any are compulsory.
 136                      continue;
 137                  }
 138                  if ($this->status && !empty($CFG->runclamonupload)) {
 139                      $this->status = clam_scan_moodle_file($this->files[$name],$this->course);
 140                  }
 141                  if (!$this->status) {
 142                      if (!$this->config->recoverifmultiple && count($this->files) > 1) {
 143                          $a = new stdClass();
 144                          $a->name    = $this->files[$name]['originalname'];
 145                          $a->problem = $this->files[$name]['uploadlog'];
 146                          if (!$this->config->silent) {
 147                              echo $OUTPUT->notification(get_string('uploadfailednotrecovering','moodle',$a));
 148                          }
 149                          else {
 150                              $this->notify .= '<br />'. get_string('uploadfailednotrecovering','moodle',$a);
 151                          }
 152                          $this->status = false;
 153                          return false;
 154  
 155                      } else if (count($this->files) == 1) {
 156  
 157                          if (!$this->config->silent and !$this->config->allownull) {
 158                              echo $OUTPUT->notification($this->files[$name]['uploadlog']);
 159                          } else {
 160                              $this->notify .= '<br />'. $this->files[$name]['uploadlog'];
 161                          }
 162                          $this->status = false;
 163                          return false;
 164                      }
 165                  }
 166                  else {
 167                      $newname = clean_filename($this->files[$name]['name']);
 168                      if ($newname != $this->files[$name]['name']) {
 169                          $a = new stdClass();
 170                          $a->oldname = $this->files[$name]['name'];
 171                          $a->newname = $newname;
 172                          $this->files[$name]['uploadlog'] .= get_string('uploadrenamedchars','moodle', $a);
 173                      }
 174                      $this->files[$name]['name'] = $newname;
 175                      $this->files[$name]['clear'] = true; // ok to save.
 176                      $this->config->somethingtosave = true;
 177                  }
 178              }
 179          }
 180          if (!is_array($_FILES) || count($_FILES) == 0) {
 181              return $this->config->allownull;
 182          }
 183          $this->status = true;
 184          return true; // if we've got this far it means that we're recovering so we want status to be ok.
 185      }
 186  
 187      /**
 188       * Validates a single file entry from _FILES
 189       *
 190       * @param object $file The entry from _FILES to validate
 191       * @return boolean True if ok.
 192       */
 193      function validate_file(&$file) {
 194          if (empty($file)) {
 195              return false;
 196          }
 197          if (!is_uploaded_file($file['tmp_name']) || $file['size'] == 0) {
 198              $file['uploadlog'] .= "\n".$this->get_file_upload_error($file);
 199              return false;
 200          }
 201          if ($file['size'] > $this->config->maxbytes) {
 202              $file['uploadlog'] .= "\n". get_string('uploadedfiletoobig', 'moodle', $this->config->maxbytes);
 203              return false;
 204          }
 205          return true;
 206      }
 207  
 208      /**
 209       * Moves all the files to the destination directory.
 210       *
 211       * @uses $CFG
 212       * @uses $USER
 213       * @param string $destination The destination directory.
 214       * @return boolean status;
 215       */
 216      function save_files($destination) {
 217          global $CFG, $USER, $OUTPUT;
 218  
 219          if (!$this->status) { // preprocess_files hasn't been run
 220              $this->preprocess_files();
 221          }
 222  
 223          // if there are no files, bail before we create an empty directory.
 224          if (empty($this->config->somethingtosave)) {
 225              return true;
 226          }
 227  
 228          $savedsomething = false;
 229  
 230          if ($this->status) {
 231              if (!(strpos($destination, $CFG->dataroot) === false)) {
 232                  // take it out for giving to make_upload_directory
 233                  $destination = substr($destination, strlen($CFG->dataroot)+1);
 234              }
 235  
 236              if ($destination{strlen($destination)-1} == '/') { // strip off a trailing / if we have one
 237                  $destination = substr($destination, 0, -1);
 238              }
 239  
 240              if (!make_upload_directory($destination, true)) {
 241                  $this->status = false;
 242                  return false;
 243              }
 244  
 245              $destination = $CFG->dataroot .'/'. $destination; // now add it back in so we have a full path
 246  
 247              $exceptions = array(); //need this later if we're deleting other files.
 248  
 249              foreach (array_keys($this->files) as $i) {
 250  
 251                  if (!$this->files[$i]['clear']) {
 252                      // not ok to save
 253                      continue;
 254                  }
 255  
 256                  if ($this->config->handlecollisions) {
 257                      $this->handle_filename_collision($destination, $this->files[$i]);
 258                  }
 259                  if (move_uploaded_file($this->files[$i]['tmp_name'], $destination.'/'.$this->files[$i]['name'])) {
 260                      chmod($destination .'/'. $this->files[$i]['name'], $CFG->directorypermissions);
 261                      $this->files[$i]['fullpath'] = $destination.'/'.$this->files[$i]['name'];
 262                      $this->files[$i]['uploadlog'] .= "\n".get_string('uploadedfile');
 263                      $this->files[$i]['saved'] = true;
 264                      $exceptions[] = $this->files[$i]['name'];
 265                      $savedsomething=true;
 266                  }
 267              }
 268              if ($savedsomething && $this->config->deleteothers) {
 269                  $this->delete_other_files($destination, $exceptions);
 270              }
 271          }
 272          if (empty($savedsomething)) {
 273              $this->status = false;
 274              if ((empty($this->config->allownull) && !empty($this->inputname)) || (empty($this->inputname) && empty($this->config->allownullmultiple))) {
 275                  echo $OUTPUT->notification(get_string('uploadnofilefound'));
 276              }
 277              return false;
 278          }
 279          return $this->status;
 280      }
 281  
 282      /**
 283       * Wrapper function that calls {@link preprocess_files()} and {@link viruscheck_files()} and then {@link save_files()}
 284       * Modules that require the insert id in the filepath should not use this and call these functions seperately in the required order.
 285       * @parameter string $destination Where to save the uploaded files to.
 286       * @return boolean
 287       */
 288      function process_file_uploads($destination) {
 289          if ($this->preprocess_files()) {
 290              return $this->save_files($destination);
 291          }
 292          return false;
 293      }
 294  
 295      /**
 296       * Deletes all the files in a given directory except for the files in $exceptions (full paths)
 297       *
 298       * @param string $destination The directory to clean up.
 299       * @param array $exceptions Full paths of files to KEEP.
 300       */
 301      function delete_other_files($destination, $exceptions=null) {
 302          global $OUTPUT;
 303          $deletedsomething = false;
 304          if ($filestodel = get_directory_list($destination)) {
 305              foreach ($filestodel as $file) {
 306                  if (!is_array($exceptions) || !in_array($file, $exceptions)) {
 307                      unlink($destination .'/'. $file);
 308                      $deletedsomething = true;
 309                  }
 310              }
 311          }
 312          if ($deletedsomething) {
 313              if (!$this->config->silent) {
 314                  echo $OUTPUT->notification(get_string('uploadoldfilesdeleted'));
 315              }
 316              else {
 317                  $this->notify .= '<br />'. get_string('uploadoldfilesdeleted');
 318              }
 319          }
 320      }
 321  
 322      /**
 323       * Handles filename collisions - if the desired filename exists it will rename it according to the pattern in $format
 324       * @param string $destination Destination directory (to check existing files against)
 325       * @param object $file Passed in by reference. The current file from $files we're processing.
 326       * @return void - modifies &$file parameter.
 327       */
 328      function handle_filename_collision($destination, &$file) {
 329          if (!file_exists($destination .'/'. $file['name'])) {
 330              return;
 331          }
 332  
 333          $parts     = explode('.', $file['name']);
 334          if (count($parts) > 1) {
 335              $extension = '.'.array_pop($parts);
 336              $name      = implode('.', $parts);
 337          } else {
 338              $extension = '';
 339              $name      = $file['name'];
 340          }
 341  
 342          $current = 0;
 343          if (preg_match('/^(.*)_(\d*)$/s', $name, $matches)) {
 344              $name    = $matches[1];
 345              $current = (int)$matches[2];
 346          }
 347          $i = $current + 1;
 348  
 349          while (!$this->check_before_renaming($destination, $name.'_'.$i.$extension, $file)) {
 350              $i++;
 351          }
 352          $a = new stdClass();
 353          $a->oldname = $file['name'];
 354          $file['name'] = $name.'_'.$i.$extension;
 355          $a->newname = $file['name'];
 356          $file['uploadlog'] .= "\n". get_string('uploadrenamedcollision','moodle', $a);
 357      }
 358  
 359      /**
 360       * This function checks a potential filename against what's on the filesystem already and what's been saved already.
 361       * @param string $destination Destination directory (to check existing files against)
 362       * @param string $nametocheck The filename to be compared.
 363       * @param object $file The current file from $files we're processing.
 364       * return boolean
 365       */
 366      function check_before_renaming($destination, $nametocheck, $file) {
 367          if (!file_exists($destination .'/'. $nametocheck)) {
 368              return true;
 369          }
 370          if ($this->config->deleteothers) {
 371              foreach ($this->files as $tocheck) {
 372                  // if we're deleting files anyway, it's not THIS file and we care about it and it has the same name and has already been saved..
 373                  if ($file['tmp_name'] != $tocheck['tmp_name'] && $tocheck['clear'] && $nametocheck == $tocheck['name'] && $tocheck['saved']) {
 374                      $collision = true;
 375                  }
 376              }
 377              if (!$collision) {
 378                  return true;
 379              }
 380          }
 381          return false;
 382      }
 383  
 384      /**
 385       * ?
 386       *
 387       * @param object $file Passed in by reference. The current file from $files we're processing.
 388       * @return string
 389       */
 390      function get_file_upload_error(&$file) {
 391  
 392          switch ($file['error']) {
 393          case 0: // UPLOAD_ERR_OK
 394              if ($file['size'] > 0) {
 395                  $errmessage = get_string('uploadproblem', $file['name']);
 396              } else {
 397                  $errmessage = get_string('uploadnofilefound'); /// probably a dud file name
 398              }
 399              break;
 400  
 401          case 1: // UPLOAD_ERR_INI_SIZE
 402              $errmessage = get_string('uploadserverlimit');
 403              break;
 404  
 405          case 2: // UPLOAD_ERR_FORM_SIZE
 406              $errmessage = get_string('uploadformlimit');
 407              break;
 408  
 409          case 3: // UPLOAD_ERR_PARTIAL
 410              $errmessage = get_string('uploadpartialfile');
 411              break;
 412  
 413          case 4: // UPLOAD_ERR_NO_FILE
 414              $errmessage = get_string('uploadnofilefound');
 415              break;
 416  
 417          // Note: there is no error with a value of 5
 418  
 419          case 6: // UPLOAD_ERR_NO_TMP_DIR
 420              $errmessage = get_string('uploadnotempdir');
 421              break;
 422  
 423          case 7: // UPLOAD_ERR_CANT_WRITE
 424              $errmessage = get_string('uploadcantwrite');
 425              break;
 426  
 427          case 8: // UPLOAD_ERR_EXTENSION
 428              $errmessage = get_string('uploadextension');
 429              break;
 430  
 431          default:
 432              $errmessage = get_string('uploadproblem', $file['name']);
 433          }
 434          return $errmessage;
 435      }
 436  
 437      /**
 438       * prints a log of everything that happened (of interest) to each file in _FILES
 439       * @param $return - optional, defaults to false (log is echoed)
 440       */
 441      function print_upload_log($return=false,$skipemptyifmultiple=false) {
 442          $str = '';
 443          foreach (array_keys($this->files) as $i => $key) {
 444              if (count($this->files) > 1 && !empty($skipemptyifmultiple) && $this->files[$key]['error'] == 4) {
 445                  continue;
 446              }
 447              $str .= '<strong>'. get_string('uploadfilelog', 'moodle', $i+1) .' '
 448                  .((!empty($this->files[$key]['originalname'])) ? '('.$this->files[$key]['originalname'].')' : '')
 449                  .'</strong> :'. nl2br($this->files[$key]['uploadlog']) .'<br />';
 450          }
 451          if ($return) {
 452              return $str;
 453          }
 454          echo $str;
 455      }
 456  
 457      /**
 458       * If we're only handling one file (if inputname was given in the constructor) this will return the (possibly changed) filename of the file.
 459       @return boolean
 460       */
 461      function get_new_filename() {
 462          if (!empty($this->inputname) and count($this->files) == 1 and $this->files[$this->inputname]['error'] != 4) {
 463              return $this->files[$this->inputname]['name'];
 464          }
 465          return false;
 466      }
 467  
 468      /**
 469       * If we're only handling one file (if input name was given in the constructor) this will return the full path to the saved file.
 470       * @return boolean
 471       */
 472      function get_new_filepath() {
 473          if (!empty($this->inputname) and count($this->files) == 1 and $this->files[$this->inputname]['error'] != 4) {
 474              return $this->files[$this->inputname]['fullpath'];
 475          }
 476          return false;
 477      }
 478  
 479      /**
 480       * If we're only handling one file (if inputname was given in the constructor) this will return the ORIGINAL filename of the file.
 481       * @return boolean
 482       */
 483      function get_original_filename() {
 484          if (!empty($this->inputname) and count($this->files) == 1 and $this->files[$this->inputname]['error'] != 4) {
 485              return $this->files[$this->inputname]['originalname'];
 486          }
 487          return false;
 488      }
 489  
 490      /**
 491       * This function returns any errors wrapped up in red.
 492       * @return string
 493       */
 494      function get_errors() {
 495          if (!empty($this->notify)) {
 496              return '<p class="notifyproblem">'. $this->notify .'</p>';
 497          } else {
 498              return null;
 499          }
 500      }
 501  }
 502  
 503  /**************************************************************************************
 504  THESE FUNCTIONS ARE OUTSIDE THE CLASS BECAUSE THEY NEED TO BE CALLED FROM OTHER PLACES.
 505  FOR EXAMPLE CLAM_HANDLE_INFECTED_FILE AND CLAM_REPLACE_INFECTED_FILE USED FROM CRON
 506  UPLOAD_PRINT_FORM_FRAGMENT DOESN'T REALLY BELONG IN THE CLASS BUT CERTAINLY IN THIS FILE
 507  ***************************************************************************************/
 508  
 509  /**
 510   * Deals with an infected file - either moves it to a quarantinedir
 511   * (specified in CFG->quarantinedir) or deletes it.
 512   *
 513   * If moving it fails, it deletes it.
 514   *
 515   * @deprecated since 2.7 - to be removed together with the upload_manager above
 516   *
 517   * @param string $file Full path to the file
 518   * @param int $userid If not used, defaults to $USER->id (there in case called from cron)
 519   * @param boolean $basiconly Admin level reporting or user level reporting.
 520   * @return string Details of what the function did.
 521   */
 522  function clam_handle_infected_file($file, $userid=0, $basiconly=false) {
 523      global $CFG, $USER;
 524  
 525      debugging('clam_handle_infected_file() is not supposed to be used, the files are now scanned in file picker', DEBUG_DEVELOPER);
 526  
 527      if ($USER && !$userid) {
 528          $userid = $USER->id;
 529      }
 530      $delete = true;
 531      $notice = '';
 532      if (file_exists($CFG->quarantinedir) && is_dir($CFG->quarantinedir) && is_writable($CFG->quarantinedir)) {
 533          $now = date('YmdHis');
 534          if (rename($file, $CFG->quarantinedir .'/'. $now .'-user-'. $userid .'-infected')) {
 535              $delete = false;
 536              if ($basiconly) {
 537                  $notice .= "\n". get_string('clammovedfilebasic');
 538              }
 539              else {
 540                  $notice .= "\n". get_string('clammovedfile', 'moodle', $CFG->quarantinedir.'/'. $now .'-user-'. $userid .'-infected');
 541              }
 542          }
 543          else {
 544              if ($basiconly) {
 545                  $notice .= "\n". get_string('clamdeletedfile');
 546              }
 547              else {
 548                  $notice .= "\n". get_string('clamquarantinedirfailed', 'moodle', $CFG->quarantinedir);
 549              }
 550          }
 551      }
 552      else {
 553          if ($basiconly) {
 554              $notice .= "\n". get_string('clamdeletedfile');
 555          }
 556          else {
 557              $notice .= "\n". get_string('clamquarantinedirfailed', 'moodle', $CFG->quarantinedir);
 558          }
 559      }
 560      if ($delete) {
 561          if (unlink($file)) {
 562              $notice .= "\n". get_string('clamdeletedfile');
 563          }
 564          else {
 565              if ($basiconly) {
 566                  // still tell the user the file has been deleted. this is only for admins.
 567                  $notice .= "\n". get_string('clamdeletedfile');
 568              }
 569              else {
 570                  $notice .= "\n". get_string('clamdeletedfilefailed');
 571              }
 572          }
 573      }
 574      return $notice;
 575  }
 576  
 577  /**
 578   * If $CFG->runclamonupload is set, we scan a given file. (called from {@link preprocess_files()})
 579   *
 580   * @deprecated since 2.7 - to be removed together with the upload_manager above
 581   *
 582   * @param mixed $file The file to scan from $files. or an absolute path to a file.
 583   * @param stdClass $course
 584   * @return int 1 if good, 0 if something goes wrong (opposite from actual error code from clam)
 585   */
 586  function clam_scan_moodle_file(&$file, $course) {
 587      global $CFG, $USER;
 588  
 589      debugging('clam_scan_moodle_file() is not supposed to be used, the files are now scanned in file picker', DEBUG_DEVELOPER);
 590  
 591      if (is_array($file) && is_uploaded_file($file['tmp_name'])) { // it's from $_FILES
 592          $appendlog = true;
 593          $fullpath = $file['tmp_name'];
 594      }
 595      else if (file_exists($file)) { // it's a path to somewhere on the filesystem!
 596          $fullpath = $file;
 597      }
 598      else {
 599          return false; // erm, what is this supposed to be then, huh?
 600      }
 601  
 602      $CFG->pathtoclam = trim($CFG->pathtoclam);
 603  
 604      if (!$CFG->pathtoclam || !file_exists($CFG->pathtoclam) || !is_executable($CFG->pathtoclam)) {
 605          $newreturn = 1;
 606          $notice = get_string('clamlost', 'moodle', $CFG->pathtoclam);
 607          if ($CFG->clamfailureonupload == 'actlikevirus') {
 608              $notice .= "\n". get_string('clamlostandactinglikevirus');
 609              $notice .= "\n". clam_handle_infected_file($fullpath);
 610              $newreturn = false;
 611          }
 612          clam_message_admins($notice);
 613          if ($appendlog) {
 614              $file['uploadlog'] .= "\n". get_string('clambroken');
 615              $file['clam'] = 1;
 616          }
 617          return $newreturn; // return 1 if we're allowing clam failures
 618      }
 619  
 620      $cmd = $CFG->pathtoclam .' '. $fullpath ." 2>&1";
 621  
 622      // before we do anything we need to change perms so that clamscan can read the file (clamdscan won't work otherwise)
 623      chmod($fullpath, $CFG->directorypermissions);
 624  
 625      exec($cmd, $output, $return);
 626  
 627  
 628      switch ($return) {
 629      case 0: // glee! we're ok.
 630          return 1; // translate clam return code into reasonable return code consistent with everything else.
 631      case 1:  // bad wicked evil, we have a virus.
 632          $info = new stdClass();
 633          if (!empty($course)) {
 634              $info->course = format_string($course->fullname, true, array('context' => context_course::instance($course->id)));
 635          }
 636          else {
 637              $info->course = 'No course';
 638          }
 639          $info->user = fullname($USER);
 640          $notice = get_string('virusfound', 'moodle', $info);
 641          $notice .= "\n\n". implode("\n", $output);
 642          $notice .= "\n\n". clam_handle_infected_file($fullpath);
 643          clam_message_admins($notice);
 644          if ($appendlog) {
 645              $info->filename = $file['originalname'];
 646              $file['uploadlog'] .= "\n". get_string('virusfounduser', 'moodle', $info);
 647              $file['virus'] = 1;
 648          }
 649          return false; // in this case, 0 means bad.
 650      default:
 651          // error - clam failed to run or something went wrong
 652          $notice = get_string('clamfailed', 'moodle', get_clam_error_code($return));
 653          $notice .= "\n\n". implode("\n", $output);
 654          $newreturn = true;
 655          if ($CFG->clamfailureonupload == 'actlikevirus') {
 656              $notice .= "\n". clam_handle_infected_file($fullpath);
 657              $newreturn = false;
 658          }
 659          clam_message_admins($notice);
 660          if ($appendlog) {
 661              $file['uploadlog'] .= "\n". get_string('clambroken');
 662              $file['clam'] = 1;
 663          }
 664          return $newreturn; // return 1 if we're allowing failures.
 665      }
 666  }
 667  
 668  /**
 669   * Emails admins about a clam outcome
 670   *
 671   * @param string $notice The body of the email to be sent.
 672   */
 673  function clam_message_admins($notice) {
 674  
 675      $site = get_site();
 676  
 677      $subject = get_string('clamemailsubject', 'moodle', format_string($site->fullname));
 678      $admins = get_admins();
 679      foreach ($admins as $admin) {
 680          $eventdata = new stdClass();
 681          $eventdata->component         = 'moodle';
 682          $eventdata->name              = 'errors';
 683          $eventdata->userfrom          = get_admin();
 684          $eventdata->userto            = $admin;
 685          $eventdata->subject           = $subject;
 686          $eventdata->fullmessage       = $notice;
 687          $eventdata->fullmessageformat = FORMAT_PLAIN;
 688          $eventdata->fullmessagehtml   = '';
 689          $eventdata->smallmessage      = '';
 690          message_send($eventdata);
 691      }
 692  }
 693  
 694  /**
 695   * Returns the string equivalent of a numeric clam error code
 696   *
 697   * @param int $returncode The numeric error code in question.
 698   * @return string The definition of the error code
 699   */
 700  function get_clam_error_code($returncode) {
 701      $returncodes = array();
 702      $returncodes[0] = 'No virus found.';
 703      $returncodes[1] = 'Virus(es) found.';
 704      $returncodes[2] = ' An error occured'; // specific to clamdscan
 705      // all after here are specific to clamscan
 706      $returncodes[40] = 'Unknown option passed.';
 707      $returncodes[50] = 'Database initialization error.';
 708      $returncodes[52] = 'Not supported file type.';
 709      $returncodes[53] = 'Can\'t open directory.';
 710      $returncodes[54] = 'Can\'t open file. (ofm)';
 711      $returncodes[55] = 'Error reading file. (ofm)';
 712      $returncodes[56] = 'Can\'t stat input file / directory.';
 713      $returncodes[57] = 'Can\'t get absolute path name of current working directory.';
 714      $returncodes[58] = 'I/O error, please check your filesystem.';
 715      $returncodes[59] = 'Can\'t get information about current user from /etc/passwd.';
 716      $returncodes[60] = 'Can\'t get information about user \'clamav\' (default name) from /etc/passwd.';
 717      $returncodes[61] = 'Can\'t fork.';
 718      $returncodes[63] = 'Can\'t create temporary files/directories (check permissions).';
 719      $returncodes[64] = 'Can\'t write to temporary directory (please specify another one).';
 720      $returncodes[70] = 'Can\'t allocate and clear memory (calloc).';
 721      $returncodes[71] = 'Can\'t allocate memory (malloc).';
 722      if ($returncodes[$returncode])
 723         return $returncodes[$returncode];
 724      return get_string('clamunknownerror');
 725  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1