MDL-23824 CLI script improvements - just define('CLI_SCRIPT', true) before require...
[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     require_once($CFG->libdir.'/adminlib.php');
31     require_once($CFG->libdir.'/gradelib.php');
33     if (!empty($CFG->showcronsql)) {
34         $DB->set_debug(true);
35     }
36     if (!empty($CFG->showcrondebugging)) {
37         $CFG->debug = DEBUG_DEVELOPER;
38         $CFG->debugdisplay = true;
39     }
41     set_time_limit(0);
42     $starttime = microtime();
44 /// increase memory limit (PHP 5.2 does different calculation, we need more memory now)
45     @raise_memory_limit('128M');
47 /// emulate normal session
48     cron_setup_user();
50 /// Start output log
52     $timenow  = time();
54     mtrace("Server Time: ".date('r',$timenow)."\n\n");
57 /// Session gc
59     mtrace("Cleaning up stale sessions");
60     session_gc();
62 /// Run all cron jobs for each module
64     mtrace("Starting activity modules");
65     get_mailer('buffer');
66     if ($mods = $DB->get_records_select("modules", "cron > 0 AND ((? - lastcron) > cron) AND visible = 1", array($timenow))) {
67         foreach ($mods as $mod) {
68             $libfile = "$CFG->dirroot/mod/$mod->name/lib.php";
69             if (file_exists($libfile)) {
70                 include_once($libfile);
71                 $cron_function = $mod->name."_cron";
72                 if (function_exists($cron_function)) {
73                     mtrace("Processing module function $cron_function ...", '');
74                     $pre_dbqueries = null;
75                     $pre_dbqueries = $DB->perf_get_queries();
76                     $pre_time      = microtime(1);
77                     if ($cron_function()) {
78                         if (!$DB->set_field("modules", "lastcron", $timenow, array("id"=>$mod->id))) {
79                             mtrace("Error: could not update timestamp for $mod->fullname");
80                         }
81                     }
82                     if (isset($pre_dbqueries)) {
83                         mtrace("... used " . ($DB->perf_get_queries() - $pre_dbqueries) . " dbqueries");
84                         mtrace("... used " . (microtime(1) - $pre_time) . " seconds");
85                     }
86                 /// Reset possible changes by modules to time_limit. MDL-11597
87                     @set_time_limit(0);
88                     mtrace("done.");
89                 }
90             }
91         }
92     }
93     get_mailer('close');
94     mtrace("Finished activity modules");
96     mtrace("Starting blocks");
97     if ($blocks = $DB->get_records_select("block", "cron > 0 AND ((? - lastcron) > cron) AND visible = 1", array($timenow))) {
98         // we will need the base class.
99         require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
100         foreach ($blocks as $block) {
101             $blockfile = $CFG->dirroot.'/blocks/'.$block->name.'/block_'.$block->name.'.php';
102             if (file_exists($blockfile)) {
103                 require_once($blockfile);
104                 $classname = 'block_'.$block->name;
105                 $blockobj = new $classname;
106                 if (method_exists($blockobj,'cron')) {
107                     mtrace("Processing cron function for ".$block->name.'....','');
108                     if ($blockobj->cron()) {
109                         if (!$DB->set_field('block', 'lastcron', $timenow, array('id'=>$block->id))) {
110                             mtrace('Error: could not update timestamp for '.$block->name);
111                         }
112                     }
113                 /// Reset possible changes by blocks to time_limit. MDL-11597
114                     @set_time_limit(0);
115                     mtrace('done.');
116                 }
117             }
119         }
120     }
121     mtrace('Finished blocks');
123     mtrace("Starting quiz reports");
124     if ($reports = $DB->get_records_select('quiz_report', "cron > 0 AND ((? - lastcron) > cron)", array($timenow))) {
125         foreach ($reports as $report) {
126             $cronfile = "$CFG->dirroot/mod/quiz/report/$report->name/cron.php";
127             if (file_exists($cronfile)) {
128                 include_once($cronfile);
129                 $cron_function = 'quiz_report_'.$report->name."_cron";
130                 if (function_exists($cron_function)) {
131                     mtrace("Processing quiz report cron function $cron_function ...", '');
132                     $pre_dbqueries = null;
133                     $pre_dbqueries = $DB->perf_get_queries();
134                     $pre_time      = microtime(1);
135                     if ($cron_function()) {
136                         if (!$DB->set_field('quiz_report', "lastcron", $timenow, array("id"=>$report->id))) {
137                             mtrace("Error: could not update timestamp for $report->name");
138                         }
139                     }
140                     if (isset($pre_dbqueries)) {
141                         mtrace("... used " . ($DB->perf_get_queries() - $pre_dbqueries) . " dbqueries");
142                         mtrace("... used " . (microtime(1) - $pre_time) . " seconds");
143                     }
144                     mtrace("done.");
145                 }
146             }
147         }
148     }
149     mtrace("Finished quiz reports");
151     mtrace('Starting admin reports');
152     // Admin reports do not have a database table that lists them. Instead a
153     // report includes cron.php with function report_reportname_cron() if it wishes
154     // to be cronned. It is up to cron.php to handle e.g. if it only needs to
155     // actually do anything occasionally.
156     $reports = get_plugin_list('report');
157     foreach($reports as $report => $reportdir) {
158         $cronfile = $reportdir.'/cron.php';
159         if (file_exists($cronfile)) {
160             require_once($cronfile);
161             $cronfunction = 'report_'.$report.'_cron';
162             mtrace('Processing cron function for '.$report.'...', '');
163             $pre_dbqueries = null;
164             $pre_dbqueries = $DB->perf_get_queries();
165             $pre_time      = microtime(true);
166             $cronfunction();
167             if (isset($pre_dbqueries)) {
168                 mtrace("... used " . ($DB->perf_get_queries() - $pre_dbqueries) . " dbqueries");
169                 mtrace("... used " . round(microtime(true) - $pre_time, 2) . " seconds");
170             }
171             mtrace('done.');
172         }
173     }
174     mtrace('Finished admin reports');
176     mtrace('Starting main gradebook job ...');
177     grade_cron();
178     mtrace('done.');
181     mtrace('Starting processing the event queue...');
182     events_cron();
183     mtrace('done.');
186     if ($CFG->enablecompletion) {
187         // Completion cron
188         mtrace('Starting the completion cron...');
189         require_once($CFG->libdir . '/completion/cron.php');
190         completion_cron();
191         mtrace('done');
192     }
195     if ($CFG->enableportfolios) {
196         // Portfolio cron
197         mtrace('Starting the portfolio cron...');
198         require_once($CFG->libdir . '/portfoliolib.php');
199         portfolio_cron();
200         mtrace('done');
201     }
203 /// Run all core cron jobs, but not every time since they aren't too important.
204 /// These don't have a timer to reduce load, so we'll use a random number
205 /// to randomly choose the percentage of times we should run these jobs.
207     srand ((double) microtime() * 10000000);
208     $random100 = rand(0,100);
210     if ($random100 < 20) {     // Approximately 20% of the time.
211         mtrace("Running clean-up tasks...");
213         /// Delete users who haven't confirmed within required period
215         if (!empty($CFG->deleteunconfirmed)) {
216             $cuttime = $timenow - ($CFG->deleteunconfirmed * 3600);
217             $rs = $DB->get_recordset_sql ("SELECT id, firstname, lastname
218                                              FROM {user}
219                                             WHERE confirmed = 0 AND firstaccess > 0
220                                                   AND firstaccess < ?", array($cuttime));
221             foreach ($rs as $user) {
222                 if ($DB->delete_records('user', array('id'=>$user->id))) {
223                     mtrace("Deleted unconfirmed user for ".fullname($user, true)." ($user->id)");
224                 }
225             }
226             $rs->close();
227         }
228         flush();
231         /// Delete users who haven't completed profile within required period
233         if (!empty($CFG->deleteincompleteusers)) {
234             $cuttime = $timenow - ($CFG->deleteincompleteusers * 3600);
235             $rs = $DB->get_recordset_sql ("SELECT id, username
236                                              FROM {user}
237                                             WHERE confirmed = 1 AND lastaccess > 0
238                                                   AND lastaccess < ? AND deleted = 0
239                                                   AND (lastname = '' OR firstname = '' OR email = '')",
240                                           array($cuttime));
241             foreach ($rs as $user) {
242                 if (delete_user($user)) {
243                     mtrace("Deleted not fully setup user $user->username ($user->id)");
244                 }
245             }
246             $rs->close();
247         }
248         flush();
251         /// Delete old logs to save space (this might need a timer to slow it down...)
253         if (!empty($CFG->loglifetime)) {  // value in days
254             $loglifetime = $timenow - ($CFG->loglifetime * 3600 * 24);
255             if ($DB->delete_records_select("log", "time < ?", array($loglifetime))) {
256                 mtrace("Deleted old log records");
257             }
258         }
259         flush();
262         /// Delete old cached texts
264         if (!empty($CFG->cachetext)) {   // Defined in config.php
265             $cachelifetime = time() - $CFG->cachetext - 60;  // Add an extra minute to allow for really heavy sites
266             if ($DB->delete_records_select('cache_text', "timemodified < ?", array($cachelifetime))) {
267                 mtrace("Deleted old cache_text records");
268             }
269         }
270         flush();
272         if (!empty($CFG->notifyloginfailures)) {
273             notify_login_failures();
274             mtrace('Notified login failured');
275         }
276         flush();
278         //
279         // generate new password emails for users
280         //
281         mtrace('checking for create_password');
282         if ($DB->count_records('user_preferences', array('name'=>'create_password', 'value'=>'1'))) {
283             mtrace('creating passwords for new users');
284             $newusers = $DB->get_records_sql("SELECT u.id as id, u.email, u.firstname,
285                                                      u.lastname, u.username,
286                                                      p.id as prefid
287                                                 FROM {user} u
288                                                 JOIN {user_preferences} p ON u.id=p.userid
289                                                WHERE p.name='create_password' AND p.value='1' AND u.email !='' ");
291             foreach ($newusers as $newuserid => $newuser) {
292                 $newuser->emailstop = 0; // send email regardless
293                 // email user
294                 if (setnew_password_and_mail($newuser)) {
295                     // remove user pref
296                     $DB->delete_records('user_preferences', array('id'=>$newuser->prefid));
297                 } else {
298                     trigger_error("Could not create and mail new user password!");
299                 }
300             }
301         }
303         if (!empty($CFG->usetags)) {
304             require_once($CFG->dirroot.'/tag/lib.php');
305             tag_cron();
306             mtrace ('Executed tag cron');
307         }
309         // Accesslib stuff
310         cleanup_contexts();
311         mtrace ('Cleaned up contexts');
312         gc_cache_flags();
313         mtrace ('Cleaned cache flags');
314         // If you suspect that the context paths are somehow corrupt
315         // replace the line below with: build_context_path(true);
316         build_context_path();
317         mtrace ('Built context paths');
319         mtrace("Finished clean-up tasks...");
321     } // End of occasional clean-up tasks
323     // Disabled until implemented. MDL-21432, MDL-22184
324     if (1 == 2 && empty($CFG->disablescheduledbackups)) {   // Defined in config.php
325         //Execute backup's cron
326         //Perhaps a long time and memory could help in large sites
327         @set_time_limit(0);
328         @raise_memory_limit("192M");
329         if (function_exists('apache_child_terminate')) {
330             // if we are running from Apache, give httpd a hint that
331             // it can recycle the process after it's done. Apache's
332             // memory management is truly awful but we can help it.
333             @apache_child_terminate();
334         }
335         if (file_exists("$CFG->dirroot/backup/backup_scheduled.php") and
336             file_exists("$CFG->dirroot/backup/backuplib.php") and
337             file_exists("$CFG->dirroot/backup/lib.php") and
338             file_exists("$CFG->libdir/blocklib.php")) {
339             include_once("$CFG->dirroot/backup/backup_scheduled.php");
340             include_once("$CFG->dirroot/backup/backuplib.php");
341             include_once("$CFG->dirroot/backup/lib.php");
342             mtrace("Running backups if required...");
344             if (! schedule_backup_cron()) {
345                 mtrace("ERROR: Something went wrong while performing backup tasks!!!");
346             } else {
347                 mtrace("Backup tasks finished.");
348             }
349         }
350     }
352 /// Run the auth cron, if any
353 /// before enrolments because it might add users that will be needed in enrol plugins
354     $auths = get_enabled_auth_plugins();
356     mtrace("Running auth crons if required...");
357     foreach ($auths as $auth) {
358         $authplugin = get_auth_plugin($auth);
359         if (method_exists($authplugin, 'cron')) {
360             mtrace("Running cron for auth/$auth...");
361             $authplugin->cron();
362             if (!empty($authplugin->log)) {
363                 mtrace($authplugin->log);
364             }
365         }
366         unset($authplugin);
367     }
369     mtrace("Running enrol crons if required...");
370     $enrols = enrol_get_plugins(true);
371     foreach($enrols as $ename=>$enrol) {
372         // do this for all plugins, disabled plugins might want to cleanup stuff such as roles
373         if (!$enrol->is_cron_required()) {
374             continue;
375         }
376         mtrace("Running cron for enrol_$ename...");
377         $enrol->cron();
378         $enrol->set_config('lastcron', time());
379     }
381     if (!empty($CFG->enablestats) and empty($CFG->disablestatsprocessing)) {
382         require_once($CFG->dirroot.'/lib/statslib.php');
383         // check we're not before our runtime
384         $timetocheck = stats_get_base_daily() + $CFG->statsruntimestarthour*60*60 + $CFG->statsruntimestartminute*60;
386         if (time() > $timetocheck) {
387             // process configured number of days as max (defaulting to 31)
388             $maxdays = empty($CFG->statsruntimedays) ? 31 : abs($CFG->statsruntimedays);
389             if (stats_cron_daily($maxdays)) {
390                 if (stats_cron_weekly()) {
391                     if (stats_cron_monthly()) {
392                         stats_clean_old();
393                     }
394                 }
395             }
396             @set_time_limit(0);
397         } else {
398             mtrace('Next stats run after:'. userdate($timetocheck));
399         }
400     }
402     // run gradebook import/export/report cron
403     if ($gradeimports = get_plugin_list('gradeimport')) {
404         foreach ($gradeimports as $gradeimport => $plugindir) {
405             if (file_exists($plugindir.'/lib.php')) {
406                 require_once($plugindir.'/lib.php');
407                 $cron_function = 'grade_import_'.$gradeimport.'_cron';
408                 if (function_exists($cron_function)) {
409                     mtrace("Processing gradebook import function $cron_function ...", '');
410                     $cron_function();
411                 }
412             }
413         }
414     }
416     if ($gradeexports = get_plugin_list('gradeexport')) {
417         foreach ($gradeexports as $gradeexport => $plugindir) {
418             if (file_exists($plugindir.'/lib.php')) {
419                 require_once($plugindir.'/lib.php');
420                 $cron_function = 'grade_export_'.$gradeexport.'_cron';
421                 if (function_exists($cron_function)) {
422                     mtrace("Processing gradebook export function $cron_function ...", '');
423                     $cron_function();
424                 }
425             }
426         }
427     }
429     if ($gradereports = get_plugin_list('gradereport')) {
430         foreach ($gradereports as $gradereport => $plugindir) {
431             if (file_exists($plugindir.'/lib.php')) {
432                 require_once($plugindir.'/lib.php');
433                 $cron_function = 'grade_report_'.$gradereport.'_cron';
434                 if (function_exists($cron_function)) {
435                     mtrace("Processing gradebook report function $cron_function ...", '');
436                     $cron_function();
437                 }
438             }
439         }
440     }
442     // Run external blog cron if needed
443     if ($CFG->useexternalblogs) {
444         require_once($CFG->dirroot . '/blog/lib.php');
445         mtrace("Fetching external blog entries...", '');
446         $sql = "timefetched < ? OR timefetched = 0";
447         $externalblogs = $DB->get_records_select('blog_external', $sql, array(mktime() - $CFG->externalblogcrontime));
449         foreach ($externalblogs as $eb) {
450             blog_sync_external_entries($eb);
451         }
452     }
454     // Run blog associations cleanup
455     if ($CFG->useblogassociations) {
456         require_once($CFG->dirroot . '/blog/lib.php');
457         // delete entries whose contextids no longer exists
458         mtrace("Deleting blog associations linked to non-existent contexts...", '');
459         $DB->delete_records_select('blog_association', 'contextid NOT IN (SELECT id FROM {context})');
460     }
462     //Run registration updated cron
463     mtrace(get_string('siteupdatesstart', 'hub'));
464     require_once($CFG->dirroot . '/admin/registration/lib.php');
465     $registrationmanager = new registration_manager();
466     $registrationmanager->cron();
467     mtrace(get_string('siteupdatesend', 'hub'));
469     // cleanup file trash
470     $fs = get_file_storage();
471     $fs->cron();
473     //cleanup old session linked tokens
474     //deletes the session linked tokens that are over a day old.
475     mtrace("Deleting session linked tokens more than one day old...", '');
476     $DB->delete_records_select('external_tokens', 'lastaccess < :onedayago AND tokentype = :tokentype',
477                     array('onedayago' => time() - DAYSECS, 'tokentype' => EXTERNAL_TOKEN_EMBEDDED));
478     mtrace('done.');
480     // run any customized cronjobs, if any
481     if ($locals = get_plugin_list('local')) {
482         mtrace('Processing customized cron scripts ...', '');
483         foreach ($locals as $local => $localdir) {
484             if (file_exists("$localdir/cron.php")) {
485                 include("$localdir/cron.php");
486             }
487         }
488         mtrace('done.');
489     }
492     mtrace("Cron script completed correctly");
494     $difftime = microtime_diff($starttime, microtime());
495     mtrace("Execution took ".$difftime." seconds");