static function cron() { // Setup user object (as admin) cron_setup_user(); // Newline after the forum... mtrace(""); // Send forum emails and digests self::email(); // Delete old playsapces self::daily_housekeeping(); }
/** * Run the deletion task. * * @throws \coding_exception if the module could not be removed. */ public function execute() { global $CFG; require_once $CFG->dirroot . '/course/lib.php'; // Set the proper user. if ($this->get_custom_data()->userid !== $this->get_custom_data()->realuserid) { $realuser = \core_user::get_user($this->get_custom_data()->realuserid, '*', MUST_EXIST); cron_setup_user($realuser); \core\session\manager::loginas($this->get_custom_data()->userid, \context_system::instance(), false); } else { $user = \core_user::get_user($this->get_custom_data()->userid, '*', MUST_EXIST); cron_setup_user($user); } $cms = $this->get_custom_data()->cms; foreach ($cms as $cm) { try { course_delete_module($cm->id); } catch (\Exception $e) { throw new \coding_exception("The course module {$cm->id} could not be deleted. {$e->getTraceAsString()}"); } } }
/** * Process a message and pass it through the Inbound Message handling systems. * * @param \Horde_Imap_Client_Data_Fetch $message The message to process * @param bool $viewreadmessages Whether to also look at messages which have been marked as read * @param bool $skipsenderverification Whether to skip the sender verification stage */ public function process_message(\Horde_Imap_Client_Data_Fetch $message, $viewreadmessages = false, $skipsenderverification = false) { global $USER; // We use the Client IDs several times - store them here. $messageid = new \Horde_Imap_Client_Ids($message->getUid()); mtrace("- Parsing message " . $messageid); // First flag this message to prevent another running hitting this message while we look at the headers. $this->add_flag_to_message($messageid, self::MESSAGE_FLAGGED); if ($this->is_bulk_message($message, $messageid)) { mtrace("- The message has a bulk header set. This is likely an auto-generated reply - discarding."); return; } // Record the user that this script is currently being run as. This is important when re-processing existing // messages, as cron_setup_user is called multiple times. $originaluser = $USER; $envelope = $message->getEnvelope(); $recipients = $envelope->to->bare_addresses; foreach ($recipients as $recipient) { if (!\core\message\inbound\address_manager::is_correct_format($recipient)) { // Message did not contain a subaddress. mtrace("- Recipient '{$recipient}' did not match Inbound Message headers."); continue; } // Message contained a match. $senders = $message->getEnvelope()->from->bare_addresses; if (count($senders) !== 1) { mtrace("- Received multiple senders. Only the first sender will be used."); } $sender = array_shift($senders); mtrace("-- Subject:\t" . $envelope->subject); mtrace("-- From:\t" . $sender); mtrace("-- Recipient:\t" . $recipient); // Grab messagedata including flags. $query = new \Horde_Imap_Client_Fetch_Query(); $query->structure(); $messagedata = $this->client->fetch($this->get_mailbox(), $query, array('ids' => $messageid))->first(); if (!$viewreadmessages && $this->message_has_flag($messageid, self::MESSAGE_SEEN)) { // Something else has already seen this message. Skip it now. mtrace("-- Skipping the message - it has been marked as seen - perhaps by another process."); continue; } // Mark it as read to lock the message. $this->add_flag_to_message($messageid, self::MESSAGE_SEEN); // Now pass it through the Inbound Message processor. $status = $this->addressmanager->process_envelope($recipient, $sender); if (($status & ~\core\message\inbound\address_manager::VALIDATION_DISABLED_HANDLER) !== $status) { // The handler is disabled. mtrace("-- Skipped message - Handler is disabled. Fail code {$status}"); // In order to handle the user error, we need more information about the message being failed. $this->process_message_data($envelope, $messagedata, $messageid); $this->inform_user_of_error(get_string('handlerdisabled', 'tool_messageinbound', $this->currentmessagedata)); return; } // Check the validation status early. No point processing garbage messages, but we do need to process it // for some validation failure types. if (!$this->passes_key_validation($status, $messageid)) { // None of the above validation failures were found. Skip this message. mtrace("-- Skipped message - it does not appear to relate to a Inbound Message pickup. Fail code {$status}"); // Remove the seen flag from the message as there may be multiple recipients. $this->remove_flag_from_message($messageid, self::MESSAGE_SEEN); // Skip further processing for this recipient. continue; } // Process the message as the user. $user = $this->addressmanager->get_data()->user; mtrace("-- Processing the message as user {$user->id} ({$user->username})."); cron_setup_user($user); // Process and retrieve the message data for this message. // This includes fetching the full content, as well as all headers, and attachments. if (!$this->process_message_data($envelope, $messagedata, $messageid)) { mtrace("--- Message could not be found on the server. Is another process removing messages?"); return; } // When processing validation replies, we need to skip the sender verification phase as this has been // manually completed. if (!$skipsenderverification && $status !== 0) { // Check the validation status for failure types which require confirmation. // The validation result is tested in a bitwise operation. mtrace("-- Message did not meet validation but is possibly recoverable. Fail code {$status}"); // This is a recoverable error, but requires user input. if ($this->handle_verification_failure($messageid, $recipient)) { mtrace("--- Original message retained on mail server and confirmation message sent to user."); } else { mtrace("--- Invalid Recipient Handler - unable to save. Informing the user of the failure."); $this->inform_user_of_error(get_string('invalidrecipientfinal', 'tool_messageinbound', $this->currentmessagedata)); } // Returning to normal cron user. mtrace("-- Returning to the original user."); cron_setup_user($originaluser); return; } // Add the content and attachment data. mtrace("-- Validation completed. Fetching rest of message content."); $this->process_message_data_body($messagedata, $messageid); // The message processor throws exceptions upon failure. These must be caught and notifications sent to // the user here. try { $result = $this->send_to_handler(); } catch (\core\message\inbound\processing_failed_exception $e) { // We know about these kinds of errors and they should result in the user being notified of the // failure. Send the user a notification here. $this->inform_user_of_error($e->getMessage()); // Returning to normal cron user. mtrace("-- Returning to the original user."); cron_setup_user($originaluser); return; } catch (\Exception $e) { // An unknown error occurred. The user is not informed, but the administrator is. mtrace("-- Message processing failed. An unexpected exception was thrown. Details follow."); mtrace($e->getMessage()); // Returning to normal cron user. mtrace("-- Returning to the original user."); cron_setup_user($originaluser); return; } if ($result) { // Handle message cleanup. Messages are deleted once fully processed. mtrace("-- Marking the message for removal."); $this->add_flag_to_message($messageid, self::MESSAGE_DELETED); } else { mtrace("-- The Inbound Message processor did not return a success status. Skipping message removal."); } // Returning to normal cron user. mtrace("-- Returning to the original user."); cron_setup_user($originaluser); mtrace("-- Finished processing " . $message->getUid()); // Skip the outer loop too. The message has already been processed and it could be possible for there to // be two recipients in the envelope which match somehow. return; } }
/** * Function to be run periodically according to the moodle * This function searches for things that need to be done, such * as sending out mail, toggling flags etc ... * @return boolean always true * @uses $CFG * @uses $DB */ function scheduler_cron() { global $CFG, $DB; $date = make_timestamp(date('Y'), date('m'), date('d'), date('H'), date('i')); // for every appointment in all schedulers $select = 'emaildate > 0 AND emaildate <= ? AND starttime > ?'; $slots = $DB->get_records_select('scheduler_slots', $select, array($date, $date), 'starttime'); foreach ($slots as $slot) { // get teacher $teacher = $DB->get_record('user', array('id' => $slot->teacherid)); // get scheduler, slot and course $scheduler = scheduler_instance::load_by_id($slot->schedulerid); $slotm = $scheduler->get_slot($slot->id); $course = $DB->get_record('course', array('id' => $scheduler->course)); // get appointed student list $appointments = $DB->get_records('scheduler_appointment', array('slotid' => $slot->id), '', 'id, studentid'); //if no email previously sent and one is required foreach ($appointments as $appointment) { $student = $DB->get_record('user', array('id' => $appointment->studentid)); cron_setup_user($student, $course); $vars = scheduler_get_mail_variables($scheduler, $slotm, $teacher, $student, $course, $student); scheduler_send_email_from_template($student, $teacher, $course, 'remindtitle', 'reminder', $vars, 'scheduler'); } // mark as sent $slot->emaildate = -1; $DB->update_record('scheduler_slots', $slot); } cron_setup_user(); return true; }
/** * Cron functions. * * @package core * @subpackage admin * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ function cron_run() { global $DB, $CFG, $OUTPUT; if (CLI_MAINTENANCE) { echo "CLI maintenance mode active, cron execution suspended.\n"; exit(1); } if (moodle_needs_upgrading()) { echo "Moodle upgrade pending, cron execution suspended.\n"; exit(1); } require_once $CFG->libdir . '/adminlib.php'; require_once $CFG->libdir . '/gradelib.php'; if (!empty($CFG->showcronsql)) { $DB->set_debug(true); } if (!empty($CFG->showcrondebugging)) { $CFG->debug = DEBUG_DEVELOPER; $CFG->debugdisplay = true; } set_time_limit(0); $starttime = microtime(); /// increase memory limit raise_memory_limit(MEMORY_EXTRA); /// emulate normal session cron_setup_user(); /// Start output log $timenow = time(); mtrace("Server Time: " . date('r', $timenow) . "\n\n"); /// Session gc mtrace("Cleaning up stale sessions"); session_gc(); /// Run all cron jobs for each module mtrace("Starting activity modules"); get_mailer('buffer'); if ($mods = $DB->get_records_select("modules", "cron > 0 AND ((? - lastcron) > cron) AND visible = 1", array($timenow))) { foreach ($mods as $mod) { $libfile = "{$CFG->dirroot}/mod/{$mod->name}/lib.php"; if (file_exists($libfile)) { include_once $libfile; $cron_function = $mod->name . "_cron"; if (function_exists($cron_function)) { mtrace("Processing module function {$cron_function} ...", ''); $pre_dbqueries = null; $pre_dbqueries = $DB->perf_get_queries(); $pre_time = microtime(1); if ($cron_function()) { $DB->set_field("modules", "lastcron", $timenow, array("id" => $mod->id)); } if (isset($pre_dbqueries)) { mtrace("... used " . ($DB->perf_get_queries() - $pre_dbqueries) . " dbqueries"); mtrace("... used " . (microtime(1) - $pre_time) . " seconds"); } /// Reset possible changes by modules to time_limit. MDL-11597 @set_time_limit(0); mtrace("done."); } } } } get_mailer('close'); mtrace("Finished activity modules"); mtrace("Starting blocks"); if ($blocks = $DB->get_records_select("block", "cron > 0 AND ((? - lastcron) > cron) AND visible = 1", array($timenow))) { // we will need the base class. require_once $CFG->dirroot . '/blocks/moodleblock.class.php'; foreach ($blocks as $block) { $blockfile = $CFG->dirroot . '/blocks/' . $block->name . '/block_' . $block->name . '.php'; if (file_exists($blockfile)) { require_once $blockfile; $classname = 'block_' . $block->name; $blockobj = new $classname(); if (method_exists($blockobj, 'cron')) { mtrace("Processing cron function for " . $block->name . '....', ''); if ($blockobj->cron()) { $DB->set_field('block', 'lastcron', $timenow, array('id' => $block->id)); } /// Reset possible changes by blocks to time_limit. MDL-11597 @set_time_limit(0); mtrace('done.'); } } } } mtrace('Finished blocks'); //now do plagiarism checks require_once $CFG->libdir . '/plagiarismlib.php'; plagiarism_cron(); mtrace("Starting quiz reports"); if ($reports = $DB->get_records_select('quiz_report', "cron > 0 AND ((? - lastcron) > cron)", array($timenow))) { foreach ($reports as $report) { $cronfile = "{$CFG->dirroot}/mod/quiz/report/{$report->name}/cron.php"; if (file_exists($cronfile)) { include_once $cronfile; $cron_function = 'quiz_report_' . $report->name . "_cron"; if (function_exists($cron_function)) { mtrace("Processing quiz report cron function {$cron_function} ...", ''); $pre_dbqueries = null; $pre_dbqueries = $DB->perf_get_queries(); $pre_time = microtime(1); if ($cron_function()) { $DB->set_field('quiz_report', "lastcron", $timenow, array("id" => $report->id)); } if (isset($pre_dbqueries)) { mtrace("... used " . ($DB->perf_get_queries() - $pre_dbqueries) . " dbqueries"); mtrace("... used " . (microtime(1) - $pre_time) . " seconds"); } mtrace("done."); } } } } mtrace("Finished quiz reports"); mtrace('Starting admin reports'); // Admin reports do not have a database table that lists them. Instead a // report includes cron.php with function report_reportname_cron() if it wishes // to be cronned. It is up to cron.php to handle e.g. if it only needs to // actually do anything occasionally. $reports = get_plugin_list('report'); foreach ($reports as $report => $reportdir) { $cronfile = $reportdir . '/cron.php'; if (file_exists($cronfile)) { require_once $cronfile; $cronfunction = 'report_' . $report . '_cron'; mtrace('Processing cron function for ' . $report . '...', ''); $pre_dbqueries = null; $pre_dbqueries = $DB->perf_get_queries(); $pre_time = microtime(true); $cronfunction(); if (isset($pre_dbqueries)) { mtrace("... used " . ($DB->perf_get_queries() - $pre_dbqueries) . " dbqueries"); mtrace("... used " . round(microtime(true) - $pre_time, 2) . " seconds"); } mtrace('done.'); } } mtrace('Finished admin reports'); mtrace('Starting main gradebook job ...'); grade_cron(); mtrace('done.'); mtrace('Starting processing the event queue...'); events_cron(); mtrace('done.'); if ($CFG->enablecompletion) { // Completion cron mtrace('Starting the completion cron...'); require_once $CFG->libdir . '/completion/cron.php'; completion_cron(); mtrace('done'); } if ($CFG->enableportfolios) { // Portfolio cron mtrace('Starting the portfolio cron...'); require_once $CFG->libdir . '/portfoliolib.php'; portfolio_cron(); mtrace('done'); } /// Run all core cron jobs, but not every time since they aren't too important. /// These don't have a timer to reduce load, so we'll use a random number /// to randomly choose the percentage of times we should run these jobs. srand((double) microtime() * 10000000); $random100 = rand(0, 100); if ($random100 < 20) { // Approximately 20% of the time. mtrace("Running clean-up tasks..."); /// Delete users who haven't confirmed within required period if (!empty($CFG->deleteunconfirmed)) { $cuttime = $timenow - $CFG->deleteunconfirmed * 3600; $rs = $DB->get_recordset_sql("SELECT id, firstname, lastname\n FROM {user}\n WHERE confirmed = 0 AND firstaccess > 0\n AND firstaccess < ?", array($cuttime)); foreach ($rs as $user) { if ($DB->delete_records('user', array('id' => $user->id))) { mtrace("Deleted unconfirmed user for " . fullname($user, true) . " ({$user->id})"); } } $rs->close(); } flush(); /// Delete users who haven't completed profile within required period if (!empty($CFG->deleteincompleteusers)) { $cuttime = $timenow - $CFG->deleteincompleteusers * 3600; $rs = $DB->get_recordset_sql("SELECT id, username\n FROM {user}\n WHERE confirmed = 1 AND lastaccess > 0\n AND lastaccess < ? AND deleted = 0\n AND (lastname = '' OR firstname = '' OR email = '')", array($cuttime)); foreach ($rs as $user) { if (delete_user($user)) { mtrace("Deleted not fully setup user {$user->username} ({$user->id})"); } } $rs->close(); } flush(); /// Delete old logs to save space (this might need a timer to slow it down...) if (!empty($CFG->loglifetime)) { // value in days $loglifetime = $timenow - $CFG->loglifetime * 3600 * 24; if ($DB->delete_records_select("log", "time < ?", array($loglifetime))) { mtrace("Deleted old log records"); } } flush(); // Delete old backup_controllers and logs if (!empty($CFG->loglifetime)) { // value in days $loglifetime = $timenow - $CFG->loglifetime * 3600 * 24; // Delete child records from backup_logs $DB->execute("DELETE FROM {backup_logs}\n WHERE EXISTS (\n SELECT 'x'\n FROM {backup_controllers} bc\n WHERE bc.backupid = {backup_logs}.backupid\n AND bc.timecreated < ?)", array($loglifetime)); // Delete records from backup_controllers $DB->execute("DELETE FROM {backup_controllers}\n WHERE timecreated < ?", array($loglifetime)); mtrace("Deleted old backup records"); } flush(); /// Delete old cached texts if (!empty($CFG->cachetext)) { // Defined in config.php $cachelifetime = time() - $CFG->cachetext - 60; // Add an extra minute to allow for really heavy sites if ($DB->delete_records_select('cache_text', "timemodified < ?", array($cachelifetime))) { mtrace("Deleted old cache_text records"); } } flush(); if (!empty($CFG->notifyloginfailures)) { notify_login_failures(); mtrace('Notified login failured'); } flush(); // // generate new password emails for users // mtrace('checking for create_password'); if ($DB->count_records('user_preferences', array('name' => 'create_password', 'value' => '1'))) { mtrace('creating passwords for new users'); $newusers = $DB->get_records_sql("SELECT u.id as id, u.email, u.firstname,\n u.lastname, u.username,\n p.id as prefid\n FROM {user} u\n JOIN {user_preferences} p ON u.id=p.userid\n WHERE p.name='create_password' AND p.value='1' AND u.email !='' "); foreach ($newusers as $newuserid => $newuser) { // email user if (setnew_password_and_mail($newuser)) { // remove user pref $DB->delete_records('user_preferences', array('id' => $newuser->prefid)); } else { trigger_error("Could not create and mail new user password!"); } } } if (!empty($CFG->usetags)) { require_once $CFG->dirroot . '/tag/lib.php'; tag_cron(); mtrace('Executed tag cron'); } // Accesslib stuff cleanup_contexts(); mtrace('Cleaned up contexts'); gc_cache_flags(); mtrace('Cleaned cache flags'); // If you suspect that the context paths are somehow corrupt // replace the line below with: build_context_path(true); build_context_path(); mtrace('Built context paths'); if (!empty($CFG->messagingdeletereadnotificationsdelay)) { $notificationdeletetime = time() - $CFG->messagingdeletereadnotificationsdelay; $DB->delete_records_select('message_read', 'notification=1 AND timeread<:notificationdeletetime', array('notificationdeletetime' => $notificationdeletetime)); mtrace('Cleaned up read notifications'); } mtrace("Finished clean-up tasks..."); } // End of occasional clean-up tasks // Run automated backups if required. require_once $CFG->dirroot . '/backup/util/includes/backup_includes.php'; require_once $CFG->dirroot . '/backup/util/helper/backup_cron_helper.class.php'; backup_cron_automated_helper::run_automated_backup(); /// Run the auth cron, if any /// before enrolments because it might add users that will be needed in enrol plugins $auths = get_enabled_auth_plugins(); mtrace("Running auth crons if required..."); foreach ($auths as $auth) { $authplugin = get_auth_plugin($auth); if (method_exists($authplugin, 'cron')) { mtrace("Running cron for auth/{$auth}..."); $authplugin->cron(); if (!empty($authplugin->log)) { mtrace($authplugin->log); } } unset($authplugin); } mtrace("Running enrol crons if required..."); $enrols = enrol_get_plugins(true); foreach ($enrols as $ename => $enrol) { // do this for all plugins, disabled plugins might want to cleanup stuff such as roles if (!$enrol->is_cron_required()) { continue; } mtrace("Running cron for enrol_{$ename}..."); $enrol->cron(); $enrol->set_config('lastcron', time()); } if (!empty($CFG->enablestats) and empty($CFG->disablestatsprocessing)) { require_once $CFG->dirroot . '/lib/statslib.php'; // check we're not before our runtime $timetocheck = stats_get_base_daily() + $CFG->statsruntimestarthour * 60 * 60 + $CFG->statsruntimestartminute * 60; if (time() > $timetocheck) { // process configured number of days as max (defaulting to 31) $maxdays = empty($CFG->statsruntimedays) ? 31 : abs($CFG->statsruntimedays); if (stats_cron_daily($maxdays)) { if (stats_cron_weekly()) { if (stats_cron_monthly()) { stats_clean_old(); } } } @set_time_limit(0); } else { mtrace('Next stats run after:' . userdate($timetocheck)); } } // run gradebook import/export/report cron if ($gradeimports = get_plugin_list('gradeimport')) { foreach ($gradeimports as $gradeimport => $plugindir) { if (file_exists($plugindir . '/lib.php')) { require_once $plugindir . '/lib.php'; $cron_function = 'grade_import_' . $gradeimport . '_cron'; if (function_exists($cron_function)) { mtrace("Processing gradebook import function {$cron_function} ...", ''); $cron_function(); } } } } if ($gradeexports = get_plugin_list('gradeexport')) { foreach ($gradeexports as $gradeexport => $plugindir) { if (file_exists($plugindir . '/lib.php')) { require_once $plugindir . '/lib.php'; $cron_function = 'grade_export_' . $gradeexport . '_cron'; if (function_exists($cron_function)) { mtrace("Processing gradebook export function {$cron_function} ...", ''); $cron_function(); } } } } if ($gradereports = get_plugin_list('gradereport')) { foreach ($gradereports as $gradereport => $plugindir) { if (file_exists($plugindir . '/lib.php')) { require_once $plugindir . '/lib.php'; $cron_function = 'grade_report_' . $gradereport . '_cron'; if (function_exists($cron_function)) { mtrace("Processing gradebook report function {$cron_function} ...", ''); $cron_function(); } } } } // Run external blog cron if needed if ($CFG->useexternalblogs) { require_once $CFG->dirroot . '/blog/lib.php'; mtrace("Fetching external blog entries...", ''); $sql = "timefetched < ? OR timefetched = 0"; $externalblogs = $DB->get_records_select('blog_external', $sql, array(mktime() - $CFG->externalblogcrontime)); foreach ($externalblogs as $eb) { blog_sync_external_entries($eb); } } // Run blog associations cleanup if ($CFG->useblogassociations) { require_once $CFG->dirroot . '/blog/lib.php'; // delete entries whose contextids no longer exists mtrace("Deleting blog associations linked to non-existent contexts...", ''); $DB->delete_records_select('blog_association', 'contextid NOT IN (SELECT id FROM {context})'); } //Run registration updated cron mtrace(get_string('siteupdatesstart', 'hub')); require_once $CFG->dirroot . '/admin/registration/lib.php'; $registrationmanager = new registration_manager(); $registrationmanager->cron(); mtrace(get_string('siteupdatesend', 'hub')); // cleanup file trash $fs = get_file_storage(); $fs->cron(); //cleanup old session linked tokens //deletes the session linked tokens that are over a day old. mtrace("Deleting session linked tokens more than one day old...", ''); $DB->delete_records_select('external_tokens', 'lastaccess < :onedayago AND tokentype = :tokentype', array('onedayago' => time() - DAYSECS, 'tokentype' => EXTERNAL_TOKEN_EMBEDDED)); mtrace('done.'); // run any customized cronjobs, if any if ($locals = get_plugin_list('local')) { mtrace('Processing customized cron scripts ...', ''); foreach ($locals as $local => $localdir) { if (file_exists("{$localdir}/cron.php")) { include "{$localdir}/cron.php"; } } mtrace('done.'); } mtrace("Cron script completed correctly"); $difftime = microtime_diff($starttime, microtime()); mtrace("Execution took " . $difftime . " seconds"); }
public function test_cron_message_includes_courseid() { // First run cron so there are no messages waiting to be sent (from other tests). cron_setup_user(); assign::cron(); // Now create an assignment. $this->setUser($this->editingteachers[0]); $assign = $this->create_instance(array('sendstudentnotifications' => 1)); // Simulate adding a grade. $this->setUser($this->teachers[0]); $data = new stdClass(); $data->grade = '50.0'; $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0); $this->preventResetByRollback(); $sink = $this->redirectEvents(); $this->expectOutputRegex('/Done processing 1 assignment submissions/'); assign::cron(); $events = $sink->get_events(); // Two messages are sent, one to student and one to teacher. This generates // four events: // core\event\message_sent // core\event\message_viewed // core\event\message_sent // core\event\message_viewed. $event = reset($events); $this->assertInstanceOf('\\core\\event\\message_sent', $event); $this->assertEquals($assign->get_course()->id, $event->other['courseid']); $sink->close(); }
/** * Execute cron tasks */ function cron_run() { global $DB, $CFG, $OUTPUT; if (CLI_MAINTENANCE) { echo "CLI maintenance mode active, cron execution suspended.\n"; exit(1); } if (moodle_needs_upgrading()) { echo "Moodle upgrade pending, cron execution suspended.\n"; exit(1); } require_once $CFG->libdir . '/adminlib.php'; require_once $CFG->libdir . '/gradelib.php'; if (!empty($CFG->showcronsql)) { $DB->set_debug(true); } if (!empty($CFG->showcrondebugging)) { set_debugging(DEBUG_DEVELOPER, true); } set_time_limit(0); $starttime = microtime(); // Increase memory limit raise_memory_limit(MEMORY_EXTRA); // Emulate normal session - we use admin accoutn by default cron_setup_user(); // Start output log $timenow = time(); mtrace("Server Time: " . date('r', $timenow) . "\n\n"); // Run cleanup core cron jobs, but not every time since they aren't too important. // These don't have a timer to reduce load, so we'll use a random number // to randomly choose the percentage of times we should run these jobs. $random100 = rand(0, 100); if ($random100 < 20) { // Approximately 20% of the time. mtrace("Running clean-up tasks..."); cron_trace_time_and_memory(); // Delete users who haven't confirmed within required period if (!empty($CFG->deleteunconfirmed)) { $cuttime = $timenow - $CFG->deleteunconfirmed * 3600; $rs = $DB->get_recordset_sql("SELECT *\n FROM {user}\n WHERE confirmed = 0 AND firstaccess > 0\n AND firstaccess < ?", array($cuttime)); foreach ($rs as $user) { delete_user($user); // we MUST delete user properly first $DB->delete_records('user', array('id' => $user->id)); // this is a bloody hack, but it might work mtrace(" Deleted unconfirmed user for " . fullname($user, true) . " ({$user->id})"); } $rs->close(); } // Delete users who haven't completed profile within required period if (!empty($CFG->deleteincompleteusers)) { $cuttime = $timenow - $CFG->deleteincompleteusers * 3600; $rs = $DB->get_recordset_sql("SELECT *\n FROM {user}\n WHERE confirmed = 1 AND lastaccess > 0\n AND lastaccess < ? AND deleted = 0\n AND (lastname = '' OR firstname = '' OR email = '')", array($cuttime)); foreach ($rs as $user) { if (isguestuser($user) or is_siteadmin($user)) { continue; } delete_user($user); mtrace(" Deleted not fully setup user {$user->username} ({$user->id})"); } $rs->close(); } // Delete old logs to save space (this might need a timer to slow it down...) if (!empty($CFG->loglifetime)) { // value in days $loglifetime = $timenow - $CFG->loglifetime * 3600 * 24; $DB->delete_records_select("log", "time < ?", array($loglifetime)); mtrace(" Deleted old log records"); } // Delete old backup_controllers and logs. $loglifetime = get_config('backup', 'loglifetime'); if (!empty($loglifetime)) { // Value in days. $loglifetime = $timenow - $loglifetime * 3600 * 24; // Delete child records from backup_logs. $DB->execute("DELETE FROM {backup_logs}\n WHERE EXISTS (\n SELECT 'x'\n FROM {backup_controllers} bc\n WHERE bc.backupid = {backup_logs}.backupid\n AND bc.timecreated < ?)", array($loglifetime)); // Delete records from backup_controllers. $DB->execute("DELETE FROM {backup_controllers}\n WHERE timecreated < ?", array($loglifetime)); mtrace(" Deleted old backup records"); } // Delete old cached texts if (!empty($CFG->cachetext)) { // Defined in config.php $cachelifetime = time() - $CFG->cachetext - 60; // Add an extra minute to allow for really heavy sites $DB->delete_records_select('cache_text', "timemodified < ?", array($cachelifetime)); mtrace(" Deleted old cache_text records"); } if (!empty($CFG->usetags)) { require_once $CFG->dirroot . '/tag/lib.php'; tag_cron(); mtrace(' Executed tag cron'); } // Context maintenance stuff context_helper::cleanup_instances(); mtrace(' Cleaned up context instances'); context_helper::build_all_paths(false); // If you suspect that the context paths are somehow corrupt // replace the line below with: context_helper::build_all_paths(true); mtrace(' Built context paths'); // Remove expired cache flags gc_cache_flags(); mtrace(' Cleaned cache flags'); // Cleanup messaging if (!empty($CFG->messagingdeletereadnotificationsdelay)) { $notificationdeletetime = time() - $CFG->messagingdeletereadnotificationsdelay; $DB->delete_records_select('message_read', 'notification=1 AND timeread<:notificationdeletetime', array('notificationdeletetime' => $notificationdeletetime)); mtrace(' Cleaned up read notifications'); } mtrace(' Deleting temporary files...'); cron_delete_from_temp(); // Cleanup user password reset records // Delete any reset request records which are expired by more than a day. // (We keep recently expired requests around so we can give a different error msg to users who // are trying to user a recently expired reset attempt). $pwresettime = isset($CFG->pwresettime) ? $CFG->pwresettime : 1800; $earliestvalid = time() - $pwresettime - DAYSECS; $DB->delete_records_select('user_password_resets', "timerequested < ?", array($earliestvalid)); mtrace(' Cleaned up old password reset records'); mtrace("...finished clean-up tasks"); } // End of occasional clean-up tasks // Send login failures notification - brute force protection in moodle is weak, // we should at least send notices early in each cron execution if (notify_login_failures()) { mtrace(' Notified login failures'); } // Make sure all context instances are properly created - they may be required in auth, enrol, etc. context_helper::create_instances(); mtrace(' Created missing context instances'); // Session gc. mtrace("Running session gc tasks..."); \core\session\manager::gc(); mtrace("...finished stale session cleanup"); // Run the auth cron, if any before enrolments // because it might add users that will be needed in enrol plugins $auths = get_enabled_auth_plugins(); mtrace("Running auth crons if required..."); cron_trace_time_and_memory(); foreach ($auths as $auth) { $authplugin = get_auth_plugin($auth); if (method_exists($authplugin, 'cron')) { mtrace("Running cron for auth/{$auth}..."); $authplugin->cron(); if (!empty($authplugin->log)) { mtrace($authplugin->log); } } unset($authplugin); } // Generate new password emails for users - ppl expect these generated asap if ($DB->count_records('user_preferences', array('name' => 'create_password', 'value' => '1'))) { mtrace('Creating passwords for new users...'); $usernamefields = get_all_user_name_fields(true, 'u'); $newusers = $DB->get_recordset_sql("SELECT u.id as id, u.email,\n {$usernamefields}, u.username, u.lang,\n p.id as prefid\n FROM {user} u\n JOIN {user_preferences} p ON u.id=p.userid\n WHERE p.name='create_password' AND p.value='1' AND u.email !='' AND u.suspended = 0 AND u.auth != 'nologin' AND u.deleted = 0"); // note: we can not send emails to suspended accounts foreach ($newusers as $newuser) { // Use a low cost factor when generating bcrypt hash otherwise // hashing would be slow when emailing lots of users. Hashes // will be automatically updated to a higher cost factor the first // time the user logs in. if (setnew_password_and_mail($newuser, true)) { unset_user_preference('create_password', $newuser); set_user_preference('auth_forcepasswordchange', 1, $newuser); } else { trigger_error("Could not create and mail new user password!"); } } $newusers->close(); } // It is very important to run enrol early // because other plugins depend on correct enrolment info. mtrace("Running enrol crons if required..."); $enrols = enrol_get_plugins(true); foreach ($enrols as $ename => $enrol) { // do this for all plugins, disabled plugins might want to cleanup stuff such as roles if (!$enrol->is_cron_required()) { continue; } mtrace("Running cron for enrol_{$ename}..."); cron_trace_time_and_memory(); $enrol->cron(); $enrol->set_config('lastcron', time()); } // Run all cron jobs for each module mtrace("Starting activity modules"); get_mailer('buffer'); if ($mods = $DB->get_records_select("modules", "cron > 0 AND ((? - lastcron) > cron) AND visible = 1", array($timenow))) { foreach ($mods as $mod) { $libfile = "{$CFG->dirroot}/mod/{$mod->name}/lib.php"; if (file_exists($libfile)) { include_once $libfile; $cron_function = $mod->name . "_cron"; if (function_exists($cron_function)) { mtrace("Processing module function {$cron_function} ...", ''); cron_trace_time_and_memory(); $pre_dbqueries = null; $pre_dbqueries = $DB->perf_get_queries(); $pre_time = microtime(1); if ($cron_function()) { $DB->set_field("modules", "lastcron", $timenow, array("id" => $mod->id)); } if (isset($pre_dbqueries)) { mtrace("... used " . ($DB->perf_get_queries() - $pre_dbqueries) . " dbqueries"); mtrace("... used " . (microtime(1) - $pre_time) . " seconds"); } // Reset possible changes by modules to time_limit. MDL-11597 @set_time_limit(0); mtrace("done."); } } } } get_mailer('close'); mtrace("Finished activity modules"); mtrace("Starting blocks"); if ($blocks = $DB->get_records_select("block", "cron > 0 AND ((? - lastcron) > cron) AND visible = 1", array($timenow))) { // We will need the base class. require_once $CFG->dirroot . '/blocks/moodleblock.class.php'; foreach ($blocks as $block) { $blockfile = $CFG->dirroot . '/blocks/' . $block->name . '/block_' . $block->name . '.php'; if (file_exists($blockfile)) { require_once $blockfile; $classname = 'block_' . $block->name; $blockobj = new $classname(); if (method_exists($blockobj, 'cron')) { mtrace("Processing cron function for " . $block->name . '....', ''); cron_trace_time_and_memory(); if ($blockobj->cron()) { $DB->set_field('block', 'lastcron', $timenow, array('id' => $block->id)); } // Reset possible changes by blocks to time_limit. MDL-11597 @set_time_limit(0); mtrace('done.'); } } } } mtrace('Finished blocks'); mtrace('Starting admin reports'); cron_execute_plugin_type('report'); mtrace('Finished admin reports'); mtrace('Starting main gradebook job...'); cron_trace_time_and_memory(); grade_cron(); mtrace('done.'); mtrace('Starting processing the event queue...'); cron_trace_time_and_memory(); events_cron(); mtrace('done.'); if ($CFG->enablecompletion) { // Completion cron mtrace('Starting the completion cron...'); cron_trace_time_and_memory(); require_once $CFG->dirroot . '/completion/cron.php'; completion_cron(); mtrace('done'); } if ($CFG->enableportfolios) { // Portfolio cron mtrace('Starting the portfolio cron...'); cron_trace_time_and_memory(); require_once $CFG->libdir . '/portfoliolib.php'; portfolio_cron(); mtrace('done'); } //now do plagiarism checks require_once $CFG->libdir . '/plagiarismlib.php'; plagiarism_cron(); mtrace('Starting course reports'); cron_execute_plugin_type('coursereport'); mtrace('Finished course reports'); // run gradebook import/export/report cron mtrace('Starting gradebook plugins'); cron_execute_plugin_type('gradeimport'); cron_execute_plugin_type('gradeexport'); cron_execute_plugin_type('gradereport'); mtrace('Finished gradebook plugins'); // run calendar cron require_once "{$CFG->dirroot}/calendar/lib.php"; calendar_cron(); // Run external blog cron if needed if (!empty($CFG->enableblogs) && $CFG->useexternalblogs) { require_once $CFG->dirroot . '/blog/lib.php'; mtrace("Fetching external blog entries...", ''); cron_trace_time_and_memory(); $sql = "timefetched < ? OR timefetched = 0"; $externalblogs = $DB->get_records_select('blog_external', $sql, array(time() - $CFG->externalblogcrontime)); foreach ($externalblogs as $eb) { blog_sync_external_entries($eb); } mtrace('done.'); } // Run blog associations cleanup if (!empty($CFG->enableblogs) && $CFG->useblogassociations) { require_once $CFG->dirroot . '/blog/lib.php'; // delete entries whose contextids no longer exists mtrace("Deleting blog associations linked to non-existent contexts...", ''); cron_trace_time_and_memory(); $DB->delete_records_select('blog_association', 'contextid NOT IN (SELECT id FROM {context})'); mtrace('done.'); } // Run question bank clean-up. mtrace("Starting the question bank cron...", ''); cron_trace_time_and_memory(); require_once $CFG->libdir . '/questionlib.php'; question_bank::cron(); mtrace('done.'); //Run registration updated cron mtrace(get_string('siteupdatesstart', 'hub')); cron_trace_time_and_memory(); require_once $CFG->dirroot . '/' . $CFG->admin . '/registration/lib.php'; $registrationmanager = new registration_manager(); $registrationmanager->cron(); mtrace(get_string('siteupdatesend', 'hub')); // If enabled, fetch information about available updates and eventually notify site admins if (empty($CFG->disableupdatenotifications)) { $updateschecker = \core\update\checker::instance(); $updateschecker->cron(); } //cleanup old session linked tokens //deletes the session linked tokens that are over a day old. mtrace("Deleting session linked tokens more than one day old...", ''); cron_trace_time_and_memory(); $DB->delete_records_select('external_tokens', 'lastaccess < :onedayago AND tokentype = :tokentype', array('onedayago' => time() - DAYSECS, 'tokentype' => EXTERNAL_TOKEN_EMBEDDED)); mtrace('done.'); // all other plugins cron_execute_plugin_type('message', 'message plugins'); cron_execute_plugin_type('filter', 'filters'); cron_execute_plugin_type('editor', 'editors'); cron_execute_plugin_type('format', 'course formats'); cron_execute_plugin_type('profilefield', 'profile fields'); cron_execute_plugin_type('webservice', 'webservices'); cron_execute_plugin_type('repository', 'repository plugins'); cron_execute_plugin_type('qbehaviour', 'question behaviours'); cron_execute_plugin_type('qformat', 'question import/export formats'); cron_execute_plugin_type('qtype', 'question types'); cron_execute_plugin_type('plagiarism', 'plagiarism plugins'); cron_execute_plugin_type('theme', 'themes'); cron_execute_plugin_type('tool', 'admin tools'); // and finally run any local cronjobs, if any if ($locals = core_component::get_plugin_list('local')) { mtrace('Processing customized cron scripts ...', ''); // new cron functions in lib.php first cron_execute_plugin_type('local'); // legacy cron files are executed directly foreach ($locals as $local => $localdir) { if (file_exists("{$localdir}/cron.php")) { include "{$localdir}/cron.php"; } } mtrace('done.'); } mtrace('Running cache cron routines'); cache_helper::cron(); mtrace('done.'); // Run automated backups if required - these may take a long time to execute require_once $CFG->dirroot . '/backup/util/includes/backup_includes.php'; require_once $CFG->dirroot . '/backup/util/helper/backup_cron_helper.class.php'; backup_cron_automated_helper::run_automated_backup(); // Run stats as at the end because they are known to take very long time on large sites if (!empty($CFG->enablestats) and empty($CFG->disablestatsprocessing)) { require_once $CFG->dirroot . '/lib/statslib.php'; // check we're not before our runtime $timetocheck = stats_get_base_daily() + $CFG->statsruntimestarthour * 60 * 60 + $CFG->statsruntimestartminute * 60; if (time() > $timetocheck) { // process configured number of days as max (defaulting to 31) $maxdays = empty($CFG->statsruntimedays) ? 31 : abs($CFG->statsruntimedays); if (stats_cron_daily($maxdays)) { if (stats_cron_weekly()) { if (stats_cron_monthly()) { stats_clean_old(); } } } @set_time_limit(0); } else { mtrace('Next stats run after:' . userdate($timetocheck)); } } // Run badges review cron. mtrace("Starting badges cron..."); require_once $CFG->dirroot . '/badges/cron.php'; badge_cron(); mtrace('done.'); // cleanup file trash - not very important $fs = get_file_storage(); $fs->cron(); mtrace("Cron script completed correctly"); gc_collect_cycles(); mtrace('Cron completed at ' . date('H:i:s') . '. Memory used ' . display_size(memory_get_usage()) . '.'); $difftime = microtime_diff($starttime, microtime()); mtrace("Execution took " . $difftime . " seconds"); }
/** * Function to be run periodically according to the moodle cron * * Finds all assignment notifications that have yet to be mailed out, and mails them */ function assignment_cron () { global $CFG, $USER, $DB; /// first execute all crons in plugins if ($plugins = get_plugin_list('assignment')) { foreach ($plugins as $plugin=>$dir) { require_once("$dir/assignment.class.php"); $assignmentclass = "assignment_$plugin"; $ass = new $assignmentclass(); $ass->cron(); } } /// Notices older than 1 day will not be mailed. This is to avoid the problem where /// cron has not been running for a long time, and then suddenly people are flooded /// with mail from the past few weeks or months $timenow = time(); $endtime = $timenow - $CFG->maxeditingtime; $starttime = $endtime - 24 * 3600; /// One day earlier if ($submissions = assignment_get_unmailed_submissions($starttime, $endtime)) { $realuser = clone($USER); foreach ($submissions as $key => $submission) { $DB->set_field("assignment_submissions", "mailed", "1", array("id"=>$submission->id)); } $timenow = time(); foreach ($submissions as $submission) { echo "Processing assignment submission $submission->id\n"; if (! $user = $DB->get_record("user", array("id"=>$submission->userid))) { echo "Could not find user $user->id\n"; continue; } if (! $course = $DB->get_record("course", array("id"=>$submission->course))) { echo "Could not find course $submission->course\n"; continue; } /// Override the language and timezone of the "current" user, so that /// mail is customised for the receiver. cron_setup_user($user, $course); if (!is_enrolled(get_context_instance(CONTEXT_COURSE, $submission->course), $user->id)) { echo fullname($user)." not an active participant in " . format_string($course->shortname) . "\n"; continue; } if (! $teacher = $DB->get_record("user", array("id"=>$submission->teacher))) { echo "Could not find teacher $submission->teacher\n"; continue; } if (! $mod = get_coursemodule_from_instance("assignment", $submission->assignment, $course->id)) { echo "Could not find course module for assignment id $submission->assignment\n"; continue; } if (! $mod->visible) { /// Hold mail notification for hidden assignments until later continue; } $strassignments = get_string("modulenameplural", "assignment"); $strassignment = get_string("modulename", "assignment"); $assignmentinfo = new stdClass(); $assignmentinfo->teacher = fullname($teacher); $assignmentinfo->assignment = format_string($submission->name,true); $assignmentinfo->url = "$CFG->wwwroot/mod/assignment/view.php?id=$mod->id"; $postsubject = "$course->shortname: $strassignments: ".format_string($submission->name,true); $posttext = "$course->shortname -> $strassignments -> ".format_string($submission->name,true)."\n"; $posttext .= "---------------------------------------------------------------------\n"; $posttext .= get_string("assignmentmail", "assignment", $assignmentinfo)."\n"; $posttext .= "---------------------------------------------------------------------\n"; if ($user->mailformat == 1) { // HTML $posthtml = "<p><font face=\"sans-serif\">". "<a href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> ->". "<a href=\"$CFG->wwwroot/mod/assignment/index.php?id=$course->id\">$strassignments</a> ->". "<a href=\"$CFG->wwwroot/mod/assignment/view.php?id=$mod->id\">".format_string($submission->name,true)."</a></font></p>"; $posthtml .= "<hr /><font face=\"sans-serif\">"; $posthtml .= "<p>".get_string("assignmentmailhtml", "assignment", $assignmentinfo)."</p>"; $posthtml .= "</font><hr />"; } else { $posthtml = ""; } $eventdata = new stdClass(); $eventdata->modulename = 'assignment'; $eventdata->userfrom = $teacher; $eventdata->userto = $user; $eventdata->subject = $postsubject; $eventdata->fullmessage = $posttext; $eventdata->fullmessageformat = FORMAT_PLAIN; $eventdata->fullmessagehtml = $posthtml; $eventdata->smallmessage = ''; $eventdata->name = 'assignment_updates'; $eventdata->component = 'mod_assignment'; $eventdata->notification = 1; message_send($eventdata); } cron_setup_user(); } return true; }
/** * Finds all assignment notifications that have yet to be mailed out, and mails them. * * Cron function to be run periodically according to the moodle cron. * * @return bool */ public static function cron() { global $DB; // Only ever send a max of one days worth of updates. $yesterday = time() - 24 * 3600; $timenow = time(); $lastcron = $DB->get_field('modules', 'lastcron', array('name' => 'assign')); // Collect all submissions that require mailing. // Submissions are included if all are true: // - The assignment is visible in the gradebook. // - No previous notification has been sent. // - If marking workflow is not enabled, the grade was updated in the past 24 hours, or // if marking workflow is enabled, the workflow state is at 'released'. $sql = "SELECT g.id as gradeid, a.course, a.name, a.blindmarking, a.revealidentities,\n g.*, g.timemodified as lastmodified, cm.id as cmid\n FROM {assign} a\n JOIN {assign_grades} g ON g.assignment = a.id\n LEFT JOIN {assign_user_flags} uf ON uf.assignment = a.id AND uf.userid = g.userid\n JOIN {course_modules} cm ON cm.course = a.course AND cm.instance = a.id\n JOIN {modules} md ON md.id = cm.module AND md.name = 'assign'\n JOIN {grade_items} gri ON gri.iteminstance = a.id AND gri.courseid = a.course AND gri.itemmodule = md.name\n WHERE ((a.markingworkflow = 0 AND g.timemodified >= :yesterday AND g.timemodified <= :today) OR\n (a.markingworkflow = 1 AND uf.workflowstate = :wfreleased)) AND\n uf.mailed = 0 AND gri.hidden = 0\n ORDER BY a.course, cm.id"; $params = array('yesterday' => $yesterday, 'today' => $timenow, 'wfreleased' => ASSIGN_MARKING_WORKFLOW_STATE_RELEASED); $submissions = $DB->get_records_sql($sql, $params); if (!empty($submissions)) { mtrace('Processing ' . count($submissions) . ' assignment submissions ...'); // Preload courses we are going to need those. $courseids = array(); foreach ($submissions as $submission) { $courseids[] = $submission->course; } // Filter out duplicates. $courseids = array_unique($courseids); $ctxselect = context_helper::get_preload_record_columns_sql('ctx'); list($courseidsql, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED); $sql = 'SELECT c.*, ' . $ctxselect . ' FROM {course} c LEFT JOIN {context} ctx ON ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel WHERE c.id ' . $courseidsql; $params['contextlevel'] = CONTEXT_COURSE; $courses = $DB->get_records_sql($sql, $params); // Clean up... this could go on for a while. unset($courseids); unset($ctxselect); unset($courseidsql); unset($params); // Message students about new feedback. foreach ($submissions as $submission) { mtrace("Processing assignment submission {$submission->id} ..."); // Do not cache user lookups - could be too many. if (!($user = $DB->get_record('user', array('id' => $submission->userid)))) { mtrace('Could not find user ' . $submission->userid); continue; } // Use a cache to prevent the same DB queries happening over and over. if (!array_key_exists($submission->course, $courses)) { mtrace('Could not find course ' . $submission->course); continue; } $course = $courses[$submission->course]; if (isset($course->ctxid)) { // Context has not yet been preloaded. Do so now. context_helper::preload_from_record($course); } // Override the language and timezone of the "current" user, so that // mail is customised for the receiver. cron_setup_user($user, $course); // Context lookups are already cached. $coursecontext = context_course::instance($course->id); if (!is_enrolled($coursecontext, $user->id)) { $courseshortname = format_string($course->shortname, true, array('context' => $coursecontext)); mtrace(fullname($user) . ' not an active participant in ' . $courseshortname); continue; } if (!($grader = $DB->get_record('user', array('id' => $submission->grader)))) { mtrace('Could not find grader ' . $submission->grader); continue; } $modinfo = get_fast_modinfo($course, $user->id); $cm = $modinfo->get_cm($submission->cmid); // Context lookups are already cached. $contextmodule = context_module::instance($cm->id); if (!$cm->uservisible) { // Hold mail notification for assignments the user cannot access until later. continue; } // Need to send this to the student. $messagetype = 'feedbackavailable'; $eventtype = 'assign_notification'; $updatetime = $submission->lastmodified; $modulename = get_string('modulename', 'assign'); $uniqueid = 0; if ($submission->blindmarking && !$submission->revealidentities) { $uniqueid = self::get_uniqueid_for_user_static($submission->assignment, $user->id); } $showusers = $submission->blindmarking && !$submission->revealidentities; self::send_assignment_notification($grader, $user, $messagetype, $eventtype, $updatetime, $cm, $contextmodule, $course, $modulename, $submission->name, $showusers, $uniqueid); $flags = $DB->get_record('assign_user_flags', array('userid' => $user->id, 'assignment' => $submission->assignment)); if ($flags) { $flags->mailed = 1; $DB->update_record('assign_user_flags', $flags); } else { $flags = new stdClass(); $flags->userid = $user->id; $flags->assignment = $submission->assignment; $flags->mailed = 1; $DB->insert_record('assign_user_flags', $flags); } mtrace('Done'); } mtrace('Done processing ' . count($submissions) . ' assignment submissions'); cron_setup_user(); // Free up memory just to be sure. unset($courses); } // Update calendar events to provide a description. $sql = 'SELECT id FROM {assign} WHERE allowsubmissionsfromdate >= :lastcron AND allowsubmissionsfromdate <= :timenow AND alwaysshowdescription = 0'; $params = array('lastcron' => $lastcron, 'timenow' => $timenow); $newlyavailable = $DB->get_records_sql($sql, $params); foreach ($newlyavailable as $record) { $cm = get_coursemodule_from_instance('assign', $record->id, 0, false, MUST_EXIST); $context = context_module::instance($cm->id); $assignment = new assign($context, null, null); $assignment->update_calendar($cm->id); } return true; }
/** * Notify users about enrolments that are going to expire soon! * This function is run by admin/cron.php * @return void */ function cron() { global $CFG, $USER, $SITE, $DB; if (!isset($CFG->lastexpirynotify)) { set_config('lastexpirynotify', 0); } // notify once a day only - TODO: add some tz handling here, maybe use timestamps if ($CFG->lastexpirynotify == date('Ymd')) { return; } if ($rs = $DB->get_recordset_select('course', 'enrolperiod > 0 AND expirynotify > 0 AND expirythreshold > 0')) { $admin = get_admin(); foreach ($rs as $course) { $a = new object(); $a->coursename = $course->shortname . '/' . $course->fullname; // must be processed by format_string later $a->threshold = $course->expirythreshold / 86400; $a->extendurl = $CFG->wwwroot . '/user/index.php?id=' . $course->id; $a->current = array(); $a->past = array(); $expiry = time() + $course->expirythreshold; $cname = $course->fullname; /// Get all the manual role assignments for this course that have expired. if (!($context = get_context_instance(CONTEXT_COURSE, $course->id))) { continue; } if ($oldenrolments = $DB->get_records_sql("\n SELECT u.*, ra.timeend\n FROM {user} u\n JOIN {role_assignments} ra ON (ra.userid = u.id)\n WHERE ra.contextid = {$context->id}\n AND ra.timeend > 0 AND ra.timeend <= {$expiry}\n AND ra.enrol = 'manual'")) { // inform user who can assign roles or admin if ($teachers = get_users_by_capability($context, 'moodle/role:assign', '', '', '', '', '', '', false)) { $teachers = sort_by_roleassignment_authority($teachers, $context); $teacher = reset($teachers); } else { $teachers = array($admin); $teacher = $admin; } $a->teacherstr = fullname($teacher, true); foreach ($oldenrolments as $user) { /// Email all users about to expire $a->studentstr = fullname($user, true); if ($user->timeend < $expiry - 86400) { $a->past[] = fullname($user) . " <{$user->email}>"; } else { $a->current[] = fullname($user) . " <{$user->email}>"; if ($course->notifystudents) { // Send this guy notice // setup global $COURSE properly - needed for languages cron_setup_user($user, $course); $a->coursename = format_string($cname); $a->course = $a->coursename; $strexpirynotifystudentsemail = get_string('expirynotifystudentsemail', '', $a); $strexpirynotify = get_string('expirynotify'); $eventdata = new object(); $eventdata->modulename = 'moodle'; $eventdata->userfrom = $teacher; $eventdata->userto = $user; $eventdata->subject = format_string($SITE->fullname) . ' ' . $strexpirynotify; $eventdata->fullmessage = $strexpirynotifystudentsemail; $eventdata->fullmessageformat = FORMAT_PLAIN; $eventdata->fullmessagehtml = ''; $eventdata->smallmessage = ''; events_trigger('message_send', $eventdata); } } } $a->current = implode("\n", $a->current); $a->past = implode("\n", $a->past); if ($a->current || $a->past) { foreach ($teachers as $teacher) { // setup global $COURSE properly - needed for languages cron_setup_user($teacher, $course); $a->coursename = format_string($cname); $strexpirynotifyemail = get_string('expirynotifyemail', '', $a); $strexpirynotify = get_string('expirynotify'); $eventdata = new object(); $eventdata->modulename = 'moodle'; $eventdata->userfrom = $admin; $eventdata->userto = $teacher; $eventdata->subject = $a->coursename . ' ' . $strexpirynotify; $eventdata->fullmessage = $strexpirynotifyemail; $eventdata->fullmessageformat = FORMAT_PLAIN; $eventdata->fullmessagehtml = ''; $eventdata->smallmessage = ''; events_trigger('message_send', $eventdata); } } } } $rs->close(); cron_setup_user(); } set_config('lastexpirynotify', date('Ymd')); }
/** * Execute cron tasks */ function cron_run() { global $DB, $CFG, $OUTPUT; if (CLI_MAINTENANCE) { echo "CLI maintenance mode active, cron execution suspended.\n"; exit(1); } if (moodle_needs_upgrading()) { echo "Moodle upgrade pending, cron execution suspended.\n"; exit(1); } require_once $CFG->libdir . '/adminlib.php'; if (!empty($CFG->showcronsql)) { $DB->set_debug(true); } if (!empty($CFG->showcrondebugging)) { set_debugging(DEBUG_DEVELOPER, true); } core_php_time_limit::raise(); $starttime = microtime(); // Increase memory limit raise_memory_limit(MEMORY_EXTRA); // Emulate normal session - we use admin accoutn by default cron_setup_user(); // Start output log $timenow = time(); mtrace("Server Time: " . date('r', $timenow) . "\n\n"); // Run all scheduled tasks. while (!\core\task\manager::static_caches_cleared_since($timenow) && ($task = \core\task\manager::get_next_scheduled_task($timenow))) { mtrace("Execute scheduled task: " . $task->get_name()); cron_trace_time_and_memory(); $predbqueries = null; $predbqueries = $DB->perf_get_queries(); $pretime = microtime(1); try { get_mailer('buffer'); $task->execute(); if ($DB->is_transaction_started()) { throw new coding_exception("Task left transaction open"); } if (isset($predbqueries)) { mtrace("... used " . ($DB->perf_get_queries() - $predbqueries) . " dbqueries"); mtrace("... used " . (microtime(1) - $pretime) . " seconds"); } mtrace("Scheduled task complete: " . $task->get_name()); \core\task\manager::scheduled_task_complete($task); } catch (Exception $e) { if ($DB && $DB->is_transaction_started()) { error_log('Database transaction aborted automatically in ' . get_class($task)); $DB->force_transaction_rollback(); } if (isset($predbqueries)) { mtrace("... used " . ($DB->perf_get_queries() - $predbqueries) . " dbqueries"); mtrace("... used " . (microtime(1) - $pretime) . " seconds"); } mtrace("Scheduled task failed: " . $task->get_name() . "," . $e->getMessage()); if ($CFG->debugdeveloper) { if (!empty($e->debuginfo)) { mtrace("Debug info:"); mtrace($e->debuginfo); } mtrace("Backtrace:"); mtrace(format_backtrace($e->getTrace(), true)); } \core\task\manager::scheduled_task_failed($task); } get_mailer('close'); unset($task); } // Run all adhoc tasks. while (!\core\task\manager::static_caches_cleared_since($timenow) && ($task = \core\task\manager::get_next_adhoc_task($timenow))) { mtrace("Execute adhoc task: " . get_class($task)); cron_trace_time_and_memory(); $predbqueries = null; $predbqueries = $DB->perf_get_queries(); $pretime = microtime(1); try { get_mailer('buffer'); $task->execute(); if ($DB->is_transaction_started()) { throw new coding_exception("Task left transaction open"); } if (isset($predbqueries)) { mtrace("... used " . ($DB->perf_get_queries() - $predbqueries) . " dbqueries"); mtrace("... used " . (microtime(1) - $pretime) . " seconds"); } mtrace("Adhoc task complete: " . get_class($task)); \core\task\manager::adhoc_task_complete($task); } catch (Exception $e) { if ($DB && $DB->is_transaction_started()) { error_log('Database transaction aborted automatically in ' . get_class($task)); $DB->force_transaction_rollback(); } if (isset($predbqueries)) { mtrace("... used " . ($DB->perf_get_queries() - $predbqueries) . " dbqueries"); mtrace("... used " . (microtime(1) - $pretime) . " seconds"); } mtrace("Adhoc task failed: " . get_class($task) . "," . $e->getMessage()); if ($CFG->debugdeveloper) { if (!empty($e->debuginfo)) { mtrace("Debug info:"); mtrace($e->debuginfo); } mtrace("Backtrace:"); mtrace(format_backtrace($e->getTrace(), true)); } \core\task\manager::adhoc_task_failed($task); } get_mailer('close'); unset($task); } mtrace("Cron script completed correctly"); gc_collect_cycles(); mtrace('Cron completed at ' . date('H:i:s') . '. Memory used ' . display_size(memory_get_usage()) . '.'); $difftime = microtime_diff($starttime, microtime()); mtrace("Execution took " . $difftime . " seconds"); }
/** * Runs all ad-hoc tasks in the queue. * * This is faster and more reliable than running cron (running cron won't * work more than once in the same test, for instance). However it is * a little less 'realistic'. * * While the task is running, we suppress mtrace output because it makes * the Behat result look ugly. * * @Given /^I run all adhoc tasks$/ * @throws DriverException */ public function i_run_all_adhoc_tasks() { // Do setup for cron task. cron_setup_user(); // Run tasks. Locking is handled by get_next_adhoc_task. $now = time(); ob_start(); // Discard task output as not appropriate for Behat output! while (($task = \core\task\manager::get_next_adhoc_task($now)) !== null) { try { $task->execute(); // Mark task complete. \core\task\manager::adhoc_task_complete($task); } catch (Exception $e) { // Mark task failed and throw exception. \core\task\manager::adhoc_task_failed($task); ob_end_clean(); throw new DriverException('An adhoc task failed', 0, $e); } } ob_end_clean(); }
public function test_cron_setup_user() { global $PAGE, $USER, $SESSION, $SITE, $CFG; $this->resetAfterTest(); // NOTE: this function contains some static caches, let's reset first. cron_setup_user('reset'); $admin = get_admin(); $user1 = $this->getDataGenerator()->create_user(); $user2 = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); cron_setup_user(); $this->assertSame($admin->id, $USER->id); $this->assertSame($PAGE->context, context_course::instance($SITE->id)); $this->assertSame($CFG->timezone, $USER->timezone); $this->assertSame('', $USER->lang); $this->assertSame('', $USER->theme); $SESSION->test1 = true; $adminsession = $SESSION; $adminuser = $USER; $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); $this->assertSame($GLOBALS['SESSION'], $SESSION); $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); $this->assertSame($GLOBALS['USER'], $USER); cron_setup_user(null, $course); $this->assertSame($admin->id, $USER->id); $this->assertSame($PAGE->context, context_course::instance($course->id)); $this->assertSame($adminsession, $SESSION); $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); $this->assertSame($GLOBALS['SESSION'], $SESSION); $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); $this->assertSame($GLOBALS['USER'], $USER); cron_setup_user($user1); $this->assertSame($user1->id, $USER->id); $this->assertSame($PAGE->context, context_course::instance($SITE->id)); $this->assertNotSame($adminsession, $SESSION); $this->assertObjectNotHasAttribute('test1', $SESSION); $this->assertEmpty((array) $SESSION); $usersession1 = $SESSION; $SESSION->test2 = true; $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); $this->assertSame($GLOBALS['SESSION'], $SESSION); $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); $this->assertSame($GLOBALS['USER'], $USER); cron_setup_user($user1); $this->assertSame($user1->id, $USER->id); $this->assertSame($PAGE->context, context_course::instance($SITE->id)); $this->assertNotSame($adminsession, $SESSION); $this->assertSame($usersession1, $SESSION); $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); $this->assertSame($GLOBALS['SESSION'], $SESSION); $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); $this->assertSame($GLOBALS['USER'], $USER); cron_setup_user($user2); $this->assertSame($user2->id, $USER->id); $this->assertSame($PAGE->context, context_course::instance($SITE->id)); $this->assertNotSame($adminsession, $SESSION); $this->assertNotSame($usersession1, $SESSION); $this->assertEmpty((array) $SESSION); $usersession2 = $SESSION; $usersession2->test3 = true; $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); $this->assertSame($GLOBALS['SESSION'], $SESSION); $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); $this->assertSame($GLOBALS['USER'], $USER); cron_setup_user($user2, $course); $this->assertSame($user2->id, $USER->id); $this->assertSame($PAGE->context, context_course::instance($course->id)); $this->assertNotSame($adminsession, $SESSION); $this->assertNotSame($usersession1, $SESSION); $this->assertSame($usersession2, $SESSION); $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); $this->assertSame($GLOBALS['SESSION'], $SESSION); $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); $this->assertSame($GLOBALS['USER'], $USER); cron_setup_user($user1); $this->assertSame($user1->id, $USER->id); $this->assertSame($PAGE->context, context_course::instance($SITE->id)); $this->assertNotSame($adminsession, $SESSION); $this->assertNotSame($usersession1, $SESSION); $this->assertEmpty((array) $SESSION); $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); $this->assertSame($GLOBALS['SESSION'], $SESSION); $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); $this->assertSame($GLOBALS['USER'], $USER); cron_setup_user(); $this->assertSame($admin->id, $USER->id); $this->assertSame($PAGE->context, context_course::instance($SITE->id)); $this->assertSame($adminsession, $SESSION); $this->assertSame($adminuser, $USER); $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); $this->assertSame($GLOBALS['SESSION'], $SESSION); $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); $this->assertSame($GLOBALS['USER'], $USER); cron_setup_user('reset'); $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); $this->assertSame($GLOBALS['SESSION'], $SESSION); $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); $this->assertSame($GLOBALS['USER'], $USER); cron_setup_user(); $this->assertNotSame($adminsession, $SESSION); $this->assertNotSame($adminuser, $USER); $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); $this->assertSame($GLOBALS['SESSION'], $SESSION); $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); $this->assertSame($GLOBALS['USER'], $USER); }
/** * Gets called by the adhoc_taskmanager and its task in send_distribution_notification * * @param user $userfrom */ public function notify_users_distribution($userfrom) { global $CFG; $userfrom = get_complete_user_data('id', $userfrom); // make sure we have not sent them yet if ($this->origdbrecord->{this_db\ratingallocate::NOTIFICATIONSEND} != -1) { mtrace('seems we have sent them already'); return true; } $choices = $this->get_choices_with_allocationcount(); $allocations = $this->get_allocations(); foreach ($allocations as $userid => $allocobj) { // get the assigned choice_id $alloc_choic_id = $allocobj->choiceid; // Prepare the email to be sent to the user $userto = get_complete_user_data('id', $allocobj->userid); cron_setup_user($userto); // prepare Text $notiftext = $this->make_mail_text($choices[$alloc_choic_id]); $notifhtml = $this->make_mail_html($choices[$alloc_choic_id]); $notifsubject = format_string($this->course->shortname, true) . ': ' . get_string('allocation_notification_message_subject', 'ratingallocate', $this->ratingallocate->name); // Send the post now! if (empty($userto->mailformat) || $userto->mailformat != 1) { // This user DOESN'T want to receive HTML $notifhtml = ''; } $attachment = $attachname = ''; $mailresult = email_to_user($userto, $userfrom, $notifsubject, $notiftext, $notifhtml, $attachment, $attachname); if (!$mailresult) { mtrace("ERROR: mod/ratingallocate/locallib.php: Could not send out digest mail to user {$userto->id} " . "({$userto->email})... not trying again."); } else { mtrace("success."); } } // update the 'notified' flag $this->origdbrecord->{this_db\ratingallocate::NOTIFICATIONSEND} = 1; $this->ratingallocate = new ratingallocate_db_wrapper($this->origdbrecord); $this->db->update_record(this_db\ratingallocate::TABLE, $this->origdbrecord); }
public function test_cron() { // First run cron so there are no messages waiting to be sent (from other tests). cron_setup_user(); assign::cron(); // Now create an assignment and add some feedback. $this->setUser($this->editingteachers[0]); $assign = $this->create_instance(array('sendstudentnotifications' => 1)); // Simulate adding a grade. $this->setUser($this->teachers[0]); $data = new stdClass(); $data->grade = '50.0'; $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0); $assign->testable_apply_grade_to_user($data, $this->students[1]->id, 0); $data->sendstudentnotifications = false; $assign->testable_apply_grade_to_user($data, $this->students[2]->id, 0); // Now run cron and see that one message was sent. $this->preventResetByRollback(); $sink = $this->redirectMessages(); cron_setup_user(); $this->expectOutputRegex('/Done processing 2 assignment submissions/'); assign::cron(); $messages = $sink->get_messages(); // The sent count should be 2, because the 3rd one was marked as do not send notifications. $this->assertEquals(2, count($messages)); $this->assertEquals(1, $messages[0]->notification); $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname); }
/** * Duplicates a Moodle module in an existing course * @param int $cmid Course module id * @param int $courseid Course id * @return int New course module id */ function local_ltiprovider_duplicate_module($cmid, $courseid, $newidnumber, $lticontext) { global $CFG, $DB, $USER; require_once $CFG->dirroot . '/backup/util/includes/backup_includes.php'; require_once $CFG->dirroot . '/backup/util/includes/restore_includes.php'; require_once $CFG->libdir . '/filelib.php'; if (empty($USER)) { // Emulate session. cron_setup_user(); } $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST); $cm = get_coursemodule_from_id('', $cmid, 0, true, MUST_EXIST); $cmcontext = context_module::instance($cm->id); $context = context_course::instance($course->id); if (!plugin_supports('mod', $cm->modname, FEATURE_BACKUP_MOODLE2)) { $url = course_get_url($course, $cm->sectionnum, array('sr' => $sectionreturn)); print_error('duplicatenosupport', 'error', $url, $a); } // backup the activity $admin = get_admin(); $bc = new backup_controller(backup::TYPE_1ACTIVITY, $cm->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT, $admin->id); $backupid = $bc->get_backupid(); $backupbasepath = $bc->get_plan()->get_basepath(); $bc->execute_plan(); $bc->destroy(); // restore the backup immediately $rc = new restore_controller($backupid, $courseid, backup::INTERACTIVE_NO, backup::MODE_IMPORT, $admin->id, backup::TARGET_CURRENT_ADDING); if (!$rc->execute_precheck()) { $precheckresults = $rc->get_precheck_results(); if (is_array($precheckresults) && !empty($precheckresults['errors'])) { if (empty($CFG->keeptempdirectoriesonbackup)) { fulldelete($backupbasepath); } print_r($precheckresults); die; } } $rc->execute_plan(); $newcmid = null; $tasks = $rc->get_plan()->get_tasks(); foreach ($tasks as $task) { if (is_subclass_of($task, 'restore_activity_task')) { if ($task->get_old_contextid() == $cmcontext->id) { $newcmid = $task->get_moduleid(); break; } } } $rc->destroy(); if ($module = $DB->get_record('course_modules', array('id' => $newcmid))) { $module->idnumber = $newidnumber; $DB->update_record('course_modules', $module); } $newtoolid = 0; if ($tools = $DB->get_records('local_ltiprovider', array('contextid' => $cmcontext->id))) { $newcmcontext = context_module::instance($newcmid); foreach ($tools as $tool) { $tool->courseid = $course->id; $tool->contextid = $newcmcontext->id; $newtoolid = $DB->insert_record('local_ltiprovider', $tool); } } if (!$newtoolid) { $tool = local_ltiprovider_create_tool($course->id, $newcmcontext->id, $lticontext); } if (empty($CFG->keeptempdirectoriesonbackup)) { fulldelete($backupbasepath); } return $newcmid; }
/** * Function to be run periodically according to the scheduled task. * * Finds all posts that have yet to be mailed out, and mails them * out to all subscribers as well as other maintance tasks. * * NOTE: Since 2.7.2 this function is run by scheduled task rather * than standard cron. * * @todo MDL-44734 The function will be split up into seperate tasks. */ function forum_cron() { global $CFG, $USER, $DB, $PAGE; $site = get_site(); // The main renderers. $htmlout = $PAGE->get_renderer('mod_forum', 'email', 'htmlemail'); $textout = $PAGE->get_renderer('mod_forum', 'email', 'textemail'); $htmldigestfullout = $PAGE->get_renderer('mod_forum', 'emaildigestfull', 'htmlemail'); $textdigestfullout = $PAGE->get_renderer('mod_forum', 'emaildigestfull', 'textemail'); $htmldigestbasicout = $PAGE->get_renderer('mod_forum', 'emaildigestbasic', 'htmlemail'); $textdigestbasicout = $PAGE->get_renderer('mod_forum', 'emaildigestbasic', 'textemail'); // All users that are subscribed to any post that needs sending, // please increase $CFG->extramemorylimit on large sites that // send notifications to a large number of users. $users = array(); $userscount = 0; // Cached user counter - count($users) in PHP is horribly slow!!! // Status arrays. $mailcount = array(); $errorcount = array(); // caches $discussions = array(); $forums = array(); $courses = array(); $coursemodules = array(); $subscribedusers = array(); $messageinboundhandlers = array(); // Posts older than 2 days will not be mailed. This is to avoid the problem where // cron has not been running for a long time, and then suddenly people are flooded // with mail from the past few weeks or months $timenow = time(); $endtime = $timenow - $CFG->maxeditingtime; $starttime = $endtime - 48 * 3600; // Two days earlier // Get the list of forum subscriptions for per-user per-forum maildigest settings. $digestsset = $DB->get_recordset('forum_digests', null, '', 'id, userid, forum, maildigest'); $digests = array(); foreach ($digestsset as $thisrow) { if (!isset($digests[$thisrow->forum])) { $digests[$thisrow->forum] = array(); } $digests[$thisrow->forum][$thisrow->userid] = $thisrow->maildigest; } $digestsset->close(); // Create the generic messageinboundgenerator. $messageinboundgenerator = new \core\message\inbound\address_manager(); $messageinboundgenerator->set_handler('\\mod_forum\\message\\inbound\\reply_handler'); if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) { // Mark them all now as being mailed. It's unlikely but possible there // might be an error later so that a post is NOT actually mailed out, // but since mail isn't crucial, we can accept this risk. Doing it now // prevents the risk of duplicated mails, which is a worse problem. if (!forum_mark_old_posts_as_mailed($endtime)) { mtrace('Errors occurred while trying to mark some posts as being mailed.'); return false; // Don't continue trying to mail them, in case we are in a cron loop } // checking post validity, and adding users to loop through later foreach ($posts as $pid => $post) { $discussionid = $post->discussion; if (!isset($discussions[$discussionid])) { if ($discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion))) { $discussions[$discussionid] = $discussion; \mod_forum\subscriptions::fill_subscription_cache($discussion->forum); \mod_forum\subscriptions::fill_discussion_subscription_cache($discussion->forum); } else { mtrace('Could not find discussion ' . $discussionid); unset($posts[$pid]); continue; } } $forumid = $discussions[$discussionid]->forum; if (!isset($forums[$forumid])) { if ($forum = $DB->get_record('forum', array('id' => $forumid))) { $forums[$forumid] = $forum; } else { mtrace('Could not find forum ' . $forumid); unset($posts[$pid]); continue; } } $courseid = $forums[$forumid]->course; if (!isset($courses[$courseid])) { if ($course = $DB->get_record('course', array('id' => $courseid))) { $courses[$courseid] = $course; } else { mtrace('Could not find course ' . $courseid); unset($posts[$pid]); continue; } } if (!isset($coursemodules[$forumid])) { if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) { $coursemodules[$forumid] = $cm; } else { mtrace('Could not find course module for forum ' . $forumid); unset($posts[$pid]); continue; } } // Save the Inbound Message datakey here to reduce DB queries later. $messageinboundgenerator->set_data($pid); $messageinboundhandlers[$pid] = $messageinboundgenerator->fetch_data_key(); // Caching subscribed users of each forum. if (!isset($subscribedusers[$forumid])) { $modcontext = context_module::instance($coursemodules[$forumid]->id); if ($subusers = \mod_forum\subscriptions::fetch_subscribed_users($forums[$forumid], 0, $modcontext, 'u.*', true)) { foreach ($subusers as $postuser) { // this user is subscribed to this forum $subscribedusers[$forumid][$postuser->id] = $postuser->id; $userscount++; if ($userscount > FORUM_CRON_USER_CACHE) { // Store minimal user info. $minuser = new stdClass(); $minuser->id = $postuser->id; $users[$postuser->id] = $minuser; } else { // Cache full user record. forum_cron_minimise_user_record($postuser); $users[$postuser->id] = $postuser; } } // Release memory. unset($subusers); unset($postuser); } } $mailcount[$pid] = 0; $errorcount[$pid] = 0; } } if ($users && $posts) { foreach ($users as $userto) { // Terminate if processing of any account takes longer than 2 minutes. core_php_time_limit::raise(120); mtrace('Processing user ' . $userto->id); // Init user caches - we keep the cache for one cycle only, otherwise it could consume too much memory. if (isset($userto->username)) { $userto = clone $userto; } else { $userto = $DB->get_record('user', array('id' => $userto->id)); forum_cron_minimise_user_record($userto); } $userto->viewfullnames = array(); $userto->canpost = array(); $userto->markposts = array(); // Setup this user so that the capabilities are cached, and environment matches receiving user. cron_setup_user($userto); // Reset the caches. foreach ($coursemodules as $forumid => $unused) { $coursemodules[$forumid]->cache = new stdClass(); $coursemodules[$forumid]->cache->caps = array(); unset($coursemodules[$forumid]->uservisible); } foreach ($posts as $pid => $post) { $discussion = $discussions[$post->discussion]; $forum = $forums[$discussion->forum]; $course = $courses[$forum->course]; $cm =& $coursemodules[$forum->id]; // Do some checks to see if we can bail out now. // Only active enrolled users are in the list of subscribers. // This does not necessarily mean that the user is subscribed to the forum or to the discussion though. if (!isset($subscribedusers[$forum->id][$userto->id])) { // The user does not subscribe to this forum. continue; } if (!\mod_forum\subscriptions::is_subscribed($userto->id, $forum, $post->discussion, $coursemodules[$forum->id])) { // The user does not subscribe to this forum, or to this specific discussion. continue; } if ($subscriptiontime = \mod_forum\subscriptions::fetch_discussion_subscription($forum->id, $userto->id)) { // Skip posts if the user subscribed to the discussion after it was created. if (isset($subscriptiontime[$post->discussion]) && $subscriptiontime[$post->discussion] > $post->created) { continue; } } // Don't send email if the forum is Q&A and the user has not posted. // Initial topics are still mailed. if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id) && $pid != $discussion->firstpost) { mtrace('Did not email ' . $userto->id . ' because user has not posted in discussion'); continue; } // Get info about the sending user. if (array_key_exists($post->userid, $users)) { // We might know the user already. $userfrom = $users[$post->userid]; if (!isset($userfrom->idnumber)) { // Minimalised user info, fetch full record. $userfrom = $DB->get_record('user', array('id' => $userfrom->id)); forum_cron_minimise_user_record($userfrom); } } else { if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) { forum_cron_minimise_user_record($userfrom); // Fetch only once if possible, we can add it to user list, it will be skipped anyway. if ($userscount <= FORUM_CRON_USER_CACHE) { $userscount++; $users[$userfrom->id] = $userfrom; } } else { mtrace('Could not find user ' . $post->userid . ', author of post ' . $post->id . '. Unable to send message.'); continue; } } // Note: If we want to check that userto and userfrom are not the same person this is probably the spot to do it. // Setup global $COURSE properly - needed for roles and languages. cron_setup_user($userto, $course); // Fill caches. if (!isset($userto->viewfullnames[$forum->id])) { $modcontext = context_module::instance($cm->id); $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext); } if (!isset($userto->canpost[$discussion->id])) { $modcontext = context_module::instance($cm->id); $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext); } if (!isset($userfrom->groups[$forum->id])) { if (!isset($userfrom->groups)) { $userfrom->groups = array(); if (isset($users[$userfrom->id])) { $users[$userfrom->id]->groups = array(); } } $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid); if (isset($users[$userfrom->id])) { $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id]; } } // Make sure groups allow this user to see this email. if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used. if (!groups_group_exists($discussion->groupid)) { // Can't find group - be safe and don't this message. continue; } if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) { // Do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS. continue; } } // Make sure we're allowed to see the post. if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm)) { mtrace('User ' . $userto->id . ' can not see ' . $post->id . '. Not sending message.'); continue; } // OK so we need to send the email. // Does the user want this post in a digest? If so postpone it for now. $maildigest = forum_get_user_maildigest_bulk($digests, $userto, $forum->id); if ($maildigest > 0) { // This user wants the mails to be in digest form. $queue = new stdClass(); $queue->userid = $userto->id; $queue->discussionid = $discussion->id; $queue->postid = $post->id; $queue->timemodified = $post->created; $DB->insert_record('forum_queue', $queue); continue; } // Prepare to actually send the post now, and build up the content. $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name))); $userfrom->customheaders = array('List-Id: "' . $cleanforumname . '" ' . generate_email_messageid('moodleforum' . $forum->id), 'List-Help: ' . $CFG->wwwroot . '/mod/forum/view.php?f=' . $forum->id, 'Message-ID: ' . forum_get_email_message_id($post->id, $userto->id), 'X-Course-Id: ' . $course->id, 'X-Course-Name: ' . format_string($course->fullname, true), 'Precedence: Bulk', 'X-Auto-Response-Suppress: All', 'Auto-Submitted: auto-generated'); $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id))); // Generate a reply-to address from using the Inbound Message handler. $replyaddress = null; if ($userto->canpost[$discussion->id] && array_key_exists($post->id, $messageinboundhandlers)) { $messageinboundgenerator->set_data($post->id, $messageinboundhandlers[$post->id]); $replyaddress = $messageinboundgenerator->generate($userto->id); } if (!isset($userto->canpost[$discussion->id])) { $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext); } else { $canreply = $userto->canpost[$discussion->id]; } $data = new \mod_forum\output\forum_post_email($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $canreply); $userfrom->customheaders[] = sprintf('List-Unsubscribe: <%s>', $data->get_unsubscribediscussionlink()); if (!isset($userto->viewfullnames[$forum->id])) { $data->viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id); } else { $data->viewfullnames = $userto->viewfullnames[$forum->id]; } // Not all of these variables are used in the default language // string but are made available to support custom subjects. $a = new stdClass(); $a->subject = $data->get_subject(); $a->forumname = $cleanforumname; $a->sitefullname = format_string($site->fullname); $a->siteshortname = format_string($site->shortname); $a->courseidnumber = $data->get_courseidnumber(); $a->coursefullname = $data->get_coursefullname(); $a->courseshortname = $data->get_coursename(); $postsubject = html_to_text(get_string('postmailsubject', 'forum', $a), 0); $rootid = forum_get_email_message_id($discussion->firstpost, $userto->id); if ($post->parent) { // This post is a reply, so add reply header (RFC 2822). $parentid = forum_get_email_message_id($post->parent, $userto->id); $userfrom->customheaders[] = "In-Reply-To: {$parentid}"; // If the post is deeply nested we also reference the parent message id and // the root message id (if different) to aid threading when parts of the email // conversation have been deleted (RFC1036). if ($post->parent != $discussion->firstpost) { $userfrom->customheaders[] = "References: {$rootid} {$parentid}"; } else { $userfrom->customheaders[] = "References: {$parentid}"; } } // MS Outlook / Office uses poorly documented and non standard headers, including // Thread-Topic which overrides the Subject and shouldn't contain Re: or Fwd: etc. $a->subject = $discussion->name; $threadtopic = html_to_text(get_string('postmailsubject', 'forum', $a), 0); $userfrom->customheaders[] = "Thread-Topic: {$threadtopic}"; $userfrom->customheaders[] = "Thread-Index: " . substr($rootid, 1, 28); // Send the post now! mtrace('Sending ', ''); $eventdata = new \core\message\message(); $eventdata->courseid = $course->id; $eventdata->component = 'mod_forum'; $eventdata->name = 'posts'; $eventdata->userfrom = $userfrom; $eventdata->userto = $userto; $eventdata->subject = $postsubject; $eventdata->fullmessage = $textout->render($data); $eventdata->fullmessageformat = FORMAT_PLAIN; $eventdata->fullmessagehtml = $htmlout->render($data); $eventdata->notification = 1; $eventdata->replyto = $replyaddress; if (!empty($replyaddress)) { // Add extra text to email messages if they can reply back. $textfooter = "\n\n" . get_string('replytopostbyemail', 'mod_forum'); $htmlfooter = html_writer::tag('p', get_string('replytopostbyemail', 'mod_forum')); $additionalcontent = array('fullmessage' => array('footer' => $textfooter), 'fullmessagehtml' => array('footer' => $htmlfooter)); $eventdata->set_additional_content('email', $additionalcontent); } $smallmessagestrings = new stdClass(); $smallmessagestrings->user = fullname($userfrom); $smallmessagestrings->forumname = "{$shortname}: " . format_string($forum->name, true) . ": " . $discussion->name; $smallmessagestrings->message = $post->message; // Make sure strings are in message recipients language. $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang); $contexturl = new moodle_url('/mod/forum/discuss.php', array('d' => $discussion->id), 'p' . $post->id); $eventdata->contexturl = $contexturl->out(); $eventdata->contexturlname = $discussion->name; $mailresult = message_send($eventdata); if (!$mailresult) { mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id {$post->id} to user {$userto->id}" . " ({$userto->email}) .. not trying again."); $errorcount[$post->id]++; } else { $mailcount[$post->id]++; // Mark post as read if forum_usermarksread is set off. if (!$CFG->forum_usermarksread) { $userto->markposts[$post->id] = $post->id; } } mtrace('post ' . $post->id . ': ' . $post->subject); } // Mark processed posts as read. if (get_user_preferences('forum_markasreadonnotification', 1, $userto->id) == 1) { forum_tp_mark_posts_read($userto, $userto->markposts); } unset($userto); } } if ($posts) { foreach ($posts as $post) { mtrace($mailcount[$post->id] . " users were sent post {$post->id}, '{$post->subject}'"); if ($errorcount[$post->id]) { $DB->set_field('forum_posts', 'mailed', FORUM_MAILED_ERROR, array('id' => $post->id)); } } } // release some memory unset($subscribedusers); unset($mailcount); unset($errorcount); cron_setup_user(); $sitetimezone = core_date::get_server_timezone(); // Now see if there are any digest mails waiting to be sent, and if we should send them mtrace('Starting digest processing...'); core_php_time_limit::raise(300); // terminate if not able to fetch all digests in 5 minutes if (!isset($CFG->digestmailtimelast)) { // To catch the first time set_config('digestmailtimelast', 0); } $timenow = time(); $digesttime = usergetmidnight($timenow, $sitetimezone) + $CFG->digestmailtime * 3600; // Delete any really old ones (normally there shouldn't be any) $weekago = $timenow - 7 * 24 * 3600; $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago)); mtrace('Cleaned old digest records'); if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) { mtrace('Sending forum digests: ' . userdate($timenow, '', $sitetimezone)); $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime)); if ($digestposts_rs->valid()) { // We have work to do $usermailcount = 0; //caches - reuse the those filled before too $discussionposts = array(); $userdiscussions = array(); foreach ($digestposts_rs as $digestpost) { if (!isset($posts[$digestpost->postid])) { if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) { $posts[$digestpost->postid] = $post; } else { continue; } } $discussionid = $digestpost->discussionid; if (!isset($discussions[$discussionid])) { if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) { $discussions[$discussionid] = $discussion; } else { continue; } } $forumid = $discussions[$discussionid]->forum; if (!isset($forums[$forumid])) { if ($forum = $DB->get_record('forum', array('id' => $forumid))) { $forums[$forumid] = $forum; } else { continue; } } $courseid = $forums[$forumid]->course; if (!isset($courses[$courseid])) { if ($course = $DB->get_record('course', array('id' => $courseid))) { $courses[$courseid] = $course; } else { continue; } } if (!isset($coursemodules[$forumid])) { if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) { $coursemodules[$forumid] = $cm; } else { continue; } } $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid; $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid; } $digestposts_rs->close(); /// Finished iteration, let's close the resultset // Data collected, start sending out emails to each user foreach ($userdiscussions as $userid => $thesediscussions) { core_php_time_limit::raise(120); // terminate if processing of any account takes longer than 2 minutes cron_setup_user(); mtrace(get_string('processingdigest', 'forum', $userid), '... '); // First of all delete all the queue entries for this user $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime)); // Init user caches - we keep the cache for one cycle only, // otherwise it would unnecessarily consume memory. if (array_key_exists($userid, $users) and isset($users[$userid]->username)) { $userto = clone $users[$userid]; } else { $userto = $DB->get_record('user', array('id' => $userid)); forum_cron_minimise_user_record($userto); } $userto->viewfullnames = array(); $userto->canpost = array(); $userto->markposts = array(); // Override the language and timezone of the "current" user, so that // mail is customised for the receiver. cron_setup_user($userto); $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true)); $headerdata = new stdClass(); $headerdata->sitename = format_string($site->fullname, true); $headerdata->userprefs = $CFG->wwwroot . '/user/forum.php?id=' . $userid . '&course=' . $site->id; $posttext = get_string('digestmailheader', 'forum', $headerdata) . "\n\n"; $headerdata->userprefs = '<a target="_blank" href="' . $headerdata->userprefs . '">' . get_string('digestmailprefs', 'forum') . '</a>'; $posthtml = '<p>' . get_string('digestmailheader', 'forum', $headerdata) . '</p>' . '<br /><hr size="1" noshade="noshade" />'; foreach ($thesediscussions as $discussionid) { core_php_time_limit::raise(120); // to be reset for each post $discussion = $discussions[$discussionid]; $forum = $forums[$discussion->forum]; $course = $courses[$forum->course]; $cm = $coursemodules[$forum->id]; //override language cron_setup_user($userto, $course); // Fill caches if (!isset($userto->viewfullnames[$forum->id])) { $modcontext = context_module::instance($cm->id); $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext); } if (!isset($userto->canpost[$discussion->id])) { $modcontext = context_module::instance($cm->id); $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext); } $strforums = get_string('forums', 'forum'); $canunsubscribe = !\mod_forum\subscriptions::is_forcesubscribed($forum); $canreply = $userto->canpost[$discussion->id]; $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id))); $posttext .= "\n \n"; $posttext .= '====================================================================='; $posttext .= "\n \n"; $posttext .= "{$shortname} -> {$strforums} -> " . format_string($forum->name, true); if ($discussion->name != $forum->name) { $posttext .= " -> " . format_string($discussion->name, true); } $posttext .= "\n"; $posttext .= $CFG->wwwroot . '/mod/forum/discuss.php?d=' . $discussion->id; $posttext .= "\n"; $posthtml .= "<p><font face=\"sans-serif\">" . "<a target=\"_blank\" href=\"{$CFG->wwwroot}/course/view.php?id={$course->id}\">{$shortname}</a> -> " . "<a target=\"_blank\" href=\"{$CFG->wwwroot}/mod/forum/index.php?id={$course->id}\">{$strforums}</a> -> " . "<a target=\"_blank\" href=\"{$CFG->wwwroot}/mod/forum/view.php?f={$forum->id}\">" . format_string($forum->name, true) . "</a>"; if ($discussion->name == $forum->name) { $posthtml .= "</font></p>"; } else { $posthtml .= " -> <a target=\"_blank\" href=\"{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}\">" . format_string($discussion->name, true) . "</a></font></p>"; } $posthtml .= '<p>'; $postsarray = $discussionposts[$discussionid]; sort($postsarray); $sentcount = 0; foreach ($postsarray as $postid) { $post = $posts[$postid]; if (array_key_exists($post->userid, $users)) { // we might know him/her already $userfrom = $users[$post->userid]; if (!isset($userfrom->idnumber)) { $userfrom = $DB->get_record('user', array('id' => $userfrom->id)); forum_cron_minimise_user_record($userfrom); } } else { if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) { forum_cron_minimise_user_record($userfrom); if ($userscount <= FORUM_CRON_USER_CACHE) { $userscount++; $users[$userfrom->id] = $userfrom; } } else { mtrace('Could not find user ' . $post->userid); continue; } } if (!isset($userfrom->groups[$forum->id])) { if (!isset($userfrom->groups)) { $userfrom->groups = array(); if (isset($users[$userfrom->id])) { $users[$userfrom->id]->groups = array(); } } $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid); if (isset($users[$userfrom->id])) { $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id]; } } // Headers to help prevent auto-responders. $userfrom->customheaders = array("Precedence: Bulk", 'X-Auto-Response-Suppress: All', 'Auto-Submitted: auto-generated'); $maildigest = forum_get_user_maildigest_bulk($digests, $userto, $forum->id); if (!isset($userto->canpost[$discussion->id])) { $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext); } else { $canreply = $userto->canpost[$discussion->id]; } $data = new \mod_forum\output\forum_post_email($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $canreply); if (!isset($userto->viewfullnames[$forum->id])) { $data->viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id); } else { $data->viewfullnames = $userto->viewfullnames[$forum->id]; } if ($maildigest == 2) { // Subjects and link only. $posttext .= $textdigestbasicout->render($data); $posthtml .= $htmldigestbasicout->render($data); } else { // The full treatment. $posttext .= $textdigestfullout->render($data); $posthtml .= $htmldigestfullout->render($data); // Create an array of postid's for this user to mark as read. if (!$CFG->forum_usermarksread) { $userto->markposts[$post->id] = $post->id; } } $sentcount++; } $footerlinks = array(); if ($canunsubscribe) { $footerlinks[] = "<a href=\"{$CFG->wwwroot}/mod/forum/subscribe.php?id={$forum->id}\">" . get_string("unsubscribe", "forum") . "</a>"; } else { $footerlinks[] = get_string("everyoneissubscribed", "forum"); } $footerlinks[] = "<a href='{$CFG->wwwroot}/mod/forum/index.php?id={$forum->course}'>" . get_string("digestmailpost", "forum") . '</a>'; $posthtml .= "\n<div class='mdl-right'><font size=\"1\">" . implode(' ', $footerlinks) . '</font></div>'; $posthtml .= '<hr size="1" noshade="noshade" /></p>'; } if (empty($userto->mailformat) || $userto->mailformat != 1) { // This user DOESN'T want to receive HTML $posthtml = ''; } $eventdata = new \core\message\message(); $eventdata->courseid = SITEID; $eventdata->component = 'mod_forum'; $eventdata->name = 'digests'; $eventdata->userfrom = core_user::get_noreply_user(); $eventdata->userto = $userto; $eventdata->subject = $postsubject; $eventdata->fullmessage = $posttext; $eventdata->fullmessageformat = FORMAT_PLAIN; $eventdata->fullmessagehtml = $posthtml; $eventdata->notification = 1; $eventdata->smallmessage = get_string('smallmessagedigest', 'forum', $sentcount); $mailresult = message_send($eventdata); if (!$mailresult) { mtrace("ERROR: mod/forum/cron.php: Could not send out digest mail to user {$userto->id} " . "({$userto->email})... not trying again."); } else { mtrace("success."); $usermailcount++; // Mark post as read if forum_usermarksread is set off if (get_user_preferences('forum_markasreadonnotification', 1, $userto->id) == 1) { forum_tp_mark_posts_read($userto, $userto->markposts); } } } } /// We have finishied all digest emails, update $CFG->digestmailtimelast set_config('digestmailtimelast', $timenow); } cron_setup_user(); if (!empty($usermailcount)) { mtrace(get_string('digestsentusers', 'forum', $usermailcount)); } if (!empty($CFG->forum_lastreadclean)) { $timenow = time(); if ($CFG->forum_lastreadclean + 24 * 3600 < $timenow) { set_config('forum_lastreadclean', $timenow); mtrace('Removing old forum read tracking info...'); forum_tp_clean_read_records(); } } else { set_config('forum_lastreadclean', time()); } return true; }
/** * Test delivery of grade notifications as controlled by marking workflow. */ public function test_markingworkflow_cron() { // First run cron so there are no messages waiting to be sent (from other tests). cron_setup_user(); assign::cron(); // Now create an assignment with marking workflow enabled. $this->setUser($this->editingteachers[0]); $assign = $this->create_instance(array('sendstudentnotifications' => 1, 'markingworkflow' => 1)); // Simulate adding a grade. $this->setUser($this->teachers[0]); $data = new stdClass(); $data->grade = '50.0'; // This student will not receive notification. $data->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE; $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0); // This student will receive notification. $data->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_RELEASED; $assign->testable_apply_grade_to_user($data, $this->students[1]->id, 0); // Now run cron and see that one message was sent. $this->preventResetByRollback(); $sink = $this->redirectMessages(); cron_setup_user(); $this->expectOutputRegex('/Done processing 1 assignment submissions/'); assign::cron(); $messages = $sink->get_messages(); $this->assertEquals(1, count($messages)); $this->assertEquals($messages[0]->useridto, $this->students[1]->id); $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname); }
/** * Function to be run periodically according to the moodle cron * * Finds all assignment notifications that have yet to be mailed out, and mails them */ function assignment_cron() { global $CFG, $USER, $DB; /// first execute all crons in plugins if ($plugins = get_plugin_list('assignment')) { foreach ($plugins as $plugin => $dir) { require_once "{$dir}/assignment.class.php"; $assignmentclass = "assignment_{$plugin}"; $ass = new $assignmentclass(); $ass->cron(); } } /// Notices older than 1 day will not be mailed. This is to avoid the problem where /// cron has not been running for a long time, and then suddenly people are flooded /// with mail from the past few weeks or months $timenow = time(); $endtime = $timenow - $CFG->maxeditingtime; $starttime = $endtime - 24 * 3600; /// One day earlier if ($submissions = assignment_get_unmailed_submissions($starttime, $endtime)) { $realuser = clone $USER; foreach ($submissions as $key => $submission) { if (!$DB->set_field("assignment_submissions", "mailed", "1", array("id" => $submission->id))) { echo "Could not update the mailed field for id {$submission->id}. Not mailed.\n"; unset($submissions[$key]); } } $timenow = time(); foreach ($submissions as $submission) { echo "Processing assignment submission {$submission->id}\n"; if (!($user = $DB->get_record("user", array("id" => $submission->userid)))) { echo "Could not find user {$post->userid}\n"; continue; } if (!($course = $DB->get_record("course", array("id" => $submission->course)))) { echo "Could not find course {$submission->course}\n"; continue; } /// Override the language and timezone of the "current" user, so that /// mail is customised for the receiver. cron_setup_user($user, $course); if (!has_capability('moodle/course:view', get_context_instance(CONTEXT_COURSE, $submission->course), $user->id)) { echo fullname($user) . " not an active participant in " . format_string($course->shortname) . "\n"; continue; } if (!($teacher = $DB->get_record("user", array("id" => $submission->teacher)))) { echo "Could not find teacher {$submission->teacher}\n"; continue; } if (!($mod = get_coursemodule_from_instance("assignment", $submission->assignment, $course->id))) { echo "Could not find course module for assignment id {$submission->assignment}\n"; continue; } if (!$mod->visible) { /// Hold mail notification for hidden assignments until later continue; } $strassignments = get_string("modulenameplural", "assignment"); $strassignment = get_string("modulename", "assignment"); $assignmentinfo = new object(); $assignmentinfo->teacher = fullname($teacher); $assignmentinfo->assignment = format_string($submission->name, true); $assignmentinfo->url = "{$CFG->wwwroot}/mod/assignment/view.php?id={$mod->id}"; $postsubject = "{$course->shortname}: {$strassignments}: " . format_string($submission->name, true); $posttext = "{$course->shortname} -> {$strassignments} -> " . format_string($submission->name, true) . "\n"; $posttext .= "---------------------------------------------------------------------\n"; $posttext .= get_string("assignmentmail", "assignment", $assignmentinfo) . "\n"; $posttext .= "---------------------------------------------------------------------\n"; if ($user->mailformat == 1) { // HTML $posthtml = "<p><font face=\"sans-serif\">" . "<a href=\"{$CFG->wwwroot}/course/view.php?id={$course->id}\">{$course->shortname}</a> ->" . "<a href=\"{$CFG->wwwroot}/mod/assignment/index.php?id={$course->id}\">{$strassignments}</a> ->" . "<a href=\"{$CFG->wwwroot}/mod/assignment/view.php?id={$mod->id}\">" . format_string($submission->name, true) . "</a></font></p>"; $posthtml .= "<hr /><font face=\"sans-serif\">"; $posthtml .= "<p>" . get_string("assignmentmailhtml", "assignment", $assignmentinfo) . "</p>"; $posthtml .= "</font><hr />"; } else { $posthtml = ""; } $eventdata = new object(); $eventdata->modulename = 'assignment'; $eventdata->userfrom = $teacher; $eventdata->userto = $user; $eventdata->subject = $postsubject; $eventdata->fullmessage = $posttext; $eventdata->fullmessageformat = FORMAT_PLAIN; $eventdata->fullmessagehtml = $posthtml; $eventdata->smallmessage = ''; if (events_trigger('message_send', $eventdata) > 0) { echo "Error: assignment cron: Could not send out mail for id {$submission->id} to user {$user->id} ({$user->email})\n"; } } cron_setup_user(); } return true; }
/** * Runs a scheduled task immediately, given full class name. * * This is faster and more reliable than running cron (running cron won't * work more than once in the same test, for instance). However it is * a little less 'realistic'. * * While the task is running, we suppress mtrace output because it makes * the Behat result look ugly. * * Note: Most of the code relating to running a task is based on * admin/tool/task/cli/schedule_task.php. * * @Given /^I run the scheduled task "(?P<task_name>[^"]+)"$/ * @param string $taskname Name of task e.g. 'mod_whatever\task\do_something' */ public function i_run_the_scheduled_task($taskname) { $task = \core\task\manager::get_scheduled_task($taskname); if (!$task) { throw new DriverException('The "' . $taskname . '" scheduled task does not exist'); } // Do setup for cron task. raise_memory_limit(MEMORY_EXTRA); cron_setup_user(); // Get lock. $cronlockfactory = \core\lock\lock_config::get_lock_factory('cron'); if (!($cronlock = $cronlockfactory->get_lock('core_cron', 10))) { throw new DriverException('Unable to obtain core_cron lock for scheduled task'); } if (!($lock = $cronlockfactory->get_lock('\\' . get_class($task), 10))) { $cronlock->release(); throw new DriverException('Unable to obtain task lock for scheduled task'); } $task->set_lock($lock); if (!$task->is_blocking()) { $cronlock->release(); } else { $task->set_cron_lock($cronlock); } try { // Discard task output as not appropriate for Behat output! ob_start(); $task->execute(); ob_end_clean(); // Mark task complete. \core\task\manager::scheduled_task_complete($task); } catch (Exception $e) { // Mark task failed and throw exception. \core\task\manager::scheduled_task_failed($task); throw new DriverException('The "' . $taskname . '" scheduled task failed', 0, $e); } }
/** * Finds all assignment notifications that have yet to be mailed out, and mails them. * * Cron function to be run periodically according to the moodle cron. * * @return bool */ public static function cron() { global $DB; // Only ever send a max of one days worth of updates. $yesterday = time() - 24 * 3600; $timenow = time(); // Collect all submissions from the past 24 hours that require mailing. $sql = 'SELECT g.id as gradeid, a.course, a.name, a.blindmarking, a.revealidentities, g.*, g.timemodified as lastmodified FROM {assign} a JOIN {assign_grades} g ON g.assignment = a.id LEFT JOIN {assign_user_flags} uf ON uf.assignment = a.id AND uf.userid = g.userid WHERE g.timemodified >= :yesterday AND g.timemodified <= :today AND uf.mailed = 0'; $params = array('yesterday' => $yesterday, 'today' => $timenow); $submissions = $DB->get_records_sql($sql, $params); if (empty($submissions)) { return true; } mtrace('Processing ' . count($submissions) . ' assignment submissions ...'); // Preload courses we are going to need those. $courseids = array(); foreach ($submissions as $submission) { $courseids[] = $submission->course; } // Filter out duplicates. $courseids = array_unique($courseids); $ctxselect = context_helper::get_preload_record_columns_sql('ctx'); list($courseidsql, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED); $sql = 'SELECT c.*, ' . $ctxselect . ' FROM {course} c LEFT JOIN {context} ctx ON ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel WHERE c.id ' . $courseidsql; $params['contextlevel'] = CONTEXT_COURSE; $courses = $DB->get_records_sql($sql, $params); // Clean up... this could go on for a while. unset($courseids); unset($ctxselect); unset($courseidsql); unset($params); // Simple array we'll use for caching modules. $modcache = array(); // Message students about new feedback. foreach ($submissions as $submission) { mtrace("Processing assignment submission {$submission->id} ..."); // Do not cache user lookups - could be too many. if (!($user = $DB->get_record('user', array('id' => $submission->userid)))) { mtrace('Could not find user ' . $submission->userid); continue; } // Use a cache to prevent the same DB queries happening over and over. if (!array_key_exists($submission->course, $courses)) { mtrace('Could not find course ' . $submission->course); continue; } $course = $courses[$submission->course]; if (isset($course->ctxid)) { // Context has not yet been preloaded. Do so now. context_helper::preload_from_record($course); } // Override the language and timezone of the "current" user, so that // mail is customised for the receiver. cron_setup_user($user, $course); // Context lookups are already cached. $coursecontext = context_course::instance($course->id); if (!is_enrolled($coursecontext, $user->id)) { $courseshortname = format_string($course->shortname, true, array('context' => $coursecontext)); mtrace(fullname($user) . ' not an active participant in ' . $courseshortname); continue; } if (!($grader = $DB->get_record('user', array('id' => $submission->grader)))) { mtrace('Could not find grader ' . $submission->grader); continue; } if (!array_key_exists($submission->assignment, $modcache)) { $mod = get_coursemodule_from_instance('assign', $submission->assignment, $course->id); if (empty($mod)) { mtrace('Could not find course module for assignment id ' . $submission->assignment); continue; } $modcache[$submission->assignment] = $mod; } else { $mod = $modcache[$submission->assignment]; } // Context lookups are already cached. $contextmodule = context_module::instance($mod->id); if (!$mod->visible) { // Hold mail notification for hidden assignments until later. continue; } // Need to send this to the student. $messagetype = 'feedbackavailable'; $eventtype = 'assign_notification'; $updatetime = $submission->lastmodified; $modulename = get_string('modulename', 'assign'); $uniqueid = 0; if ($submission->blindmarking && !$submission->revealidentities) { $uniqueid = self::get_uniqueid_for_user_static($submission->assignment, $user->id); } $showusers = $submission->blindmarking && !$submission->revealidentities; self::send_assignment_notification($grader, $user, $messagetype, $eventtype, $updatetime, $mod, $contextmodule, $course, $modulename, $submission->name, $showusers, $uniqueid); $flags = $DB->get_record('assign_user_flags', array('userid' => $user->id, 'assignment' => $submission->assignment)); if ($flags) { $flags->mailed = 1; $DB->update_record('assign_user_flags', $flags); } else { $flags = new stdClass(); $flags->userid = $user->id; $flags->assignment = $submission->assignment; $flags->mailed = 1; $DB->insert_record('assign_user_flags', $flags); } mtrace('Done'); } mtrace('Done processing ' . count($submissions) . ' assignment submissions'); cron_setup_user(); // Free up memory just to be sure. unset($courses); unset($modcache); return true; }
<?php /** * Entirely enables the Web Services. */ define('CLI_SCRIPT', true); require dirname(__FILE__) . '/config.php'; require_once $CFG->libdir . '/testing/generator/data_generator.php'; require_once $CFG->libdir . '/accesslib.php'; require_once $CFG->libdir . '/externallib.php'; require_once $CFG->dirroot . '/webservice/lib.php'; // We don't really need to be admin, except to be able to see the generated tokens // in the admin settings page, while logged in as admin. cron_setup_user(); // Enable the Web Services. set_config('enablewebservices', 1); // Enable Web Services documentation. set_config('enablewsdocumentation', 1); // Enable each protocol. set_config('webserviceprotocols', 'amf,rest,soap,xmlrpc'); // Create the Web Service user. $user = $DB->get_record('user', array('username' => 'testtete')); if (!$user) { $user = new stdClass(); $user->username = '******'; $user->firstname = 'Web'; $user->lastname = 'Service'; $user->password = '******'; $dg = new testing_data_generator(); $user = $dg->create_user($user); }
/** * Function to be run periodically according to the moodle cron * Finds all posts that have yet to be mailed out, and mails them * out to all subscribers * * @global object * @global object * @global object * @uses CONTEXT_MODULE * @uses CONTEXT_COURSE * @uses SITEID * @uses FORMAT_PLAIN * @return void */ function forum_cron() { global $CFG, $USER, $DB; $site = get_site(); // All users that are subscribed to any post that needs sending, // please increase $CFG->extramemorylimit on large sites that // send notifications to a large number of users. $users = array(); $userscount = 0; // Cached user counter - count($users) in PHP is horribly slow!!! // status arrays $mailcount = array(); $errorcount = array(); // caches $discussions = array(); $forums = array(); $courses = array(); $coursemodules = array(); $subscribedusers = array(); // Posts older than 2 days will not be mailed. This is to avoid the problem where // cron has not been running for a long time, and then suddenly people are flooded // with mail from the past few weeks or months $timenow = time(); $endtime = $timenow - $CFG->maxeditingtime; $starttime = $endtime - 48 * 3600; // Two days earlier if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) { // Mark them all now as being mailed. It's unlikely but possible there // might be an error later so that a post is NOT actually mailed out, // but since mail isn't crucial, we can accept this risk. Doing it now // prevents the risk of duplicated mails, which is a worse problem. if (!forum_mark_old_posts_as_mailed($endtime)) { mtrace('Errors occurred while trying to mark some posts as being mailed.'); return false; // Don't continue trying to mail them, in case we are in a cron loop } // checking post validity, and adding users to loop through later foreach ($posts as $pid => $post) { $discussionid = $post->discussion; if (!isset($discussions[$discussionid])) { if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) { $discussions[$discussionid] = $discussion; } else { mtrace('Could not find discussion '.$discussionid); unset($posts[$pid]); continue; } } $forumid = $discussions[$discussionid]->forum; if (!isset($forums[$forumid])) { if ($forum = $DB->get_record('forum', array('id' => $forumid))) { $forums[$forumid] = $forum; } else { mtrace('Could not find forum '.$forumid); unset($posts[$pid]); continue; } } $courseid = $forums[$forumid]->course; if (!isset($courses[$courseid])) { if ($course = $DB->get_record('course', array('id' => $courseid))) { $courses[$courseid] = $course; } else { mtrace('Could not find course '.$courseid); unset($posts[$pid]); continue; } } if (!isset($coursemodules[$forumid])) { if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) { $coursemodules[$forumid] = $cm; } else { mtrace('Could not find course module for forum '.$forumid); unset($posts[$pid]); continue; } } // caching subscribed users of each forum if (!isset($subscribedusers[$forumid])) { $modcontext = context_module::instance($coursemodules[$forumid]->id); if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext, "u.*")) { foreach ($subusers as $postuser) { // this user is subscribed to this forum $subscribedusers[$forumid][$postuser->id] = $postuser->id; $userscount++; if ($userscount > FORUM_CRON_USER_CACHE) { // Store minimal user info. $minuser = new stdClass(); $minuser->id = $postuser->id; $users[$postuser->id] = $minuser; } else { // Cache full user record. forum_cron_minimise_user_record($postuser); $users[$postuser->id] = $postuser; } } // Release memory. unset($subusers); unset($postuser); } } $mailcount[$pid] = 0; $errorcount[$pid] = 0; } } if ($users && $posts) { $urlinfo = parse_url($CFG->wwwroot); $hostname = $urlinfo['host']; foreach ($users as $userto) { @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes mtrace('Processing user '.$userto->id); // Init user caches - we keep the cache for one cycle only, // otherwise it could consume too much memory. if (isset($userto->username)) { $userto = clone($userto); } else { $userto = $DB->get_record('user', array('id' => $userto->id)); forum_cron_minimise_user_record($userto); } $userto->viewfullnames = array(); $userto->canpost = array(); $userto->markposts = array(); // set this so that the capabilities are cached, and environment matches receiving user cron_setup_user($userto); // reset the caches foreach ($coursemodules as $forumid=>$unused) { $coursemodules[$forumid]->cache = new stdClass(); $coursemodules[$forumid]->cache->caps = array(); unset($coursemodules[$forumid]->uservisible); } foreach ($posts as $pid => $post) { // Set up the environment for the post, discussion, forum, course $discussion = $discussions[$post->discussion]; $forum = $forums[$discussion->forum]; $course = $courses[$forum->course]; $cm =& $coursemodules[$forum->id]; // Do some checks to see if we can bail out now // Only active enrolled users are in the list of subscribers if (!isset($subscribedusers[$forum->id][$userto->id])) { continue; // user does not subscribe to this forum } // Don't send email if the forum is Q&A and the user has not posted // Initial topics are still mailed if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id) && $pid != $discussion->firstpost) { mtrace('Did not email '.$userto->id.' because user has not posted in discussion'); continue; } // Get info about the sending user if (array_key_exists($post->userid, $users)) { // we might know him/her already $userfrom = $users[$post->userid]; if (!isset($userfrom->idnumber)) { // Minimalised user info, fetch full record. $userfrom = $DB->get_record('user', array('id' => $userfrom->id)); forum_cron_minimise_user_record($userfrom); } } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) { forum_cron_minimise_user_record($userfrom); // Fetch only once if possible, we can add it to user list, it will be skipped anyway. if ($userscount <= FORUM_CRON_USER_CACHE) { $userscount++; $users[$userfrom->id] = $userfrom; } } else { mtrace('Could not find user '.$post->userid); continue; } //if we want to check that userto and userfrom are not the same person this is probably the spot to do it // setup global $COURSE properly - needed for roles and languages cron_setup_user($userto, $course); // Fill caches if (!isset($userto->viewfullnames[$forum->id])) { $modcontext = context_module::instance($cm->id); $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext); } if (!isset($userto->canpost[$discussion->id])) { $modcontext = context_module::instance($cm->id); $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext); } if (!isset($userfrom->groups[$forum->id])) { if (!isset($userfrom->groups)) { $userfrom->groups = array(); if (isset($users[$userfrom->id])) { $users[$userfrom->id]->groups = array(); } } $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid); if (isset($users[$userfrom->id])) { $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id]; } } // Make sure groups allow this user to see this email if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used if (!groups_group_exists($discussion->groupid)) { // Can't find group continue; // Be safe and don't send it to anyone } if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) { // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS continue; } } // Make sure we're allowed to see it... if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) { mtrace('user '.$userto->id. ' can not see '.$post->id); continue; } // OK so we need to send the email. // Does the user want this post in a digest? If so postpone it for now. if ($userto->maildigest > 0) { // This user wants the mails to be in digest form $queue = new stdClass(); $queue->userid = $userto->id; $queue->discussionid = $discussion->id; $queue->postid = $post->id; $queue->timemodified = $post->created; $DB->insert_record('forum_queue', $queue); continue; } // Prepare to actually send the post now, and build up the content $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name))); $userfrom->customheaders = array ( // Headers to make emails easier to track 'Precedence: Bulk', 'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>', 'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id, 'Message-ID: '.forum_get_email_message_id($post->id, $userto->id, $hostname), 'X-Course-Id: '.$course->id, 'X-Course-Name: '.format_string($course->fullname, true) ); if ($post->parent) { // This post is a reply, so add headers for threading (see MDL-22551) $userfrom->customheaders[] = 'In-Reply-To: '.forum_get_email_message_id($post->parent, $userto->id, $hostname); $userfrom->customheaders[] = 'References: '.forum_get_email_message_id($post->parent, $userto->id, $hostname); } $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id))); $postsubject = html_to_text("$shortname: ".format_string($post->subject, true)); $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto); $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto); // Send the post now! mtrace('Sending ', ''); $eventdata = new stdClass(); $eventdata->component = 'mod_forum'; $eventdata->name = 'posts'; $eventdata->userfrom = $userfrom; $eventdata->userto = $userto; $eventdata->subject = $postsubject; $eventdata->fullmessage = $posttext; $eventdata->fullmessageformat = FORMAT_PLAIN; $eventdata->fullmessagehtml = $posthtml; $eventdata->notification = 1; // If forum_replytouser is not set then send mail using the noreplyaddress. if (empty($CFG->forum_replytouser)) { // Clone userfrom as it is referenced by $users. $cloneduserfrom = clone($userfrom); $cloneduserfrom->email = $CFG->noreplyaddress; $eventdata->userfrom = $cloneduserfrom; } $smallmessagestrings = new stdClass(); $smallmessagestrings->user = fullname($userfrom); $smallmessagestrings->forumname = "$shortname: ".format_string($forum->name,true).": ".$discussion->name; $smallmessagestrings->message = $post->message; //make sure strings are in message recipients language $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang); $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}"; $eventdata->contexturlname = $discussion->name; $mailresult = message_send($eventdata); if (!$mailresult){ mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id". " ($userto->email) .. not trying again."); add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id", substr(format_string($post->subject,true),0,30), $cm->id, $userto->id); $errorcount[$post->id]++; } else { $mailcount[$post->id]++; // Mark post as read if forum_usermarksread is set off if (!$CFG->forum_usermarksread) { $userto->markposts[$post->id] = $post->id; } } mtrace('post '.$post->id. ': '.$post->subject); } // mark processed posts as read forum_tp_mark_posts_read($userto, $userto->markposts); unset($userto); } } if ($posts) { foreach ($posts as $post) { mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'"); if ($errorcount[$post->id]) { $DB->set_field('forum_posts', 'mailed', FORUM_MAILED_ERROR, array('id' => $post->id)); } } } // release some memory unset($subscribedusers); unset($mailcount); unset($errorcount); cron_setup_user(); $sitetimezone = $CFG->timezone; // Now see if there are any digest mails waiting to be sent, and if we should send them mtrace('Starting digest processing...'); @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes if (!isset($CFG->digestmailtimelast)) { // To catch the first time set_config('digestmailtimelast', 0); } $timenow = time(); $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600); // Delete any really old ones (normally there shouldn't be any) $weekago = $timenow - (7 * 24 * 3600); $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago)); mtrace ('Cleaned old digest records'); if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) { mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone)); $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime)); if ($digestposts_rs->valid()) { // We have work to do $usermailcount = 0; //caches - reuse the those filled before too $discussionposts = array(); $userdiscussions = array(); foreach ($digestposts_rs as $digestpost) { if (!isset($posts[$digestpost->postid])) { if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) { $posts[$digestpost->postid] = $post; } else { continue; } } $discussionid = $digestpost->discussionid; if (!isset($discussions[$discussionid])) { if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) { $discussions[$discussionid] = $discussion; } else { continue; } } $forumid = $discussions[$discussionid]->forum; if (!isset($forums[$forumid])) { if ($forum = $DB->get_record('forum', array('id' => $forumid))) { $forums[$forumid] = $forum; } else { continue; } } $courseid = $forums[$forumid]->course; if (!isset($courses[$courseid])) { if ($course = $DB->get_record('course', array('id' => $courseid))) { $courses[$courseid] = $course; } else { continue; } } if (!isset($coursemodules[$forumid])) { if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) { $coursemodules[$forumid] = $cm; } else { continue; } } $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid; $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid; } $digestposts_rs->close(); /// Finished iteration, let's close the resultset // Data collected, start sending out emails to each user foreach ($userdiscussions as $userid => $thesediscussions) { @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes cron_setup_user(); mtrace(get_string('processingdigest', 'forum', $userid), '... '); // First of all delete all the queue entries for this user $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime)); // Init user caches - we keep the cache for one cycle only, // otherwise it would unnecessarily consume memory. if (array_key_exists($userid, $users) and isset($users[$userid]->username)) { $userto = clone($users[$userid]); } else { $userto = $DB->get_record('user', array('id' => $userid)); forum_cron_minimise_user_record($userto); } $userto->viewfullnames = array(); $userto->canpost = array(); $userto->markposts = array(); // Override the language and timezone of the "current" user, so that // mail is customised for the receiver. cron_setup_user($userto); $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true)); $headerdata = new stdClass(); $headerdata->sitename = format_string($site->fullname, true); $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&course='.$site->id; $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n"; $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>'; $posthtml = "<head>"; /* foreach ($CFG->stylesheets as $stylesheet) { //TODO: MDL-21120 $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n"; }*/ $posthtml .= "</head>\n<body id=\"email\">\n"; $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />'; foreach ($thesediscussions as $discussionid) { @set_time_limit(120); // to be reset for each post $discussion = $discussions[$discussionid]; $forum = $forums[$discussion->forum]; $course = $courses[$forum->course]; $cm = $coursemodules[$forum->id]; //override language cron_setup_user($userto, $course); // Fill caches if (!isset($userto->viewfullnames[$forum->id])) { $modcontext = context_module::instance($cm->id); $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext); } if (!isset($userto->canpost[$discussion->id])) { $modcontext = context_module::instance($cm->id); $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext); } $strforums = get_string('forums', 'forum'); $canunsubscribe = ! forum_is_forcesubscribed($forum); $canreply = $userto->canpost[$discussion->id]; $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id))); $posttext .= "\n \n"; $posttext .= '====================================================================='; $posttext .= "\n \n"; $posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true); if ($discussion->name != $forum->name) { $posttext .= " -> ".format_string($discussion->name,true); } $posttext .= "\n"; $posthtml .= "<p><font face=\"sans-serif\">". "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ". "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ". "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>"; if ($discussion->name == $forum->name) { $posthtml .= "</font></p>"; } else { $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>"; } $posthtml .= '<p>'; $postsarray = $discussionposts[$discussionid]; sort($postsarray); foreach ($postsarray as $postid) { $post = $posts[$postid]; if (array_key_exists($post->userid, $users)) { // we might know him/her already $userfrom = $users[$post->userid]; if (!isset($userfrom->idnumber)) { $userfrom = $DB->get_record('user', array('id' => $userfrom->id)); forum_cron_minimise_user_record($userfrom); } } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) { forum_cron_minimise_user_record($userfrom); if ($userscount <= FORUM_CRON_USER_CACHE) { $userscount++; $users[$userfrom->id] = $userfrom; } } else { mtrace('Could not find user '.$post->userid); continue; } if (!isset($userfrom->groups[$forum->id])) { if (!isset($userfrom->groups)) { $userfrom->groups = array(); if (isset($users[$userfrom->id])) { $users[$userfrom->id]->groups = array(); } } $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid); if (isset($users[$userfrom->id])) { $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id]; } } $userfrom->customheaders = array ("Precedence: Bulk"); if ($userto->maildigest == 2) { // Subjects only $by = new stdClass(); $by->name = fullname($userfrom); $by->date = userdate($post->modified); $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by); $posttext .= "\n---------------------------------------------------------------------"; $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&course=$course->id\">$by->name</a>"; $posthtml .= '<div><a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'#p'.$post->id.'">'.format_string($post->subject,true).'</a> '.get_string("bynameondate", "forum", $by).'</div>'; } else { // The full treatment $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true); $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false); // Create an array of postid's for this user to mark as read. if (!$CFG->forum_usermarksread) { $userto->markposts[$post->id] = $post->id; } } } if ($canunsubscribe) { $posthtml .= "\n<div class='mdl-right'><font size=\"1\"><a href=\"$CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\">".get_string("unsubscribe", "forum")."</a></font></div>"; } else { $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>"; } $posthtml .= '<hr size="1" noshade="noshade" /></p>'; } $posthtml .= '</body>'; if (empty($userto->mailformat) || $userto->mailformat != 1) { // This user DOESN'T want to receive HTML $posthtml = ''; } $attachment = $attachname=''; // Directly email forum digests rather than sending them via messaging, use the // site shortname as 'from name', the noreply address will be used by email_to_user. $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname); if (!$mailresult) { mtrace("ERROR!"); echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n"; add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id); } else { mtrace("success."); $usermailcount++; // Mark post as read if forum_usermarksread is set off forum_tp_mark_posts_read($userto, $userto->markposts); } } } /// We have finishied all digest emails, update $CFG->digestmailtimelast set_config('digestmailtimelast', $timenow); } cron_setup_user(); if (!empty($usermailcount)) { mtrace(get_string('digestsentusers', 'forum', $usermailcount)); } if (!empty($CFG->forum_lastreadclean)) { $timenow = time(); if ($CFG->forum_lastreadclean + (24*3600) < $timenow) { set_config('forum_lastreadclean', $timenow); mtrace('Removing old forum read tracking info...'); forum_tp_clean_read_records(); } } else { set_config('forum_lastreadclean', time()); } return true; }
function referentiel_cron_taches() { global $CFG, $USER; global $DB; $cronuser = clone $USER; $site = get_site(); // all users that are subscribed to any post that needs sending $users = array(); // status arrays $mailcount = array(); $errorcount = array(); // caches $tasks = array(); $courses = array(); $coursemodules = array(); $subscribedusers = array(); // Posts older than 2 days will not be mailed. This is to avoid the problem where // cron has not been running for a long time, and then suddenly people are flooded // with mail from the past few weeks or months $timenow = time(); if (NOTIFICATION_DELAI) { $endtime = $timenow - $CFG->maxeditingtime; } else { $endtime = $timenow; } $starttime = $endtime - NOTIFICATION_INTERVALLE_JOUR * 24 * 3600; // Two days earlier // JF // DEBUG : cron_lib.php : mtrace("DEBUT CRON REFERENTIEL TACHES"); $taches = referentiel_get_unmailed_tasks($starttime, $endtime); if ($taches) { if (REFERENTIEL_DEBUG) { mtrace("\nlib_cron.php :: 1675 :: TACHES\n"); print_r($taches); mtrace("\n"); } // Mark them all now as being mailed. It's unlikely but possible there // might be an error later so that a post is NOT actually mailed out, // but since mail isn't crucial, we can accept this risk. Doing it now // prevents the risk of duplicated mails, which is a worse problem. if (!referentiel_mark_old_tasks_as_mailed($endtime)) { mtrace('Errors occurred while trying to mark some referentiel tasks as being mailed.'); return false; // Don't continue trying to mail them, in case we are in a cron loop } // checking task validity, and adding users to loop through later foreach ($taches as $tid => $task) { // DEBUG : cron_lib.php : // mtrace('task '.$task->id.' '.$tid); if (REFERENTIEL_DEBUG) { mtrace("\nlib_cron.php :: 1694 :: TASK\n"); print_r($task); mtrace("\n"); } if (!isset($tasks[$tid])) { $tasks[$tid] = $task; } // cours $courseid = $tasks[$tid]->ref_course; if (!isset($courses[$courseid])) { if ($course = $DB->get_record("course", array("id" => "{$courseid}"))) { $courses[$courseid] = $course; } else { mtrace('Could not find course ' . $courseid); unset($tasks[$tid]); continue; } } // modules $instanceid = $task->ref_instance; if (!isset($coursemodules[$instanceid])) { if ($cm = get_coursemodule_from_instance('referentiel', $instanceid, $courseid)) { $coursemodules[$instanceid] = $cm; } else { mtrace('./mod/referentiel/lib.php : 1355 : Could not load course module for referentiel instance ' . $instanceid); unset($tasks[$tid]); continue; } } // caching subscribed students of each task $students = array(); $teachers = referentiel_get_teachers_course($courseid); // DEBUG : cron_lib.php : if (!isset($subscribedusers[$tid])) { // notifier tous les etudiants // ICI MODIFIER LA COLLECTE // NE PRENDRE QUE LES ETUDIANTS ACCOMPAGNES // $students=referentiel_get_students_accompagne($courseid,$task->auteurid); $students = referentiel_get_accompagnements_teacher($task->ref_instance, $task->ref_course, $task->auteurid); if (empty($students)) { $students = referentiel_get_students_course($courseid, 0, 0, false); } if ($students) { foreach ($students as $student) { $user = referentiel_get_user($student->userid); if ($user->emailstop) { if (!empty($CFG->forum_logblocked)) { // add_to_log(SITEID, 'referentiel', 'mail blocked', '', '', 0, $user->id); } } else { // this user is subscribed to this notification $subscribedusers[$tid][$student->userid] = $student->userid; // this user is a user we have to process later $users[$student->userid] = $user; if (REFERENTIEL_DEBUG) { mtrace("\nDEBUG :: lib_cron:: 1758 :: \nDESTINATAIRE ETUDIANT ID: " . $user->id . "\n"); //print_r($user); } } } } unset($students); // release memory if (NOTIFICATION_TACHES_AUX_REFERENTS) { // if ($teachers) { foreach ($teachers as $teacher) { $user = referentiel_get_user($teacher->userid); if ($user->emailstop) { if (!empty($CFG->forum_logblocked)) { // add_to_log(SITEID, 'referentiel', 'mail blocked', '', '', 0, $teacher->userid); } } else { // this user is subscribed to this notification $subscribedusers[$tid][$user->id] = $user->id; // this user is a user we have to process later $users[$user->id] = $user; // DEBUG : cron_lib.php : if (REFERENTIEL_DEBUG) { mtrace("\nDEBUG :: lib_cron:: 1782 :: \nDESTINATAIRE TEACHER: " . $user->id . " \n"); //print_r($user); } } } } } unset($teachers); // release memory $mailcount[$tid] = 0; $errorcount[$tid] = 0; } } } if ($users && $tasks) { // DEBUG : cron_lib.php : if (REFERENTIEL_DEBUG) { mtrace("TRAITEMENT DES MESSAGES TACHES\n"); } $urlinfo = parse_url($CFG->wwwroot); $hostname = $urlinfo['host']; foreach ($users as $userto) { @set_time_limit(TIME_LIMIT); // terminate if processing of any account takes longer than 2 minutes // set this so that the capabilities are cached, and environment matches receiving user $USER = $userto; // mtrace('./mod/referentiel/lib.php :: Line 253 : Processing user '.$userto->id); // init caches $userto->viewfullnames = array(); // $userto->canpost = array(); // $userto->markposts = array(); $userto->enrolledin = array(); // reset the caches foreach ($coursemodules as $coursemoduleid => $unused) { $coursemodules[$coursemoduleid]->cache = new object(); $coursemodules[$coursemoduleid]->cache->caps = array(); unset($coursemodules[$coursemoduleid]->uservisible); } foreach ($tasks as $tid => $task) { // Set up the environment for activity, course $course = $courses[$task->ref_course]; $cm =& $coursemodules[$task->ref_instance]; // Do some checks to see if we can mail out now if (!isset($subscribedusers[$tid][$userto->id])) { continue; // user does not subscribe to this activity } // Get info about the author user if (array_key_exists($task->auteurid, $users)) { // we might know him/her already but we need a really complete user object, so.. get it again! if ($userfrom = $DB->get_record("user", array("id" => "{$task->auteurid}"))) { $users[$task->auteurid] = $userfrom; } else { // error mtrace('Could not find user ' . $task->auteurid); continue; } } else { if ($userfrom = $DB->get_record("user", array("id" => "{$task->auteurid}"))) { $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway } else { mtrace('Could not find user ' . $task->auteurid); continue; } } // setup global $COURSE properly - needed for roles and languages // course_setup($course); // More environment // setup global $COURSE properly - needed for roles and languages cron_setup_user($userto, $course); // Fill caches if (!isset($userto->viewfullnames[$tid])) { $modcontext = context_module::instance($cm->id); $userto->viewfullnames[$tid] = has_capability('moodle/site:viewfullnames', $modcontext); } if (!isset($userfrom->groups[$tid])) { if (!isset($userfrom->groups)) { $userfrom->groups = array(); $users[$userfrom->id]->groups = array(); } $userfrom->groups[$tid] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid); $users[$userfrom->id]->groups[$tid] = $userfrom->groups[$tid]; } // OK so we need to send the email. // Does the user want this post in a digest? If so postpone it for now. if ($userto->maildigest > 0) { $queue = new object(); $queue->userid = $userto->id; $queue->activiteid = $task->id; $queue->timemodified = $task->date_modif; $queue->type = TYPE_TACHE; // 1 if (!$DB->insert_record('referentiel_notification', $queue)) { mtrace("Error: mod/referentiel/lib.php : Line 1510 : Could not queue for digest mail for id {$task->id} to user {$userto->id} ({$userto->email}) .. not trying again."); } continue; } // Prepare to actually send the post now, and build up the content $strreferentielname = get_string('referentiel', 'referentiel') . ': ' . referentiel_get_instance_name($task->ref_instance); $cleanactivityname = str_replace('"', "'", strip_tags(format_string($strreferentielname . ' -> ' . $task->type_task))); $userfrom->customheaders = array('Precedence: Bulk', 'List-Id: "' . $cleanactivityname . '" <moodle_referentiel_activity_' . $task->id . '@' . $hostname . '>', 'List-Help: ' . $CFG->wwwroot . '/mod/referentiel/task.php?d=' . $task->ref_instance . '&task_id=' . $task->id . '&mode=listtaskall', 'Message-ID: <moodle_referentiel_task_' . $task->id . '@' . $hostname . '>', 'X-Course-Id: ' . $course->id, 'X-Course-Name: ' . format_string($course->fullname, true)); if (!($cm = get_coursemodule_from_instance('referentiel', $task->ref_instance, $course->id))) { print_error('Course Module ID was incorrect'); } $postsubject = "{$course->shortname}: " . format_string($strreferentielname . ' ' . $task->type_task, true); $context = context_module::instance($cm->id); $posttext = referentiel_make_mail_text(TYPE_TACHE, $context, $course, $task, $userfrom, $userto); $posthtml = referentiel_make_mail_html(TYPE_TACHE, $context, $course, $task, $userfrom, $userto); // Send the post now! // mtrace('Sending ', ''); if (!($mailresult = email_to_user($userto, $userfrom, $postsubject, $posttext, $posthtml, '', '', $CFG->forum_replytouser))) { mtrace("Error: Could not send out mail for id {$task->id} to user {$userto->id}" . " ({$userto->email}) .. not trying again."); // add_to_log($course->id, 'referentiel', 'mail error', "task $task->id to $userto->id ($userto->email)","", $cm->id, $userto->id); $errorcount[$tid]++; } else { if ($mailresult === 'emailstop') { // should not be reached anymore - see check above } else { $mailcount[$tid]++; } } } } } if ($tasks) { foreach ($tasks as $task) { mtrace($mailcount[$task->id] . " users were sent task {$task->id}, {$task->type_task}"); if ($errorcount[$task->id]) { $DB->set_field("referentiel_task", "mailed", "2", array("id" => "{$task->id}")); } } } // release some memory unset($subscribedusers); unset($mailcount); unset($errorcount); $USER = clone $cronuser; // course_setup(SITEID); cron_setup_user(); $sitetimezone = $CFG->timezone; // Now see if there are any digest mails waiting to be sent, and if we should send them mtrace('Starting digest processing...'); @set_time_limit(TIME_LIMIT * 2); // terminate if not able to fetch all digests in 5 minutes if (!isset($CFG->digesttasktimelast)) { // To catch the first time set_config('digesttasktimelast', 0); } $timenow = time(); $digesttime = usergetmidnight($timenow, $sitetimezone) + $CFG->digestmailtime * 3600; // Delete any really old ones (normally there shouldn't be any) $weekago = $timenow - 7 * 24 * 3600; $DB->delete_records_select('referentiel_notification', "timemodified < {$weekago} AND type='" . TYPE_TACHE . "'", NULL); mtrace('Cleaned old digest records'); if ($CFG->digesttasktimelast < $digesttime and $timenow > $digesttime) { mtrace("Sending task digests: " . userdate($timenow, '', $sitetimezone)); $params = array("digesttime" => "{$digesttime}", "type" => TYPE_TACHE); $sql = " (timemodified < :digesttime) AND (type=:type) "; $digestposts_rs = $DB->get_recordset_select('referentiel_notification', $sql, $params); // if (!rs_EOF($digestposts_rs)) { // deprecated if ($digestposts_rs->valid()) { // We have work to do $usermailcount = 0; //caches - reuse the those filled before too $userposts = array(); // while ($digestpost = rs_fetch_next_record($digestposts_rs)) { foreach ($digestposts_rs as $digestpost) { if (!isset($users[$digestpost->userid])) { if ($user = $DB->get_record("user", array("id" => "{$digestpost->userid}"))) { $users[$digestpost->userid] = $user; } else { continue; } } $postuser = $users[$digestpost->userid]; if ($postuser->emailstop) { if (!empty($CFG->forum_logblocked)) { // add_to_log(SITEID, 'referentiel', 'mail blocked', '', '', 0, $postuser->id); } continue; } // contenu activite if (!isset($taches[$digestpost->activiteid])) { if ($tache = $DB->get_record("referentiel_task", array("id" => "{$digestpost->activiteid}"))) { $taches[$digestpost->activiteid] = $tache; } else { continue; } } $courseid = $taches[$digestpost->activiteid]->ref_course; if (!isset($courses[$courseid])) { if ($course = $DB->get_record("course", array("id" => "{$courseid}"))) { $courses[$courseid] = $course; } else { continue; } } if (!isset($coursemodules[$taches[$digestpost->activiteid]->ref_instance]) && $taches[$digestpost->activiteid]) { if ($cm = get_coursemodule_from_instance('referentiel', $taches[$digestpost->activiteid]->ref_instance, $courseid)) { $coursemodules[$taches[$digestpost->activiteid]->ref_instance] = $cm; } else { continue; } } $userposts[$digestpost->userid][$digestpost->activiteid] = $digestpost->activiteid; } $digestposts_rs->close(); /// Finished iteration, let's close the resultset // Data collected, start sending out emails to each user // foreach ($userdiscussions as $userid => $thesediscussions) { foreach ($userposts as $userid => $theseactivities) { @set_time_limit(TIME_LIMIT); // terminate if processing of any account takes longer than 2 minutes $USER = $cronuser; // course_setup(SITEID); // reset cron user language, theme and timezone settings cron_setup_user(); mtrace(get_string('processingdigest', 'referentiel', $userid), '... '); // First of all delete all the queue entries for this user $DB->delete_records_select('referentiel_notification', "userid = {$userid} AND timemodified < {$digesttime} AND type='" . TYPE_TACHE . "'", NULL); $userto = $users[$userid]; // Override the language and timezone of the "current" user, so that // mail is customised for the receiver. $USER = $userto; // course_setup(SITEID); cron_setup_user(); // init caches $userto->viewfullnames = array(); // $userto->canpost = array(); // $userto->markposts = array(); $postsubject = get_string('digestmailsubject', 'referentiel', format_string($site->shortname, true)); $headerdata = new object(); $headerdata->sitename = format_string($site->fullname, true); $headerdata->userprefs = $CFG->wwwroot . '/user/edit.php?id=' . $userid . '&course=' . $site->id; $posttext = get_string('digestmailheader', 'referentiel', $headerdata) . "\n\n"; $headerdata->userprefs = '<a target="_blank" href="' . $headerdata->userprefs . '">' . get_string('digestmailprefs', 'referentiel') . '</a>'; $posthtml = "<head>"; /* foreach ($CFG->stylesheets as $stylesheet) { $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n"; } */ $posthtml .= "</head>\n<body id=\"email\">\n"; $posthtml .= '<p>' . get_string('digestmailheader', 'referentiel', $headerdata) . '</p><br /><hr size="1" noshade="noshade" />'; foreach ($theseactivities as $tid) { @set_time_limit(TIME_LIMIT); // to be reset for each post $type_notification = TYPE_TACHE; $tache = $taches[$tid]; $course = $courses[$taches[$tid]->ref_course]; $cm = $coursemodules[$taches[$tid]->ref_instance]; //override language // course_setup($course); // setup global $COURSE properly - needed for roles and languages cron_setup_user($userto, $course); // Fill caches if (!isset($userto->viewfullnames[$tache->id])) { $modcontext = context_module::instance($cm->id); $userto->viewfullnames[$tache->id] = has_capability('moodle/site:viewfullnames', $modcontext); } $strreferentiels = get_string('referentiels', 'referentiel'); $posttext .= "\n \n"; $posttext .= '====================================================================='; $posttext .= "\n \n"; $posttext .= "{$course->shortname} -> {$strreferentiels} -> " . format_string($tache->type_task, true); $posttext .= "\n"; $posthtml .= "<p><font face=\"sans-serif\">" . "<a target=\"_blank\" href=\"{$CFG->wwwroot}/course/view.php?id={$course->id}\">{$course->shortname}</a> -> " . "<a target=\"_blank\" href=\"{$CFG->wwwroot}/mod/referentiel/index.php?id={$course->id}\">{$strreferentiels}</a> -> " . "<a target=\"_blank\" href=\"{$CFG->wwwroot}/mod/referentiel/tache.php?id={$tache->ref_instance}&activite_id={$tache->id}\">" . format_string($tache->type_task, true) . "</a>"; $posthtml .= "</font></p>"; $posthtml .= '<p>'; //$postsarray = $userposts[$tache->id]; $postsarray = $userposts[$userid]; sort($postsarray); foreach ($postsarray as $activiteid) { $post = $taches[$activiteid]; if (array_key_exists($post->auteurid, $users)) { // we might know him/her already $userfrom = $users[$post->auteurid]; } else { if ($userfrom = $DB->get_record("user", array("id" => "{$post->auteurid}"))) { $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway } else { mtrace('Could not find user ' . $post->auteurid); continue; } } if (!isset($userfrom->groups[$post->id])) { if (!isset($userfrom->groups)) { $userfrom->groups = array(); $users[$userfrom->id]->groups = array(); } $userfrom->groups[$post->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid); $users[$userfrom->id]->groups[$post->id] = $userfrom->groups[$post->id]; } $userfrom->customheaders = array("Precedence: Bulk"); if ($userto->maildigest == 2) { // Subjects only $by = new object(); $by->name = fullname($userfrom); $by->date = userdate($post->date_modif); $posttext .= "\n" . format_string($post->type_task, true) . ' ' . get_string("bynameondate", "referentiel", $by); $posttext .= "\n---------------------------------------------------------------------"; $by->name = "<a target=\"_blank\" href=\"{$CFG->wwwroot}/user/view.php?id={$userfrom->id}&course={$course->id}\">{$by->name}</a>"; $posthtml .= '<div><a target="_blank" href="' . $CFG->wwwroot . '/mod/referentiel/task.php?d=' . $post->ref_instance . '&activite_id=' . $post->id . '">' . format_string($post->type_activite, true) . '</a> ' . get_string("bynameondate", "referentiel", $by) . '</div>'; } else { $context = context_module::instance($cm->id); $posttext = referentiel_make_mail_text(TYPE_TACHE, $context, $course, $post, $userfrom, $userto, true); $posthtml = referentiel_make_mail_post(TYPE_TACHE, $context, $course, $post, $userfrom, $userto, false, true, false); } } $posthtml .= '<hr size="1" noshade="noshade" /></p>'; } $posthtml .= '</body>'; if ($userto->mailformat != 1) { // This user DOESN'T want to receive HTML $posthtml = ''; } if (!($mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, '', '', $CFG->forum_replytouser))) { mtrace("ERROR!"); mtrace("Error: Could not send out digest mail to user {$userto->id} ({$userto->email})... not trying again.\n"); // add_to_log($course->id, 'referentiel', 'mail digest error', '', '', $cm->id, $userto->id); } else { if ($mailresult === 'emailstop') { // should not happen anymore - see check above } else { mtrace("success."); $usermailcount++; } } } } /// We have finishied all digest emails activities, update $CFG->digestactivitytimelast set_config('digesttasktimelast', $timenow); } $USER = $cronuser; // course_setup(SITEID); // reset cron user language, theme and timezone settings cron_setup_user(); if (!empty($usermailcount)) { mtrace(get_string('digestsentusers', 'referentiel', $usermailcount)); } mtrace("FIN CRON REFERENTIEL TACHE.\n"); return true; }