ceacba389776d7dff056b0aeede3b71624116951
[moodle.git] / lib / cronlib.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * Cron functions.
20  *
21  * @package    core
22  * @subpackage admin
23  * @copyright  1999 onwards Martin Dougiamas  http://dougiamas.com
24  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25  */
27 function cron_run() {
28     global $DB, $CFG, $OUTPUT;
30     if (CLI_MAINTENANCE) {
31         echo "CLI maintenance mode active, cron execution suspended.\n";
32         exit(1);
33     }
35     if (moodle_needs_upgrading()) {
36         echo "Moodle upgrade pending, cron execution suspended.\n";
37         exit(1);
38     }
40     require_once($CFG->libdir.'/adminlib.php');
41     require_once($CFG->libdir.'/gradelib.php');
43     if (!empty($CFG->showcronsql)) {
44         $DB->set_debug(true);
45     }
46     if (!empty($CFG->showcrondebugging)) {
47         $CFG->debug = DEBUG_DEVELOPER;
48         $CFG->debugdisplay = true;
49     }
51     set_time_limit(0);
52     $starttime = microtime();
54 /// increase memory limit (PHP 5.2 does different calculation, we need more memory now)
55     @raise_memory_limit('128M');
57 /// emulate normal session
58     cron_setup_user();
60 /// Start output log
62     $timenow  = time();
64     mtrace("Server Time: ".date('r',$timenow)."\n\n");
67 /// Session gc
69     mtrace("Cleaning up stale sessions");
70     session_gc();
72 /// Run all cron jobs for each module
74     mtrace("Starting activity modules");
75     get_mailer('buffer');
76     if ($mods = $DB->get_records_select("modules", "cron > 0 AND ((? - lastcron) > cron) AND visible = 1", array($timenow))) {
77         foreach ($mods as $mod) {
78             $libfile = "$CFG->dirroot/mod/$mod->name/lib.php";
79             if (file_exists($libfile)) {
80                 include_once($libfile);
81                 $cron_function = $mod->name."_cron";
82                 if (function_exists($cron_function)) {
83                     mtrace("Processing module function $cron_function ...", '');
84                     $pre_dbqueries = null;
85                     $pre_dbqueries = $DB->perf_get_queries();
86                     $pre_time      = microtime(1);
87                     if ($cron_function()) {
88                         $DB->set_field("modules", "lastcron", $timenow, array("id"=>$mod->id));
89                     }
90                     if (isset($pre_dbqueries)) {
91                         mtrace("... used " . ($DB->perf_get_queries() - $pre_dbqueries) . " dbqueries");
92                         mtrace("... used " . (microtime(1) - $pre_time) . " seconds");
93                     }
94                 /// Reset possible changes by modules to time_limit. MDL-11597
95                     @set_time_limit(0);
96                     mtrace("done.");
97                 }
98             }
99         }
100     }
101     get_mailer('close');
102     mtrace("Finished activity modules");
104     mtrace("Starting blocks");
105     if ($blocks = $DB->get_records_select("block", "cron > 0 AND ((? - lastcron) > cron) AND visible = 1", array($timenow))) {
106         // we will need the base class.
107         require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
108         foreach ($blocks as $block) {
109             $blockfile = $CFG->dirroot.'/blocks/'.$block->name.'/block_'.$block->name.'.php';
110             if (file_exists($blockfile)) {
111                 require_once($blockfile);
112                 $classname = 'block_'.$block->name;
113                 $blockobj = new $classname;
114                 if (method_exists($blockobj,'cron')) {
115                     mtrace("Processing cron function for ".$block->name.'....','');
116                     if ($blockobj->cron()) {
117                         $DB->set_field('block', 'lastcron', $timenow, array('id'=>$block->id));
118                     }
119                 /// Reset possible changes by blocks to time_limit. MDL-11597
120                     @set_time_limit(0);
121                     mtrace('done.');
122                 }
123             }
125         }
126     }
127     mtrace('Finished blocks');
129     //now do plagiarism checks
130     require_once($CFG->libdir.'/plagiarismlib.php');
131     plagiarism_cron();
133     mtrace("Starting quiz reports");
134     if ($reports = $DB->get_records_select('quiz_report', "cron > 0 AND ((? - lastcron) > cron)", array($timenow))) {
135         foreach ($reports as $report) {
136             $cronfile = "$CFG->dirroot/mod/quiz/report/$report->name/cron.php";
137             if (file_exists($cronfile)) {
138                 include_once($cronfile);
139                 $cron_function = 'quiz_report_'.$report->name."_cron";
140                 if (function_exists($cron_function)) {
141                     mtrace("Processing quiz report cron function $cron_function ...", '');
142                     $pre_dbqueries = null;
143                     $pre_dbqueries = $DB->perf_get_queries();
144                     $pre_time      = microtime(1);
145                     if ($cron_function()) {
146                         $DB->set_field('quiz_report', "lastcron", $timenow, array("id"=>$report->id));
147                     }
148                     if (isset($pre_dbqueries)) {
149                         mtrace("... used " . ($DB->perf_get_queries() - $pre_dbqueries) . " dbqueries");
150                         mtrace("... used " . (microtime(1) - $pre_time) . " seconds");
151                     }
152                     mtrace("done.");
153                 }
154             }
155         }
156     }
157     mtrace("Finished quiz reports");
159     mtrace('Starting admin reports');
160     // Admin reports do not have a database table that lists them. Instead a
161     // report includes cron.php with function report_reportname_cron() if it wishes
162     // to be cronned. It is up to cron.php to handle e.g. if it only needs to
163     // actually do anything occasionally.
164     $reports = get_plugin_list('report');
165     foreach($reports as $report => $reportdir) {
166         $cronfile = $reportdir.'/cron.php';
167         if (file_exists($cronfile)) {
168             require_once($cronfile);
169             $cronfunction = 'report_'.$report.'_cron';
170             mtrace('Processing cron function for '.$report.'...', '');
171             $pre_dbqueries = null;
172             $pre_dbqueries = $DB->perf_get_queries();
173             $pre_time      = microtime(true);
174             $cronfunction();
175             if (isset($pre_dbqueries)) {
176                 mtrace("... used " . ($DB->perf_get_queries() - $pre_dbqueries) . " dbqueries");
177                 mtrace("... used " . round(microtime(true) - $pre_time, 2) . " seconds");
178             }
179             mtrace('done.');
180         }
181     }
182     mtrace('Finished admin reports');
184     mtrace('Starting main gradebook job ...');
185     grade_cron();
186     mtrace('done.');
189     mtrace('Starting processing the event queue...');
190     events_cron();
191     mtrace('done.');
194     if ($CFG->enablecompletion) {
195         // Completion cron
196         mtrace('Starting the completion cron...');
197         require_once($CFG->libdir . '/completion/cron.php');
198         completion_cron();
199         mtrace('done');
200     }
203     if ($CFG->enableportfolios) {
204         // Portfolio cron
205         mtrace('Starting the portfolio cron...');
206         require_once($CFG->libdir . '/portfoliolib.php');
207         portfolio_cron();
208         mtrace('done');
209     }
211 /// Run all core cron jobs, but not every time since they aren't too important.
212 /// These don't have a timer to reduce load, so we'll use a random number
213 /// to randomly choose the percentage of times we should run these jobs.
215     srand ((double) microtime() * 10000000);
216     $random100 = rand(0,100);
218     if ($random100 < 20) {     // Approximately 20% of the time.
219         mtrace("Running clean-up tasks...");
221         /// Delete users who haven't confirmed within required period
223         if (!empty($CFG->deleteunconfirmed)) {
224             $cuttime = $timenow - ($CFG->deleteunconfirmed * 3600);
225             $rs = $DB->get_recordset_sql ("SELECT id, firstname, lastname
226                                              FROM {user}
227                                             WHERE confirmed = 0 AND firstaccess > 0
228                                                   AND firstaccess < ?", array($cuttime));
229             foreach ($rs as $user) {
230                 if ($DB->delete_records('user', array('id'=>$user->id))) {
231                     mtrace("Deleted unconfirmed user for ".fullname($user, true)." ($user->id)");
232                 }
233             }
234             $rs->close();
235         }
236         flush();
239         /// Delete users who haven't completed profile within required period
241         if (!empty($CFG->deleteincompleteusers)) {
242             $cuttime = $timenow - ($CFG->deleteincompleteusers * 3600);
243             $rs = $DB->get_recordset_sql ("SELECT id, username
244                                              FROM {user}
245                                             WHERE confirmed = 1 AND lastaccess > 0
246                                                   AND lastaccess < ? AND deleted = 0
247                                                   AND (lastname = '' OR firstname = '' OR email = '')",
248                                           array($cuttime));
249             foreach ($rs as $user) {
250                 if (delete_user($user)) {
251                     mtrace("Deleted not fully setup user $user->username ($user->id)");
252                 }
253             }
254             $rs->close();
255         }
256         flush();
259         /// Delete old logs to save space (this might need a timer to slow it down...)
261         if (!empty($CFG->loglifetime)) {  // value in days
262             $loglifetime = $timenow - ($CFG->loglifetime * 3600 * 24);
263             if ($DB->delete_records_select("log", "time < ?", array($loglifetime))) {
264                 mtrace("Deleted old log records");
265             }
266         }
267         flush();
270         /// Delete old cached texts
272         if (!empty($CFG->cachetext)) {   // Defined in config.php
273             $cachelifetime = time() - $CFG->cachetext - 60;  // Add an extra minute to allow for really heavy sites
274             if ($DB->delete_records_select('cache_text', "timemodified < ?", array($cachelifetime))) {
275                 mtrace("Deleted old cache_text records");
276             }
277         }
278         flush();
280         if (!empty($CFG->notifyloginfailures)) {
281             notify_login_failures();
282             mtrace('Notified login failured');
283         }
284         flush();
286         //
287         // generate new password emails for users
288         //
289         mtrace('checking for create_password');
290         if ($DB->count_records('user_preferences', array('name'=>'create_password', 'value'=>'1'))) {
291             mtrace('creating passwords for new users');
292             $newusers = $DB->get_records_sql("SELECT u.id as id, u.email, u.firstname,
293                                                      u.lastname, u.username,
294                                                      p.id as prefid
295                                                 FROM {user} u
296                                                 JOIN {user_preferences} p ON u.id=p.userid
297                                                WHERE p.name='create_password' AND p.value='1' AND u.email !='' ");
299             foreach ($newusers as $newuserid => $newuser) {
300                 $newuser->emailstop = 0; // send email regardless
301                 // email user
302                 if (setnew_password_and_mail($newuser)) {
303                     // remove user pref
304                     $DB->delete_records('user_preferences', array('id'=>$newuser->prefid));
305                 } else {
306                     trigger_error("Could not create and mail new user password!");
307                 }
308             }
309         }
311         if (!empty($CFG->usetags)) {
312             require_once($CFG->dirroot.'/tag/lib.php');
313             tag_cron();
314             mtrace ('Executed tag cron');
315         }
317         // Accesslib stuff
318         cleanup_contexts();
319         mtrace ('Cleaned up contexts');
320         gc_cache_flags();
321         mtrace ('Cleaned cache flags');
322         // If you suspect that the context paths are somehow corrupt
323         // replace the line below with: build_context_path(true);
324         build_context_path();
325         mtrace ('Built context paths');
327         mtrace("Finished clean-up tasks...");
329     } // End of occasional clean-up tasks
331     // Disabled until implemented. MDL-21432, MDL-22184
332     if (1 == 2 && empty($CFG->disablescheduledbackups)) {   // Defined in config.php
333         //Execute backup's cron
334         //Perhaps a long time and memory could help in large sites
335         @set_time_limit(0);
336         @raise_memory_limit("192M");
337         if (function_exists('apache_child_terminate')) {
338             // if we are running from Apache, give httpd a hint that
339             // it can recycle the process after it's done. Apache's
340             // memory management is truly awful but we can help it.
341             @apache_child_terminate();
342         }
343         if (file_exists("$CFG->dirroot/backup/backup_scheduled.php") and
344             file_exists("$CFG->dirroot/backup/backuplib.php") and
345             file_exists("$CFG->dirroot/backup/lib.php") and
346             file_exists("$CFG->libdir/blocklib.php")) {
347             include_once("$CFG->dirroot/backup/backup_scheduled.php");
348             include_once("$CFG->dirroot/backup/backuplib.php");
349             include_once("$CFG->dirroot/backup/lib.php");
350             mtrace("Running backups if required...");
352             if (! schedule_backup_cron()) {
353                 mtrace("ERROR: Something went wrong while performing backup tasks!!!");
354             } else {
355                 mtrace("Backup tasks finished.");
356             }
357         }
358     }
360 /// Run the auth cron, if any
361 /// before enrolments because it might add users that will be needed in enrol plugins
362     $auths = get_enabled_auth_plugins();
364     mtrace("Running auth crons if required...");
365     foreach ($auths as $auth) {
366         $authplugin = get_auth_plugin($auth);
367         if (method_exists($authplugin, 'cron')) {
368             mtrace("Running cron for auth/$auth...");
369             $authplugin->cron();
370             if (!empty($authplugin->log)) {
371                 mtrace($authplugin->log);
372             }
373         }
374         unset($authplugin);
375     }
377     mtrace("Running enrol crons if required...");
378     $enrols = enrol_get_plugins(true);
379     foreach($enrols as $ename=>$enrol) {
380         // do this for all plugins, disabled plugins might want to cleanup stuff such as roles
381         if (!$enrol->is_cron_required()) {
382             continue;
383         }
384         mtrace("Running cron for enrol_$ename...");
385         $enrol->cron();
386         $enrol->set_config('lastcron', time());
387     }
389     if (!empty($CFG->enablestats) and empty($CFG->disablestatsprocessing)) {
390         require_once($CFG->dirroot.'/lib/statslib.php');
391         // check we're not before our runtime
392         $timetocheck = stats_get_base_daily() + $CFG->statsruntimestarthour*60*60 + $CFG->statsruntimestartminute*60;
394         if (time() > $timetocheck) {
395             // process configured number of days as max (defaulting to 31)
396             $maxdays = empty($CFG->statsruntimedays) ? 31 : abs($CFG->statsruntimedays);
397             if (stats_cron_daily($maxdays)) {
398                 if (stats_cron_weekly()) {
399                     if (stats_cron_monthly()) {
400                         stats_clean_old();
401                     }
402                 }
403             }
404             @set_time_limit(0);
405         } else {
406             mtrace('Next stats run after:'. userdate($timetocheck));
407         }
408     }
410     // run gradebook import/export/report cron
411     if ($gradeimports = get_plugin_list('gradeimport')) {
412         foreach ($gradeimports as $gradeimport => $plugindir) {
413             if (file_exists($plugindir.'/lib.php')) {
414                 require_once($plugindir.'/lib.php');
415                 $cron_function = 'grade_import_'.$gradeimport.'_cron';
416                 if (function_exists($cron_function)) {
417                     mtrace("Processing gradebook import function $cron_function ...", '');
418                     $cron_function();
419                 }
420             }
421         }
422     }
424     if ($gradeexports = get_plugin_list('gradeexport')) {
425         foreach ($gradeexports as $gradeexport => $plugindir) {
426             if (file_exists($plugindir.'/lib.php')) {
427                 require_once($plugindir.'/lib.php');
428                 $cron_function = 'grade_export_'.$gradeexport.'_cron';
429                 if (function_exists($cron_function)) {
430                     mtrace("Processing gradebook export function $cron_function ...", '');
431                     $cron_function();
432                 }
433             }
434         }
435     }
437     if ($gradereports = get_plugin_list('gradereport')) {
438         foreach ($gradereports as $gradereport => $plugindir) {
439             if (file_exists($plugindir.'/lib.php')) {
440                 require_once($plugindir.'/lib.php');
441                 $cron_function = 'grade_report_'.$gradereport.'_cron';
442                 if (function_exists($cron_function)) {
443                     mtrace("Processing gradebook report function $cron_function ...", '');
444                     $cron_function();
445                 }
446             }
447         }
448     }
450     // Run external blog cron if needed
451     if ($CFG->useexternalblogs) {
452         require_once($CFG->dirroot . '/blog/lib.php');
453         mtrace("Fetching external blog entries...", '');
454         $sql = "timefetched < ? OR timefetched = 0";
455         $externalblogs = $DB->get_records_select('blog_external', $sql, array(mktime() - $CFG->externalblogcrontime));
457         foreach ($externalblogs as $eb) {
458             blog_sync_external_entries($eb);
459         }
460     }
462     // Run blog associations cleanup
463     if ($CFG->useblogassociations) {
464         require_once($CFG->dirroot . '/blog/lib.php');
465         // delete entries whose contextids no longer exists
466         mtrace("Deleting blog associations linked to non-existent contexts...", '');
467         $DB->delete_records_select('blog_association', 'contextid NOT IN (SELECT id FROM {context})');
468     }
470     //Run registration updated cron
471     mtrace(get_string('siteupdatesstart', 'hub'));
472     require_once($CFG->dirroot . '/admin/registration/lib.php');
473     $registrationmanager = new registration_manager();
474     $registrationmanager->cron();
475     mtrace(get_string('siteupdatesend', 'hub'));
477     // cleanup file trash
478     $fs = get_file_storage();
479     $fs->cron();
481     //cleanup old session linked tokens
482     //deletes the session linked tokens that are over a day old.
483     mtrace("Deleting session linked tokens more than one day old...", '');
484     $DB->delete_records_select('external_tokens', 'lastaccess < :onedayago AND tokentype = :tokentype',
485                     array('onedayago' => time() - DAYSECS, 'tokentype' => EXTERNAL_TOKEN_EMBEDDED));
486     mtrace('done.');
488     // run any customized cronjobs, if any
489     if ($locals = get_plugin_list('local')) {
490         mtrace('Processing customized cron scripts ...', '');
491         foreach ($locals as $local => $localdir) {
492             if (file_exists("$localdir/cron.php")) {
493                 include("$localdir/cron.php");
494             }
495         }
496         mtrace('done.');
497     }
500     mtrace("Cron script completed correctly");
502     $difftime = microtime_diff($starttime, microtime());
503     mtrace("Execution took ".$difftime." seconds");