/**
  * 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();
 }
Example #2
0
 /**
  * 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);
     }
 }
Example #3
0
 /**
  * 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;
 }
Example #4
0
/**
 * 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();
    }
}
Example #5
0
 /**
  * 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);
     }
 }
Example #6
0
     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);
 }
Example #7
0
 /**
  * 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;
 }
Example #8
0
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']);
}
Example #9
0
    /**
     * 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;
    }
Example #10
0
        $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) {
Example #11
0
 /**
  * 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);
 }
Example #12
0
/**
 * 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();
    }
}