[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/backup/util/helper/ -> backup_cron_helper.class.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   * Utility helper for automated backups run through cron.
  20   *
  21   * @package    core
  22   * @subpackage backup
  23   * @copyright  2010 Sam Hemelryk
  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 is an abstract class with methods that can be called to aid the
  31   * running of automated backups over cron.
  32   */
  33  abstract class backup_cron_automated_helper {
  34  
  35      /** Automated backups are active and ready to run */
  36      const STATE_OK = 0;
  37      /** Automated backups are disabled and will not be run */
  38      const STATE_DISABLED = 1;
  39      /** Automated backups are all ready running! */
  40      const STATE_RUNNING = 2;
  41  
  42      /** Course automated backup completed successfully */
  43      const BACKUP_STATUS_OK = 1;
  44      /** Course automated backup errored */
  45      const BACKUP_STATUS_ERROR = 0;
  46      /** Course automated backup never finished */
  47      const BACKUP_STATUS_UNFINISHED = 2;
  48      /** Course automated backup was skipped */
  49      const BACKUP_STATUS_SKIPPED = 3;
  50      /** Course automated backup had warnings */
  51      const BACKUP_STATUS_WARNING = 4;
  52      /** Course automated backup has yet to be run */
  53      const BACKUP_STATUS_NOTYETRUN = 5;
  54  
  55      /** Run if required by the schedule set in config. Default. **/
  56      const RUN_ON_SCHEDULE = 0;
  57      /** Run immediately. **/
  58      const RUN_IMMEDIATELY = 1;
  59  
  60      const AUTO_BACKUP_DISABLED = 0;
  61      const AUTO_BACKUP_ENABLED = 1;
  62      const AUTO_BACKUP_MANUAL = 2;
  63  
  64      /**
  65       * Runs the automated backups if required
  66       *
  67       * @global moodle_database $DB
  68       */
  69      public static function run_automated_backup($rundirective = self::RUN_ON_SCHEDULE) {
  70          global $CFG, $DB;
  71  
  72          $status = true;
  73          $emailpending = false;
  74          $now = time();
  75          $config = get_config('backup');
  76  
  77          mtrace("Checking automated backup status",'...');
  78          $state = backup_cron_automated_helper::get_automated_backup_state($rundirective);
  79          if ($state === backup_cron_automated_helper::STATE_DISABLED) {
  80              mtrace('INACTIVE');
  81              return $state;
  82          } else if ($state === backup_cron_automated_helper::STATE_RUNNING) {
  83              mtrace('RUNNING');
  84              if ($rundirective == self::RUN_IMMEDIATELY) {
  85                  mtrace('Automated backups are already running. If this script is being run by cron this constitues an error. You will need to increase the time between executions within cron.');
  86              } else {
  87                  mtrace("automated backup are already running. Execution delayed");
  88              }
  89              return $state;
  90          } else {
  91              mtrace('OK');
  92          }
  93          backup_cron_automated_helper::set_state_running();
  94  
  95          mtrace("Getting admin info");
  96          $admin = get_admin();
  97          if (!$admin) {
  98              mtrace("Error: No admin account was found");
  99              $state = false;
 100          }
 101  
 102          if ($status) {
 103              mtrace("Checking courses");
 104              mtrace("Skipping deleted courses", '...');
 105              mtrace(sprintf("%d courses", backup_cron_automated_helper::remove_deleted_courses_from_schedule()));
 106          }
 107  
 108          if ($status) {
 109  
 110              mtrace('Running required automated backups...');
 111              cron_trace_time_and_memory();
 112  
 113              // This could take a while!
 114              core_php_time_limit::raise();
 115              raise_memory_limit(MEMORY_EXTRA);
 116  
 117              $nextstarttime = backup_cron_automated_helper::calculate_next_automated_backup($admin->timezone, $now);
 118              $showtime = "undefined";
 119              if ($nextstarttime > 0) {
 120                  $showtime = date('r', $nextstarttime);
 121              }
 122  
 123              $rs = $DB->get_recordset('course');
 124              foreach ($rs as $course) {
 125                  $backupcourse = $DB->get_record('backup_courses', array('courseid' => $course->id));
 126                  if (!$backupcourse) {
 127                      $backupcourse = new stdClass;
 128                      $backupcourse->courseid = $course->id;
 129                      $backupcourse->laststatus = self::BACKUP_STATUS_NOTYETRUN;
 130                      $DB->insert_record('backup_courses', $backupcourse);
 131                      $backupcourse = $DB->get_record('backup_courses', array('courseid' => $course->id));
 132                  }
 133  
 134                  // The last backup is considered as successful when OK or SKIPPED.
 135                  $lastbackupwassuccessful =  ($backupcourse->laststatus == self::BACKUP_STATUS_SKIPPED ||
 136                                              $backupcourse->laststatus == self::BACKUP_STATUS_OK) && (
 137                                              $backupcourse->laststarttime > 0 && $backupcourse->lastendtime > 0);
 138  
 139                  // Assume that we are not skipping anything.
 140                  $skipped = false;
 141                  $skippedmessage = '';
 142  
 143                  // Check if we are going to be running the backup now.
 144                  $shouldrunnow = (($backupcourse->nextstarttime > 0 && $backupcourse->nextstarttime < $now)
 145                      || $rundirective == self::RUN_IMMEDIATELY);
 146  
 147                  // If config backup_auto_skip_hidden is set to true, skip courses that are not visible.
 148                  if ($shouldrunnow && $config->backup_auto_skip_hidden) {
 149                      $skipped = ($config->backup_auto_skip_hidden && !$course->visible);
 150                      $skippedmessage = 'Not visible';
 151                  }
 152  
 153                  // If config backup_auto_skip_modif_days is set to true, skip courses
 154                  // that have not been modified since the number of days defined.
 155                  if ($shouldrunnow && !$skipped && $lastbackupwassuccessful && $config->backup_auto_skip_modif_days) {
 156                      $timenotmodifsincedays = $now - ($config->backup_auto_skip_modif_days * DAYSECS);
 157                      // Check log if there were any modifications to the course content.
 158                      $logexists = self::is_course_modified($course->id, $timenotmodifsincedays);
 159                      $skipped = ($course->timemodified <= $timenotmodifsincedays && !$logexists);
 160                      $skippedmessage = 'Not modified in the past '.$config->backup_auto_skip_modif_days.' days';
 161                  }
 162  
 163                  // If config backup_auto_skip_modif_prev is set to true, skip courses
 164                  // that have not been modified since previous backup.
 165                  if ($shouldrunnow && !$skipped && $lastbackupwassuccessful && $config->backup_auto_skip_modif_prev) {
 166                      // Check log if there were any modifications to the course content.
 167                      $logexists = self::is_course_modified($course->id, $backupcourse->laststarttime);
 168                      $skipped = ($course->timemodified <= $backupcourse->laststarttime && !$logexists);
 169                      $skippedmessage = 'Not modified since previous backup';
 170                  }
 171  
 172                  // Check if the course is not scheduled to run right now.
 173                  if (!$shouldrunnow) {
 174                      $backupcourse->nextstarttime = $nextstarttime;
 175                      $DB->update_record('backup_courses', $backupcourse);
 176                      mtrace('Skipping ' . $course->fullname . ' (Not scheduled for backup until ' . $showtime . ')');
 177                  } else if ($skipped) { // Must have been skipped for a reason.
 178                      $backupcourse->laststatus = self::BACKUP_STATUS_SKIPPED;
 179                      $backupcourse->nextstarttime = $nextstarttime;
 180                      $DB->update_record('backup_courses', $backupcourse);
 181                      mtrace('Skipping ' . $course->fullname . ' (' . $skippedmessage . ')');
 182                      mtrace('Backup of \'' . $course->fullname . '\' is scheduled on ' . $showtime);
 183                  } else {
 184                      // Backup every non-skipped courses.
 185                      mtrace('Backing up '.$course->fullname.'...');
 186  
 187                      // We have to send an email because we have included at least one backup.
 188                      $emailpending = true;
 189  
 190                      // Only make the backup if laststatus isn't 2-UNFINISHED (uncontrolled error).
 191                      if ($backupcourse->laststatus != self::BACKUP_STATUS_UNFINISHED) {
 192                          // Set laststarttime.
 193                          $starttime = time();
 194  
 195                          $backupcourse->laststarttime = time();
 196                          $backupcourse->laststatus = self::BACKUP_STATUS_UNFINISHED;
 197                          $DB->update_record('backup_courses', $backupcourse);
 198  
 199                          $backupcourse->laststatus = backup_cron_automated_helper::launch_automated_backup($course, $backupcourse->laststarttime, $admin->id);
 200                          $backupcourse->lastendtime = time();
 201                          $backupcourse->nextstarttime = $nextstarttime;
 202  
 203                          $DB->update_record('backup_courses', $backupcourse);
 204  
 205                          if ($backupcourse->laststatus === self::BACKUP_STATUS_OK) {
 206                              // Clean up any excess course backups now that we have
 207                              // taken a successful backup.
 208                              $removedcount = backup_cron_automated_helper::remove_excess_backups($course);
 209                          }
 210                      }
 211  
 212                      mtrace("complete - next execution: $showtime");
 213                  }
 214              }
 215              $rs->close();
 216          }
 217  
 218          //Send email to admin if necessary
 219          if ($emailpending) {
 220              mtrace("Sending email to admin");
 221              $message = "";
 222  
 223              $count = backup_cron_automated_helper::get_backup_status_array();
 224              $haserrors = ($count[self::BACKUP_STATUS_ERROR] != 0 || $count[self::BACKUP_STATUS_UNFINISHED] != 0);
 225  
 226              // Build the message text.
 227              // Summary.
 228              $message .= get_string('summary') . "\n";
 229              $message .= "==================================================\n";
 230              $message .= '  ' . get_string('courses') . '; ' . array_sum($count) . "\n";
 231              $message .= '  ' . get_string('ok') . '; ' . $count[self::BACKUP_STATUS_OK] . "\n";
 232              $message .= '  ' . get_string('skipped') . '; ' . $count[self::BACKUP_STATUS_SKIPPED] . "\n";
 233              $message .= '  ' . get_string('error') . '; ' . $count[self::BACKUP_STATUS_ERROR] . "\n";
 234              $message .= '  ' . get_string('unfinished') . '; ' . $count[self::BACKUP_STATUS_UNFINISHED] . "\n";
 235              $message .= '  ' . get_string('warning') . '; ' . $count[self::BACKUP_STATUS_WARNING] . "\n";
 236              $message .= '  ' . get_string('backupnotyetrun') . '; ' . $count[self::BACKUP_STATUS_NOTYETRUN]."\n\n";
 237  
 238              //Reference
 239              if ($haserrors) {
 240                  $message .= "  ".get_string('backupfailed')."\n\n";
 241                  $dest_url = "$CFG->wwwroot/report/backups/index.php";
 242                  $message .= "  ".get_string('backuptakealook','',$dest_url)."\n\n";
 243                  //Set message priority
 244                  $admin->priority = 1;
 245                  //Reset unfinished to error
 246                  $DB->set_field('backup_courses','laststatus','0', array('laststatus'=>'2'));
 247              } else {
 248                  $message .= "  ".get_string('backupfinished')."\n";
 249              }
 250  
 251              //Build the message subject
 252              $site = get_site();
 253              $prefix = format_string($site->shortname, true, array('context' => context_course::instance(SITEID))).": ";
 254              if ($haserrors) {
 255                  $prefix .= "[".strtoupper(get_string('error'))."] ";
 256              }
 257              $subject = $prefix.get_string('automatedbackupstatus', 'backup');
 258  
 259              //Send the message
 260              $eventdata = new stdClass();
 261              $eventdata->modulename        = 'moodle';
 262              $eventdata->userfrom          = $admin;
 263              $eventdata->userto            = $admin;
 264              $eventdata->subject           = $subject;
 265              $eventdata->fullmessage       = $message;
 266              $eventdata->fullmessageformat = FORMAT_PLAIN;
 267              $eventdata->fullmessagehtml   = '';
 268              $eventdata->smallmessage      = '';
 269  
 270              $eventdata->component         = 'moodle';
 271              $eventdata->name         = 'backup';
 272  
 273              message_send($eventdata);
 274          }
 275  
 276          //Everything is finished stop backup_auto_running
 277          backup_cron_automated_helper::set_state_running(false);
 278  
 279          mtrace('Automated backups complete.');
 280  
 281          return $status;
 282      }
 283  
 284      /**
 285       * Gets the results from the last automated backup that was run based upon
 286       * the statuses of the courses that were looked at.
 287       *
 288       * @global moodle_database $DB
 289       * @return array
 290       */
 291      public static function get_backup_status_array() {
 292          global $DB;
 293  
 294          $result = array(
 295              self::BACKUP_STATUS_ERROR => 0,
 296              self::BACKUP_STATUS_OK => 0,
 297              self::BACKUP_STATUS_UNFINISHED => 0,
 298              self::BACKUP_STATUS_SKIPPED => 0,
 299              self::BACKUP_STATUS_WARNING => 0,
 300              self::BACKUP_STATUS_NOTYETRUN => 0
 301          );
 302  
 303          $statuses = $DB->get_records_sql('SELECT DISTINCT bc.laststatus, COUNT(bc.courseid) AS statuscount FROM {backup_courses} bc GROUP BY bc.laststatus');
 304  
 305          foreach ($statuses as $status) {
 306              if (empty($status->statuscount)) {
 307                  $status->statuscount = 0;
 308              }
 309              $result[(int)$status->laststatus] += $status->statuscount;
 310          }
 311  
 312          return $result;
 313      }
 314  
 315      /**
 316       * Works out the next time the automated backup should be run.
 317       *
 318       * @param mixed $timezone user timezone
 319       * @param int $now timestamp, should not be in the past, most likely time()
 320       * @return int timestamp of the next execution at server time
 321       */
 322      public static function calculate_next_automated_backup($timezone, $now) {
 323  
 324          $result = 0;
 325          $config = get_config('backup');
 326          $autohour = $config->backup_auto_hour;
 327          $automin = $config->backup_auto_minute;
 328  
 329          // Gets the user time relatively to the server time.
 330          $date = usergetdate($now, $timezone);
 331          $usertime = mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year']);
 332          $diff = $now - $usertime;
 333  
 334          // Get number of days (from user's today) to execute backups.
 335          $automateddays = substr($config->backup_auto_weekdays, $date['wday']) . $config->backup_auto_weekdays;
 336          $daysfromnow = strpos($automateddays, "1");
 337  
 338          // Error, there are no days to schedule the backup for.
 339          if ($daysfromnow === false) {
 340              return 0;
 341          }
 342  
 343          // Checks if the date would happen in the future (of the user).
 344          $userresult = mktime($autohour, $automin, 0, $date['mon'], $date['mday'] + $daysfromnow, $date['year']);
 345          if ($userresult <= $usertime) {
 346              // If not, we skip the first scheduled day, that should fix it.
 347              $daysfromnow = strpos($automateddays, "1", 1);
 348              $userresult = mktime($autohour, $automin, 0, $date['mon'], $date['mday'] + $daysfromnow, $date['year']);
 349          }
 350  
 351          // Now we generate the time relative to the server.
 352          $result = $userresult + $diff;
 353  
 354          // If that time is past, call the function recursively to obtain the next valid day.
 355          if ($result <= $now) {
 356              // Checking time() in here works, but makes PHPUnit Tests extremely hard to predict.
 357              // $now should never be earlier than time() anyway...
 358              $result = self::calculate_next_automated_backup($timezone, $now + DAYSECS);
 359          }
 360  
 361          return $result;
 362      }
 363  
 364      /**
 365       * Launches a automated backup routine for the given course
 366       *
 367       * @param stdClass $course
 368       * @param int $starttime
 369       * @param int $userid
 370       * @return bool
 371       */
 372      public static function launch_automated_backup($course, $starttime, $userid) {
 373  
 374          $outcome = self::BACKUP_STATUS_OK;
 375          $config = get_config('backup');
 376          $dir = $config->backup_auto_destination;
 377          $storage = (int)$config->backup_auto_storage;
 378  
 379          $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO,
 380                  backup::MODE_AUTOMATED, $userid);
 381  
 382          try {
 383  
 384              // Set the default filename.
 385              $format = $bc->get_format();
 386              $type = $bc->get_type();
 387              $id = $bc->get_id();
 388              $users = $bc->get_plan()->get_setting('users')->get_value();
 389              $anonymised = $bc->get_plan()->get_setting('anonymize')->get_value();
 390              $bc->get_plan()->get_setting('filename')->set_value(backup_plan_dbops::get_default_backup_filename($format, $type,
 391                      $id, $users, $anonymised));
 392  
 393              $bc->set_status(backup::STATUS_AWAITING);
 394  
 395              $bc->execute_plan();
 396              $results = $bc->get_results();
 397              $outcome = self::outcome_from_results($results);
 398              $file = $results['backup_destination']; // May be empty if file already moved to target location.
 399              if (!file_exists($dir) || !is_dir($dir) || !is_writable($dir)) {
 400                  $dir = null;
 401              }
 402              // Copy file only if there was no error.
 403              if ($file && !empty($dir) && $storage !== 0 && $outcome != self::BACKUP_STATUS_ERROR) {
 404                  $filename = backup_plan_dbops::get_default_backup_filename($format, $type, $course->id, $users, $anonymised,
 405                          !$config->backup_shortname);
 406                  if (!$file->copy_content_to($dir.'/'.$filename)) {
 407                      $outcome = self::BACKUP_STATUS_ERROR;
 408                  }
 409                  if ($outcome != self::BACKUP_STATUS_ERROR && $storage === 1) {
 410                      $file->delete();
 411                  }
 412              }
 413  
 414          } catch (moodle_exception $e) {
 415              $bc->log('backup_auto_failed_on_course', backup::LOG_ERROR, $course->shortname); // Log error header.
 416              $bc->log('Exception: ' . $e->errorcode, backup::LOG_ERROR, $e->a, 1); // Log original exception problem.
 417              $bc->log('Debug: ' . $e->debuginfo, backup::LOG_DEBUG, null, 1); // Log original debug information.
 418              $outcome = self::BACKUP_STATUS_ERROR;
 419          }
 420  
 421          // Delete the backup file immediately if something went wrong.
 422          if ($outcome === self::BACKUP_STATUS_ERROR) {
 423  
 424              // Delete the file from file area if exists.
 425              if (!empty($file)) {
 426                  $file->delete();
 427              }
 428  
 429              // Delete file from external storage if exists.
 430              if ($storage !== 0 && !empty($filename) && file_exists($dir.'/'.$filename)) {
 431                  @unlink($dir.'/'.$filename);
 432              }
 433          }
 434  
 435          $bc->destroy();
 436          unset($bc);
 437  
 438          return $outcome;
 439      }
 440  
 441      /**
 442       * Returns the backup outcome by analysing its results.
 443       *
 444       * @param array $results returned by a backup
 445       * @return int {@link self::BACKUP_STATUS_OK} and other constants
 446       */
 447      public static function outcome_from_results($results) {
 448          $outcome = self::BACKUP_STATUS_OK;
 449          foreach ($results as $code => $value) {
 450              // Each possible error and warning code has to be specified in this switch
 451              // which basically analyses the results to return the correct backup status.
 452              switch ($code) {
 453                  case 'missing_files_in_pool':
 454                      $outcome = self::BACKUP_STATUS_WARNING;
 455                      break;
 456              }
 457              // If we found the highest error level, we exit the loop.
 458              if ($outcome == self::BACKUP_STATUS_ERROR) {
 459                  break;
 460              }
 461          }
 462          return $outcome;
 463      }
 464  
 465      /**
 466       * Removes deleted courses fromn the backup_courses table so that we don't
 467       * waste time backing them up.
 468       *
 469       * @global moodle_database $DB
 470       * @return int
 471       */
 472      public static function remove_deleted_courses_from_schedule() {
 473          global $DB;
 474          $skipped = 0;
 475          $sql = "SELECT bc.courseid FROM {backup_courses} bc WHERE bc.courseid NOT IN (SELECT c.id FROM {course} c)";
 476          $rs = $DB->get_recordset_sql($sql);
 477          foreach ($rs as $deletedcourse) {
 478              //Doesn't exist, so delete from backup tables
 479              $DB->delete_records('backup_courses', array('courseid'=>$deletedcourse->courseid));
 480              $skipped++;
 481          }
 482          $rs->close();
 483          return $skipped;
 484      }
 485  
 486      /**
 487       * Gets the state of the automated backup system.
 488       *
 489       * @global moodle_database $DB
 490       * @return int One of self::STATE_*
 491       */
 492      public static function get_automated_backup_state($rundirective = self::RUN_ON_SCHEDULE) {
 493          global $DB;
 494  
 495          $config = get_config('backup');
 496          $active = (int)$config->backup_auto_active;
 497          $weekdays = (string)$config->backup_auto_weekdays;
 498  
 499          // In case of automated backup also check that it is scheduled for at least one weekday.
 500          if ($active === self::AUTO_BACKUP_DISABLED ||
 501                  ($rundirective == self::RUN_ON_SCHEDULE && $active === self::AUTO_BACKUP_MANUAL) ||
 502                  ($rundirective == self::RUN_ON_SCHEDULE && strpos($weekdays, '1') === false)) {
 503              return self::STATE_DISABLED;
 504          } else if (!empty($config->backup_auto_running)) {
 505              // Detect if the backup_auto_running semaphore is a valid one
 506              // by looking for recent activity in the backup_controllers table
 507              // for backups of type backup::MODE_AUTOMATED
 508              $timetosee = 60 * 90; // Time to consider in order to clean the semaphore
 509              $params = array( 'purpose'   => backup::MODE_AUTOMATED, 'timetolook' => (time() - $timetosee));
 510              if ($DB->record_exists_select('backup_controllers',
 511                  "operation = 'backup' AND type = 'course' AND purpose = :purpose AND timemodified > :timetolook", $params)) {
 512                  return self::STATE_RUNNING; // Recent activity found, still running
 513              } else {
 514                  // No recent activity found, let's clean the semaphore
 515                  mtrace('Automated backups activity not found in last ' . (int)$timetosee/60 . ' minutes. Cleaning running status');
 516                  backup_cron_automated_helper::set_state_running(false);
 517              }
 518          }
 519          return self::STATE_OK;
 520      }
 521  
 522      /**
 523       * Sets the state of the automated backup system.
 524       *
 525       * @param bool $running
 526       * @return bool
 527       */
 528      public static function set_state_running($running = true) {
 529          if ($running === true) {
 530              if (self::get_automated_backup_state() === self::STATE_RUNNING) {
 531                  throw new backup_helper_exception('backup_automated_already_running');
 532              }
 533              set_config('backup_auto_running', '1', 'backup');
 534          } else {
 535              unset_config('backup_auto_running', 'backup');
 536          }
 537          return true;
 538      }
 539  
 540      /**
 541       * Removes excess backups from the external system and the local file system.
 542       *
 543       * The number of backups keep comes from $config->backup_auto_keep.
 544       *
 545       * @param stdClass $course object
 546       * @return bool
 547       */
 548      public static function remove_excess_backups($course) {
 549          $config = get_config('backup');
 550          $keep =     (int)$config->backup_auto_keep;
 551          $storage =  $config->backup_auto_storage;
 552          $dir =      $config->backup_auto_destination;
 553  
 554          if ($keep == 0) {
 555              // Means keep all backup files.
 556              return true;
 557          }
 558  
 559          $backupword = str_replace(' ', '_', core_text::strtolower(get_string('backupfilename')));
 560          $backupword = trim(clean_filename($backupword), '_');
 561  
 562          if (!file_exists($dir) || !is_dir($dir) || !is_writable($dir)) {
 563              $dir = null;
 564          }
 565  
 566          // Clean up excess backups in the course backup filearea.
 567          if ($storage == 0 || $storage == 2) {
 568              $fs = get_file_storage();
 569              $context = context_course::instance($course->id);
 570              $component = 'backup';
 571              $filearea = 'automated';
 572              $itemid = 0;
 573              $files = array();
 574              // Store all the matching files into timemodified => stored_file array.
 575              foreach ($fs->get_area_files($context->id, $component, $filearea, $itemid) as $file) {
 576                  if (strpos($file->get_filename(), $backupword) !== 0) {
 577                      continue;
 578                  }
 579                  $files[$file->get_timemodified()] = $file;
 580              }
 581              if (count($files) <= $keep) {
 582                  // There are less matching files than the desired number to keep there is nothing to clean up.
 583                  return 0;
 584              }
 585              // Sort by keys descending (newer to older filemodified).
 586              krsort($files);
 587              $remove = array_splice($files, $keep);
 588              foreach ($remove as $file) {
 589                  $file->delete();
 590              }
 591              //mtrace('Removed '.count($remove).' old backup file(s) from the automated filearea');
 592          }
 593  
 594          // Clean up excess backups in the specified external directory.
 595          if (!empty($dir) && ($storage == 1 || $storage == 2)) {
 596              // Calculate backup filename regex, ignoring the date/time/info parts that can be
 597              // variable, depending of languages, formats and automated backup settings.
 598              $filename = $backupword . '-' . backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-' . $course->id . '-';
 599              $regex = '#^'.preg_quote($filename, '#').'.*\.mbz$#';
 600  
 601              // Store all the matching files into filename => timemodified array.
 602              $files = array();
 603              foreach (scandir($dir) as $file) {
 604                  // Skip files not matching the naming convention.
 605                  if (!preg_match($regex, $file, $matches)) {
 606                      continue;
 607                  }
 608  
 609                  // Read the information contained in the backup itself.
 610                  try {
 611                      $bcinfo = backup_general_helper::get_backup_information_from_mbz($dir . '/' . $file);
 612                  } catch (backup_helper_exception $e) {
 613                      mtrace('Error: ' . $file . ' does not appear to be a valid backup (' . $e->errorcode . ')');
 614                      continue;
 615                  }
 616  
 617                  // Make sure this backup concerns the course and site we are looking for.
 618                  if ($bcinfo->format === backup::FORMAT_MOODLE &&
 619                          $bcinfo->type === backup::TYPE_1COURSE &&
 620                          $bcinfo->original_course_id == $course->id &&
 621                          backup_general_helper::backup_is_samesite($bcinfo)) {
 622                      $files[$file] = $bcinfo->backup_date;
 623                  }
 624              }
 625              if (count($files) <= $keep) {
 626                  // There are less matching files than the desired number to keep there is nothing to clean up.
 627                  return 0;
 628              }
 629              // Sort by values descending (newer to older filemodified).
 630              arsort($files);
 631              $remove = array_splice($files, $keep);
 632              foreach (array_keys($remove) as $file) {
 633                  unlink($dir . '/' . $file);
 634              }
 635              //mtrace('Removed '.count($remove).' old backup file(s) from external directory');
 636          }
 637  
 638          return true;
 639      }
 640  
 641      /**
 642       * Check logs to find out if a course was modified since the given time.
 643       *
 644       * @param int $courseid course id to check
 645       * @param int $since timestamp, from which to check
 646       *
 647       * @return bool true if the course was modified, false otherwise. This also returns false if no readers are enabled. This is
 648       * intentional, since we cannot reliably determine if any modification was made or not.
 649       */
 650      protected static function is_course_modified($courseid, $since) {
 651          $logmang = get_log_manager();
 652          $readers = $logmang->get_readers('core\log\sql_select_reader');
 653          $where = "courseid = :courseid and timecreated > :since and crud <> 'r'";
 654          $params = array('courseid' => $courseid, 'since' => $since);
 655          foreach ($readers as $reader) {
 656              if ($reader->get_events_select_count($where, $params)) {
 657                  return true;
 658              }
 659          }
 660          return false;
 661      }
 662  }


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