Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/backup/util/helper/backup_cron_helper.class.php
Go to the documentation of this file.
00001 <?php
00002 
00003 // This file is part of Moodle - http://moodle.org/
00004 //
00005 // Moodle is free software: you can redistribute it and/or modify
00006 // it under the terms of the GNU General Public License as published by
00007 // the Free Software Foundation, either version 3 of the License, or
00008 // (at your option) any later version.
00009 //
00010 // Moodle is distributed in the hope that it will be useful,
00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013 // GNU General Public License for more details.
00014 //
00015 // You should have received a copy of the GNU General Public License
00016 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
00017 
00018 
00032 abstract class backup_cron_automated_helper {
00033 
00035     const STATE_OK = 0;
00037     const STATE_DISABLED = 1;
00039     const STATE_RUNNING = 2;
00040 
00042     const BACKUP_STATUS_OK = 1;
00044     const BACKUP_STATUS_ERROR = 0;
00046     const BACKUP_STATUS_UNFINISHED = 2;
00048     const BACKUP_STATUS_SKIPPED = 3;
00049 
00051     const RUN_ON_SCHEDULE = 0;
00053     const RUN_IMMEDIATELY = 1;
00054 
00055     const AUTO_BACKUP_DISABLED = 0;
00056     const AUTO_BACKUP_ENABLED = 1;
00057     const AUTO_BACKUP_MANUAL = 2;
00058 
00064     public static function run_automated_backup($rundirective = self::RUN_ON_SCHEDULE) {
00065         global $CFG, $DB;
00066 
00067         $status = true;
00068         $emailpending = false;
00069         $now = time();
00070 
00071         mtrace("Checking automated backup status",'...');
00072         $state = backup_cron_automated_helper::get_automated_backup_state($rundirective);
00073         if ($state === backup_cron_automated_helper::STATE_DISABLED) {
00074             mtrace('INACTIVE');
00075             return $state;
00076         } else if ($state === backup_cron_automated_helper::STATE_RUNNING) {
00077             mtrace('RUNNING');
00078             if ($rundirective == self::RUN_IMMEDIATELY) {
00079                 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.');
00080             } else {
00081                 mtrace("automated backup are already running. Execution delayed");
00082             }
00083             return $state;
00084         } else {
00085             mtrace('OK');
00086         }
00087         backup_cron_automated_helper::set_state_running();
00088 
00089         mtrace("Getting admin info");
00090         $admin = get_admin();
00091         if (!$admin) {
00092             mtrace("Error: No admin account was found");
00093             $state = false;
00094         }
00095 
00096         if ($status) {
00097             mtrace("Checking courses");
00098             mtrace("Skipping deleted courses", '...');
00099             mtrace(sprintf("%d courses", backup_cron_automated_helper::remove_deleted_courses_from_schedule()));
00100         }
00101 
00102         if ($status) {
00103 
00104             mtrace('Running required automated backups...');
00105 
00106             // This could take a while!
00107             @set_time_limit(0);
00108             raise_memory_limit(MEMORY_EXTRA);
00109 
00110             $nextstarttime = backup_cron_automated_helper::calculate_next_automated_backup($admin->timezone, $now);
00111             $showtime = "undefined";
00112             if ($nextstarttime > 0) {
00113                 $showtime = userdate($nextstarttime,"",$admin->timezone);
00114             }
00115 
00116             $rs = $DB->get_recordset('course');
00117             foreach ($rs as $course) {
00118                 $backupcourse = $DB->get_record('backup_courses', array('courseid'=>$course->id));
00119                 if (!$backupcourse) {
00120                     $backupcourse = new stdClass;
00121                     $backupcourse->courseid = $course->id;
00122                     $DB->insert_record('backup_courses',$backupcourse);
00123                     $backupcourse = $DB->get_record('backup_courses', array('courseid'=>$course->id));
00124                 }
00125 
00126                 // Skip courses that do not yet need backup
00127                 $skipped = !(($backupcourse->nextstarttime >= 0 && $backupcourse->nextstarttime < $now) || $rundirective == self::RUN_IMMEDIATELY);
00128                 // Skip backup of unavailable courses that have remained unmodified in a month
00129                 if (!$skipped && empty($course->visible) && ($now - $course->timemodified) > 31*24*60*60) {  //Hidden + settings were unmodified last month
00130                     //Check log if there were any modifications to the course content
00131                     $sqlwhere = "course=:courseid AND time>:time AND ". $DB->sql_like('action', ':action', false, true, true);
00132                     $params = array('courseid' => $course->id, 'time' => $now-31*24*60*60, 'action' => '%view%');
00133                     $logexists = $DB->record_exists_select('log', $sqlwhere, $params);
00134                     if (!$logexists) {
00135                         $backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_SKIPPED;
00136                         $backupcourse->nextstarttime = $nextstarttime;
00137                         $DB->update_record('backup_courses', $backupcourse);
00138                         mtrace('Skipping unchanged course '.$course->fullname);
00139                         $skipped = true;
00140                     }
00141                 }
00142                 //Now we backup every non-skipped course
00143                 if (!$skipped) {
00144                     mtrace('Backing up '.$course->fullname, '...');
00145 
00146                     //We have to send a email because we have included at least one backup
00147                     $emailpending = true;
00148 
00149                     //Only make the backup if laststatus isn't 2-UNFINISHED (uncontrolled error)
00150                     if ($backupcourse->laststatus != 2) {
00151                         //Set laststarttime
00152                         $starttime = time();
00153 
00154                         $backupcourse->laststarttime = time();
00155                         $backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_UNFINISHED;
00156                         $DB->update_record('backup_courses', $backupcourse);
00157 
00158                         $backupcourse->laststatus = backup_cron_automated_helper::launch_automated_backup($course, $backupcourse->laststarttime, $admin->id);
00159                         $backupcourse->lastendtime = time();
00160                         $backupcourse->nextstarttime = $nextstarttime;
00161 
00162                         $DB->update_record('backup_courses', $backupcourse);
00163 
00164                         if ($backupcourse->laststatus) {
00165                             // Clean up any excess course backups now that we have
00166                             // taken a successful backup.
00167                             $removedcount = backup_cron_automated_helper::remove_excess_backups($course);
00168                         }
00169                     }
00170 
00171                     mtrace("complete - next execution: $showtime");
00172                 }
00173             }
00174             $rs->close();
00175         }
00176 
00177         //Send email to admin if necessary
00178         if ($emailpending) {
00179             mtrace("Sending email to admin");
00180             $message = "";
00181 
00182             $count = backup_cron_automated_helper::get_backup_status_array();
00183             $haserrors = ($count[backup_cron_automated_helper::BACKUP_STATUS_ERROR] != 0 || $count[backup_cron_automated_helper::BACKUP_STATUS_UNFINISHED] != 0);
00184 
00185             //Build the message text
00186             //Summary
00187             $message .= get_string('summary')."\n";
00188             $message .= "==================================================\n";
00189             $message .= "  ".get_string('courses').": ".array_sum($count)."\n";
00190             $message .= "  ".get_string('ok').": ".$count[backup_cron_automated_helper::BACKUP_STATUS_OK]."\n";
00191             $message .= "  ".get_string('skipped').": ".$count[backup_cron_automated_helper::BACKUP_STATUS_SKIPPED]."\n";
00192             $message .= "  ".get_string('error').": ".$count[backup_cron_automated_helper::BACKUP_STATUS_ERROR]."\n";
00193             $message .= "  ".get_string('unfinished').": ".$count[backup_cron_automated_helper::BACKUP_STATUS_UNFINISHED]."\n\n";
00194 
00195             //Reference
00196             if ($haserrors) {
00197                 $message .= "  ".get_string('backupfailed')."\n\n";
00198                 $dest_url = "$CFG->wwwroot/report/backups/index.php";
00199                 $message .= "  ".get_string('backuptakealook','',$dest_url)."\n\n";
00200                 //Set message priority
00201                 $admin->priority = 1;
00202                 //Reset unfinished to error
00203                 $DB->set_field('backup_courses','laststatus','0', array('laststatus'=>'2'));
00204             } else {
00205                 $message .= "  ".get_string('backupfinished')."\n";
00206             }
00207 
00208             //Build the message subject
00209             $site = get_site();
00210             $prefix = format_string($site->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, SITEID))).": ";
00211             if ($haserrors) {
00212                 $prefix .= "[".strtoupper(get_string('error'))."] ";
00213             }
00214             $subject = $prefix.get_string('automatedbackupstatus', 'backup');
00215 
00216             //Send the message
00217             $eventdata = new stdClass();
00218             $eventdata->modulename        = 'moodle';
00219             $eventdata->userfrom          = $admin;
00220             $eventdata->userto            = $admin;
00221             $eventdata->subject           = $subject;
00222             $eventdata->fullmessage       = $message;
00223             $eventdata->fullmessageformat = FORMAT_PLAIN;
00224             $eventdata->fullmessagehtml   = '';
00225             $eventdata->smallmessage      = '';
00226 
00227             $eventdata->component         = 'moodle';
00228             $eventdata->name         = 'backup';
00229 
00230             message_send($eventdata);
00231         }
00232 
00233         //Everything is finished stop backup_auto_running
00234         backup_cron_automated_helper::set_state_running(false);
00235 
00236         mtrace('Automated backups complete.');
00237 
00238         return $status;
00239     }
00240 
00248     public static function get_backup_status_array() {
00249         global $DB;
00250 
00251         $result = array(
00252             self::BACKUP_STATUS_ERROR => 0,
00253             self::BACKUP_STATUS_OK => 0,
00254             self::BACKUP_STATUS_UNFINISHED => 0,
00255             self::BACKUP_STATUS_SKIPPED => 0,
00256         );
00257 
00258         $statuses = $DB->get_records_sql('SELECT DISTINCT bc.laststatus, COUNT(bc.courseid) statuscount FROM {backup_courses} bc GROUP BY bc.laststatus');
00259 
00260         foreach ($statuses as $status) {
00261             if (empty($status->statuscount)) {
00262                 $status->statuscount = 0;
00263             }
00264             $result[(int)$status->laststatus] += $status->statuscount;
00265         }
00266 
00267         return $result;
00268     }
00269 
00277     public static function calculate_next_automated_backup($timezone, $now) {
00278 
00279         $result = -1;
00280         $config = get_config('backup');
00281         $midnight = usergetmidnight($now, $timezone);
00282         $date = usergetdate($now, $timezone);
00283 
00284         //Get number of days (from today) to execute backups
00285         $automateddays = substr($config->backup_auto_weekdays,$date['wday']) . $config->backup_auto_weekdays;
00286         $daysfromtoday = strpos($automateddays, "1");
00287         if (empty($daysfromtoday)) {
00288             $daysfromtoday = 1;
00289         }
00290 
00291         //If some day has been found
00292         if ($daysfromtoday !== false) {
00293             //Calculate distance
00294             $dist = ($daysfromtoday * 86400) +                //Days distance
00295                     ($config->backup_auto_hour * 3600) +      //Hours distance
00296                     ($config->backup_auto_minute * 60);       //Minutes distance
00297             $result = $midnight + $dist;
00298         }
00299 
00300         //If that time is past, call the function recursively to obtain the next valid day
00301         if ($result > 0 && $result < time()) {
00302             $result = self::calculate_next_automated_backup($timezone, $result);
00303         }
00304 
00305         return $result;
00306     }
00307 
00316     public static function launch_automated_backup($course, $starttime, $userid) {
00317 
00318         $config = get_config('backup');
00319         $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_AUTOMATED, $userid);
00320 
00321         try {
00322 
00323             $settings = array(
00324                 'users' => 'backup_auto_users',
00325                 'role_assignments' => 'backup_auto_role_assignments',
00326                 'user_files' => 'backup_auto_user_files',
00327                 'activities' => 'backup_auto_activities',
00328                 'blocks' => 'backup_auto_blocks',
00329                 'filters' => 'backup_auto_filters',
00330                 'comments' => 'backup_auto_comments',
00331                 'completion_information' => 'backup_auto_userscompletion',
00332                 'logs' => 'backup_auto_logs',
00333                 'histories' => 'backup_auto_histories'
00334             );
00335             foreach ($settings as $setting => $configsetting) {
00336                 if ($bc->get_plan()->setting_exists($setting)) {
00337                     $bc->get_plan()->get_setting($setting)->set_value($config->{$configsetting});
00338                 }
00339             }
00340 
00341             // Set the default filename
00342             $format = $bc->get_format();
00343             $type = $bc->get_type();
00344             $id = $bc->get_id();
00345             $users = $bc->get_plan()->get_setting('users')->get_value();
00346             $anonymised = $bc->get_plan()->get_setting('anonymize')->get_value();
00347             $bc->get_plan()->get_setting('filename')->set_value(backup_plan_dbops::get_default_backup_filename($format, $type, $id, $users, $anonymised));
00348 
00349             $bc->set_status(backup::STATUS_AWAITING);
00350 
00351             $outcome = $bc->execute_plan();
00352             $results = $bc->get_results();
00353             $file = $results['backup_destination'];
00354             $dir = $config->backup_auto_destination;
00355             $storage = (int)$config->backup_auto_storage;
00356             if (!file_exists($dir) || !is_dir($dir) || !is_writable($dir)) {
00357                 $dir = null;
00358             }
00359             if (!empty($dir) && $storage !== 0) {
00360                 $filename = backup_plan_dbops::get_default_backup_filename($format, $type, $course->id, $users, $anonymised, true);
00361                 $outcome = $file->copy_content_to($dir.'/'.$filename);
00362                 if ($outcome && $storage === 1) {
00363                     $file->delete();
00364                 }
00365             }
00366 
00367             $outcome = true;
00368         } catch (backup_exception $e) {
00369             $bc->log('backup_auto_failed_on_course', backup::LOG_WARNING, $course->shortname);
00370             $outcome = false;
00371         }
00372 
00373         $bc->destroy();
00374         unset($bc);
00375 
00376         return true;
00377     }
00378 
00386     public static function remove_deleted_courses_from_schedule() {
00387         global $DB;
00388         $skipped = 0;
00389         $sql = "SELECT bc.courseid FROM {backup_courses} bc WHERE bc.courseid NOT IN (SELECT c.id FROM {course} c)";
00390         $rs = $DB->get_recordset_sql($sql);
00391         foreach ($rs as $deletedcourse) {
00392             //Doesn't exist, so delete from backup tables
00393             $DB->delete_records('backup_courses', array('courseid'=>$deletedcourse->courseid));
00394             $skipped++;
00395         }
00396         $rs->close();
00397         return $skipped;
00398     }
00399 
00406     public static function get_automated_backup_state($rundirective = self::RUN_ON_SCHEDULE) {
00407         global $DB;
00408 
00409         $config = get_config('backup');
00410         $active = (int)$config->backup_auto_active;
00411         if ($active === self::AUTO_BACKUP_DISABLED || ($rundirective == self::RUN_ON_SCHEDULE && $active === self::AUTO_BACKUP_MANUAL)) {
00412             return self::STATE_DISABLED;
00413         } else if (!empty($config->backup_auto_running)) {
00414             // Detect if the backup_auto_running semaphore is a valid one
00415             // by looking for recent activity in the backup_controllers table
00416             // for backups of type backup::MODE_AUTOMATED
00417             $timetosee = 60 * 90; // Time to consider in order to clean the semaphore
00418             $params = array( 'purpose'   => backup::MODE_AUTOMATED, 'timetolook' => (time() - $timetosee));
00419             if ($DB->record_exists_select('backup_controllers',
00420                 "operation = 'backup' AND type = 'course' AND purpose = :purpose AND timemodified > :timetolook", $params)) {
00421                 return self::STATE_RUNNING; // Recent activity found, still running
00422             } else {
00423                 // No recent activity found, let's clean the semaphore
00424                 mtrace('Automated backups activity not found in last ' . (int)$timetosee/60 . ' minutes. Cleaning running status');
00425                 backup_cron_automated_helper::set_state_running(false);
00426             }
00427         }
00428         return self::STATE_OK;
00429     }
00430 
00437     public static function set_state_running($running = true) {
00438         if ($running === true) {
00439             if (self::get_automated_backup_state() === self::STATE_RUNNING) {
00440                 throw new backup_exception('backup_automated_already_running');
00441             }
00442             set_config('backup_auto_running', '1', 'backup');
00443         } else {
00444             unset_config('backup_auto_running', 'backup');
00445         }
00446         return true;
00447     }
00448 
00457     public static function remove_excess_backups($course) {
00458         $config = get_config('backup');
00459         $keep =     (int)$config->backup_auto_keep;
00460         $storage =  $config->backup_auto_storage;
00461         $dir =      $config->backup_auto_destination;
00462 
00463         $backupword = str_replace(' ', '_', moodle_strtolower(get_string('backupfilename')));
00464         $backupword = trim(clean_filename($backupword), '_');
00465 
00466         if (!file_exists($dir) || !is_dir($dir) || !is_writable($dir)) {
00467             $dir = null;
00468         }
00469 
00470         // Clean up excess backups in the course backup filearea
00471         if ($storage == 0 || $storage == 2) {
00472             $fs = get_file_storage();
00473             $context = get_context_instance(CONTEXT_COURSE, $course->id);
00474             $component = 'backup';
00475             $filearea = 'automated';
00476             $itemid = 0;
00477             $files = array();
00478             // Store all the matching files into timemodified => stored_file array
00479             foreach ($fs->get_area_files($context->id, $component, $filearea, $itemid) as $file) {
00480                 if (strpos($file->get_filename(), $backupword) !== 0) {
00481                     continue;
00482                 }
00483                 $files[$file->get_timemodified()] = $file;
00484             }
00485             if (count($files) <= $keep) {
00486                 // There are less matching files than the desired number to keep
00487                 // do there is nothing to clean up.
00488                 return 0;
00489             }
00490             // Sort by keys descending (newer to older filemodified)
00491             krsort($files);
00492             $remove = array_splice($files, $keep);
00493             foreach ($remove as $file) {
00494                 $file->delete();
00495             }
00496             //mtrace('Removed '.count($remove).' old backup file(s) from the automated filearea');
00497         }
00498 
00499         // Clean up excess backups in the specified external directory
00500         if (!empty($dir) && ($storage == 1 || $storage == 2)) {
00501             // Calculate backup filename regex, ignoring the date/time/info parts that can be
00502             // variable, depending of languages, formats and automated backup settings
00503             $filename = $backupword . '-' . backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-' .$course->id . '-';
00504             $regex = '#^'.preg_quote($filename, '#').'.*\.mbz$#';
00505 
00506             // Store all the matching files into fullpath => timemodified array
00507             $files = array();
00508             foreach (scandir($dir) as $file) {
00509                 if (preg_match($regex, $file, $matches)) {
00510                     $files[$file] = filemtime($dir . '/' . $file);
00511                 }
00512             }
00513             if (count($files) <= $keep) {
00514                 // There are less matching files than the desired number to keep
00515                 // do there is nothing to clean up.
00516                 return 0;
00517             }
00518             // Sort by values descending (newer to older filemodified)
00519             arsort($files);
00520             $remove = array_splice($files, $keep);
00521             foreach (array_keys($remove) as $file) {
00522                 unlink($dir . '/' . $file);
00523             }
00524             //mtrace('Removed '.count($remove).' old backup file(s) from external directory');
00525         }
00526 
00527         return true;
00528     }
00529 }
 All Data Structures Namespaces Files Functions Variables Enumerations