/** * Adds a grade category and moves the specified item into it. * Uses locks to prevent race conditions (see MDL-37055). */ public function execute() { $customdata = $this->get_custom_data(); // Get lock timeout. $timeout = 5; // A namespace for the locks. $locktype = 'block_mhaairs_add_category'; // Resource key - course id and category name. $resource = "course: {$customdata->courseid}; catname: {$customdata->catname}"; // Get an instance of the currently configured lock_factory. $lockfactory = \core\lock\lock_config::get_lock_factory($locktype); // Open a lock. $lock = $lockfactory->get_lock($resource, $timeout); // Add the category. $catparams = array('fullname' => $customdata->catname, 'courseid' => $customdata->courseid); if (!($category = \grade_category::fetch($catparams))) { // If the category does not exist we create it. $gradeaggregation = get_config('core', 'grade_aggregation'); if ($gradeaggregation === false) { $gradeaggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2; } // Parent category is automatically added(created) during insert. $catparams['hidden'] = false; $catparams['aggregation'] = $gradeaggregation; try { $category = new \grade_category($catparams, false); $category->id = $category->insert(); } catch (Exception $e) { // Must release the locks. $lock->release(); // Rethrow to reschedule task. throw $e; } } // Release locks. $lock->release(); // Add the item to the category. $gitem = \grade_item::fetch(array('id' => $customdata->itemid)); $gitem->categoryid = $category->id; $gitem->update(); }
/** * Tests the static parse charset method * @return void */ public function test_lock_config() { global $CFG; $original = null; if (isset($CFG->lock_factory)) { $original = $CFG->lock_factory; } // Test no configuration. unset($CFG->lock_factory); $factory = \core\lock\lock_config::get_lock_factory('cache'); $this->assertNotEmpty($factory, 'Get a default factory with no configuration'); $CFG->lock_factory = '\\core\\lock\\file_lock_factory'; $factory = \core\lock\lock_config::get_lock_factory('cache'); $this->assertTrue($factory instanceof \core\lock\file_lock_factory, 'Get a default factory with a set configuration'); $CFG->lock_factory = '\\core\\lock\\db_record_lock_factory'; $factory = \core\lock\lock_config::get_lock_factory('cache'); $this->assertTrue($factory instanceof \core\lock\db_record_lock_factory, 'Get a default factory with a changed configuration'); if ($original) { $CFG->lock_factory = $original; } else { unset($CFG->lock_factory); } }
/** * This function will dispatch the next scheduled task in the queue. The task will be handed out * with an open lock - possibly on the entire cron process. Make sure you call either * {@link scheduled_task_failed} or {@link scheduled_task_complete} to release the lock and reschedule the task. * * @param int $timestart - The start of the cron process - do not repeat any tasks that have been run more recently than this. * @return \core\task\scheduled_task or null */ public static function get_next_scheduled_task($timestart) { global $DB; $cronlockfactory = \core\lock\lock_config::get_lock_factory('cron'); if (!($cronlock = $cronlockfactory->get_lock('core_cron', 10))) { throw new \moodle_exception('locktimeout'); } $where = "(lastruntime IS NULL OR lastruntime < :timestart1)\n AND (nextruntime IS NULL OR nextruntime < :timestart2)\n AND disabled = 0"; $params = array('timestart1' => $timestart, 'timestart2' => $timestart); $records = $DB->get_records_select('task_scheduled', $where, $params); $pluginmanager = \core_plugin_manager::instance(); foreach ($records as $record) { if ($lock = $cronlockfactory->get_lock($record->classname, 10)) { $classname = '\\' . $record->classname; $task = self::scheduled_task_from_record($record); $task->set_lock($lock); // See if the component is disabled. $plugininfo = $pluginmanager->get_plugin_info($task->get_component()); if ($plugininfo) { if (!$task->get_run_if_component_disabled() && !$plugininfo->is_enabled()) { $lock->release(); continue; } } if (!$task->is_blocking()) { $cronlock->release(); } else { $task->set_cron_lock($cronlock); } return $task; } } // No tasks. $cronlock->release(); return null; }
/** * Process user submitted answers for a choice, * and either updating them or saving new answers. * * @param int $formanswer users submitted answers. * @param object $choice the selected choice. * @param int $userid user identifier. * @param object $course current course. * @param object $cm course context. * @return void */ function choice_user_submit_response($formanswer, $choice, $userid, $course, $cm) { global $DB, $CFG; require_once $CFG->libdir . '/completionlib.php'; $continueurl = new moodle_url('/mod/choice/view.php', array('id' => $cm->id)); if (empty($formanswer)) { print_error('atleastoneoption', 'choice', $continueurl); } if (is_array($formanswer)) { if (!$choice->allowmultiple) { print_error('multiplenotallowederror', 'choice', $continueurl); } $formanswers = $formanswer; } else { $formanswers = array($formanswer); } $options = $DB->get_records('choice_options', array('choiceid' => $choice->id), '', 'id'); foreach ($formanswers as $key => $val) { if (!isset($options[$val])) { print_error('cannotsubmit', 'choice', $continueurl); } } // Start lock to prevent synchronous access to the same data // before it's updated, if using limits. if ($choice->limitanswers) { $timeout = 10; $locktype = 'mod_choice_choice_user_submit_response'; // Limiting access to this choice. $resouce = 'choiceid:' . $choice->id; $lockfactory = \core\lock\lock_config::get_lock_factory($locktype); // Opening the lock. $choicelock = $lockfactory->get_lock($resouce, $timeout); if (!$choicelock) { print_error('cannotsubmit', 'choice', $continueurl); } } $current = $DB->get_records('choice_answers', array('choiceid' => $choice->id, 'userid' => $userid)); $context = context_module::instance($cm->id); $choicesexceeded = false; $countanswers = array(); foreach ($formanswers as $val) { $countanswers[$val] = 0; } if ($choice->limitanswers) { // Find out whether groups are being used and enabled if (groups_get_activity_groupmode($cm) > 0) { $currentgroup = groups_get_activity_group($cm); } else { $currentgroup = 0; } list($insql, $params) = $DB->get_in_or_equal($formanswers, SQL_PARAMS_NAMED); if ($currentgroup) { // If groups are being used, retrieve responses only for users in // current group global $CFG; $params['groupid'] = $currentgroup; $sql = "SELECT ca.*\n FROM {choice_answers} ca\n INNER JOIN {groups_members} gm ON ca.userid=gm.userid\n WHERE optionid {$insql}\n AND gm.groupid= :groupid"; } else { // Groups are not used, retrieve all answers for this option ID $sql = "SELECT ca.*\n FROM {choice_answers} ca\n WHERE optionid {$insql}"; } $answers = $DB->get_records_sql($sql, $params); if ($answers) { foreach ($answers as $a) { //only return enrolled users. if (is_enrolled($context, $a->userid, 'mod/choice:choose')) { $countanswers[$a->optionid]++; } } } foreach ($countanswers as $opt => $count) { if ($count >= $choice->maxanswers[$opt]) { $choicesexceeded = true; break; } } } // Check the user hasn't exceeded the maximum selections for the choice(s) they have selected. if (!($choice->limitanswers && $choicesexceeded)) { $answersnapshots = array(); if ($current) { // Update an existing answer. $existingchoices = array(); foreach ($current as $c) { if (in_array($c->optionid, $formanswers)) { $existingchoices[] = $c->optionid; $DB->set_field('choice_answers', 'timemodified', time(), array('id' => $c->id)); $answersnapshots[] = $c; } else { $DB->delete_records('choice_answers', array('id' => $c->id)); } } // Add new ones. foreach ($formanswers as $f) { if (!in_array($f, $existingchoices)) { $newanswer = new stdClass(); $newanswer->optionid = $f; $newanswer->choiceid = $choice->id; $newanswer->userid = $userid; $newanswer->timemodified = time(); $newanswer->id = $DB->insert_record("choice_answers", $newanswer); $answersnapshots[] = $newanswer; } } // Initialised as true, meaning we updated the answer. $answerupdated = true; } else { // Add new answer. foreach ($formanswers as $answer) { $newanswer = new stdClass(); $newanswer->choiceid = $choice->id; $newanswer->userid = $userid; $newanswer->optionid = $answer; $newanswer->timemodified = time(); $newanswer->id = $DB->insert_record("choice_answers", $newanswer); $answersnapshots[] = $newanswer; } // Update completion state $completion = new completion_info($course); if ($completion->is_enabled($cm) && $choice->completionsubmit) { $completion->update_state($cm, COMPLETION_COMPLETE); } // Initalised as false, meaning we submitted a new answer. $answerupdated = false; } } else { // Check to see if current choice already selected - if not display error. $currentids = array_keys($current); if (array_diff($currentids, $formanswers) || array_diff($formanswers, $currentids)) { // Release lock before error. $choicelock->release(); print_error('choicefull', 'choice', $continueurl); } } // Release lock. if (isset($choicelock)) { $choicelock->release(); } // Now record completed event. if (isset($answerupdated)) { $eventdata = array(); $eventdata['context'] = $context; $eventdata['objectid'] = $choice->id; $eventdata['userid'] = $userid; $eventdata['courseid'] = $course->id; $eventdata['other'] = array(); $eventdata['other']['choiceid'] = $choice->id; if ($answerupdated) { $eventdata['other']['optionid'] = $formanswer; $event = \mod_choice\event\answer_updated::create($eventdata); } else { $eventdata['other']['optionid'] = $formanswers; $event = \mod_choice\event\answer_submitted::create($eventdata); } $event->add_record_snapshot('course', $course); $event->add_record_snapshot('course_modules', $cm); $event->add_record_snapshot('choice', $choice); foreach ($answersnapshots as $record) { $event->add_record_snapshot('choice_answers', $record); } $event->trigger(); } }
/** * 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); } }
exit(1); } if (moodle_needs_upgrading()) { mtrace("Moodle upgrade pending, cannot execute tasks."); exit(1); } // Increase memory limit. raise_memory_limit(MEMORY_EXTRA); // Emulate normal session - we use admin account by default. cron_setup_user(); $predbqueries = $DB->perf_get_queries(); $pretime = microtime(true); mtrace("Scheduled task: " . $task->get_name()); // NOTE: it would be tricky to move this code to \core\task\manager class, // because we want to do detailed error reporting. $cronlockfactory = \core\lock\lock_config::get_lock_factory('cron'); if (!($cronlock = $cronlockfactory->get_lock('core_cron', 10))) { mtrace('Cannot obtain cron lock'); exit(129); } if (!($lock = $cronlockfactory->get_lock('\\' . get_class($task), 10))) { $cronlock->release(); mtrace('Cannot obtain task lock'); exit(130); } $task->set_lock($lock); if (!$task->is_blocking()) { $cronlock->release(); } else { $task->set_cron_lock($cronlock); }
/** * This function will dispatch the next scheduled task in the queue. The task will be handed out * with an open lock - possibly on the entire cron process. Make sure you call either * {@link scheduled_task_failed} or {@link scheduled_task_complete} to release the lock and reschedule the task. * * @param int $timestart - The start of the cron process - do not repeat any tasks that have been run more recently than this. * @return \core\task\scheduled_task or null */ public static function get_next_scheduled_task($timestart) { global $DB; $cronlockfactory = \core\lock\lock_config::get_lock_factory('cron'); if (!($cronlock = $cronlockfactory->get_lock('core_cron', 10))) { throw new \moodle_exception('locktimeout'); } $where = "(lastruntime IS NULL OR lastruntime < :timestart1)\n AND (nextruntime IS NULL OR nextruntime < :timestart2)\n AND disabled = 0\n ORDER BY lastruntime, id ASC"; $params = array('timestart1' => $timestart, 'timestart2' => $timestart); $records = $DB->get_records_select('task_scheduled', $where, $params); $pluginmanager = \core_plugin_manager::instance(); foreach ($records as $record) { if ($lock = $cronlockfactory->get_lock($record->classname, 10)) { $classname = '\\' . $record->classname; $task = self::scheduled_task_from_record($record); // Safety check in case the task in the DB does not match a real class (maybe something was uninstalled). if (!$task) { $lock->release(); continue; } $task->set_lock($lock); // See if the component is disabled. $plugininfo = $pluginmanager->get_plugin_info($task->get_component()); if ($plugininfo) { if ($plugininfo->is_enabled() === false && !$task->get_run_if_component_disabled()) { $lock->release(); continue; } } // Make sure the task data is unchanged. if (!$DB->record_exists('task_scheduled', (array) $record)) { $lock->release(); continue; } if (!$task->is_blocking()) { $cronlock->release(); } else { $task->set_cron_lock($cronlock); } return $task; } } // No tasks. $cronlock->release(); return null; }
function generate_roster_file() { global $cm, $iru, $COURSE, $USER; // Only teachers/admins can view iclicker that is not their own. if (!has_capability('mod/iclickerregistration:viewenrolled', context_module::instance($cm->id))) { echo json_encode(array("status" => "access denied")); return; } // RESOURCE LOCK. // For more info: https://docs.moodle.org/dev/Lock_API // Get an instance of the currently configured lock_factory. $resource = 'user: '******'mod_iclickerregistration'); // ACQUIRE LOCK. $lock = $lockfactory->get_lock("{$resource}", 5); $allusers = $iru->get_all_users_left_join_iclickers(array("courseid" => $COURSE->id)); $responsetext = ""; foreach ($allusers as $iclickeruser) { $responsetext .= "{$iclickeruser->lastname}, {$iclickeruser->firstname}, {$iclickeruser->idnumber}\r\n"; } $fs = get_file_storage(); $fs->delete_area_files($cm->id, 'mod_iclickerregistration'); $dummyfilename = 'roster.txt'; $fs = get_file_storage(); $filerecord = array('contextid' => $cm->id, 'component' => 'mod_iclickerregistration', 'filearea' => 'intro', 'itemid' => 0, 'filepath' => '/', 'filename' => $dummyfilename, 'timecreated' => time(), 'timemodified' => time()); $file = $fs->create_file_from_string($filerecord, $responsetext); // RELEASE LOCK. $lock->release(); if (!$file) { return false; // File don't exist. } // Force it to be csv. Otherwise, moodle throws an error for some reason. send_file($file, $dummyfilename, 0, true, false, false, get_mimetypes_array()['csv']['type']); }
/** * This function will dispatch the next scheduled task in the queue. The task will be handed out * with an open lock - possibly on the entire cron process. Make sure you call either * {@link scheduled_task_failed} or {@link scheduled_task_complete} to release the lock and reschedule the task. * * @param int $timestart - The start of the cron process - do not repeat any tasks that have been run more recently than this. * @return \core\task\scheduled_task or null */ public static function get_next_scheduled_task($timestart) { global $DB; $cronlockfactory = \core\lock\lock_config::get_lock_factory('cron'); if (!$cronlock = $cronlockfactory->get_lock('core_cron', 10)) { throw new \moodle_exception('locktimeout'); } $where = "(lastruntime IS NULL OR lastruntime < :timestart1) AND (nextruntime IS NULL OR nextruntime < :timestart2) AND disabled = 0"; $params = array('timestart1' => $timestart, 'timestart2' => $timestart); $records = $DB->get_records_select('task_scheduled', $where, $params); foreach ($records as $record) { if ($lock = $cronlockfactory->get_lock(($record->classname), 10)) { $classname = '\\' . $record->classname; $task = self::scheduled_task_from_record($record); // Safety check in case the task in the DB does not match a real class (maybe something was uninstalled). if (!$task) { $lock->release(); continue; } $task->set_lock($lock); if (!$task->is_blocking()) { $cronlock->release(); } else { $task->set_cron_lock($cronlock); } return $task; } } // No tasks. $cronlock->release(); return null; }
$candidatename .= '.' . $chunk; } $candidatesheet = "{$candidatedir}/{$candidatename}.css"; $etag = sha1($etag); } make_localcache_directory('theme', false); if ($type === 'editor') { $csscontent = $theme->get_css_content_editor(); css_store_css($theme, "{$candidatedir}/editor.css", $csscontent, false); } else { $lock = null; // Lock system to prevent concurrent requests to compile LESS/SCSS, which is really slow and CPU intensive. // Each client should wait for one to finish the compilation before starting a new compiling process. // We only do this when the file will be cached... if (in_array($type, ['less', 'scss']) && $cache) { $lockfactory = \core\lock\lock_config::get_lock_factory('core_theme_get_css_content'); // We wait for the lock to be acquired, the timeout does not need to be strict here. $lock = $lockfactory->get_lock($themename, rand(15, 30)); if (file_exists($candidatesheet)) { // The file was built while we waited for the lock, we release the lock and serve the file. if ($lock) { $lock->release(); } css_send_cached_css($candidatesheet, $etag); } } // Older IEs require smaller chunks. $csscontent = $theme->get_css_content(); $relroot = preg_replace('|^http.?://[^/]+|', '', $CFG->wwwroot); if (!empty($slashargument)) { if ($usesvg) {
/** * Tests the testable lock factories. * @return void */ public function test_locks() { // Run the suite on the current configured default (may be non-core). $defaultfactory = \core\lock\lock_config::get_lock_factory('default'); $this->run_on_lock_factory($defaultfactory); // Manually create the core no-configuration factories. $dblockfactory = new \core\lock\db_record_lock_factory('test'); $this->run_on_lock_factory($dblockfactory); $filelockfactory = new \core\lock\file_lock_factory('test'); $this->run_on_lock_factory($filelockfactory); }
/** * Process user submitted answers for a choice, * and either updating them or saving new answers. * * @param int $formanswer users submitted answers. * @param object $choice the selected choice. * @param int $userid user identifier. * @param object $course current course. * @param object $cm course context. * @return void */ function choice_user_submit_response($formanswer, $choice, $userid, $course, $cm) { global $DB, $CFG; require_once $CFG->libdir . '/completionlib.php'; $continueurl = new moodle_url('/mod/choice/view.php', array('id' => $cm->id)); // Start lock to prevent synchronous access to the same data // before it's updated, if using limits. if ($choice->limitanswers) { $timeout = 10; $locktype = 'mod_choice_choice_user_submit_response'; // Limiting access to this choice. $resouce = 'choiceid:' . $choice->id; $lockfactory = \core\lock\lock_config::get_lock_factory($locktype); // Opening the lock. $choicelock = $lockfactory->get_lock($resouce, $timeout); if (!$choicelock) { print_error('cannotsubmit', 'choice', $continueurl); } } $current = $DB->get_record('choice_answers', array('choiceid' => $choice->id, 'userid' => $userid)); $context = context_module::instance($cm->id); $countanswers = 0; if ($choice->limitanswers) { // Find out whether groups are being used and enabled if (groups_get_activity_groupmode($cm) > 0) { $currentgroup = groups_get_activity_group($cm); } else { $currentgroup = 0; } if ($currentgroup) { // If groups are being used, retrieve responses only for users in // current group global $CFG; $answers = $DB->get_records_sql("\nSELECT\n ca.*\nFROM\n {choice_answers} ca\n INNER JOIN {groups_members} gm ON ca.userid=gm.userid\nWHERE\n optionid=?\n AND gm.groupid=?", array($formanswer, $currentgroup)); } else { // Groups are not used, retrieve all answers for this option ID $answers = $DB->get_records("choice_answers", array("optionid" => $formanswer)); } if ($answers) { foreach ($answers as $a) { //only return enrolled users. if (is_enrolled($context, $a->userid, 'mod/choice:choose')) { $countanswers++; } } } $maxans = $choice->maxanswers[$formanswer]; } // Check the user hasn't exceeded the maximum selections for the choice(s) they have selected. if (!($choice->limitanswers && $countanswers >= $maxans)) { if ($current) { // Update an existing answer. $newanswer = $current; $newanswer->optionid = $formanswer; $newanswer->timemodified = time(); $DB->update_record("choice_answers", $newanswer); // Initialised as true, meaning we updated the answer. $answerupdated = true; } else { // Add new answer. $newanswer = new stdClass(); $newanswer->choiceid = $choice->id; $newanswer->userid = $userid; $newanswer->optionid = $formanswer; $newanswer->timemodified = time(); $newanswer->id = $DB->insert_record("choice_answers", $newanswer); // Update completion state $completion = new completion_info($course); if ($completion->is_enabled($cm) && $choice->completionsubmit) { $completion->update_state($cm, COMPLETION_COMPLETE); } // Initalised as false, meaning we submitted a new answer. $answerupdated = false; } } else { if (!($current->optionid == $formanswer)) { // Check to see if current choice already selected - if not display error. // Release lock before error. $choicelock->release(); print_error('choicefull', 'choice', $continueurl); } } // Release lock. if (isset($choicelock)) { $choicelock->release(); } // Now record completed event. if (isset($answerupdated)) { $eventdata = array(); $eventdata['context'] = $context; $eventdata['objectid'] = $newanswer->id; $eventdata['userid'] = $userid; $eventdata['courseid'] = $course->id; $eventdata['other'] = array(); $eventdata['other']['choiceid'] = $choice->id; $eventdata['other']['optionid'] = $formanswer; if ($answerupdated) { $event = \mod_choice\event\answer_updated::create($eventdata); } else { $event = \mod_choice\event\answer_submitted::create($eventdata); } $event->add_record_snapshot('choice_answers', $newanswer); $event->add_record_snapshot('course', $course); $event->add_record_snapshot('course_modules', $cm); $event->trigger(); } }