MDL-20523 Script for upgrading sites that use the old bloglevel settings.
[moodle.git] / admin / cron.php
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 /// 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 ;-)
18     define('CLI_SCRIPT', true);
20 /// Following hack used to identify cron and other CLI scripts - use CLI_SCRIPT or $FULLME == "/$CFG->admin/cropn.php" instead
21     //define('FULLME', 'cron');
23 /// Do not set moodle cookie because we do not need it here, it is better to emulate session
24     define('NO_MOODLE_COOKIES', true);
26     require_once(dirname(__FILE__) . '/../config.php');
27     require_once($CFG->libdir.'/adminlib.php');
28     require_once($CFG->libdir.'/gradelib.php');
30 /// Extra debugging (set in config.php)
31     if (!empty($CFG->showcronsql)) {
32         $DB->set_debug(true);
33     }
34     if (!empty($CFG->showcrondebugging)) {
35         $CFG->debug = DEBUG_DEVELOPER;
36         $CFG->debugdisplay = true;
37     }
39 /// extra safety
40     session_get_instance()->write_close();
42 /// check if execution allowed
43     if (isset($_SERVER['REMOTE_ADDR'])) { // if the script is accessed via the web.
44         if (!empty($CFG->cronclionly)) { 
45             // This script can only be run via the cli.
46             print_error('cronerrorclionly', 'admin');
47             exit;
48         }
49         // This script is being called via the web, so check the password if there is one.
50         if (!empty($CFG->cronremotepassword)) {
51             $pass = optional_param('password', '', PARAM_RAW);
52             if($pass != $CFG->cronremotepassword) {
53                 // wrong password.
54                 print_error('cronerrorpassword', 'admin'); 
55                 exit;
56             }
57         }
58     }
61 /// emulate normal session
62     cron_setup_user();
64 /// send mime type and encoding
65     if (check_browser_version('MSIE')) {
66         //ugly IE hack to work around downloading instead of viewing
67         @header('Content-Type: text/html; charset=utf-8');
68         echo "<xmp>"; //<pre> is not good enough for us here
69     } else {
70         //send proper plaintext header
71         @header('Content-Type: text/plain; charset=utf-8');
72     }
74 /// no more headers and buffers
75     while(@ob_end_flush());
77 /// increase memory limit (PHP 5.2 does different calculation, we need more memory now)
78     @raise_memory_limit('128M');
80 /// Start output log
82     $timenow  = time();
84     mtrace("Server Time: ".date('r',$timenow)."\n\n");
87 /// Session gc
89     mtrace("Cleaning up stale sessions");
90     session_gc();
92 /// Run all cron jobs for each module
94     mtrace("Starting activity modules");
95     get_mailer('buffer');
96     if ($mods = $DB->get_records_select("modules", "cron > 0 AND ((? - lastcron) > cron) AND visible = 1", array($timenow))) {
97         foreach ($mods as $mod) {
98             $libfile = "$CFG->dirroot/mod/$mod->name/lib.php";
99             if (file_exists($libfile)) {
100                 include_once($libfile);
101                 $cron_function = $mod->name."_cron";
102                 if (function_exists($cron_function)) {
103                     mtrace("Processing module function $cron_function ...", '');
104                     $pre_dbqueries = null;
105                     $pre_dbqueries = $DB->perf_get_queries();
106                     $pre_time      = microtime(1);
107                     if ($cron_function()) {
108                         if (!$DB->set_field("modules", "lastcron", $timenow, array("id"=>$mod->id))) {
109                             mtrace("Error: could not update timestamp for $mod->fullname");
110                         }
111                     }
112                     if (isset($pre_dbqueries)) {
113                         mtrace("... used " . ($DB->perf_get_queries() - $pre_dbqueries) . " dbqueries");
114                         mtrace("... used " . (microtime(1) - $pre_time) . " seconds");
115                     }
116                 /// Reset possible changes by modules to time_limit. MDL-11597
117                     @set_time_limit(0);
118                     mtrace("done.");
119                 }
120             }
121         }
122     }
123     get_mailer('close');
124     mtrace("Finished activity modules");
126     mtrace("Starting blocks");
127     if ($blocks = $DB->get_records_select("block", "cron > 0 AND ((? - lastcron) > cron) AND visible = 1", array($timenow))) {
128         // we will need the base class.
129         require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
130         foreach ($blocks as $block) {
131             $blockfile = $CFG->dirroot.'/blocks/'.$block->name.'/block_'.$block->name.'.php';
132             if (file_exists($blockfile)) {
133                 require_once($blockfile);
134                 $classname = 'block_'.$block->name;
135                 $blockobj = new $classname; 
136                 if (method_exists($blockobj,'cron')) {
137                     mtrace("Processing cron function for ".$block->name.'....','');
138                     if ($blockobj->cron()) {
139                         if (!$DB->set_field('block', 'lastcron', $timenow, array('id'=>$block->id))) {
140                             mtrace('Error: could not update timestamp for '.$block->name);
141                         }
142                     }
143                 /// Reset possible changes by blocks to time_limit. MDL-11597
144                     @set_time_limit(0);
145                     mtrace('done.');
146                 }
147             }
149         }
150     }
151     mtrace('Finished blocks');
153     mtrace("Starting quiz reports");
154     if ($reports = $DB->get_records_select('quiz_report', "cron > 0 AND ((? - lastcron) > cron)", array($timenow))) {
155         foreach ($reports as $report) {
156             $cronfile = "$CFG->dirroot/mod/quiz/report/$report->name/cron.php";
157             if (file_exists($cronfile)) {
158                 include_once($cronfile);
159                 $cron_function = 'quiz_report_'.$report->name."_cron";
160                 if (function_exists($cron_function)) {
161                     mtrace("Processing quiz report cron function $cron_function ...", '');
162                     $pre_dbqueries = null;
163                     $pre_dbqueries = $DB->perf_get_queries();
164                     $pre_time      = microtime(1);
165                     if ($cron_function()) {
166                         if (!$DB->set_field('quiz_report', "lastcron", $timenow, array("id"=>$report->id))) {
167                             mtrace("Error: could not update timestamp for $report->name");
168                         }
169                     }
170                     if (isset($pre_dbqueries)) {
171                         mtrace("... used " . ($DB->perf_get_queries() - $pre_dbqueries) . " dbqueries");
172                         mtrace("... used " . (microtime(1) - $pre_time) . " seconds");
173                     }
174                     mtrace("done.");
175                 }
176             }
177         }
178     }
179     mtrace("Finished quiz reports");
180     
181     mtrace('Starting admin reports');
182     // Admin reports do not have a database table that lists them. Instead a
183     // report includes cron.php with function report_reportname_cron() if it wishes
184     // to be cronned. It is up to cron.php to handle e.g. if it only needs to
185     // actually do anything occasionally.
186     $reports = get_plugin_list('report');
187     foreach($reports as $report => $reportdir) {
188         $cronfile = $reportdir.'/cron.php';
189         if (file_exists($cronfile)) {
190             require_once($cronfile);
191             $cronfunction = 'report_'.$report.'_cron';
192             mtrace('Processing cron function for '.$report.'...', '');
193             $pre_dbqueries = null;
194             $pre_dbqueries = $DB->perf_get_queries();
195             $pre_time      = microtime(true);
196             $cronfunction();
197             if (isset($pre_dbqueries)) {
198                 mtrace("... used " . ($DB->perf_get_queries() - $pre_dbqueries) . " dbqueries");
199                 mtrace("... used " . round(microtime(true) - $pre_time, 2) . " seconds");
200             }
201             mtrace('done.');
202         }
203     }
204     mtrace('Finished admin reports');
206     if (!empty($CFG->langcache)) {
207         mtrace('Updating languages cache');
208         get_list_of_languages(true);
209     }
211     mtrace('Removing expired enrolments ...', '');     // See MDL-8785
212     $timenow = time();
213     $somefound = false;
214     // The preferred way saves memory, datablib
215     // find courses where limited enrolment is enabled
216     $sql = "SELECT ra.roleid, ra.userid, ra.contextid
217               FROM {course} c
218               JOIN {context} cx ON cx.instanceid = c.id
219               JOIN {role_assignments} ra ON ra.contextid = cx.id
220              WHERE cx.contextlevel = '".CONTEXT_COURSE."'
221                    AND ra.timeend > 0
222                    AND ra.timeend < ?
223                    AND c.enrolperiod > 0";
224     if ($rs = $DB->get_recordset_sql($sql, array($timenow))) {
225         foreach ($rs as $oldenrolment) {
226             role_unassign($oldenrolment->roleid, $oldenrolment->userid, 0, $oldenrolment->contextid);
227             $somefound = true;
228         }
229         $rs->close();
230     }
231     if ($somefound) {
232         mtrace('Done');
233     } else {
234         mtrace('none found');
235     }
239     mtrace('Starting main gradebook job ...');
240     grade_cron();
241     mtrace('done.');
244     mtrace('Starting processing the event queue...');
245     events_cron();
246     mtrace('done.');
248     if ($CFG->enableportfolios) {
249         // Portfolio cron
250         mtrace('Starting the portfolio cron...');
251         require_once($CFG->libdir . '/portfoliolib.php');
252         portfolio_cron();
253         mtrace('done');
254     }
256 /// Run all core cron jobs, but not every time since they aren't too important.
257 /// These don't have a timer to reduce load, so we'll use a random number 
258 /// to randomly choose the percentage of times we should run these jobs.
260     srand ((double) microtime() * 10000000);
261     $random100 = rand(0,100);
263     if ($random100 < 20) {     // Approximately 20% of the time.
264         mtrace("Running clean-up tasks...");
266         /// Unenrol users who haven't logged in for $CFG->longtimenosee
268         if ($CFG->longtimenosee) { // value in days
269             $cuttime = $timenow - ($CFG->longtimenosee * 3600 * 24);
270             $rs = $DB->get_recordset_sql ("SELECT id, userid, courseid
271                                              FROM {user_lastaccess}
272                                             WHERE courseid != ".SITEID."
273                                                   AND timeaccess < ?", array($cuttime));
274             foreach ($rs as $assign) {
275                 if ($context = get_context_instance(CONTEXT_COURSE, $assign->courseid)) {
276                     if (role_unassign(0, $assign->userid, 0, $context->id)) {
277                         mtrace("removing user $assign->userid from course $assign->courseid as they have not accessed the course for over $CFG->longtimenosee days");
278                     }
279                 }
280             }
281             $rs->close();
282         /// Execute the same query again, looking for remaining records and deleting them
283         /// if the user hasn't moodle/course:view in the CONTEXT_COURSE context (orphan records)
284             $rs = $DB->get_recordset_sql ("SELECT id, userid, courseid
285                                              FROM {user_lastaccess}
286                                             WHERE courseid != ".SITEID."
287                                                   AND timeaccess < ?", array($cuttime));
288             foreach ($rs as $assign) {
289                 if ($context = get_context_instance(CONTEXT_COURSE, $assign->courseid)) {
290                     if (!has_capability('moodle/course:view', $context, $assign->userid)) {
291                         $DB->delete_records('user_lastaccess', array('userid'=>$assign->userid, 'courseid'=>$assign->courseid));
292                         mtrace("Deleted orphan user_lastaccess for user $assign->userid from course $assign->courseid");
293                     }
294                 }
295             }
296             $rs->close();
297         }
298         flush();
301         /// Delete users who haven't confirmed within required period
303         if (!empty($CFG->deleteunconfirmed)) {
304             $cuttime = $timenow - ($CFG->deleteunconfirmed * 3600);
305             $rs = $DB->get_recordset_sql ("SELECT id, firstname, lastname
306                                              FROM {user}
307                                             WHERE confirmed = 0 AND firstaccess > 0
308                                                   AND firstaccess < ?", array($cuttime));
309             foreach ($rs as $user) {
310                 if ($DB->delete_records('user', array('id'=>$user->id))) {
311                     mtrace("Deleted unconfirmed user for ".fullname($user, true)." ($user->id)");
312                 }
313             }
314             $rs->close();
315         }
316         flush();
319         /// Delete users who haven't completed profile within required period
321         if (!empty($CFG->deleteincompleteusers)) {
322             $cuttime = $timenow - ($CFG->deleteincompleteusers * 3600);
323             $rs = $DB->get_recordset_sql ("SELECT id, username
324                                              FROM {user}
325                                             WHERE confirmed = 1 AND lastaccess > 0
326                                                   AND lastaccess < ? AND deleted = 0
327                                                   AND (lastname = '' OR firstname = '' OR email = '')",
328                                           array($cuttime));
329             foreach ($rs as $user) {
330                 if (delete_user($user)) {
331                     mtrace("Deleted not fully setup user $user->username ($user->id)");
332                 }
333             }
334             $rs->close();
335         }
336         flush();
339         /// Delete old logs to save space (this might need a timer to slow it down...)
341         if (!empty($CFG->loglifetime)) {  // value in days
342             $loglifetime = $timenow - ($CFG->loglifetime * 3600 * 24);
343             if ($DB->delete_records_select("log", "time < ?", array($loglifetime))) {
344                 mtrace("Deleted old log records");
345             }
346         }
347         flush();
350         /// Delete old cached texts
352         if (!empty($CFG->cachetext)) {   // Defined in config.php
353             $cachelifetime = time() - $CFG->cachetext - 60;  // Add an extra minute to allow for really heavy sites
354             if ($DB->delete_records_select('cache_text', "timemodified < ?", array($cachelifetime))) {
355                 mtrace("Deleted old cache_text records");
356             }
357         }
358         flush();
360         if (!empty($CFG->notifyloginfailures)) {
361             notify_login_failures();
362             mtrace('Notified login failured');
363         }
364         flush();
366         sync_metacourses();
367         mtrace('Synchronised metacourses');
369         //
370         // generate new password emails for users 
371         //
372         mtrace('checking for create_password');
373         if ($DB->count_records('user_preferences', array('name'=>'create_password', 'value'=>'1'))) {
374             mtrace('creating passwords for new users');
375             $newusers = $DB->get_records_sql("SELECT u.id as id, u.email, u.firstname, 
376                                                      u.lastname, u.username,
377                                                      p.id as prefid 
378                                                 FROM {user} u 
379                                                 JOIN {user_preferences} p ON u.id=p.userid
380                                                WHERE p.name='create_password' AND p.value='1' AND u.email !='' ");
382             foreach ($newusers as $newuserid => $newuser) {
383                 $newuser->emailstop = 0; // send email regardless
384                 // email user                               
385                 if (setnew_password_and_mail($newuser)) {
386                     // remove user pref
387                     $DB->delete_records('user_preferences', array('id'=>$newuser->prefid));
388                 } else {
389                     trigger_error("Could not create and mail new user password!");
390                 }
391             }
392         }
393         
394         if (!empty($CFG->usetags)) {
395             require_once($CFG->dirroot.'/tag/lib.php');
396             tag_cron();
397             mtrace ('Executed tag cron');
398         }
399         
400         // Accesslib stuff
401         cleanup_contexts();
402         mtrace ('Cleaned up contexts');
403         gc_cache_flags();
404         mtrace ('Cleaned cache flags');
405         // If you suspect that the context paths are somehow corrupt
406         // replace the line below with: build_context_path(true); 
407         build_context_path();
408         mtrace ('Built context paths');
410         mtrace("Finished clean-up tasks...");
412     } // End of occasional clean-up tasks
415     if (empty($CFG->disablescheduledbackups)) {   // Defined in config.php
416         //Execute backup's cron
417         //Perhaps a long time and memory could help in large sites
418         @set_time_limit(0);
419         @raise_memory_limit("192M");
420         if (function_exists('apache_child_terminate')) {
421             // if we are running from Apache, give httpd a hint that 
422             // it can recycle the process after it's done. Apache's 
423             // memory management is truly awful but we can help it.
424             @apache_child_terminate();
425         }
426         if (file_exists("$CFG->dirroot/backup/backup_scheduled.php") and
427             file_exists("$CFG->dirroot/backup/backuplib.php") and
428             file_exists("$CFG->dirroot/backup/lib.php") and
429             file_exists("$CFG->libdir/blocklib.php")) {
430             include_once("$CFG->dirroot/backup/backup_scheduled.php");
431             include_once("$CFG->dirroot/backup/backuplib.php");
432             include_once("$CFG->dirroot/backup/lib.php");
433             mtrace("Running backups if required...");
434     
435             if (! schedule_backup_cron()) {
436                 mtrace("ERROR: Something went wrong while performing backup tasks!!!");
437             } else {
438                 mtrace("Backup tasks finished.");
439             }
440         }
441     }
443     if (!empty($CFG->enablerssfeeds)) {  //Defined in admin/variables page
444         include_once("$CFG->libdir/rsslib.php");
445         mtrace("Running rssfeeds if required...");
447         if ( ! cron_rss_feeds()) {
448             mtrace("Something went wrong while generating rssfeeds!!!");
449         } else {
450             mtrace("Rssfeeds finished");
451         }
452     }
454 /// Run the auth cron, if any
455 /// before enrolments because it might add users that will be needed in enrol plugins
456     $auths = get_enabled_auth_plugins();
458     mtrace("Running auth crons if required...");
459     foreach ($auths as $auth) {
460         $authplugin = get_auth_plugin($auth);
461         if (method_exists($authplugin, 'cron')) {
462             mtrace("Running cron for auth/$auth...");
463             $authplugin->cron();
464             if (!empty($authplugin->log)) {
465                 mtrace($authplugin->log);
466             }
467         }
468         unset($authplugin);
469     }
471 /// Run the enrolment cron, if any
472     if (!($plugins = explode(',', $CFG->enrol_plugins_enabled))) {
473         $plugins = array($CFG->enrol);
474     }
475     require_once($CFG->dirroot .'/enrol/enrol.class.php');
476     foreach ($plugins as $p) {
477         $enrol = enrolment_factory::factory($p);
478         if (method_exists($enrol, 'cron')) {
479             $enrol->cron();
480         }
481         if (!empty($enrol->log)) {
482             mtrace($enrol->log);
483         }
484         unset($enrol);
485     }
487     if (!empty($CFG->enablestats) and empty($CFG->disablestatsprocessing)) {
488         require_once($CFG->dirroot.'/lib/statslib.php');
489         // check we're not before our runtime
490         $timetocheck = stats_get_base_daily() + $CFG->statsruntimestarthour*60*60 + $CFG->statsruntimestartminute*60;
492         if (time() > $timetocheck) {
493             // process configured number of days as max (defaulting to 31)
494             $maxdays = empty($CFG->statsruntimedays) ? 31 : abs($CFG->statsruntimedays);
495             if (stats_cron_daily($maxdays)) {
496                 if (stats_cron_weekly()) {
497                     if (stats_cron_monthly()) {
498                         stats_clean_old();
499                     }
500                 }
501             }
502             @set_time_limit(0);
503         } else {
504             mtrace('Next stats run after:'. userdate($timetocheck));
505         }
506     }
508     // run gradebook import/export/report cron
509     if ($gradeimports = get_plugin_list('gradeimport')) {
510         foreach ($gradeimports as $gradeimport => $plugindir) {           
511             if (file_exists($plugindir.'/lib.php')) {
512                 require_once($plugindir.'/lib.php');
513                 $cron_function = 'grade_import_'.$gradeimport.'_cron';                                    
514                 if (function_exists($cron_function)) {
515                     mtrace("Processing gradebook import function $cron_function ...", '');
516                     $cron_function;  
517                 }
518             }
519         }      
520     }
522     if ($gradeexports = get_plugin_list('gradeexport')) {
523         foreach ($gradeexports as $gradeexport => $plugindir) {           
524             if (file_exists($plugindir.'/lib.php')) {
525                 require_once($plugindir.'/lib.php');
526                 $cron_function = 'grade_export_'.$gradeexport.'_cron';                                    
527                 if (function_exists($cron_function)) {
528                     mtrace("Processing gradebook export function $cron_function ...", '');
529                     $cron_function;  
530                 }
531             }
532         }
533     }
535     if ($gradereports = get_plugin_list('gradereport')) {
536         foreach ($gradereports as $gradereport => $plugindir) {           
537             if (file_exists($plugindir.'/lib.php')) {
538                 require_once($plugindir.'/lib.php');
539                 $cron_function = 'grade_report_'.$gradereport.'_cron';                                    
540                 if (function_exists($cron_function)) {
541                     mtrace("Processing gradebook report function $cron_function ...", '');
542                     $cron_function;  
543                 }
544             }
545         }
546     }
547     
548     // Run external blog cron if needed
549     if ($CFG->useexternalblogs) {
550         require_once($CFG->dirroot . '/blog/lib.php');
551         mtrace("Fetching external blog entries...", '');
552         $sql = "timefetched < ? - ? OR timefetched = 0";
553         $externalblogs = $DB->get_records_select('blog_external', $sql, array(mktime(), $CFG->externalblogcrontime));
554         
555         foreach ($external_blogs as $eb) {
556             blog_sync_external_entries($eb);
557         }
558     }
560     // Run blog associations cleanup
561     if ($CFG->useblogassociations) {
562         require_once($CFG->dirroot . '/blog/lib.php');
563         // delete entries whose contextids no longer exists
564         mtrace("Deleting blog associations linked to non-existent contexts...", '');
565         $DB->delete_records_select('blog_association', 'contextid NOT IN (SELECT id FROM {context})');
566     }
568     // cleanup file trash
569     $fs = get_file_storage();
570     $fs->cron();
572     // run any customized cronjobs, if any
573     if ($locals = get_plugin_list('local')) {
574         mtrace('Processing customized cron scripts ...', '');
575         foreach ($locals as $local => $localdir) {
576             if (file_exists("$localdir/cron.php")) {
577                 include("$localdir/cron.php");
578             }
579         }
580         mtrace('done.');
581     }
584     mtrace("Cron script completed correctly");
586     $difftime = microtime_diff($starttime, microtime());
587     mtrace("Execution took ".$difftime." seconds"); 
589 /// finish the IE hack
590     if (check_browser_version('MSIE')) {
591         echo "</xmp>";
592     }
594 ?>