MDL-14216 "change query to work on Postgres." - some adjustments to make it work...
[moodle.git] / admin / cron.php
CommitLineData
fce3ef94 1<?php // $Id$
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'
12/// or php /web/moodle/admin/cron.php
168df8f3 13 set_time_limit(0);
498d4032 14 $starttime = microtime();
f9f4d999 15
16/// The following is a hack necessary to allow this script to work well
17/// from the command line.
18
19 define('FULLME', 'cron');
9152fc99 20
21
22/// Do not set moodle cookie because we do not need it here, it is better to emulate session
6800d78e 23 define('NO_MOODLE_COOKIES', true);
9152fc99 24
d5f286f1 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()
28
29 if (!isset($_SERVER['REMOTE_ADDR']) && isset($_SERVER['argv'][0])) {
30 chdir(dirname($_SERVER['argv'][0]));
31 }
498d4032 32
331ca297 33 require_once(dirname(__FILE__) . '/../config.php');
6e4dc10f 34 require_once($CFG->libdir.'/adminlib.php');
2650c51e 35 require_once($CFG->libdir.'/gradelib.php');
f9903ed0 36
63a11f3c 37/// Extra debugging (set in config.php)
38 if (!empty($CFG->showcronsql)) {
f33e1ed4 39 $DB->set_debug(true);
63a11f3c 40 }
41 if (!empty($CFG->showcrondebugging)) {
42 $CFG->debug = DEBUG_DEVELOPER;
43 $CFG->displaydebug = true;
44 }
45
9152fc99 46/// extra safety
47 @session_write_close();
48
49/// check if execution allowed
e08df351 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;
55 }
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;
63 }
64 }
65 }
66
afabdb14 67
9152fc99 68/// emulate normal session
9152fc99 69 $USER = get_admin(); /// Temporarily, to provide environment for this script
00de82df 70
9152fc99 71/// ignore admins timezone, language and locale - use site deafult instead!
72 $USER->timezone = $CFG->timezone;
5e601222 73 $USER->lang = '';
9152fc99 74 $USER->theme = '';
75 course_setup(SITEID);
5e601222 76
77/// send mime type and encoding
58e8f85b 78 if (check_browser_version('MSIE')) {
79 //ugly IE hack to work around downloading instead of viewing
810944af 80 @header('Content-Type: text/html; charset=utf-8');
58e8f85b 81 echo "<xmp>"; //<pre> is not good enough for us here
82 } else {
83 //send proper plaintext header
810944af 84 @header('Content-Type: text/plain; charset=utf-8');
58e8f85b 85 }
5e601222 86
9152fc99 87/// no more headers and buffers
88 while(@ob_end_flush());
89
4eefee97 90/// increase memory limit (PHP 5.2 does different calculation, we need more memory now)
91 @raise_memory_limit('128M');
92
428b6fec 93/// Start output log
f9903ed0 94
f9903ed0 95 $timenow = time();
428b6fec 96
defaac4c 97 mtrace("Server Time: ".date('r',$timenow)."\n\n");
f9903ed0 98
b2ce450a 99/// Run all cron jobs for each module
c88f014a 100
defaac4c 101 mtrace("Starting activity modules");
c2f6fe2f 102 get_mailer('buffer');
953d72db 103 if ($mods = $DB->get_records_select("modules", "cron > 0 AND ((? - lastcron) > cron) AND visible = 1", array($timenow))) {
f9903ed0 104 foreach ($mods as $mod) {
673de292 105 $libfile = "$CFG->dirroot/mod/$mod->name/lib.php";
106 if (file_exists($libfile)) {
107 include_once($libfile);
108 $cron_function = $mod->name."_cron";
109 if (function_exists($cron_function)) {
defaac4c 110 mtrace("Processing module function $cron_function ...", '');
d9b06327 111 $pre_dbqueries = null;
ec22e380 112 $pre_dbqueries = $DB->perf_get_queries();
ab130a0b 113 $pre_time = microtime(1);
673de292 114 if ($cron_function()) {
953d72db 115 if (!$DB->set_field("modules", "lastcron", $timenow, array("id"=>$mod->id))) {
defaac4c 116 mtrace("Error: could not update timestamp for $mod->fullname");
673de292 117 }
118 }
ff991c05 119 if (isset($pre_dbqueries)) {
ec22e380 120 mtrace("... used " . ($DB->perf_get_queries() - $pre_dbqueries) . " dbqueries");
ff991c05 121 mtrace("... used " . (microtime(1) - $pre_time) . " seconds");
122 }
5e867b23 123 /// Reset possible changes by modules to time_limit. MDL-11597
124 @set_time_limit(0);
defaac4c 125 mtrace("done.");
f9903ed0 126 }
127 }
128 }
129 }
c2f6fe2f 130 get_mailer('close');
defaac4c 131 mtrace("Finished activity modules");
c88f014a 132
f9648e77 133 mtrace("Starting blocks");
953d72db 134 if ($blocks = $DB->get_records_select("block", "cron > 0 AND ((? - lastcron) > cron) AND visible = 1", array($timenow))) {
f9648e77 135 // we will need the base class.
136 require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
137 foreach ($blocks as $block) {
138 $blockfile = $CFG->dirroot.'/blocks/'.$block->name.'/block_'.$block->name.'.php';
139 if (file_exists($blockfile)) {
140 require_once($blockfile);
141 $classname = 'block_'.$block->name;
142 $blockobj = new $classname;
143 if (method_exists($blockobj,'cron')) {
144 mtrace("Processing cron function for ".$block->name.'....','');
145 if ($blockobj->cron()) {
953d72db 146 if (!$DB->set_field('block', 'lastcron', $timenow, array('id'=>$block->id))) {
f9648e77 147 mtrace('Error: could not update timestamp for '.$block->name);
148 }
149 }
44853b81 150 /// Reset possible changes by blocks to time_limit. MDL-11597
151 @set_time_limit(0);
f9648e77 152 mtrace('done.');
153 }
154 }
155
156 }
157 }
0616d3e8 158 mtrace('Finished blocks');
f9648e77 159
d9b06327 160 mtrace('Starting admin reports');
161 // Admin reports do not have a database table that lists them. Instead a
162 // report includes cron.php with function report_reportname_cron() if it wishes
163 // to be cronned. It is up to cron.php to handle e.g. if it only needs to
164 // actually do anything occasionally.
165 $reports = get_list_of_plugins($CFG->admin.'/report');
166 foreach($reports as $report) {
167 $cronfile = $CFG->dirroot.'/'.$CFG->admin.'/report/'.$report.'/cron.php';
168 if (file_exists($cronfile)) {
169 require_once($cronfile);
170 $cronfunction = 'report_'.$report.'_cron';
171 mtrace('Processing cron function for '.$report.'...', '');
172 $pre_dbqueries = null;
ec22e380 173 $pre_dbqueries = $DB->perf_get_queries();
ab130a0b 174 $pre_time = microtime(true);
d9b06327 175 $cronfunction();
176 if (isset($pre_dbqueries)) {
ec22e380 177 mtrace("... used " . ($DB->perf_get_queries() - $pre_dbqueries) . " dbqueries");
d9b06327 178 mtrace("... used " . round(microtime(true) - $pre_time, 2) . " seconds");
179 }
180 mtrace('done.');
181 }
182 }
183 mtrace('Finished admin reports');
184
d6ead3a2 185 if (!empty($CFG->langcache)) {
186 mtrace('Updating languages cache');
0a5ce9dd 187 get_list_of_languages(true);
d6ead3a2 188 }
189
0616d3e8 190 mtrace('Removing expired enrolments ...', ''); // See MDL-8785
191 $timenow = time();
cf057534 192 $somefound = false;
3dce78e1 193 // The preferred way saves memory, datablib
cf057534 194 // find courses where limited enrolment is enabled
953d72db 195 $sql = "SELECT ra.roleid, ra.userid, ra.contextid
196 FROM {course} c
197 JOIN {context} cx ON cx.instanceid = c.id
198 JOIN {role_assignments} ra ON ra.contextid = cx.id
199 WHERE cx.contextlevel = '".CONTEXT_COURSE."'
200 AND ra.timeend > 0
201 AND ra.timeend < ?
202 AND c.enrolperiod > 0";
203 if ($rs = $DB->get_recordset_sql($sql, array($timenow))) {
204 foreach ($rs as $oldenrolment) {
205 role_unassign($oldenrolment->roleid, $oldenrolment->userid, 0, $oldenrolment->contextid);
206 $somefound = true;
207 }
208 $rs->close();
cf057534 209 }
953d72db 210 if ($somefound) {
0616d3e8 211 mtrace('Done');
212 } else {
213 mtrace('none found');
214 }
215
d6ead3a2 216
d9b06327 217
1ee0df06 218 mtrace('Starting main gradebook job ...');
2650c51e 219 grade_cron();
1ee0df06 220 mtrace('done.');
2650c51e 221
222
a5d56dc1 223 mtrace('Starting processing the event queue...');
224 events_cron();
225 mtrace('done.');
226
227
b2ce450a 228/// Run all core cron jobs, but not every time since they aren't too important.
229/// These don't have a timer to reduce load, so we'll use a random number
230/// to randomly choose the percentage of times we should run these jobs.
c88f014a 231
b2ce450a 232 srand ((double) microtime() * 10000000);
233 $random100 = rand(0,100);
c88f014a 234
b2ce450a 235 if ($random100 < 20) { // Approximately 20% of the time.
defaac4c 236 mtrace("Running clean-up tasks...");
b2ce450a 237
238 /// Unenrol users who haven't logged in for $CFG->longtimenosee
239
240 if ($CFG->longtimenosee) { // value in days
fce3ef94 241 $cuttime = $timenow - ($CFG->longtimenosee * 3600 * 24);
953d72db 242 $rs = $DB->get_recordset_sql ("SELECT id, userid, courseid
243 FROM {user_lastaccess}
244 WHERE courseid != ".SITEID."
245 AND timeaccess < ?", array($cuttime));
246 foreach ($rs as $assign) {
fce3ef94 247 if ($context = get_context_instance(CONTEXT_COURSE, $assign->courseid)) {
248 if (role_unassign(0, $assign->userid, 0, $context->id)) {
249 mtrace("Deleted assignment for user $assign->userid from course $assign->courseid");
b2ce450a 250 }
251 }
252 }
953d72db 253 $rs->close();
d9420631 254 /// Execute the same query again, looking for remaining records and deleting them
255 /// if the user hasn't moodle/course:view in the CONTEXT_COURSE context (orphan records)
953d72db 256 $rs = $DB->get_recordset_sql ("SELECT id, userid, courseid
257 FROM {user_lastaccess}
258 WHERE courseid != ".SITEID."
259 AND timeaccess < ?", array($cuttime));
260 foreach ($rs as $assign) {
d9420631 261 if ($context = get_context_instance(CONTEXT_COURSE, $assign->courseid)) {
262 if (!has_capability('moodle/course:view', $context, $assign->userid)) {
953d72db 263 $DB->delete_records('user_lastaccess', array('userid'=>$assign->userid, 'courseid'=>$assign->courseid));
d9420631 264 mtrace("Deleted orphan user_lastaccess for user $assign->userid from course $assign->courseid");
265 }
266 }
267 }
953d72db 268 $rs->close();
b2ce450a 269 }
fce3ef94 270 flush();
271
272
6c61fd04 273 /// Delete users who haven't confirmed within required period
c6c43ae9 274
8c182006 275 if (!empty($CFG->deleteunconfirmed)) {
fce3ef94 276 $cuttime = $timenow - ($CFG->deleteunconfirmed * 3600);
953d72db 277 $rs = $DB->get_recordset_sql ("SELECT id, firstname, lastname
278 FROM {user}
279 WHERE confirmed = 0 AND firstaccess > 0
280 AND firstaccess < ?", array($cuttime));
281 foreach ($rs as $user) {
282 if ($DB->delete_records('user', array('id'=>$user->id))) {
fce3ef94 283 mtrace("Deleted unconfirmed user for ".fullname($user, true)." ($user->id)");
f282e631 284 }
6cb86b39 285 }
953d72db 286 $rs->close();
6cb86b39 287 }
69f78bcb 288 flush();
7b8cc339 289
290
7b8cc339 291 /// Delete users who haven't completed profile within required period
292
a4a57da9 293 if (!empty($CFG->deleteincompleteusers)) {
294 $cuttime = $timenow - ($CFG->deleteincompleteusers * 3600);
953d72db 295 $rs = $DB->get_recordset_sql ("SELECT id, username
296 FROM {user}
297 WHERE confirmed = 1 AND lastaccess > 0
298 AND lastaccess < ? AND deleted = 0
299 AND (lastname = '' OR firstname = '' OR email = '')",
300 array($cuttime));
301 foreach ($rs as $user) {
a4a57da9 302 if (delete_user($user)) {
fce3ef94 303 mtrace("Deleted not fully setup user $user->username ($user->id)");
7b8cc339 304 }
305 }
953d72db 306 $rs->close();
7b8cc339 307 }
308 flush();
309
310
b2ce450a 311 /// Delete old logs to save space (this might need a timer to slow it down...)
fce3ef94 312
b2ce450a 313 if (!empty($CFG->loglifetime)) { // value in days
314 $loglifetime = $timenow - ($CFG->loglifetime * 3600 * 24);
953d72db 315 if ($DB->delete_records_select("log", "time < ?", array($loglifetime))) {
fce3ef94 316 mtrace("Deleted old log records");
317 }
b2ce450a 318 }
69f78bcb 319 flush();
cb3a4484 320
fce3ef94 321
cb3a4484 322 /// Delete old cached texts
323
324 if (!empty($CFG->cachetext)) { // Defined in config.php
a9743837 325 $cachelifetime = time() - $CFG->cachetext - 60; // Add an extra minute to allow for really heavy sites
953d72db 326 if ($DB->delete_records_select('cache_text', "timemodified < ?", array($cachelifetime))) {
fce3ef94 327 mtrace("Deleted old cache_text records");
328 }
cb3a4484 329 }
69f78bcb 330 flush();
d846e50a 331
332 if (!empty($CFG->notifyloginfailures)) {
333 notify_login_failures();
f1dc7614 334 mtrace('Notified login failured');
d846e50a 335 }
69f78bcb 336 flush();
d846e50a 337
b61efafb 338 sync_metacourses();
f1dc7614 339 mtrace('Synchronised metacourses');
b61efafb 340
e3b6b2b0 341 //
342 // generate new password emails for users
343 //
344 mtrace('checking for create_password');
953d72db 345 if ($DB->count_records('user_preferences', array('name'=>'create_password', 'value'=>'1'))) {
e3b6b2b0 346 mtrace('creating passwords for new users');
953d72db 347 $newusers = $DB->get_records_sql("SELECT u.id as id, u.email, u.firstname,
348 u.lastname, u.username,
349 p.id as prefid
350 FROM {user} u
351 JOIN {user_preferences} p ON u.id=p.userid
352 WHERE p.name='create_password' AND p.value=1 AND u.email !='' ");
e3b6b2b0 353
354 foreach ($newusers as $newuserid => $newuser) {
355 $newuser->emailstop = 0; // send email regardless
356 // email user
357 if (setnew_password_and_mail($newuser)) {
358 // remove user pref
953d72db 359 $DB->delete_records('user_preferences', array('id'=>$newuser->prefid));
e3b6b2b0 360 } else {
361 trigger_error("Could not create and mail new user password!");
362 }
363 }
364 }
7ea9b095 365
2cb5f03a 366 if (!empty($CFG->usetags)) {
19e41f75 367 require_once($CFG->dirroot.'/tag/lib.php');
368 tag_cron();
f1dc7614 369 mtrace ('Executed tag cron');
19e41f75 370 }
371
ad72d0d7 372 // Accesslib stuff
373 cleanup_contexts();
f1dc7614 374 mtrace ('Cleaned up contexts');
fce3ef94 375 gc_cache_flags();
f1dc7614 376 mtrace ('Cleaned cache flags');
ad72d0d7 377 // If you suspect that the context paths are somehow corrupt
378 // replace the line below with: build_context_path(true);
379 build_context_path();
f1dc7614 380 mtrace ('Built context paths');
381
382 mtrace("Finished clean-up tasks...");
ad72d0d7 383
d846e50a 384 } // End of occasional clean-up tasks
540bab47 385
750eeaeb 386
50999a0b 387 if (empty($CFG->disablescheduledbackups)) { // Defined in config.php
74736e49 388 //Execute backup's cron
389 //Perhaps a long time and memory could help in large sites
a71bfa1c 390 @set_time_limit(0);
8891e81c 391 @raise_memory_limit("192M");
a2ce7344 392 if (function_exists('apache_child_terminate')) {
393 // if we are running from Apache, give httpd a hint that
394 // it can recycle the process after it's done. Apache's
395 // memory management is truly awful but we can help it.
396 @apache_child_terminate();
397 }
74736e49 398 if (file_exists("$CFG->dirroot/backup/backup_scheduled.php") and
399 file_exists("$CFG->dirroot/backup/backuplib.php") and
e91e2b6a 400 file_exists("$CFG->dirroot/backup/lib.php") and
401 file_exists("$CFG->libdir/blocklib.php")) {
74736e49 402 include_once("$CFG->dirroot/backup/backup_scheduled.php");
403 include_once("$CFG->dirroot/backup/backuplib.php");
404 include_once("$CFG->dirroot/backup/lib.php");
e91e2b6a 405 require_once ("$CFG->libdir/blocklib.php");
defaac4c 406 mtrace("Running backups if required...");
74736e49 407
408 if (! schedule_backup_cron()) {
04ddd06c 409 mtrace("ERROR: Something went wrong while performing backup tasks!!!");
74736e49 410 } else {
defaac4c 411 mtrace("Backup tasks finished.");
74736e49 412 }
e6d273eb 413 }
d3159192 414 }
415
8adcb49f 416 if (!empty($CFG->enablerssfeeds)) { //Defined in admin/variables page
3c02d134 417 include_once("$CFG->libdir/rsslib.php");
418 mtrace("Running rssfeeds if required...");
8adcb49f 419
3c02d134 420 if ( ! cron_rss_feeds()) {
421 mtrace("Something went wrong while generating rssfeeds!!!");
422 } else {
423 mtrace("Rssfeeds finished");
8adcb49f 424 }
425 }
426
04e44325 427/// Run the enrolment cron, if any
f9667a5a 428 if (!($plugins = explode(',', $CFG->enrol_plugins_enabled))) {
429 $plugins = array($CFG->enrol);
430 }
431 require_once($CFG->dirroot .'/enrol/enrol.class.php');
432 foreach ($plugins as $p) {
433 $enrol = enrolment_factory::factory($p);
434 if (method_exists($enrol, 'cron')) {
435 $enrol->cron();
436 }
437 if (!empty($enrol->log)) {
438 mtrace($enrol->log);
439 }
440 unset($enrol);
7e192c78 441 }
04e44325 442
d14383e0 443/// Run the auth cron, if any
c7b10b5f 444 $auths = get_enabled_auth_plugins();
445
d14383e0 446 mtrace("Running auth crons if required...");
447 foreach ($auths as $auth) {
448 $authplugin = get_auth_plugin($auth);
449 if (method_exists($authplugin, 'cron')) {
450 mtrace("Running cron for auth/$auth...");
451 $authplugin->cron();
452 if (!empty($authplugin->log)) {
453 mtrace($authplugin->log);
454 }
455 }
456 unset($authplugin);
457 }
458
72df7e1e 459 if (!empty($CFG->enablestats) and empty($CFG->disablestatsprocessing)) {
61460dd6 460 require_once($CFG->dirroot.'/lib/statslib.php');
4900433c 461 // check we're not before our runtime
61460dd6 462 $timetocheck = stats_get_base_daily() + $CFG->statsruntimestarthour*60*60 + $CFG->statsruntimestartminute*60;
4900433c 463
464 if (time() > $timetocheck) {
61460dd6 465 // process max 31 days per cron execution
466 if (stats_cron_daily(31)) {
467 if (stats_cron_weekly()) {
468 if (stats_cron_monthly()) {
469 stats_clean_old();
470 }
4900433c 471 }
f3221af9 472 }
61460dd6 473 @set_time_limit(0);
6fdde1d2 474 } else {
5a4b2e8e 475 mtrace('Next stats run after:'. userdate($timetocheck));
f3221af9 476 }
477 }
1ee0df06 478
ce34ed3a 479 // run gradebook import/export/report cron
480 if ($gradeimports = get_list_of_plugins('grade/import')) {
481 foreach ($gradeimports as $gradeimport) {
1ee0df06 482 if (file_exists($CFG->dirroot.'/grade/import/'.$gradeimport.'/lib.php')) {
483 require_once($CFG->dirroot.'/grade/import/'.$gradeimport.'/lib.php');
484 $cron_function = 'grade_import_'.$gradeimport.'_cron';
ce34ed3a 485 if (function_exists($cron_function)) {
486 mtrace("Processing gradebook import function $cron_function ...", '');
487 $cron_function;
488 }
489 }
490 }
491 }
492
493 if ($gradeexports = get_list_of_plugins('grade/export')) {
494 foreach ($gradeexports as $gradeexport) {
1ee0df06 495 if (file_exists($CFG->dirroot.'/grade/export/'.$gradeexport.'/lib.php')) {
496 require_once($CFG->dirroot.'/grade/export/'.$gradeexport.'/lib.php');
497 $cron_function = 'grade_export_'.$gradeexport.'_cron';
ce34ed3a 498 if (function_exists($cron_function)) {
499 mtrace("Processing gradebook export function $cron_function ...", '');
500 $cron_function;
501 }
502 }
503 }
504 }
1ee0df06 505
ce34ed3a 506 if ($gradereports = get_list_of_plugins('grade/report')) {
507 foreach ($gradereports as $gradereport) {
1ee0df06 508 if (file_exists($CFG->dirroot.'/grade/report/'.$gradereport.'/lib.php')) {
509 require_once($CFG->dirroot.'/grade/report/'.$gradereport.'/lib.php');
510 $cron_function = 'grade_report_'.$gradereport.'_cron';
ce34ed3a 511 if (function_exists($cron_function)) {
512 mtrace("Processing gradebook report function $cron_function ...", '');
513 $cron_function;
514 }
515 }
516 }
517 }
ba9b44f9 518
519 // run any customized cronjobs, if any
520 // looking for functions in lib/local/cron.php
40e473bf 521 if (file_exists($CFG->dirroot.'/local/cron.php')) {
ba9b44f9 522 mtrace('Processing customized cron script ...', '');
40e473bf 523 include_once($CFG->dirroot.'/local/cron.php');
ba9b44f9 524 mtrace('done.');
525 }
526
f3221af9 527
db6b033d 528 //Unset session variables and destroy it
529 @session_unset();
530 @session_destroy();
531
defaac4c 532 mtrace("Cron script completed correctly");
f9903ed0 533
498d4032 534 $difftime = microtime_diff($starttime, microtime());
defaac4c 535 mtrace("Execution took ".$difftime." seconds");
498d4032 536
28a082f1 537/// finish the IE hack
58e8f85b 538 if (check_browser_version('MSIE')) {
539 echo "</xmp>";
540 }
541
1afb11e7 542?>