0b812304464a6c532c62e3c52e5aecd59b9c2b83
[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 /// 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     define('NO_MOODLE_COOKIES', 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]));
31     }
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->set_debug(true);
40     }
41     if (!empty($CFG->showcrondebugging)) {
42         $CFG->debug = DEBUG_DEVELOPER;
43         $CFG->debugdisplay = true;
44     }
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;
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     }
68 /// emulate normal session
69     $USER = get_admin();      /// Temporarily, to provide environment for this script
71 /// ignore admins timezone, language and locale - use site deafult instead!
72     $USER->timezone = $CFG->timezone;
73     $USER->lang = '';
74     $USER->theme = '';
75     course_setup(SITEID);
77 /// send mime type and encoding
78     if (check_browser_version('MSIE')) {
79         //ugly IE hack to work around downloading instead of viewing
80         @header('Content-Type: text/html; charset=utf-8');
81         echo "<xmp>"; //<pre> is not good enough for us here
82     } else {
83         //send proper plaintext header
84         @header('Content-Type: text/plain; charset=utf-8');
85     }
87 /// no more headers and buffers
88     while(@ob_end_flush());
90 /// increase memory limit (PHP 5.2 does different calculation, we need more memory now)
91     @raise_memory_limit('128M');
93 /// Start output log
95     $timenow  = time();
97     mtrace("Server Time: ".date('r',$timenow)."\n\n");
99 /// Run all cron jobs for each module
101     mtrace("Starting activity modules");
102     get_mailer('buffer');
103     if ($mods = $DB->get_records_select("modules", "cron > 0 AND ((? - lastcron) > cron) AND visible = 1", array($timenow))) {
104         foreach ($mods as $mod) {
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)) {
110                     mtrace("Processing module function $cron_function ...", '');
111                     $pre_dbqueries = null;
112                     $pre_dbqueries = $DB->perf_get_queries();
113                     $pre_time      = microtime(1);
114                     if ($cron_function()) {
115                         if (!$DB->set_field("modules", "lastcron", $timenow, array("id"=>$mod->id))) {
116                             mtrace("Error: could not update timestamp for $mod->fullname");
117                         }
118                     }
119                     if (isset($pre_dbqueries)) {
120                         mtrace("... used " . ($DB->perf_get_queries() - $pre_dbqueries) . " dbqueries");
121                         mtrace("... used " . (microtime(1) - $pre_time) . " seconds");
122                     }
123                 /// Reset possible changes by modules to time_limit. MDL-11597
124                     @set_time_limit(0);
125                     mtrace("done.");
126                 }
127             }
128         }
129     }
130     get_mailer('close');
131     mtrace("Finished activity modules");
133     mtrace("Starting blocks");
134     if ($blocks = $DB->get_records_select("block", "cron > 0 AND ((? - lastcron) > cron) AND visible = 1", array($timenow))) {
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()) {
146                         if (!$DB->set_field('block', 'lastcron', $timenow, array('id'=>$block->id))) {
147                             mtrace('Error: could not update timestamp for '.$block->name);
148                         }
149                     }
150                 /// Reset possible changes by blocks to time_limit. MDL-11597
151                     @set_time_limit(0);
152                     mtrace('done.');
153                 }
154             }
156         }
157     }
158     mtrace('Finished blocks');
160     mtrace("Starting quiz reports");
161     if ($reports = $DB->get_records_select('quiz_report', "cron > 0 AND ((? - lastcron) > cron)", array($timenow))) {
162         foreach ($reports as $report) {
163             $cronfile = "$CFG->dirroot/mod/quiz/report/$report->name/cron.php";
164             if (file_exists($cronfile)) {
165                 include_once($cronfile);
166                 $cron_function = 'quiz_report_'.$report->name."_cron";
167                 if (function_exists($cron_function)) {
168                     mtrace("Processing quiz report cron function $cron_function ...", '');
169                     $pre_dbqueries = null;
170                     $pre_dbqueries = $DB->perf_get_queries();
171                     $pre_time      = microtime(1);
172                     if ($cron_function()) {
173                         if (!$DB->set_field('quiz_report', "lastcron", $timenow, array("id"=>$report->id))) {
174                             mtrace("Error: could not update timestamp for $report->name");
175                         }
176                     }
177                     if (isset($pre_dbqueries)) {
178                         mtrace("... used " . ($DB->perf_get_queries() - $pre_dbqueries) . " dbqueries");
179                         mtrace("... used " . (microtime(1) - $pre_time) . " seconds");
180                     }
181                     mtrace("done.");
182                 }
183             }
184         }
185     }
186     mtrace("Finished quiz reports");
187     
188     mtrace('Starting admin reports');
189     // Admin reports do not have a database table that lists them. Instead a
190     // report includes cron.php with function report_reportname_cron() if it wishes
191     // to be cronned. It is up to cron.php to handle e.g. if it only needs to
192     // actually do anything occasionally.
193     $reports = get_list_of_plugins($CFG->admin.'/report');
194     foreach($reports as $report) {
195         $cronfile = $CFG->dirroot.'/'.$CFG->admin.'/report/'.$report.'/cron.php';
196         if (file_exists($cronfile)) {
197             require_once($cronfile);
198             $cronfunction = 'report_'.$report.'_cron';
199             mtrace('Processing cron function for '.$report.'...', '');
200             $pre_dbqueries = null;
201             $pre_dbqueries = $DB->perf_get_queries();
202             $pre_time      = microtime(true);
203             $cronfunction();
204             if (isset($pre_dbqueries)) {
205                 mtrace("... used " . ($DB->perf_get_queries() - $pre_dbqueries) . " dbqueries");
206                 mtrace("... used " . round(microtime(true) - $pre_time, 2) . " seconds");
207             }
208             mtrace('done.');
209         }
210     }
211     mtrace('Finished admin reports');
213     if (!empty($CFG->langcache)) {
214         mtrace('Updating languages cache');
215         get_list_of_languages(true);
216     }
218     mtrace('Removing expired enrolments ...', '');     // See MDL-8785
219     $timenow = time();
220     $somefound = false;
221     // The preferred way saves memory, datablib
222     // find courses where limited enrolment is enabled
223     $sql = "SELECT ra.roleid, ra.userid, ra.contextid
224               FROM {course} c
225               JOIN {context} cx ON cx.instanceid = c.id
226               JOIN {role_assignments} ra ON ra.contextid = cx.id
227              WHERE cx.contextlevel = '".CONTEXT_COURSE."'
228                    AND ra.timeend > 0
229                    AND ra.timeend < ?
230                    AND c.enrolperiod > 0";
231     if ($rs = $DB->get_recordset_sql($sql, array($timenow))) {
232         foreach ($rs as $oldenrolment) {
233             role_unassign($oldenrolment->roleid, $oldenrolment->userid, 0, $oldenrolment->contextid);
234             $somefound = true;
235         }
236         $rs->close();
237     }
238     if ($somefound) {
239         mtrace('Done');
240     } else {
241         mtrace('none found');
242     }
246     mtrace('Starting main gradebook job ...');
247     grade_cron();
248     mtrace('done.');
251     mtrace('Starting processing the event queue...');
252     events_cron();
253     mtrace('done.');
255     if ($CFG->enableportfolios) {
256         // Portfolio cron
257         mtrace('Starting the portfolio cron...');
258         require_once($CFG->libdir . '/portfoliolib.php');
259         portfolio_cron();
260         mtrace('done');
261     }
263 /// Run all core cron jobs, but not every time since they aren't too important.
264 /// These don't have a timer to reduce load, so we'll use a random number 
265 /// to randomly choose the percentage of times we should run these jobs.
267     srand ((double) microtime() * 10000000);
268     $random100 = rand(0,100);
270     if ($random100 < 20) {     // Approximately 20% of the time.
271         mtrace("Running clean-up tasks...");
273         /// Unenrol users who haven't logged in for $CFG->longtimenosee
275         if ($CFG->longtimenosee) { // value in days
276             $cuttime = $timenow - ($CFG->longtimenosee * 3600 * 24);
277             $rs = $DB->get_recordset_sql ("SELECT id, userid, courseid
278                                              FROM {user_lastaccess}
279                                             WHERE courseid != ".SITEID."
280                                                   AND timeaccess < ?", array($cuttime));
281             foreach ($rs as $assign) {
282                 if ($context = get_context_instance(CONTEXT_COURSE, $assign->courseid)) {
283                     if (role_unassign(0, $assign->userid, 0, $context->id)) {
284                         mtrace("removing user $assign->userid from course $assign->courseid as they have not accessed the course for over $CFG->longtimenosee days");
285                     }
286                 }
287             }
288             $rs->close();
289         /// Execute the same query again, looking for remaining records and deleting them
290         /// if the user hasn't moodle/course:view in the CONTEXT_COURSE context (orphan records)
291             $rs = $DB->get_recordset_sql ("SELECT id, userid, courseid
292                                              FROM {user_lastaccess}
293                                             WHERE courseid != ".SITEID."
294                                                   AND timeaccess < ?", array($cuttime));
295             foreach ($rs as $assign) {
296                 if ($context = get_context_instance(CONTEXT_COURSE, $assign->courseid)) {
297                     if (!has_capability('moodle/course:view', $context, $assign->userid)) {
298                         $DB->delete_records('user_lastaccess', array('userid'=>$assign->userid, 'courseid'=>$assign->courseid));
299                         mtrace("Deleted orphan user_lastaccess for user $assign->userid from course $assign->courseid");
300                     }
301                 }
302             }
303             $rs->close();
304         }
305         flush();
308         /// Delete users who haven't confirmed within required period
310         if (!empty($CFG->deleteunconfirmed)) {
311             $cuttime = $timenow - ($CFG->deleteunconfirmed * 3600);
312             $rs = $DB->get_recordset_sql ("SELECT id, firstname, lastname
313                                              FROM {user}
314                                             WHERE confirmed = 0 AND firstaccess > 0
315                                                   AND firstaccess < ?", array($cuttime));
316             foreach ($rs as $user) {
317                 if ($DB->delete_records('user', array('id'=>$user->id))) {
318                     mtrace("Deleted unconfirmed user for ".fullname($user, true)." ($user->id)");
319                 }
320             }
321             $rs->close();
322         }
323         flush();
326         /// Delete users who haven't completed profile within required period
328         if (!empty($CFG->deleteincompleteusers)) {
329             $cuttime = $timenow - ($CFG->deleteincompleteusers * 3600);
330             $rs = $DB->get_recordset_sql ("SELECT id, username
331                                              FROM {user}
332                                             WHERE confirmed = 1 AND lastaccess > 0
333                                                   AND lastaccess < ? AND deleted = 0
334                                                   AND (lastname = '' OR firstname = '' OR email = '')",
335                                           array($cuttime));
336             foreach ($rs as $user) {
337                 if (delete_user($user)) {
338                     mtrace("Deleted not fully setup user $user->username ($user->id)");
339                 }
340             }
341             $rs->close();
342         }
343         flush();
346         /// Delete old logs to save space (this might need a timer to slow it down...)
348         if (!empty($CFG->loglifetime)) {  // value in days
349             $loglifetime = $timenow - ($CFG->loglifetime * 3600 * 24);
350             if ($DB->delete_records_select("log", "time < ?", array($loglifetime))) {
351                 mtrace("Deleted old log records");
352             }
353         }
354         flush();
357         /// Delete old cached texts
359         if (!empty($CFG->cachetext)) {   // Defined in config.php
360             $cachelifetime = time() - $CFG->cachetext - 60;  // Add an extra minute to allow for really heavy sites
361             if ($DB->delete_records_select('cache_text', "timemodified < ?", array($cachelifetime))) {
362                 mtrace("Deleted old cache_text records");
363             }
364         }
365         flush();
367         if (!empty($CFG->notifyloginfailures)) {
368             notify_login_failures();
369             mtrace('Notified login failured');
370         }
371         flush();
373         sync_metacourses();
374         mtrace('Synchronised metacourses');
376         //
377         // generate new password emails for users 
378         //
379         mtrace('checking for create_password');
380         if ($DB->count_records('user_preferences', array('name'=>'create_password', 'value'=>'1'))) {
381             mtrace('creating passwords for new users');
382             $newusers = $DB->get_records_sql("SELECT u.id as id, u.email, u.firstname, 
383                                                      u.lastname, u.username,
384                                                      p.id as prefid 
385                                                 FROM {user} u 
386                                                 JOIN {user_preferences} p ON u.id=p.userid
387                                                WHERE p.name='create_password' AND p.value=1 AND u.email !='' ");
389             foreach ($newusers as $newuserid => $newuser) {
390                 $newuser->emailstop = 0; // send email regardless
391                 // email user                               
392                 if (setnew_password_and_mail($newuser)) {
393                     // remove user pref
394                     $DB->delete_records('user_preferences', array('id'=>$newuser->prefid));
395                 } else {
396                     trigger_error("Could not create and mail new user password!");
397                 }
398             }
399         }
400         
401         if (!empty($CFG->usetags)) {
402             require_once($CFG->dirroot.'/tag/lib.php');
403             tag_cron();
404             mtrace ('Executed tag cron');
405         }
406         
407         // Accesslib stuff
408         cleanup_contexts();
409         mtrace ('Cleaned up contexts');
410         gc_cache_flags();
411         mtrace ('Cleaned cache flags');
412         // If you suspect that the context paths are somehow corrupt
413         // replace the line below with: build_context_path(true); 
414         build_context_path();
415         mtrace ('Built context paths');
417         mtrace("Finished clean-up tasks...");
419     } // End of occasional clean-up tasks
422     if (empty($CFG->disablescheduledbackups)) {   // Defined in config.php
423         //Execute backup's cron
424         //Perhaps a long time and memory could help in large sites
425         @set_time_limit(0);
426         @raise_memory_limit("192M");
427         if (function_exists('apache_child_terminate')) {
428             // if we are running from Apache, give httpd a hint that 
429             // it can recycle the process after it's done. Apache's 
430             // memory management is truly awful but we can help it.
431             @apache_child_terminate();
432         }
433         if (file_exists("$CFG->dirroot/backup/backup_scheduled.php") and
434             file_exists("$CFG->dirroot/backup/backuplib.php") and
435             file_exists("$CFG->dirroot/backup/lib.php") and
436             file_exists("$CFG->libdir/blocklib.php")) {
437             include_once("$CFG->dirroot/backup/backup_scheduled.php");
438             include_once("$CFG->dirroot/backup/backuplib.php");
439             include_once("$CFG->dirroot/backup/lib.php");
440             require_once ("$CFG->libdir/blocklib.php");
441             mtrace("Running backups if required...");
442     
443             if (! schedule_backup_cron()) {
444                 mtrace("ERROR: Something went wrong while performing backup tasks!!!");
445             } else {
446                 mtrace("Backup tasks finished.");
447             }
448         }
449     }
451     if (!empty($CFG->enablerssfeeds)) {  //Defined in admin/variables page
452         include_once("$CFG->libdir/rsslib.php");
453         mtrace("Running rssfeeds if required...");
455         if ( ! cron_rss_feeds()) {
456             mtrace("Something went wrong while generating rssfeeds!!!");
457         } else {
458             mtrace("Rssfeeds finished");
459         }
460     }
462 /// Run the enrolment cron, if any
463     if (!($plugins = explode(',', $CFG->enrol_plugins_enabled))) {
464         $plugins = array($CFG->enrol);
465     }
466     require_once($CFG->dirroot .'/enrol/enrol.class.php');
467     foreach ($plugins as $p) {
468         $enrol = enrolment_factory::factory($p);
469         if (method_exists($enrol, 'cron')) {
470             $enrol->cron();
471         }
472         if (!empty($enrol->log)) {
473             mtrace($enrol->log);
474         }
475         unset($enrol);
476     }
478 /// Run the auth cron, if any
479     $auths = get_enabled_auth_plugins();
481     mtrace("Running auth crons if required...");
482     foreach ($auths as $auth) {
483         $authplugin = get_auth_plugin($auth);
484         if (method_exists($authplugin, 'cron')) {
485             mtrace("Running cron for auth/$auth...");
486             $authplugin->cron();
487             if (!empty($authplugin->log)) {
488                 mtrace($authplugin->log);
489             }
490         }
491         unset($authplugin);
492     }
494     if (!empty($CFG->enablestats) and empty($CFG->disablestatsprocessing)) {
495         require_once($CFG->dirroot.'/lib/statslib.php');
496         // check we're not before our runtime
497         $timetocheck = stats_get_base_daily() + $CFG->statsruntimestarthour*60*60 + $CFG->statsruntimestartminute*60;
499         if (time() > $timetocheck) {
500             // process configured number of days as max (defaulting to 31)
501             $maxdays = empty($CFG->statsruntimedays) ? 31 : abs($CFG->statsruntimedays);
502             if (stats_cron_daily($maxdays)) {
503                 if (stats_cron_weekly()) {
504                     if (stats_cron_monthly()) {
505                         stats_clean_old();
506                     }
507                 }
508             }
509             @set_time_limit(0);
510         } else {
511             mtrace('Next stats run after:'. userdate($timetocheck));
512         }
513     }
515     // run gradebook import/export/report cron
516     if ($gradeimports = get_list_of_plugins('grade/import')) {
517         foreach ($gradeimports as $gradeimport) {           
518             if (file_exists($CFG->dirroot.'/grade/import/'.$gradeimport.'/lib.php')) {
519                 require_once($CFG->dirroot.'/grade/import/'.$gradeimport.'/lib.php');
520                 $cron_function = 'grade_import_'.$gradeimport.'_cron';                                    
521                 if (function_exists($cron_function)) {
522                     mtrace("Processing gradebook import function $cron_function ...", '');
523                     $cron_function;  
524                 }
525             }
526         }      
527     }
529     if ($gradeexports = get_list_of_plugins('grade/export')) {
530         foreach ($gradeexports as $gradeexport) {           
531             if (file_exists($CFG->dirroot.'/grade/export/'.$gradeexport.'/lib.php')) {
532                 require_once($CFG->dirroot.'/grade/export/'.$gradeexport.'/lib.php');
533                 $cron_function = 'grade_export_'.$gradeexport.'_cron';                                    
534                 if (function_exists($cron_function)) {
535                     mtrace("Processing gradebook export function $cron_function ...", '');
536                     $cron_function;  
537                 }
538             }
539         }
540     }
542     if ($gradereports = get_list_of_plugins('grade/report')) {
543         foreach ($gradereports as $gradereport) {           
544             if (file_exists($CFG->dirroot.'/grade/report/'.$gradereport.'/lib.php')) {
545                 require_once($CFG->dirroot.'/grade/report/'.$gradereport.'/lib.php');
546                 $cron_function = 'grade_report_'.$gradereport.'_cron';                                    
547                 if (function_exists($cron_function)) {
548                     mtrace("Processing gradebook report function $cron_function ...", '');
549                     $cron_function;  
550                 }
551             }
552         }
553     }
554     
555     // run any customized cronjobs, if any
556     // looking for functions in lib/local/cron.php
557     if (file_exists($CFG->dirroot.'/local/cron.php')) {
558         mtrace('Processing customized cron script ...', '');
559         include_once($CFG->dirroot.'/local/cron.php');
560         mtrace('done.');
561     }
564     //Unset session variables and destroy it
565     @session_unset();
566     @session_destroy();
568     mtrace("Cron script completed correctly");
570     $difftime = microtime_diff($starttime, microtime());
571     mtrace("Execution took ".$difftime." seconds"); 
573 /// finish the IE hack
574     if (check_browser_version('MSIE')) {
575         echo "</xmp>";
576     }
578 ?>