[ Index ] |
PHP Cross Reference of moodle-2.8 |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |