weekly release 1.9.14+
[moodle.git] / admin / cron.php
blob4872d4a6e58efc55d1380c47550b83701484bad4
1 <?php // $Id$
3 /// This script looks through all the module directories for cron.php files
4 /// and runs them. These files can contain cleanup functions, email functions
5 /// or anything that needs to be run on a regular basis.
6 ///
7 /// This file is best run from cron on the host system (ie outside PHP).
8 /// The script can either be invoked via the web server or via a standalone
9 /// version of PHP compiled for CGI.
10 ///
11 /// eg wget -q -O /dev/null 'http://moodle.somewhere.edu/admin/cron.php'
12 /// or php /web/moodle/admin/cron.php
13 set_time_limit(0);
14 $starttime = microtime();
16 /// The following is a hack necessary to allow this script to work well
17 /// from the command line.
19 define('FULLME', 'cron');
22 /// Do not set moodle cookie because we do not need it here, it is better to emulate session
23 $nomoodlecookie = true;
25 /// The current directory in PHP version 4.3.0 and above isn't necessarily the
26 /// directory of the script when run from the command line. The require_once()
27 /// would fail, so we'll have to chdir()
29 if (!isset($_SERVER['REMOTE_ADDR']) && isset($_SERVER['argv'][0])) {
30 chdir(dirname($_SERVER['argv'][0]));
33 require_once(dirname(__FILE__) . '/../config.php');
34 require_once($CFG->libdir.'/adminlib.php');
35 require_once($CFG->libdir.'/gradelib.php');
37 /// Extra debugging (set in config.php)
38 if (!empty($CFG->showcronsql)) {
39 $db->debug = true;
41 if (!empty($CFG->showcrondebugging)) {
42 $CFG->debug = DEBUG_DEVELOPER;
43 $CFG->debugdisplay = true;
46 /// extra safety
47 @session_write_close();
49 /// check if execution allowed
50 if (isset($_SERVER['REMOTE_ADDR'])) { // if the script is accessed via the web.
51 if (!empty($CFG->cronclionly)) {
52 // This script can only be run via the cli.
53 print_error('cronerrorclionly', 'admin');
54 exit;
56 // This script is being called via the web, so check the password if there is one.
57 if (!empty($CFG->cronremotepassword)) {
58 $pass = optional_param('password', '', PARAM_RAW);
59 if($pass != $CFG->cronremotepassword) {
60 // wrong password.
61 print_error('cronerrorpassword', 'admin');
62 exit;
68 /// emulate normal session
69 $SESSION = new object();
70 $USER = get_admin(); /// Temporarily, to provide environment for this script
72 /// ignore admins timezone, language and locale - use site deafult instead!
73 $USER->timezone = $CFG->timezone;
74 $USER->lang = '';
75 $USER->theme = '';
76 course_setup(SITEID);
78 /// send mime type and encoding
79 if (check_browser_version('MSIE')) {
80 //ugly IE hack to work around downloading instead of viewing
81 @header('Content-Type: text/html; charset=utf-8');
82 echo "<xmp>"; //<pre> is not good enough for us here
83 } else {
84 //send proper plaintext header
85 @header('Content-Type: text/plain; charset=utf-8');
88 /// no more headers and buffers
89 while(@ob_end_flush());
91 /// increase memory limit (PHP 5.2 does different calculation, we need more memory now)
92 @raise_memory_limit('128M');
94 /// Start output log
96 $timenow = time();
98 mtrace("Server Time: ".date('r',$timenow)."\n\n");
100 /// Run all cron jobs for each module
102 mtrace("Starting activity modules");
103 get_mailer('buffer');
104 if ($mods = get_records_select("modules", "cron > 0 AND (($timenow - lastcron) > cron) AND visible = 1 ")) {
105 foreach ($mods as $mod) {
106 $libfile = "$CFG->dirroot/mod/$mod->name/lib.php";
107 if (file_exists($libfile)) {
108 include_once($libfile);
109 $cron_function = $mod->name."_cron";
110 if (function_exists($cron_function)) {
111 mtrace("Processing module function $cron_function ...", '');
112 $pre_dbqueries = null;
113 if (!empty($PERF->dbqueries)) {
114 $pre_dbqueries = $PERF->dbqueries;
115 $pre_time = microtime(1);
117 if ($cron_function()) {
118 if (! set_field("modules", "lastcron", $timenow, "id", $mod->id)) {
119 mtrace("Error: could not update timestamp for $mod->fullname");
122 if (isset($pre_dbqueries)) {
123 mtrace("... used " . ($PERF->dbqueries - $pre_dbqueries) . " dbqueries");
124 mtrace("... used " . (microtime(1) - $pre_time) . " seconds");
126 /// Reset possible changes by modules to time_limit. MDL-11597
127 @set_time_limit(0);
128 mtrace("done.");
133 get_mailer('close');
134 mtrace("Finished activity modules");
136 mtrace("Starting blocks");
137 if ($blocks = get_records_select("block", "cron > 0 AND (($timenow - lastcron) > cron) AND visible = 1")) {
138 // we will need the base class.
139 require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
140 foreach ($blocks as $block) {
141 $blockfile = $CFG->dirroot.'/blocks/'.$block->name.'/block_'.$block->name.'.php';
142 if (file_exists($blockfile)) {
143 require_once($blockfile);
144 $classname = 'block_'.$block->name;
145 $blockobj = new $classname;
146 if (method_exists($blockobj,'cron')) {
147 mtrace("Processing cron function for ".$block->name.'....','');
148 if ($blockobj->cron()) {
149 if (!set_field('block','lastcron',$timenow,'id',$block->id)) {
150 mtrace('Error: could not update timestamp for '.$block->name);
153 /// Reset possible changes by blocks to time_limit. MDL-11597
154 @set_time_limit(0);
155 mtrace('done.');
161 mtrace('Finished blocks');
163 mtrace('Starting admin reports');
164 // Admin reports do not have a database table that lists them. Instead a
165 // report includes cron.php with function report_reportname_cron() if it wishes
166 // to be cronned. It is up to cron.php to handle e.g. if it only needs to
167 // actually do anything occasionally.
168 $reports = get_list_of_plugins($CFG->admin.'/report');
169 foreach($reports as $report) {
170 $cronfile = $CFG->dirroot.'/'.$CFG->admin.'/report/'.$report.'/cron.php';
171 if (file_exists($cronfile)) {
172 require_once($cronfile);
173 $cronfunction = 'report_'.$report.'_cron';
174 mtrace('Processing cron function for '.$report.'...', '');
175 $pre_dbqueries = null;
176 if (!empty($PERF->dbqueries)) {
177 $pre_dbqueries = $PERF->dbqueries;
178 $pre_time = microtime(true);
180 $cronfunction();
181 if (isset($pre_dbqueries)) {
182 mtrace("... used " . ($PERF->dbqueries - $pre_dbqueries) . " dbqueries");
183 mtrace("... used " . round(microtime(true) - $pre_time, 2) . " seconds");
185 mtrace('done.');
188 mtrace('Finished admin reports');
190 if (!empty($CFG->langcache)) {
191 mtrace('Updating languages cache');
192 get_list_of_languages(true);
195 mtrace('Removing expired enrolments ...', ''); // See MDL-8785
196 $timenow = time();
197 $somefound = false;
198 // The preferred way saves memory, dmllib.php
199 // find courses where limited enrolment is enabled
200 global $CFG;
201 $rs_enrol = get_recordset_sql("SELECT ra.roleid, ra.userid, ra.contextid
202 FROM {$CFG->prefix}course c
203 INNER JOIN {$CFG->prefix}context cx ON cx.instanceid = c.id
204 INNER JOIN {$CFG->prefix}role_assignments ra ON ra.contextid = cx.id
205 WHERE cx.contextlevel = '".CONTEXT_COURSE."'
206 AND ra.timeend > 0
207 AND ra.timeend < '$timenow'
208 AND c.enrolperiod > 0
210 while ($oldenrolment = rs_fetch_next_record($rs_enrol)) {
211 role_unassign($oldenrolment->roleid, $oldenrolment->userid, 0, $oldenrolment->contextid);
212 $somefound = true;
214 rs_close($rs_enrol);
215 if($somefound) {
216 mtrace('Done');
217 } else {
218 mtrace('none found');
222 mtrace('Starting main gradebook job ...');
223 grade_cron();
224 mtrace('done.');
226 mtrace('Starting processing the event queue...');
227 events_cron();
228 mtrace('done.');
230 /// Run all core cron jobs, but not every time since they aren't too important.
231 /// These don't have a timer to reduce load, so we'll use a random number
232 /// to randomly choose the percentage of times we should run these jobs.
234 srand ((double) microtime() * 10000000);
235 $random100 = rand(0,100);
237 if ($random100 < 20) { // Approximately 20% of the time.
238 mtrace("Running clean-up tasks...");
240 /// Unenrol users who haven't logged in for $CFG->longtimenosee
242 if ($CFG->longtimenosee) { // value in days
243 $cuttime = $timenow - ($CFG->longtimenosee * 3600 * 24);
244 $rs = get_recordset_sql ("SELECT la.id, la.userid, la.courseid
245 FROM {$CFG->prefix}user_lastaccess la
246 JOIN {$CFG->prefix}course c
247 ON c.id = la.courseid
248 WHERE la.courseid != ".SITEID."
249 AND la.timeaccess < $cuttime
250 AND c.metacourse = 0 ");
251 while ($assign = rs_fetch_next_record($rs)) {
252 if ($context = get_context_instance(CONTEXT_COURSE, $assign->courseid)) {
253 if (role_unassign(0, $assign->userid, 0, $context->id)) {
254 mtrace("removing user $assign->userid from course $assign->courseid as they have not accessed the course for over $CFG->longtimenosee days");
258 rs_close($rs);
259 /// Execute the same query again, looking for remaining records and deleting them
260 /// if the user hasn't moodle/course:view in the CONTEXT_COURSE context (orphan records)
261 $rs = get_recordset_sql ("SELECT id, userid, courseid
262 FROM {$CFG->prefix}user_lastaccess
263 WHERE courseid != ".SITEID."
264 AND timeaccess < $cuttime ");
265 while ($assign = rs_fetch_next_record($rs)) {
266 if ($context = get_context_instance(CONTEXT_COURSE, $assign->courseid)) {
267 if (!has_capability('moodle/course:view', $context, $assign->userid)) {
268 delete_records('user_lastaccess', 'userid', $assign->userid, 'courseid', $assign->courseid);
269 mtrace("Deleted orphan user_lastaccess for user $assign->userid from course $assign->courseid");
273 rs_close($rs);
275 flush();
278 /// Delete users who haven't confirmed within required period
280 if (!empty($CFG->deleteunconfirmed)) {
281 $cuttime = $timenow - ($CFG->deleteunconfirmed * 3600);
282 $rs = get_recordset_sql ("SELECT id, firstname, lastname
283 FROM {$CFG->prefix}user
284 WHERE confirmed = 0
285 AND firstaccess > 0
286 AND firstaccess < $cuttime");
287 while ($user = rs_fetch_next_record($rs)) {
288 if (delete_records('user', 'id', $user->id)) {
289 mtrace("Deleted unconfirmed user for ".fullname($user, true)." ($user->id)");
292 rs_close($rs);
294 flush();
297 /// Delete users who haven't completed profile within required period
299 if (!empty($CFG->deleteincompleteusers)) {
300 $cuttime = $timenow - ($CFG->deleteincompleteusers * 3600);
301 $rs = get_recordset_sql ("SELECT id, username
302 FROM {$CFG->prefix}user
303 WHERE confirmed = 1
304 AND lastaccess > 0
305 AND lastaccess < $cuttime
306 AND deleted = 0
307 AND (lastname = '' OR firstname = '' OR email = '')");
308 while ($user = rs_fetch_next_record($rs)) {
309 if (delete_user($user)) {
310 mtrace("Deleted not fully setup user $user->username ($user->id)");
313 rs_close($rs);
315 flush();
318 /// Delete old logs to save space (this might need a timer to slow it down...)
320 if (!empty($CFG->loglifetime)) { // value in days
321 $loglifetime = $timenow - ($CFG->loglifetime * 3600 * 24);
322 if (delete_records_select("log", "time < '$loglifetime'")) {
323 mtrace("Deleted old log records");
326 flush();
329 /// Delete old cached texts
331 if (!empty($CFG->cachetext)) { // Defined in config.php
332 $cachelifetime = time() - $CFG->cachetext - 60; // Add an extra minute to allow for really heavy sites
333 if (delete_records_select('cache_text', "timemodified < '$cachelifetime'")) {
334 mtrace("Deleted old cache_text records");
337 flush();
339 if (!empty($CFG->notifyloginfailures)) {
340 notify_login_failures();
341 mtrace('Notified login failured');
343 flush();
345 sync_metacourses();
346 mtrace('Synchronised metacourses');
349 // generate new password emails for users
351 mtrace('checking for create_password');
352 if (count_records('user_preferences', 'name', 'create_password', 'value', '1')) {
353 mtrace('creating passwords for new users');
354 $newuserssql = "SELECT u.id as id, u.email, u.firstname, u.lastname, u.username, p.id as prefid
355 FROM {$CFG->prefix}user u
356 JOIN {$CFG->prefix}user_preferences p ON u.id=p.userid
357 WHERE p.name='create_password' AND p.value='1' AND u.email !='' ";
358 if ($newusers = get_records_sql($newuserssql)) {
359 foreach ($newusers as $newuserid => $newuser) {
360 $newuser->emailstop = 0; // send email regardless
361 // email user
362 if (setnew_password_and_mail($newuser)) {
363 // remove user pref
364 delete_records('user_preferences', 'id', $newuser->prefid);
365 } else {
366 trigger_error("Could not create and mail new user password!");
372 if (!empty($CFG->usetags)) {
373 require_once($CFG->dirroot.'/tag/lib.php');
374 tag_cron();
375 mtrace ('Executed tag cron');
378 // Accesslib stuff
379 cleanup_contexts();
380 mtrace ('Cleaned up contexts');
381 gc_cache_flags();
382 mtrace ('Cleaned cache flags');
383 // If you suspect that the context paths are somehow corrupt
384 // replace the line below with: build_context_path(true);
385 build_context_path();
386 mtrace ('Built context paths');
388 mtrace("Finished clean-up tasks...");
390 } // End of occasional clean-up tasks
393 if (empty($CFG->disablescheduledbackups)) { // Defined in config.php
394 //Execute backup's cron
395 //Perhaps a long time and memory could help in large sites
396 @set_time_limit(0);
397 @raise_memory_limit("192M");
398 if (function_exists('apache_child_terminate')) {
399 // if we are running from Apache, give httpd a hint that
400 // it can recycle the process after it's done. Apache's
401 // memory management is truly awful but we can help it.
402 @apache_child_terminate();
404 if (file_exists("$CFG->dirroot/backup/backup_scheduled.php") and
405 file_exists("$CFG->dirroot/backup/backuplib.php") and
406 file_exists("$CFG->dirroot/backup/lib.php") and
407 file_exists("$CFG->libdir/blocklib.php")) {
408 include_once("$CFG->dirroot/backup/backup_scheduled.php");
409 include_once("$CFG->dirroot/backup/backuplib.php");
410 include_once("$CFG->dirroot/backup/lib.php");
411 require_once ("$CFG->libdir/blocklib.php");
412 mtrace("Running backups if required...");
414 if (! schedule_backup_cron()) {
415 mtrace("ERROR: Something went wrong while performing backup tasks!!!");
416 } else {
417 mtrace("Backup tasks finished.");
422 if (!empty($CFG->enablerssfeeds)) { //Defined in admin/variables page
423 include_once("$CFG->libdir/rsslib.php");
424 mtrace("Running rssfeeds if required...");
426 if ( ! cron_rss_feeds()) {
427 mtrace("Something went wrong while generating rssfeeds!!!");
428 } else {
429 mtrace("Rssfeeds finished");
433 /// Run the auth cron, if any
434 /// before enrolments because it might add users that will be needed in enrol plugins
435 $auths = get_enabled_auth_plugins();
437 mtrace("Running auth crons if required...");
438 foreach ($auths as $auth) {
439 $authplugin = get_auth_plugin($auth);
440 if (method_exists($authplugin, 'cron')) {
441 mtrace("Running cron for auth/$auth...");
442 $authplugin->cron();
443 if (!empty($authplugin->log)) {
444 mtrace($authplugin->log);
447 unset($authplugin);
450 /// Run the enrolment cron, if any
451 if (!($plugins = explode(',', $CFG->enrol_plugins_enabled))) {
452 $plugins = array($CFG->enrol);
454 require_once($CFG->dirroot .'/enrol/enrol.class.php');
455 foreach ($plugins as $p) {
456 $enrol = enrolment_factory::factory($p);
457 if (method_exists($enrol, 'cron')) {
458 $enrol->cron();
460 if (!empty($enrol->log)) {
461 mtrace($enrol->log);
463 unset($enrol);
466 if (!empty($CFG->enablestats) and empty($CFG->disablestatsprocessing)) {
467 require_once($CFG->dirroot.'/lib/statslib.php');
468 // check we're not before our runtime
469 $timetocheck = stats_get_base_daily() + $CFG->statsruntimestarthour*60*60 + $CFG->statsruntimestartminute*60;
471 if (time() > $timetocheck) {
472 // process configured number of days as max (defaulting to 31)
473 $maxdays = empty($CFG->statsruntimedays) ? 31 : abs($CFG->statsruntimedays);
474 if (stats_cron_daily($maxdays)) {
475 if (stats_cron_weekly()) {
476 if (stats_cron_monthly()) {
477 stats_clean_old();
481 @set_time_limit(0);
482 } else {
483 mtrace('Next stats run after:'. userdate($timetocheck));
487 // run gradebook import/export/report cron
488 if ($gradeimports = get_list_of_plugins('grade/import')) {
489 foreach ($gradeimports as $gradeimport) {
490 if (file_exists($CFG->dirroot.'/grade/import/'.$gradeimport.'/lib.php')) {
491 require_once($CFG->dirroot.'/grade/import/'.$gradeimport.'/lib.php');
492 $cron_function = 'grade_import_'.$gradeimport.'_cron';
493 if (function_exists($cron_function)) {
494 mtrace("Processing gradebook import function $cron_function ...", '');
495 $cron_function();
501 if ($gradeexports = get_list_of_plugins('grade/export')) {
502 foreach ($gradeexports as $gradeexport) {
503 if (file_exists($CFG->dirroot.'/grade/export/'.$gradeexport.'/lib.php')) {
504 require_once($CFG->dirroot.'/grade/export/'.$gradeexport.'/lib.php');
505 $cron_function = 'grade_export_'.$gradeexport.'_cron';
506 if (function_exists($cron_function)) {
507 mtrace("Processing gradebook export function $cron_function ...", '');
508 $cron_function();
514 if ($gradereports = get_list_of_plugins('grade/report')) {
515 foreach ($gradereports as $gradereport) {
516 if (file_exists($CFG->dirroot.'/grade/report/'.$gradereport.'/lib.php')) {
517 require_once($CFG->dirroot.'/grade/report/'.$gradereport.'/lib.php');
518 $cron_function = 'grade_report_'.$gradereport.'_cron';
519 if (function_exists($cron_function)) {
520 mtrace("Processing gradebook report function $cron_function ...", '');
521 $cron_function();
527 // run any customized cronjobs, if any
528 // looking for functions in lib/local/cron.php
529 if (file_exists($CFG->dirroot.'/local/cron.php')) {
530 mtrace('Processing customized cron script ...', '');
531 include_once($CFG->dirroot.'/local/cron.php');
532 mtrace('done.');
536 //Unset session variables and destroy it
537 @session_unset();
538 @session_destroy();
540 mtrace("Cron script completed correctly");
542 $difftime = microtime_diff($starttime, microtime());
543 mtrace("Execution took ".$difftime." seconds");
545 /// finish the IE hack
546 if (check_browser_version('MSIE')) {
547 echo "</xmp>";