PHP4 is dead - admins with PHP4 will get ugly errors when trying to install Moodle...
[moodle.git] / admin / cron.php
CommitLineData
f578af59 1<?php
f9903ed0 2
540bab47 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'
20207b82 12/// or php /web/moodle/admin/cron.php
168df8f3 13 set_time_limit(0);
498d4032 14 $starttime = microtime();
f9f4d999 15
a91b910e 16/// this cron script might be considered to be a CLI script even when accessed over HTTP,
17/// we do not want HTML in output and there is no real session ;-)
e13914ac
PS
18/// NOTE: do not use this hack in your cli scripts, it is defined automatically
19/// in lib/setup.php, all you need to do is to prevent access via web
a91b910e 20 define('CLI_SCRIPT', true);
21
11e7b506 22/// Following hack used to identify cron and other CLI scripts - use CLI_SCRIPT or $FULLME == "/$CFG->admin/cropn.php" instead
23 //define('FULLME', 'cron');
9152fc99 24
25/// Do not set moodle cookie because we do not need it here, it is better to emulate session
6800d78e 26 define('NO_MOODLE_COOKIES', true);
9152fc99 27
331ca297 28 require_once(dirname(__FILE__) . '/../config.php');
6e4dc10f 29 require_once($CFG->libdir.'/adminlib.php');
2650c51e 30 require_once($CFG->libdir.'/gradelib.php');
f9903ed0 31
63a11f3c 32/// Extra debugging (set in config.php)
33 if (!empty($CFG->showcronsql)) {
f33e1ed4 34 $DB->set_debug(true);
63a11f3c 35 }
36 if (!empty($CFG->showcrondebugging)) {
37 $CFG->debug = DEBUG_DEVELOPER;
7f51af40 38 $CFG->debugdisplay = true;
63a11f3c 39 }
40
9152fc99 41/// extra safety
dd9e22f8 42 session_get_instance()->write_close();
9152fc99 43
44/// check if execution allowed
e08df351 45 if (isset($_SERVER['REMOTE_ADDR'])) { // if the script is accessed via the web.
20207b82 46 if (!empty($CFG->cronclionly)) {
e08df351 47 // This script can only be run via the cli.
48 print_error('cronerrorclionly', 'admin');
49 exit;
50 }
51 // This script is being called via the web, so check the password if there is one.
52 if (!empty($CFG->cronremotepassword)) {
53 $pass = optional_param('password', '', PARAM_RAW);
54 if($pass != $CFG->cronremotepassword) {
55 // wrong password.
20207b82 56 print_error('cronerrorpassword', 'admin');
e08df351 57 exit;
58 }
59 }
60 }
61
afabdb14 62
9152fc99 63/// emulate normal session
e8b7114d 64 cron_setup_user();
5e601222 65
66/// send mime type and encoding
58e8f85b 67 if (check_browser_version('MSIE')) {
68 //ugly IE hack to work around downloading instead of viewing
810944af 69 @header('Content-Type: text/html; charset=utf-8');
58e8f85b 70 echo "<xmp>"; //<pre> is not good enough for us here
71 } else {
72 //send proper plaintext header
810944af 73 @header('Content-Type: text/plain; charset=utf-8');
58e8f85b 74 }
5e601222 75
9152fc99 76/// no more headers and buffers
77 while(@ob_end_flush());
78
4eefee97 79/// increase memory limit (PHP 5.2 does different calculation, we need more memory now)
80 @raise_memory_limit('128M');
81
428b6fec 82/// Start output log
f9903ed0 83
f9903ed0 84 $timenow = time();
428b6fec 85
defaac4c 86 mtrace("Server Time: ".date('r',$timenow)."\n\n");
f9903ed0 87
dd9e22f8 88
89/// Session gc
90
91 mtrace("Cleaning up stale sessions");
e8656bef 92 session_gc();
dd9e22f8 93
b2ce450a 94/// Run all cron jobs for each module
c88f014a 95
defaac4c 96 mtrace("Starting activity modules");
c2f6fe2f 97 get_mailer('buffer');
953d72db 98 if ($mods = $DB->get_records_select("modules", "cron > 0 AND ((? - lastcron) > cron) AND visible = 1", array($timenow))) {
f9903ed0 99 foreach ($mods as $mod) {
673de292 100 $libfile = "$CFG->dirroot/mod/$mod->name/lib.php";
101 if (file_exists($libfile)) {
102 include_once($libfile);
103 $cron_function = $mod->name."_cron";
104 if (function_exists($cron_function)) {
defaac4c 105 mtrace("Processing module function $cron_function ...", '');
d9b06327 106 $pre_dbqueries = null;
ec22e380 107 $pre_dbqueries = $DB->perf_get_queries();
ab130a0b 108 $pre_time = microtime(1);
673de292 109 if ($cron_function()) {
953d72db 110 if (!$DB->set_field("modules", "lastcron", $timenow, array("id"=>$mod->id))) {
defaac4c 111 mtrace("Error: could not update timestamp for $mod->fullname");
673de292 112 }
113 }
ff991c05 114 if (isset($pre_dbqueries)) {
ec22e380 115 mtrace("... used " . ($DB->perf_get_queries() - $pre_dbqueries) . " dbqueries");
ff991c05 116 mtrace("... used " . (microtime(1) - $pre_time) . " seconds");
117 }
5e867b23 118 /// Reset possible changes by modules to time_limit. MDL-11597
119 @set_time_limit(0);
defaac4c 120 mtrace("done.");
f9903ed0 121 }
122 }
123 }
124 }
c2f6fe2f 125 get_mailer('close');
defaac4c 126 mtrace("Finished activity modules");
c88f014a 127
f9648e77 128 mtrace("Starting blocks");
953d72db 129 if ($blocks = $DB->get_records_select("block", "cron > 0 AND ((? - lastcron) > cron) AND visible = 1", array($timenow))) {
f9648e77 130 // we will need the base class.
131 require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
132 foreach ($blocks as $block) {
133 $blockfile = $CFG->dirroot.'/blocks/'.$block->name.'/block_'.$block->name.'.php';
134 if (file_exists($blockfile)) {
135 require_once($blockfile);
136 $classname = 'block_'.$block->name;
20207b82 137 $blockobj = new $classname;
f9648e77 138 if (method_exists($blockobj,'cron')) {
139 mtrace("Processing cron function for ".$block->name.'....','');
140 if ($blockobj->cron()) {
953d72db 141 if (!$DB->set_field('block', 'lastcron', $timenow, array('id'=>$block->id))) {
f9648e77 142 mtrace('Error: could not update timestamp for '.$block->name);
143 }
144 }
44853b81 145 /// Reset possible changes by blocks to time_limit. MDL-11597
146 @set_time_limit(0);
f9648e77 147 mtrace('done.');
148 }
149 }
150
151 }
152 }
0616d3e8 153 mtrace('Finished blocks');
f9648e77 154
17f1782c 155 mtrace("Starting quiz reports");
156 if ($reports = $DB->get_records_select('quiz_report', "cron > 0 AND ((? - lastcron) > cron)", array($timenow))) {
157 foreach ($reports as $report) {
158 $cronfile = "$CFG->dirroot/mod/quiz/report/$report->name/cron.php";
159 if (file_exists($cronfile)) {
160 include_once($cronfile);
161 $cron_function = 'quiz_report_'.$report->name."_cron";
162 if (function_exists($cron_function)) {
163 mtrace("Processing quiz report cron function $cron_function ...", '');
164 $pre_dbqueries = null;
165 $pre_dbqueries = $DB->perf_get_queries();
166 $pre_time = microtime(1);
167 if ($cron_function()) {
168 if (!$DB->set_field('quiz_report', "lastcron", $timenow, array("id"=>$report->id))) {
169 mtrace("Error: could not update timestamp for $report->name");
170 }
171 }
172 if (isset($pre_dbqueries)) {
173 mtrace("... used " . ($DB->perf_get_queries() - $pre_dbqueries) . " dbqueries");
174 mtrace("... used " . (microtime(1) - $pre_time) . " seconds");
175 }
176 mtrace("done.");
177 }
178 }
179 }
180 }
181 mtrace("Finished quiz reports");
20207b82 182
d9b06327 183 mtrace('Starting admin reports');
184 // Admin reports do not have a database table that lists them. Instead a
185 // report includes cron.php with function report_reportname_cron() if it wishes
186 // to be cronned. It is up to cron.php to handle e.g. if it only needs to
187 // actually do anything occasionally.
17da2e6f 188 $reports = get_plugin_list('report');
189 foreach($reports as $report => $reportdir) {
190 $cronfile = $reportdir.'/cron.php';
d9b06327 191 if (file_exists($cronfile)) {
192 require_once($cronfile);
40ad295c 193 $cronfunction = 'report_'.$report.'_cron';
d9b06327 194 mtrace('Processing cron function for '.$report.'...', '');
195 $pre_dbqueries = null;
ec22e380 196 $pre_dbqueries = $DB->perf_get_queries();
ab130a0b 197 $pre_time = microtime(true);
d9b06327 198 $cronfunction();
199 if (isset($pre_dbqueries)) {
ec22e380 200 mtrace("... used " . ($DB->perf_get_queries() - $pre_dbqueries) . " dbqueries");
d9b06327 201 mtrace("... used " . round(microtime(true) - $pre_time, 2) . " seconds");
202 }
203 mtrace('done.');
204 }
205 }
206 mtrace('Finished admin reports');
207
1ee0df06 208 mtrace('Starting main gradebook job ...');
2650c51e 209 grade_cron();
1ee0df06 210 mtrace('done.');
2650c51e 211
212
a5d56dc1 213 mtrace('Starting processing the event queue...');
214 events_cron();
215 mtrace('done.');
216
2be4d090
MD
217
218 if ($CFG->enablecompletion) {
219 // Completion cron
220 mtrace('Starting the completion cron...');
221 require_once($CFG->libdir . '/completion/cron.php');
222 completion_cron();
223 mtrace('done');
224 }
225
226
90658eef 227 if ($CFG->enableportfolios) {
7e51bbce 228 // Portfolio cron
229 mtrace('Starting the portfolio cron...');
230 require_once($CFG->libdir . '/portfoliolib.php');
231 portfolio_cron();
232 mtrace('done');
233 }
a5d56dc1 234
b2ce450a 235/// Run all core cron jobs, but not every time since they aren't too important.
20207b82 236/// These don't have a timer to reduce load, so we'll use a random number
b2ce450a 237/// to randomly choose the percentage of times we should run these jobs.
c88f014a 238
b2ce450a 239 srand ((double) microtime() * 10000000);
240 $random100 = rand(0,100);
c88f014a 241
b2ce450a 242 if ($random100 < 20) { // Approximately 20% of the time.
defaac4c 243 mtrace("Running clean-up tasks...");
b2ce450a 244
6c61fd04 245 /// Delete users who haven't confirmed within required period
c6c43ae9 246
8c182006 247 if (!empty($CFG->deleteunconfirmed)) {
fce3ef94 248 $cuttime = $timenow - ($CFG->deleteunconfirmed * 3600);
953d72db 249 $rs = $DB->get_recordset_sql ("SELECT id, firstname, lastname
250 FROM {user}
251 WHERE confirmed = 0 AND firstaccess > 0
252 AND firstaccess < ?", array($cuttime));
253 foreach ($rs as $user) {
254 if ($DB->delete_records('user', array('id'=>$user->id))) {
fce3ef94 255 mtrace("Deleted unconfirmed user for ".fullname($user, true)." ($user->id)");
f282e631 256 }
6cb86b39 257 }
953d72db 258 $rs->close();
6cb86b39 259 }
69f78bcb 260 flush();
7b8cc339 261
262
7b8cc339 263 /// Delete users who haven't completed profile within required period
264
a4a57da9 265 if (!empty($CFG->deleteincompleteusers)) {
266 $cuttime = $timenow - ($CFG->deleteincompleteusers * 3600);
953d72db 267 $rs = $DB->get_recordset_sql ("SELECT id, username
268 FROM {user}
269 WHERE confirmed = 1 AND lastaccess > 0
270 AND lastaccess < ? AND deleted = 0
271 AND (lastname = '' OR firstname = '' OR email = '')",
272 array($cuttime));
273 foreach ($rs as $user) {
a4a57da9 274 if (delete_user($user)) {
fce3ef94 275 mtrace("Deleted not fully setup user $user->username ($user->id)");
7b8cc339 276 }
277 }
953d72db 278 $rs->close();
7b8cc339 279 }
280 flush();
281
282
b2ce450a 283 /// Delete old logs to save space (this might need a timer to slow it down...)
fce3ef94 284
b2ce450a 285 if (!empty($CFG->loglifetime)) { // value in days
286 $loglifetime = $timenow - ($CFG->loglifetime * 3600 * 24);
953d72db 287 if ($DB->delete_records_select("log", "time < ?", array($loglifetime))) {
fce3ef94 288 mtrace("Deleted old log records");
289 }
b2ce450a 290 }
69f78bcb 291 flush();
cb3a4484 292
fce3ef94 293
cb3a4484 294 /// Delete old cached texts
295
296 if (!empty($CFG->cachetext)) { // Defined in config.php
a9743837 297 $cachelifetime = time() - $CFG->cachetext - 60; // Add an extra minute to allow for really heavy sites
953d72db 298 if ($DB->delete_records_select('cache_text', "timemodified < ?", array($cachelifetime))) {
fce3ef94 299 mtrace("Deleted old cache_text records");
300 }
cb3a4484 301 }
69f78bcb 302 flush();
d846e50a 303
304 if (!empty($CFG->notifyloginfailures)) {
305 notify_login_failures();
f1dc7614 306 mtrace('Notified login failured');
d846e50a 307 }
69f78bcb 308 flush();
d846e50a 309
e3b6b2b0 310 //
20207b82 311 // generate new password emails for users
e3b6b2b0 312 //
313 mtrace('checking for create_password');
953d72db 314 if ($DB->count_records('user_preferences', array('name'=>'create_password', 'value'=>'1'))) {
e3b6b2b0 315 mtrace('creating passwords for new users');
20207b82 316 $newusers = $DB->get_records_sql("SELECT u.id as id, u.email, u.firstname,
953d72db 317 u.lastname, u.username,
20207b82
PS
318 p.id as prefid
319 FROM {user} u
953d72db 320 JOIN {user_preferences} p ON u.id=p.userid
32426545 321 WHERE p.name='create_password' AND p.value='1' AND u.email !='' ");
e3b6b2b0 322
323 foreach ($newusers as $newuserid => $newuser) {
324 $newuser->emailstop = 0; // send email regardless
20207b82 325 // email user
e3b6b2b0 326 if (setnew_password_and_mail($newuser)) {
327 // remove user pref
953d72db 328 $DB->delete_records('user_preferences', array('id'=>$newuser->prefid));
e3b6b2b0 329 } else {
330 trigger_error("Could not create and mail new user password!");
331 }
332 }
333 }
20207b82 334
2cb5f03a 335 if (!empty($CFG->usetags)) {
19e41f75 336 require_once($CFG->dirroot.'/tag/lib.php');
337 tag_cron();
f1dc7614 338 mtrace ('Executed tag cron');
19e41f75 339 }
20207b82 340
ad72d0d7 341 // Accesslib stuff
342 cleanup_contexts();
f1dc7614 343 mtrace ('Cleaned up contexts');
fce3ef94 344 gc_cache_flags();
f1dc7614 345 mtrace ('Cleaned cache flags');
ad72d0d7 346 // If you suspect that the context paths are somehow corrupt
20207b82 347 // replace the line below with: build_context_path(true);
ad72d0d7 348 build_context_path();
f1dc7614 349 mtrace ('Built context paths');
350
351 mtrace("Finished clean-up tasks...");
ad72d0d7 352
d846e50a 353 } // End of occasional clean-up tasks
540bab47 354
c717a0d3
EL
355 // Disabled until implemented. MDL-21432, MDL-22184
356 if (1 == 2 && empty($CFG->disablescheduledbackups)) { // Defined in config.php
74736e49 357 //Execute backup's cron
358 //Perhaps a long time and memory could help in large sites
a71bfa1c 359 @set_time_limit(0);
8891e81c 360 @raise_memory_limit("192M");
a2ce7344 361 if (function_exists('apache_child_terminate')) {
20207b82
PS
362 // if we are running from Apache, give httpd a hint that
363 // it can recycle the process after it's done. Apache's
a2ce7344 364 // memory management is truly awful but we can help it.
365 @apache_child_terminate();
366 }
74736e49 367 if (file_exists("$CFG->dirroot/backup/backup_scheduled.php") and
368 file_exists("$CFG->dirroot/backup/backuplib.php") and
e91e2b6a 369 file_exists("$CFG->dirroot/backup/lib.php") and
370 file_exists("$CFG->libdir/blocklib.php")) {
74736e49 371 include_once("$CFG->dirroot/backup/backup_scheduled.php");
372 include_once("$CFG->dirroot/backup/backuplib.php");
373 include_once("$CFG->dirroot/backup/lib.php");
defaac4c 374 mtrace("Running backups if required...");
20207b82 375
74736e49 376 if (! schedule_backup_cron()) {
04ddd06c 377 mtrace("ERROR: Something went wrong while performing backup tasks!!!");
74736e49 378 } else {
defaac4c 379 mtrace("Backup tasks finished.");
74736e49 380 }
e6d273eb 381 }
d3159192 382 }
383
d14383e0 384/// Run the auth cron, if any
1619d109 385/// before enrolments because it might add users that will be needed in enrol plugins
c7b10b5f 386 $auths = get_enabled_auth_plugins();
387
d14383e0 388 mtrace("Running auth crons if required...");
389 foreach ($auths as $auth) {
390 $authplugin = get_auth_plugin($auth);
391 if (method_exists($authplugin, 'cron')) {
392 mtrace("Running cron for auth/$auth...");
393 $authplugin->cron();
394 if (!empty($authplugin->log)) {
395 mtrace($authplugin->log);
396 }
397 }
398 unset($authplugin);
399 }
400
df997f84
PS
401 mtrace("Running enrol crons if required...");
402 $enrols = enrol_get_plugins(true);
403 foreach($enrols as $ename=>$enrol) {
404 // do this for all plugins, disabled plugins might want to cleanup stuff such as roles
405 if (!$enrol->is_cron_required()) {
406 continue;
1619d109 407 }
df997f84
PS
408 mtrace("Running cron for enrol_$ename...");
409 $enrol->cron();
410 $enrol->set_config('lastcron', time());
1619d109 411 }
412
72df7e1e 413 if (!empty($CFG->enablestats) and empty($CFG->disablestatsprocessing)) {
61460dd6 414 require_once($CFG->dirroot.'/lib/statslib.php');
4900433c 415 // check we're not before our runtime
61460dd6 416 $timetocheck = stats_get_base_daily() + $CFG->statsruntimestarthour*60*60 + $CFG->statsruntimestartminute*60;
4900433c 417
418 if (time() > $timetocheck) {
5e7206a8 419 // process configured number of days as max (defaulting to 31)
420 $maxdays = empty($CFG->statsruntimedays) ? 31 : abs($CFG->statsruntimedays);
421 if (stats_cron_daily($maxdays)) {
61460dd6 422 if (stats_cron_weekly()) {
423 if (stats_cron_monthly()) {
424 stats_clean_old();
425 }
4900433c 426 }
f3221af9 427 }
61460dd6 428 @set_time_limit(0);
6fdde1d2 429 } else {
5a4b2e8e 430 mtrace('Next stats run after:'. userdate($timetocheck));
f3221af9 431 }
432 }
1ee0df06 433
ce34ed3a 434 // run gradebook import/export/report cron
17da2e6f 435 if ($gradeimports = get_plugin_list('gradeimport')) {
20207b82 436 foreach ($gradeimports as $gradeimport => $plugindir) {
17da2e6f 437 if (file_exists($plugindir.'/lib.php')) {
438 require_once($plugindir.'/lib.php');
20207b82 439 $cron_function = 'grade_import_'.$gradeimport.'_cron';
ce34ed3a 440 if (function_exists($cron_function)) {
441 mtrace("Processing gradebook import function $cron_function ...", '');
384531d5 442 $cron_function();
ce34ed3a 443 }
444 }
20207b82 445 }
ce34ed3a 446 }
447
17da2e6f 448 if ($gradeexports = get_plugin_list('gradeexport')) {
20207b82 449 foreach ($gradeexports as $gradeexport => $plugindir) {
17da2e6f 450 if (file_exists($plugindir.'/lib.php')) {
451 require_once($plugindir.'/lib.php');
20207b82 452 $cron_function = 'grade_export_'.$gradeexport.'_cron';
ce34ed3a 453 if (function_exists($cron_function)) {
454 mtrace("Processing gradebook export function $cron_function ...", '');
384531d5 455 $cron_function();
ce34ed3a 456 }
457 }
458 }
459 }
1ee0df06 460
17da2e6f 461 if ($gradereports = get_plugin_list('gradereport')) {
20207b82 462 foreach ($gradereports as $gradereport => $plugindir) {
17da2e6f 463 if (file_exists($plugindir.'/lib.php')) {
464 require_once($plugindir.'/lib.php');
20207b82 465 $cron_function = 'grade_report_'.$gradereport.'_cron';
ce34ed3a 466 if (function_exists($cron_function)) {
467 mtrace("Processing gradebook report function $cron_function ...", '');
384531d5 468 $cron_function();
ce34ed3a 469 }
470 }
471 }
472 }
20207b82 473
cae83708 474 // Run external blog cron if needed
475 if ($CFG->useexternalblogs) {
476 require_once($CFG->dirroot . '/blog/lib.php');
ec22ccd5 477 mtrace("Fetching external blog entries...", '');
17e5633c
PS
478 $sql = "timefetched < ? OR timefetched = 0";
479 $externalblogs = $DB->get_records_select('blog_external', $sql, array(mktime() - $CFG->externalblogcrontime));
20207b82 480
1fc216ff 481 foreach ($externalblogs as $eb) {
ec22ccd5 482 blog_sync_external_entries($eb);
cae83708 483 }
484 }
1aa01caf 485
ec22ccd5
NC
486 // Run blog associations cleanup
487 if ($CFG->useblogassociations) {
488 require_once($CFG->dirroot . '/blog/lib.php');
489 // delete entries whose contextids no longer exists
490 mtrace("Deleting blog associations linked to non-existent contexts...", '');
491 $DB->delete_records_select('blog_association', 'contextid NOT IN (SELECT id FROM {context})');
492 }
493
2ed993a6 494 //Run registration updated cron
495 mtrace(get_string('siteupdatesstart', 'hub'));
496 require_once($CFG->dirroot . '/admin/registration/lib.php');
497 $registrationmanager = new registration_manager();
498 $registrationmanager->cron();
499 mtrace(get_string('siteupdatesend', 'hub'));
500
1aa01caf 501 // cleanup file trash
502 $fs = get_file_storage();
503 $fs->cron();
2ed993a6 504
2d0acbd5
JP
505 //cleanup old session linked tokens
506 //deletes the session linked tokens that are over a day old.
507 mtrace("Deleting session linked tokens more than one day old...", '');
2ed993a6 508 $DB->delete_records_select('external_tokens', 'lastaccess < :onedayago AND tokentype = :tokentype',
2d0acbd5 509 array('onedayago' => time() - DAYSECS, 'tokentype' => EXTERNAL_TOKEN_EMBEDDED));
548da7ee 510 mtrace('done.');
2ed993a6 511
ba9b44f9 512 // run any customized cronjobs, if any
17da2e6f 513 if ($locals = get_plugin_list('local')) {
514 mtrace('Processing customized cron scripts ...', '');
515 foreach ($locals as $local => $localdir) {
516 if (file_exists("$localdir/cron.php")) {
517 include("$localdir/cron.php");
518 }
519 }
ba9b44f9 520 mtrace('done.');
521 }
522
f3221af9 523
defaac4c 524 mtrace("Cron script completed correctly");
f9903ed0 525
498d4032 526 $difftime = microtime_diff($starttime, microtime());
20207b82 527 mtrace("Execution took ".$difftime." seconds");
498d4032 528
28a082f1 529/// finish the IE hack
58e8f85b 530 if (check_browser_version('MSIE')) {
531 echo "</xmp>";
532 }
533
f578af59 534