├── pix └── icon.png ├── README.txt ├── db ├── uninstall.php ├── install.php ├── access.php ├── install.xml └── upgrade.php ├── version.php ├── locallib.php ├── classes ├── event │ ├── course_module_instance_list_viewed.php │ ├── course_module_viewed.php │ ├── attempt_saved.php │ ├── attempt_created.php │ ├── attempt_submitted.php │ └── attempt_base.php ├── attempt_form.php ├── report.php └── attempt.php ├── grade.php ├── backup └── moodle2 │ ├── backup_answersheet_activity_task.class.php │ ├── backup_answersheet_stepslib.php │ ├── restore_answersheet_stepslib.php │ └── restore_answersheet_activity_task.class.php ├── index.php ├── lang └── en │ └── answersheet.php ├── view.php ├── mod_form.php └── lib.php /pix/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marinaglancy/moodle-mod_answersheet/master/pix/icon.png -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | moodle-mod_answersheet 2 | 3 | Display test answer sheet to the students, allow to take several 4 | attempts. Attempts are graded immediately. -------------------------------------------------------------------------------- /db/uninstall.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Provides code to be executed during the module uninstallation 19 | * 20 | * @see uninstall_plugin() 21 | * 22 | * @package mod_answersheet 23 | * @copyright 2015 Marina Glancy 24 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 | */ 26 | 27 | /** 28 | * Custom uninstallation procedure 29 | */ 30 | function xmldb_answersheet_uninstall() { 31 | return true; 32 | } 33 | -------------------------------------------------------------------------------- /version.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Defines the version and other meta-info about the plugin 19 | * 20 | * Setting the $plugin->version to 0 prevents the plugin from being installed. 21 | * See https://docs.moodle.org/dev/version.php for more info. 22 | * 23 | * @package mod_answersheet 24 | * @copyright 2015 Marina Glancy 25 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 26 | */ 27 | 28 | defined('MOODLE_INTERNAL') || die(); 29 | 30 | $plugin->component = 'mod_answersheet'; 31 | $plugin->version = 2018042100; 32 | $plugin->release = 'v2.0'; 33 | $plugin->requires = 2014051200; 34 | $plugin->maturity = MATURITY_STABLE; 35 | -------------------------------------------------------------------------------- /locallib.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Internal library of functions for module answersheet 19 | * 20 | * All the answersheet specific functions, needed to implement the module 21 | * logic, should go here. Never include this file from your lib.php! 22 | * 23 | * @package mod_answersheet 24 | * @copyright 2015 Marina Glancy 25 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 26 | */ 27 | 28 | defined('MOODLE_INTERNAL') || die(); 29 | 30 | /* 31 | * Does something really useful with the passed things 32 | * 33 | * @param array $things 34 | * @return object 35 | *function answersheet_do_something_useful(array $things) { 36 | * return new stdClass(); 37 | *} 38 | */ 39 | -------------------------------------------------------------------------------- /classes/event/course_module_instance_list_viewed.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * The mod_answersheet instance list viewed event. 19 | * 20 | * @package mod_answersheet 21 | * @copyright 2015 Marina Glancy 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_answersheet\event; 26 | 27 | defined('MOODLE_INTERNAL') || die(); 28 | 29 | /** 30 | * The mod_answersheet instance list viewed event class. 31 | * 32 | * @package mod_answersheet 33 | * @copyright 2015 Marina Glancy 34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class course_module_instance_list_viewed extends \core\event\course_module_instance_list_viewed { 37 | } 38 | -------------------------------------------------------------------------------- /db/install.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Provides code to be executed during the module installation 19 | * 20 | * This file replaces the legacy STATEMENTS section in db/install.xml, 21 | * lib.php/modulename_install() post installation hook and partially defaults.php. 22 | * 23 | * @package mod_answersheet 24 | * @copyright 2015 Marina Glancy 25 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 26 | */ 27 | 28 | /** 29 | * Post installation procedure 30 | * 31 | * @see upgrade_plugins_modules() 32 | */ 33 | function xmldb_answersheet_install() { 34 | } 35 | 36 | /** 37 | * Post installation recovery procedure 38 | * 39 | * @see upgrade_plugins_modules() 40 | */ 41 | function xmldb_answersheet_install_recovery() { 42 | } 43 | -------------------------------------------------------------------------------- /grade.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Redirect the user to the appropriate submission related page 19 | * 20 | * @package mod_answersheet 21 | * @category grade 22 | * @copyright 2015 Marina Glancy 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | require_once(__DIR__ . "../../../config.php"); 27 | 28 | $id = required_param('id', PARAM_INT);// Course module ID. 29 | // Item number may be != 0 for activities that allow more than one grade per user. 30 | $itemnumber = optional_param('itemnumber', 0, PARAM_INT); 31 | $userid = optional_param('userid', 0, PARAM_INT); // Graded user ID (optional). 32 | 33 | // In the simplest case just redirect to the view page. 34 | redirect('view.php?id='.$id.'&userid='.$userid); 35 | -------------------------------------------------------------------------------- /classes/event/course_module_viewed.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Defines the view event. 19 | * 20 | * @package mod_answersheet 21 | * @copyright 2015 Marina Glancy 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_answersheet\event; 26 | 27 | defined('MOODLE_INTERNAL') || die(); 28 | 29 | /** 30 | * The mod_answersheet instance list viewed event class 31 | * 32 | * If the view mode needs to be stored as well, you may need to 33 | * override methods get_url() and get_legacy_log_data(), too. 34 | * 35 | * @package mod_answersheet 36 | * @copyright 2015 Marina Glancy 37 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 38 | */ 39 | class course_module_viewed extends \core\event\course_module_viewed { 40 | 41 | /** 42 | * Initialize the event 43 | */ 44 | protected function init() { 45 | $this->data['objecttable'] = 'answersheet'; 46 | parent::init(); 47 | } 48 | 49 | public static function create_from_cm(\cm_info $cm, $instance, $course) { 50 | $event = static::create(array( 51 | 'objectid' => $cm->instance, 52 | 'context' => $cm->context, 53 | )); 54 | $event->add_record_snapshot('course', $course); 55 | $event->add_record_snapshot($cm->modname, $instance); 56 | return $event; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /classes/event/attempt_saved.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * The mod_answersheet attempt saved event. 19 | * 20 | * @package mod_answersheet 21 | * @copyright 2017 Marina Glancy 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later. 23 | */ 24 | 25 | namespace mod_answersheet\event; 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | /** 29 | * The mod_answersheet attempt saved event class. 30 | * 31 | * This event is triggered when an attempt is saved. 32 | * 33 | * @property-read array $other { 34 | * Extra information about event. 35 | * 36 | * - int cmid: course module id. 37 | * - int instanceid: id of instance. 38 | * } 39 | * 40 | * @package mod_answersheet 41 | * @copyright 2017 Marina Glancy 42 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later. 43 | */ 44 | class attempt_saved extends attempt_base { 45 | 46 | /** 47 | * Set basic properties for the event. 48 | */ 49 | protected function init() { 50 | parent::init(); 51 | $this->data['crud'] = 'u'; 52 | } 53 | 54 | /** 55 | * Returns localised general event name. 56 | * 57 | * @return string 58 | */ 59 | public static function get_name() { 60 | return get_string('eventattemptsaved', 'mod_answersheet'); 61 | } 62 | 63 | /** 64 | * Returns non-localised event description with id's for admin use only. 65 | * 66 | * @return string 67 | */ 68 | public function get_description() { 69 | return "The user with id '$this->userid' saved attempt on 'answersheet' activity with " 70 | . "course module id '$this->contextinstanceid'."; 71 | } 72 | } 73 | 74 | -------------------------------------------------------------------------------- /classes/event/attempt_created.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * The mod_answersheet attempt created event. 19 | * 20 | * @package mod_answersheet 21 | * @copyright 2017 Marina Glancy 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later. 23 | */ 24 | 25 | namespace mod_answersheet\event; 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | /** 29 | * The mod_answersheet attempt created event class. 30 | * 31 | * This event is triggered when an attempt is created. 32 | * 33 | * @property-read array $other { 34 | * Extra information about event. 35 | * 36 | * - int cmid: course module id. 37 | * - int instanceid: id of instance. 38 | * } 39 | * 40 | * @package mod_answersheet 41 | * @copyright 2017 Marina Glancy 42 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later. 43 | */ 44 | class attempt_created extends attempt_base { 45 | 46 | /** 47 | * Set basic properties for the event. 48 | */ 49 | protected function init() { 50 | parent::init(); 51 | $this->data['crud'] = 'c'; 52 | } 53 | 54 | /** 55 | * Returns localised general event name. 56 | * 57 | * @return string 58 | */ 59 | public static function get_name() { 60 | return get_string('eventattemptcreated', 'mod_answersheet'); 61 | } 62 | 63 | /** 64 | * Returns non-localised event description with id's for admin use only. 65 | * 66 | * @return string 67 | */ 68 | public function get_description() { 69 | return "The user with id '$this->userid' started attempt on 'answersheet' activity with " 70 | . "course module id '$this->contextinstanceid'."; 71 | } 72 | } 73 | 74 | -------------------------------------------------------------------------------- /classes/event/attempt_submitted.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * The mod_answersheet attempt submitted event. 19 | * 20 | * @package mod_answersheet 21 | * @copyright 2017 Marina Glancy 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later. 23 | */ 24 | 25 | namespace mod_answersheet\event; 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | /** 29 | * The mod_answersheet attempt submitted event class. 30 | * 31 | * This event is triggered when an attempt is submitted. 32 | * 33 | * @property-read array $other { 34 | * Extra information about event. 35 | * 36 | * - int cmid: course module id. 37 | * - int instanceid: id of instance. 38 | * } 39 | * 40 | * @package mod_answersheet 41 | * @copyright 2017 Marina Glancy 42 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later. 43 | */ 44 | class attempt_submitted extends attempt_base { 45 | 46 | /** 47 | * Set basic properties for the event. 48 | */ 49 | protected function init() { 50 | parent::init(); 51 | $this->data['crud'] = 'u'; 52 | } 53 | 54 | /** 55 | * Returns localised general event name. 56 | * 57 | * @return string 58 | */ 59 | public static function get_name() { 60 | return get_string('eventattemptsubmitted', 'mod_answersheet'); 61 | } 62 | 63 | /** 64 | * Returns non-localised event description with id's for admin use only. 65 | * 66 | * @return string 67 | */ 68 | public function get_description() { 69 | return "The user with id '$this->userid' submitted attempt on 'answersheet' activity with " 70 | . "course module id '$this->contextinstanceid'."; 71 | } 72 | } 73 | 74 | -------------------------------------------------------------------------------- /backup/moodle2/backup_answersheet_activity_task.class.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Defines backup_answersheet_activity_task class 19 | * 20 | * @package mod_answersheet 21 | * @category backup 22 | * @copyright 2015 Marina Glancy 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die; 27 | 28 | require_once($CFG->dirroot . '/mod/answersheet/backup/moodle2/backup_answersheet_stepslib.php'); 29 | 30 | /** 31 | * Provides the steps to perform one complete backup of the answersheet instance 32 | * 33 | * @package mod_answersheet 34 | * @category backup 35 | * @copyright 2015 Marina Glancy 36 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 | */ 38 | class backup_answersheet_activity_task extends backup_activity_task { 39 | 40 | /** 41 | * No specific settings for this activity 42 | */ 43 | protected function define_my_settings() { 44 | } 45 | 46 | /** 47 | * Defines a backup step to store the instance data in the answersheet.xml file 48 | */ 49 | protected function define_my_steps() { 50 | $this->add_step(new backup_answersheet_activity_structure_step('answersheet_structure', 'answersheet.xml')); 51 | } 52 | 53 | /** 54 | * Encodes URLs to the index.php and view.php scripts 55 | * 56 | * @param string $content some HTML text that eventually contains URLs to the activity instance scripts 57 | * @return string the content with the URLs encoded 58 | */ 59 | static public function encode_content_links($content) { 60 | global $CFG; 61 | 62 | $base = preg_quote($CFG->wwwroot, '/'); 63 | 64 | // Link to the list of answersheets. 65 | $search = '/('.$base.'\/mod\/answersheet\/index.php\?id\=)([0-9]+)/'; 66 | $content = preg_replace($search, '$@NEWMODULEINDEX*$2@$', $content); 67 | 68 | // Link to answersheet view by moduleid. 69 | $search = '/('.$base.'\/mod\/answersheet\/view.php\?id\=)([0-9]+)/'; 70 | $content = preg_replace($search, '$@NEWMODULEVIEWBYID*$2@$', $content); 71 | 72 | return $content; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /backup/moodle2/backup_answersheet_stepslib.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Define all the backup steps that will be used by the backup_answersheet_activity_task 19 | * 20 | * @package mod_answersheet 21 | * @category backup 22 | * @copyright 2015 Marina Glancy 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die; 27 | 28 | /** 29 | * Define the complete answersheet structure for backup, with file and id annotations 30 | * 31 | * @package mod_answersheet 32 | * @category backup 33 | * @copyright 2015 Marina Glancy 34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class backup_answersheet_activity_structure_step extends backup_activity_structure_step { 37 | 38 | /** 39 | * Defines the backup structure of the module 40 | * 41 | * @return backup_nested_element 42 | */ 43 | protected function define_structure() { 44 | 45 | // Get know if we are including userinfo. 46 | $userinfo = $this->get_setting_value('userinfo'); 47 | 48 | // Define the root element describing the answersheet instance. 49 | $answersheet = new backup_nested_element('answersheet', array('id'), array( 50 | 'name', 'intro', 'introformat', 'grade', 'questionscount', 51 | 'questionsoptions', 'answerslist', 'explanations', 'explanationsformat', 52 | 'completionsubmit', 'completionpass', 'question', 'questionformat')); 53 | 54 | // If we had more elements, we would build the tree here. 55 | 56 | // Define data sources. 57 | $answersheet->set_source_table('answersheet', array('id' => backup::VAR_ACTIVITYID)); 58 | 59 | // If we were referring to other tables, we would annotate the relation 60 | // with the element's annotate_ids() method. 61 | 62 | // Define file annotations (we do not use itemid in this example). 63 | $answersheet->annotate_files('mod_answersheet', 'intro', null); 64 | $answersheet->annotate_files('mod_answersheet', 'question', null); 65 | $answersheet->annotate_files('mod_answersheet', 'explanations', null); 66 | 67 | // Return the root element (answersheet), wrapped into standard activity structure. 68 | return $this->prepare_activity_structure($answersheet); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /backup/moodle2/restore_answersheet_stepslib.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Define all the restore steps that will be used by the restore_answersheet_activity_task 19 | * 20 | * @package mod_answersheet 21 | * @category backup 22 | * @copyright 2015 Marina Glancy 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | /** 27 | * Structure step to restore one answersheet activity 28 | * 29 | * @package mod_answersheet 30 | * @category backup 31 | * @copyright 2015 Marina Glancy 32 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 | */ 34 | class restore_answersheet_activity_structure_step extends restore_activity_structure_step { 35 | 36 | /** 37 | * Defines structure of path elements to be processed during the restore 38 | * 39 | * @return array of {@link restore_path_element} 40 | */ 41 | protected function define_structure() { 42 | 43 | $paths = array(); 44 | $paths[] = new restore_path_element('answersheet', '/activity/answersheet'); 45 | 46 | // Return the paths wrapped into standard activity structure. 47 | return $this->prepare_activity_structure($paths); 48 | } 49 | 50 | /** 51 | * Process the given restore path element data 52 | * 53 | * @param array $data parsed element data 54 | */ 55 | protected function process_answersheet($data) { 56 | global $DB; 57 | 58 | $data = (object)$data; 59 | $oldid = $data->id; 60 | $data->course = $this->get_courseid(); 61 | 62 | if (empty($data->timecreated)) { 63 | $data->timecreated = time(); 64 | } 65 | 66 | if (empty($data->timemodified)) { 67 | $data->timemodified = time(); 68 | } 69 | 70 | if ($data->grade < 0) { 71 | // Scale found, get mapping. 72 | $data->grade = -($this->get_mappingid('scale', abs($data->grade))); 73 | } 74 | 75 | // Create the answersheet instance. 76 | $newitemid = $DB->insert_record('answersheet', $data); 77 | $this->apply_activity_instance($newitemid); 78 | } 79 | 80 | /** 81 | * Post-execution actions 82 | */ 83 | protected function after_execute() { 84 | // Add answersheet related files, no need to match by itemname (just internally handled context). 85 | $this->add_related_files('mod_answersheet', 'intro', null); 86 | $this->add_related_files('mod_answersheet', 'question', null); 87 | $this->add_related_files('mod_answersheet', 'explanations', null); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /db/access.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Capability definitions for the answersheet module 19 | * 20 | * The capabilities are loaded into the database table when the module is 21 | * installed or updated. Whenever the capability definitions are updated, 22 | * the module version number should be bumped up. 23 | * 24 | * The system has four possible values for a capability: 25 | * CAP_ALLOW, CAP_PREVENT, CAP_PROHIBIT, and inherit (not set). 26 | * 27 | * It is important that capability names are unique. The naming convention 28 | * for capabilities that are specific to modules and blocks is as follows: 29 | * [mod/block]/: 30 | * 31 | * component_name should be the same as the directory name of the mod or block. 32 | * 33 | * Core moodle capabilities are defined thus: 34 | * moodle/: 35 | * 36 | * Examples: mod/forum:viewpost 37 | * block/recent_activity:view 38 | * moodle/site:deleteuser 39 | * 40 | * The variable name for the capability definitions array is $capabilities 41 | * 42 | * @package mod_answersheet 43 | * @copyright 2015 Marina Glancy 44 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 45 | */ 46 | 47 | defined('MOODLE_INTERNAL') || die(); 48 | 49 | // Modify capabilities as needed and remove this comment. 50 | $capabilities = array( 51 | 'mod/answersheet:addinstance' => array( 52 | 'riskbitmask' => RISK_XSS, 53 | 'captype' => 'write', 54 | 'contextlevel' => CONTEXT_COURSE, 55 | 'archetypes' => array( 56 | 'editingteacher' => CAP_ALLOW, 57 | 'manager' => CAP_ALLOW 58 | ), 59 | 'clonepermissionsfrom' => 'moodle/course:manageactivities' 60 | ), 61 | 62 | 'mod/answersheet:view' => array( 63 | 'captype' => 'read', 64 | 'contextlevel' => CONTEXT_MODULE, 65 | 'archetypes' => array( 66 | 'student' => CAP_ALLOW, 67 | 'teacher' => CAP_ALLOW, 68 | 'editingteacher' => CAP_ALLOW, 69 | 'manager' => CAP_ALLOW 70 | ) 71 | ), 72 | 73 | 'mod/answersheet:submit' => array( 74 | 'riskbitmask' => RISK_SPAM, 75 | 'captype' => 'write', 76 | 'contextlevel' => CONTEXT_MODULE, 77 | 'archetypes' => array( 78 | 'student' => CAP_ALLOW 79 | ) 80 | ), 81 | 82 | 'mod/answersheet:viewreports' => array( 83 | 'riskbitmask' => RISK_PERSONAL, 84 | 'captype' => 'read', 85 | 'contextlevel' => CONTEXT_MODULE, 86 | 'archetypes' => array( 87 | 'teacher' => CAP_ALLOW, 88 | 'editingteacher' => CAP_ALLOW, 89 | 'manager' => CAP_ALLOW 90 | ) 91 | ), 92 | ); 93 | -------------------------------------------------------------------------------- /db/install.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
52 |
53 |
-------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * This is a one-line short description of the file 19 | * 20 | * You can have a rather longer description of the file as well, 21 | * if you like, and it can span multiple lines. 22 | * 23 | * @package mod_answersheet 24 | * @copyright 2015 Marina Glancy 25 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 26 | */ 27 | 28 | // Replace answersheet with the name of your module and remove this line. 29 | 30 | require_once(dirname(dirname(dirname(__FILE__))).'/config.php'); 31 | require_once(dirname(__FILE__).'/lib.php'); 32 | 33 | $id = required_param('id', PARAM_INT); // Course. 34 | 35 | $course = $DB->get_record('course', array('id' => $id), '*', MUST_EXIST); 36 | 37 | require_course_login($course); 38 | 39 | $params = array( 40 | 'context' => context_course::instance($course->id) 41 | ); 42 | $event = \mod_answersheet\event\course_module_instance_list_viewed::create($params); 43 | $event->add_record_snapshot('course', $course); 44 | $event->trigger(); 45 | 46 | $strname = get_string('modulenameplural', 'mod_answersheet'); 47 | $PAGE->set_url('/mod/answersheet/index.php', array('id' => $id)); 48 | $PAGE->navbar->add($strname); 49 | $PAGE->set_title("$course->shortname: $strname"); 50 | $PAGE->set_heading($course->fullname); 51 | $PAGE->set_pagelayout('incourse'); 52 | 53 | echo $OUTPUT->header(); 54 | echo $OUTPUT->heading($strname); 55 | 56 | if (! $answersheets = get_all_instances_in_course('answersheet', $course)) { 57 | notice(get_string('noanswersheets', 'answersheet'), new moodle_url('/course/view.php', array('id' => $course->id))); 58 | } 59 | 60 | $usesections = course_format_uses_sections($course->format); 61 | 62 | $table = new html_table(); 63 | $table->attributes['class'] = 'generaltable mod_index'; 64 | 65 | if ($usesections) { 66 | $strsectionname = get_string('sectionname', 'format_'.$course->format); 67 | $table->head = array ($strsectionname, $strname); 68 | $table->align = array ('center', 'left'); 69 | } else { 70 | $table->head = array ($strname); 71 | $table->align = array ('left'); 72 | } 73 | 74 | $modinfo = get_fast_modinfo($course); 75 | $currentsection = ''; 76 | foreach ($modinfo->instances['answersheet'] as $cm) { 77 | $row = array(); 78 | if ($usesections) { 79 | if ($cm->sectionnum !== $currentsection) { 80 | if ($cm->sectionnum) { 81 | $row[] = get_section_name($course, $cm->sectionnum); 82 | } 83 | if ($currentsection !== '') { 84 | $table->data[] = 'hr'; 85 | } 86 | $currentsection = $cm->sectionnum; 87 | } 88 | } 89 | 90 | $class = $cm->visible ? null : array('class' => 'dimmed'); 91 | 92 | $row[] = html_writer::link(new moodle_url('view.php', array('id' => $cm->id)), 93 | $cm->get_formatted_name(), $class); 94 | $table->data[] = $row; 95 | } 96 | 97 | echo html_writer::table($table); 98 | 99 | echo $OUTPUT->footer(); 100 | -------------------------------------------------------------------------------- /classes/event/attempt_base.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Base class for the mod_answersheet attempt events. 19 | * 20 | * @package mod_answersheet 21 | * @copyright 2017 Marina Glancy 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later. 23 | */ 24 | 25 | namespace mod_answersheet\event; 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | /** 29 | * Base class for the mod_answersheet attempt events. 30 | * 31 | * @property-read array $other { 32 | * Extra information about event. 33 | * 34 | * - int cmid: course module id. 35 | * - int instanceid: id of instance. 36 | * } 37 | * 38 | * @package mod_answersheet 39 | * @copyright 2017 Marina Glancy 40 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later. 41 | */ 42 | abstract class attempt_base extends \core\event\base { 43 | 44 | /** 45 | * Set basic properties for the event. 46 | */ 47 | protected function init() { 48 | global $CFG; 49 | 50 | $this->data['objecttable'] = 'answersheet_attempt'; 51 | $this->data['edulevel'] = self::LEVEL_PARTICIPATING; 52 | } 53 | 54 | /** 55 | * Creates an instance from the record from db table answersheet_attempt 56 | * 57 | * @param stdClass $completed 58 | * @param stdClass|cm_info $cm 59 | * @return self 60 | */ 61 | public static function create_from_record($attempt, $cm) { 62 | $event = self::create(array( 63 | 'objectid' => $attempt->id, 64 | 'context' => \context_module::instance($cm->id), 65 | 'other' => array( 66 | 'cmid' => $cm->id, 67 | 'instanceid' => $attempt->answersheetid, 68 | ) 69 | )); 70 | $event->add_record_snapshot('answersheet_attempt', $attempt); 71 | return $event; 72 | } 73 | 74 | /** 75 | * Returns relevant URL based on the anonymous mode of the response. 76 | * @return \moodle_url 77 | */ 78 | public function get_url() { 79 | return new \moodle_url('/mod/answersheet/view.php', array('id' => $this->other['cmid'], 80 | 'attempt' => $this->objectid)); 81 | } 82 | 83 | /** 84 | * Custom validations. 85 | * 86 | * @throws \coding_exception in case of any problems. 87 | */ 88 | protected function validate_data() { 89 | parent::validate_data(); 90 | 91 | if (!isset($this->other['cmid'])) { 92 | throw new \coding_exception('The \'cmid\' value must be set in other.'); 93 | } 94 | if (!isset($this->other['instanceid'])) { 95 | throw new \coding_exception('The \'instanceid\' value must be set in other.'); 96 | } 97 | } 98 | 99 | public static function get_objectid_mapping() { 100 | return array('db' => 'answersheet_attempt', 'restore' => 'answersheet_attempt'); 101 | } 102 | 103 | public static function get_other_mapping() { 104 | $othermapped = array(); 105 | $othermapped['cmid'] = array('db' => 'course_modules', 'restore' => 'course_module'); 106 | $othermapped['instanceid'] = array('db' => 'answersheet', 'restore' => 'answersheet'); 107 | 108 | return $othermapped; 109 | } 110 | } 111 | 112 | -------------------------------------------------------------------------------- /lang/en/answersheet.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * English strings for answersheet 19 | * 20 | * You can have a rather longer description of the file as well, 21 | * if you like, and it can span multiple lines. 22 | * 23 | * @package mod_answersheet 24 | * @copyright 2015 Marina Glancy 25 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 26 | */ 27 | 28 | defined('MOODLE_INTERNAL') || die(); 29 | 30 | $string['allattempts'] = 'All attempts'; 31 | $string['answers'] = 'Answers'; 32 | $string['answersheetfieldset'] = 'Questions details'; 33 | $string['answersheetname'] = 'Activity name'; 34 | $string['answersheet'] = 'Answersheet'; 35 | $string['answersheet:addinstance'] = 'Add instance of Answersheet'; 36 | $string['answersheet:submit'] = 'Submit answers in Answersheet'; 37 | $string['answersheet:view'] = 'View Answersheet'; 38 | $string['answersheet:viewreports'] = 'View reports in Answersheet module'; 39 | $string['answerslist'] = 'List of answers'; 40 | $string['answerslist_help'] = 'Comma-separated list of correct answers. Newlines and comments will be ignored'; 41 | $string['attemptsummary'] = 'Summary of attempts'; 42 | $string['completedattempts'] = 'Completed attempts'; 43 | $string['continueattempt'] = 'Continue attempt'; 44 | $string['correct'] = 'Correct'; 45 | $string['completed'] = 'Completed'; 46 | $string['completionpass'] = 'Require passing grade'; 47 | $string['completionpass_help'] = 'If enabled, this activity is considered complete when the student receives a passing grade, with the pass grade set in the gradebook.'; 48 | $string['completionsubmit'] = 'View as completed if the answers are submitted'; 49 | $string['currentattempts'] = 'Attempts in progress'; 50 | $string['error_questionscount'] = 'Enter the number of questions'; 51 | $string['error_questionsoptions'] = 'Specify between 2 and {$a} unique options'; 52 | $string['error_answerslist_mismatch'] = 'Number of answers in this list must match number of questions'; 53 | $string['error_answerslist_invalid'] = 'List of correct answers includes invalid value(s): {$a}'; 54 | $string['eventattemptcreated'] = 'Attempt started'; 55 | $string['eventattemptsaved'] = 'Attempt saved'; 56 | $string['eventattemptsubmitted'] = 'Attempt submitted'; 57 | $string['explanations'] = 'Explanations'; 58 | $string['explanations_help'] = 'Explanations will be shown after the attempt if at least one answer was wrong'; 59 | $string['finish'] = 'Submit and finish'; 60 | $string['lastattempts'] = 'Last completed attempts'; 61 | $string['modulename'] = 'Answersheet'; 62 | $string['modulenameplural'] = 'Answersheets'; 63 | $string['modulename_help'] = 'Use to record students test answers on the answer sheet'; 64 | $string['pluginadministration'] = 'Answersheet administration'; 65 | $string['pluginname'] = 'Answersheet'; 66 | $string['question'] = 'Question text'; 67 | $string['question_help'] = 'Text to display before the answer sheet'; 68 | $string['questionscount'] = 'Number of questions'; 69 | $string['questionscount_help'] = 'Number of questions in the questionnaire and answer sheet'; 70 | $string['questionsoptions'] = 'Questions options'; 71 | $string['questionsoptions_help'] = 'Comma-separated list of the possible answers for each question'; 72 | $string['saveonly'] = 'Save to continue later'; 73 | $string['showallusers'] = 'Show all users'; 74 | $string['started'] = 'Started'; 75 | $string['startnerattempt'] = 'Start new attempt'; 76 | $string['yourgrade'] = 'Your grade is {$a->grade} ({$a->percentage}%)'; 77 | $string['wrong'] = 'Wrong'; 78 | -------------------------------------------------------------------------------- /classes/attempt_form.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * File contains mod_answersheet_attempt_form class 19 | * 20 | * @package mod_answersheet 21 | * @copyright 2015 Marina Glancy 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die(); 26 | 27 | require_once($CFG->libdir . '/formslib.php'); 28 | 29 | /** 30 | * mod_answersheet_attempt_form class 31 | * 32 | * @package mod_answersheet 33 | * @copyright 2015 Marina Glancy 34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class mod_answersheet_attempt_form extends moodleform { 37 | /** 38 | * Form definition 39 | */ 40 | protected function definition() { 41 | global $OUTPUT; 42 | $mform = $this->_form; 43 | $attempt = $this->_customdata->attempt; 44 | $answersheet = $attempt->answersheet; 45 | $questionscount = $answersheet->questionscount; 46 | $freeze = $attempt->attempt->timecompleted ? true : false; 47 | $questionsoptions = mod_answersheet_attempt::parse_options($answersheet->questionsoptions); 48 | if ($freeze) { 49 | $correctanswers = mod_answersheet_attempt::parse_answerslist($answersheet->answerslist); 50 | $answers = preg_split('/,/', $attempt->attempt->answers); 51 | } 52 | 53 | $mform->addElement('hidden', 'id', $attempt->cm->id); 54 | $mform->setType('id', PARAM_INT); 55 | $mform->addElement('hidden', 'attempt', $attempt->id); 56 | $mform->setType('attempt', PARAM_NOTAGS); 57 | 58 | for ($i=0; $i<$questionscount; $i++) { 59 | $group = array(); 60 | $answer = isset($answers[$i]) ? $answers[$i] : ''; 61 | if ($freeze) { 62 | if ($answer === $correctanswers[$i]) { 63 | $icon = 'i/grade_correct'; 64 | $alt = get_string('correct', 'answersheet'); 65 | } else { 66 | $icon = 'i/grade_incorrect'; 67 | $alt = get_string('wrong', 'answersheet'); 68 | } 69 | $group[] = &$mform->createElement('static', $i.'-x', '', 70 | $OUTPUT->pix_icon($icon, $alt)); 71 | } 72 | for ($j=0; $jcreateElement('static', $i.'-'.$j, '', $sel ? $questionsoptions[$j] : ''); 76 | } else { 77 | $group[] = &$mform->createElement('radio', $i, '', $questionsoptions[$j], $questionsoptions[$j]); 78 | } 79 | } 80 | $mform->addElement('group', 'q', $i + 1, $group, '    '); 81 | } 82 | if (!$freeze) { 83 | $this->add_abuttons(); 84 | } 85 | } 86 | 87 | /** 88 | * Adds buttons to the form 89 | */ 90 | function add_abuttons(){ 91 | $mform =& $this->_form; 92 | $buttonarray=array(); 93 | $buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('finish', 'answersheet')); 94 | $buttonarray[] = &$mform->createElement('submit', 'saveonly', get_string('saveonly', 'answersheet')); 95 | //$buttonarray[] = &$mform->createElement('cancel'); 96 | $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false); 97 | $mform->closeHeaderBefore('buttonar'); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /backup/moodle2/restore_answersheet_activity_task.class.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Provides the restore activity task class 19 | * 20 | * @package mod_answersheet 21 | * @category backup 22 | * @copyright 2015 Marina Glancy 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | require_once($CFG->dirroot . '/mod/answersheet/backup/moodle2/restore_answersheet_stepslib.php'); 29 | 30 | /** 31 | * Restore task for the answersheet activity module 32 | * 33 | * Provides all the settings and steps to perform complete restore of the activity. 34 | * 35 | * @package mod_answersheet 36 | * @category backup 37 | * @copyright 2015 Marina Glancy 38 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 39 | */ 40 | class restore_answersheet_activity_task extends restore_activity_task { 41 | 42 | /** 43 | * Define (add) particular settings this activity can have 44 | */ 45 | protected function define_my_settings() { 46 | // No particular settings for this activity. 47 | } 48 | 49 | /** 50 | * Define (add) particular steps this activity can have 51 | */ 52 | protected function define_my_steps() { 53 | // We have just one structure step here. 54 | $this->add_step(new restore_answersheet_activity_structure_step('answersheet_structure', 'answersheet.xml')); 55 | } 56 | 57 | /** 58 | * Define the contents in the activity that must be 59 | * processed by the link decoder 60 | */ 61 | static public function define_decode_contents() { 62 | $contents = array(); 63 | 64 | $contents[] = new restore_decode_content('answersheet', array('intro'), 'answersheet'); 65 | 66 | return $contents; 67 | } 68 | 69 | /** 70 | * Define the decoding rules for links belonging 71 | * to the activity to be executed by the link decoder 72 | */ 73 | static public function define_decode_rules() { 74 | $rules = array(); 75 | 76 | $rules[] = new restore_decode_rule('NEWMODULEVIEWBYID', '/mod/answersheet/view.php?id=$1', 'course_module'); 77 | $rules[] = new restore_decode_rule('NEWMODULEINDEX', '/mod/answersheet/index.php?id=$1', 'course'); 78 | 79 | return $rules; 80 | 81 | } 82 | 83 | /** 84 | * Define the restore log rules that will be applied 85 | * by the {@link restore_logs_processor} when restoring 86 | * answersheet logs. It must return one array 87 | * of {@link restore_log_rule} objects 88 | */ 89 | static public function define_restore_log_rules() { 90 | $rules = array(); 91 | 92 | $rules[] = new restore_log_rule('answersheet', 'add', 'view.php?id={course_module}', '{answersheet}'); 93 | $rules[] = new restore_log_rule('answersheet', 'update', 'view.php?id={course_module}', '{answersheet}'); 94 | $rules[] = new restore_log_rule('answersheet', 'view', 'view.php?id={course_module}', '{answersheet}'); 95 | 96 | return $rules; 97 | } 98 | 99 | /** 100 | * Define the restore log rules that will be applied 101 | * by the {@link restore_logs_processor} when restoring 102 | * course logs. It must return one array 103 | * of {@link restore_log_rule} objects 104 | * 105 | * Note this rules are applied when restoring course logs 106 | * by the restore final task, but are defined here at 107 | * activity level. All them are rules not linked to any module instance (cmid = 0) 108 | */ 109 | static public function define_restore_log_rules_for_course() { 110 | $rules = array(); 111 | 112 | $rules[] = new restore_log_rule('answersheet', 'view all', 'index.php?id={course}', null); 113 | 114 | return $rules; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /db/upgrade.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * This file keeps track of upgrades to the answersheet module 19 | * 20 | * Sometimes, changes between versions involve alterations to database 21 | * structures and other major things that may break installations. The upgrade 22 | * function in this file will attempt to perform all the necessary actions to 23 | * upgrade your older installation to the current version. If there's something 24 | * it cannot do itself, it will tell you what you need to do. The commands in 25 | * here will all be database-neutral, using the functions defined in DLL libraries. 26 | * 27 | * @package mod_answersheet 28 | * @copyright 2015 Marina Glancy 29 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 30 | */ 31 | 32 | defined('MOODLE_INTERNAL') || die(); 33 | 34 | /** 35 | * Execute answersheet upgrade from the given old version 36 | * 37 | * @param int $oldversion 38 | * @return bool 39 | */ 40 | function xmldb_answersheet_upgrade($oldversion) { 41 | global $DB; 42 | 43 | $dbman = $DB->get_manager(); // Loads ddl manager and xmldb classes. 44 | 45 | if ($oldversion < 2015031504) { 46 | 47 | // Define field islast to be added to answersheet_attempt. 48 | $table = new xmldb_table('answersheet_attempt'); 49 | $field = new xmldb_field('islast', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0', 'grade'); 50 | 51 | // Conditionally launch add field islast. 52 | if (!$dbman->field_exists($table, $field)) { 53 | $dbman->add_field($table, $field); 54 | 55 | } 56 | 57 | $records = $DB->get_records_select('answersheet_attempt', 58 | 'timecompleted IS NOT NULL', array(), 59 | 'userid, timestarted DESC, id DESC', 60 | 'id, answersheetid, userid'); 61 | $processed = array(); 62 | foreach ($records as $record) { 63 | if (!in_array($record->answersheetid.'-'.$record->userid, $processed)) { 64 | $DB->update_record('answersheet_attempt', array('id' => $record->id, 'islast' => 1)); 65 | $processed[] = $record->answersheetid.'-'.$record->userid; 66 | } 67 | } 68 | 69 | // Answersheet savepoint reached. 70 | upgrade_mod_savepoint(true, 2015031504, 'answersheet'); 71 | } 72 | 73 | if ($oldversion < 2017080600) { 74 | 75 | // Define field explanations to be added to answersheet. 76 | $table = new xmldb_table('answersheet'); 77 | $field = new xmldb_field('explanations', XMLDB_TYPE_TEXT, null, null, null, null, null, 'answerslist'); 78 | 79 | // Conditionally launch add field explanations. 80 | if (!$dbman->field_exists($table, $field)) { 81 | $dbman->add_field($table, $field); 82 | } 83 | 84 | // Answersheet savepoint reached. 85 | upgrade_mod_savepoint(true, 2017080600, 'answersheet'); 86 | } 87 | 88 | if ($oldversion < 2017080601) { 89 | 90 | // Define field explanationsformat to be added to answersheet. 91 | $table = new xmldb_table('answersheet'); 92 | $field = new xmldb_field('explanationsformat', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'explanations'); 93 | 94 | // Conditionally launch add field explanationsformat. 95 | if (!$dbman->field_exists($table, $field)) { 96 | $dbman->add_field($table, $field); 97 | } 98 | 99 | // Answersheet savepoint reached. 100 | upgrade_mod_savepoint(true, 2017080601, 'answersheet'); 101 | } 102 | 103 | if ($oldversion < 2018042100) { 104 | 105 | // Define field question to be added to answersheet. 106 | $table = new xmldb_table('answersheet'); 107 | $field = new xmldb_field('question', XMLDB_TYPE_TEXT, null, null, null, null, null, 'completionpass'); 108 | 109 | // Conditionally launch add field question. 110 | if (!$dbman->field_exists($table, $field)) { 111 | $dbman->add_field($table, $field); 112 | } 113 | 114 | // Define field questionformat to be added to answersheet. 115 | $field = new xmldb_field('questionformat', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'question'); 116 | 117 | // Conditionally launch add field questionformat. 118 | if (!$dbman->field_exists($table, $field)) { 119 | $dbman->add_field($table, $field); 120 | } 121 | 122 | // Answersheet savepoint reached. 123 | upgrade_mod_savepoint(true, 2018042100, 'answersheet'); 124 | } 125 | 126 | return true; 127 | } 128 | -------------------------------------------------------------------------------- /view.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Prints a particular instance of answersheet 19 | * 20 | * You can have a rather longer description of the file as well, 21 | * if you like, and it can span multiple lines. 22 | * 23 | * @package mod_answersheet 24 | * @copyright 2015 Marina Glancy 25 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 26 | */ 27 | 28 | // Replace answersheet with the name of your module and remove this line. 29 | 30 | require_once(dirname(dirname(dirname(__FILE__))).'/config.php'); 31 | require_once(dirname(__FILE__).'/lib.php'); 32 | require_once($CFG->libdir.'/completionlib.php'); 33 | 34 | $id = optional_param('id', 0, PARAM_INT); // Course_module ID. 35 | if ($id) { 36 | $cm = get_coursemodule_from_id('answersheet', $id, 0, false, MUST_EXIST); 37 | $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST); 38 | $answersheet = $DB->get_record('answersheet', array('id' => $cm->instance), '*', MUST_EXIST); 39 | } else { 40 | $a = required_param('a', PARAM_INT); // Answersheet ID. 41 | $answersheet = $DB->get_record('answersheet', array('id' => $a), '*', MUST_EXIST); 42 | $course = $DB->get_record('course', array('id' => $answersheet->course), '*', MUST_EXIST); 43 | $cm = get_coursemodule_from_instance('answersheet', $answersheet->id, $course->id, false, MUST_EXIST); 44 | $id = $cm->id; 45 | } 46 | 47 | require_login($course, true, $cm); 48 | $PAGE->set_activity_record($answersheet); 49 | 50 | $attempt = optional_param('attempt', null, PARAM_NOTAGS); 51 | 52 | if (!$attempt) { 53 | $event = \mod_answersheet\event\course_module_viewed::create_from_cm($PAGE->cm, $answersheet, $PAGE->course)->trigger(); 54 | } 55 | 56 | $PAGE->set_url('/mod/answersheet/view.php', array('id' => $cm->id)); 57 | $PAGE->set_title(format_string($answersheet->name)); 58 | $PAGE->set_heading(format_string($course->fullname)); 59 | 60 | if (($attempt === 'new') && mod_answersheet_attempt::can_start($PAGE->cm, $answersheet)) { 61 | $attemptobj = mod_answersheet_attempt::start($PAGE->cm, $answersheet); 62 | $url = new moodle_url('/mod/answersheet/view.php', array('id' => $id, 'attempt' => $attemptobj->id)); 63 | redirect($url); 64 | } 65 | 66 | // Update 'viewed' state if required by completion system. 67 | $completion = new completion_info($course); 68 | $completion->set_module_viewed($PAGE->cm); 69 | 70 | $contents = ''; 71 | 72 | if (((int)$attempt) > 0) { 73 | // Display individual attempt. 74 | $attemptobj = mod_answersheet_attempt::get((int)$attempt, $PAGE->cm, $answersheet); 75 | if ($attemptobj && $attemptobj->can_view()) { 76 | $contents .= $attemptobj->display(); 77 | } 78 | } else { 79 | // Display current incompleted attampt. 80 | if ($attempt = mod_answersheet_attempt::find_incomplete_attempt($PAGE->cm, $answersheet)) { 81 | $url = new moodle_url('/mod/answersheet/view.php', array('id' => $id, 'attempt' => $attempt->id)); 82 | $contents .= html_writer::tag('div', html_writer::link($url, 83 | get_string('continueattempt', 'answersheet')), 84 | array('class' => 'continueattempt')); 85 | } 86 | 87 | // Display link to start a new attempt. 88 | if (mod_answersheet_attempt::can_start($PAGE->cm, $answersheet)) { 89 | $url = new moodle_url('/mod/answersheet/view.php', array('id' => $id, 'attempt' => 'new')); 90 | $contents .= html_writer::tag('div', html_writer::link($url, 91 | get_string('startnerattempt', 'answersheet')), 92 | array('class' => 'startattempt')); 93 | } 94 | 95 | if (has_capability('mod/answersheet:viewreports', $PAGE->context)) { 96 | // View all attempts. 97 | $userid = optional_param('userid', 0, PARAM_INT); 98 | $displaytype = optional_param('displaytype', 'completedattempts', PARAM_ALPHA); 99 | $contents .= mod_answersheet_report::display($cm, $answersheet, $userid, $displaytype); 100 | } else { 101 | // View own past attempts. 102 | $contents .= mod_answersheet_report::display($cm, $answersheet, $USER->id, 'all'); 103 | } 104 | } 105 | 106 | // Output starts here. 107 | echo $OUTPUT->header(); 108 | 109 | // Conditions to show the intro can change to look for own settings or whatever. 110 | if ($answersheet->intro) { 111 | echo $OUTPUT->box(format_module_intro('answersheet', $answersheet, $cm->id), 112 | 'generalbox mod_introbox', 'answersheetintro'); 113 | } 114 | 115 | echo $contents; 116 | 117 | // Finish the page. 118 | echo $OUTPUT->footer(); 119 | -------------------------------------------------------------------------------- /classes/report.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * File contains mod_answersheet_report class 19 | * 20 | * @package mod_answersheet 21 | * @copyright 2015 Marina Glancy 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die(); 26 | 27 | /** 28 | * mod_answersheet_report class 29 | * 30 | * @package mod_answersheet 31 | * @copyright 2015 Marina Glancy 32 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 | */ 34 | class mod_answersheet_report { 35 | 36 | /** 37 | * 38 | * @param cm_info $cm 39 | * @param stdClass $answersheet 40 | * @param int $userid 41 | * @return string 42 | */ 43 | public static function display($cm, $answersheet, $userid = 0, $displaytype = 'completedonly') { 44 | global $OUTPUT, $USER; 45 | $attempts = self::get_all_attempts($cm, $answersheet, $userid, $displaytype); 46 | if (!$attempts && $displaytype === 'all') { 47 | return ''; 48 | } 49 | $baseurl = new moodle_url('/mod/answersheet/view.php', array('id' => $cm->id, 'displaytype' => $displaytype)); 50 | $canviewallusers = has_capability('mod/answersheet:viewreports', context_module::instance($cm->id)); 51 | $table = new html_table(); 52 | $alluserslink = ''; 53 | if ($userid && $canviewallusers) { 54 | $alluserslink = '
'.html_writer::link($baseurl, get_string('showallusers', 'answersheet')); 55 | } 56 | $table->head = array( 57 | get_string('user') . $alluserslink, 58 | get_string('started', 'answersheet'), 59 | get_string('grade'), 60 | get_string('percentage', 'grades') 61 | ); 62 | if ($userid && !$canviewallusers) { 63 | array_shift($table->head); 64 | } 65 | foreach ($attempts as $attempt) { 66 | $attempturl = new moodle_url($baseurl, array('attempt' => $attempt->id)); 67 | $userurl = new moodle_url($baseurl, array('userid' => $attempt->userid, 'displaytype' => 'allattempts')); 68 | $data = array(html_writer::link($userurl, fullname($attempt)), 69 | html_writer::link($attempturl, userdate($attempt->timestarted, get_string('strftimedatetime', 'core_langconfig'))), 70 | $attempt->timecompleted ? mod_answersheet_attempt::convert_grade($answersheet, $attempt->grade, true) : '', 71 | $attempt->timecompleted ? (round($attempt->grade * 100, 2).'%') : ''); 72 | if ($userid && !$canviewallusers) { 73 | array_shift($data); 74 | } 75 | if ($attempt->islast) { 76 | foreach ($data as $i => $v) { 77 | $data[$i] = html_writer::tag('strong', $v); 78 | } 79 | } 80 | $table->data[] = $data; 81 | } 82 | return self::get_tabs($cm, $userid, $displaytype). 83 | html_writer::table($table); 84 | } 85 | 86 | protected static function get_tabs($cm, $userid, $displaytype) { 87 | global $OUTPUT; 88 | 89 | if ($displaytype === 'all') { 90 | return $OUTPUT->heading(get_string('attemptsummary', 'answersheet'), 3); 91 | } 92 | 93 | $baseurl = new moodle_url('/mod/answersheet/view.php', array('id' => $cm->id)); 94 | if ($userid) { 95 | $baseurl->param('userid', $userid); 96 | } 97 | $tabs = array(); 98 | 99 | $completedurl = new moodle_url($baseurl); 100 | $completedlabel = get_string('completedattempts', 'answersheet'); 101 | $tabs[] = new tabobject('completedattempts', $completedurl, $completedlabel, $completedlabel, false); 102 | 103 | $lasturl = new moodle_url($baseurl, array('displaytype' => 'lastattempts')); 104 | $lastlabel = get_string('lastattempts', 'answersheet'); 105 | $tabs[] = new tabobject('lastattempts', $lasturl, $lastlabel, $lastlabel, false); 106 | 107 | $allurl = new moodle_url($baseurl, array('displaytype' => 'allattempts')); 108 | $alllabel = get_string('allattempts', 'answersheet'); 109 | $tabs[] = new tabobject('allattempts', $allurl, $alllabel, $alllabel, false); 110 | 111 | $currenturl = new moodle_url($baseurl, array('displaytype' => 'currentattempts')); 112 | $currentlabel = get_string('currentattempts', 'answersheet'); 113 | $tabs[] = new tabobject('currentattempts', $currenturl, $currentlabel, $currentlabel, false); 114 | 115 | return print_tabs(array($tabs), $displaytype, null, null, true); 116 | 117 | } 118 | 119 | /** 120 | * Retrieves all attempts for the activity or for the user 121 | * 122 | * @param cm_info $cm 123 | * @param stdClass $answersheet 124 | * @param int $userid 125 | * @param bool $completedonly 126 | * @return array 127 | */ 128 | protected static function get_all_attempts($cm, $answersheet, $userid = 0, $displaytype = 'completedattempts') { 129 | global $DB; 130 | $namefields = get_all_user_name_fields(true, 'u'); 131 | $records = $DB->get_records_sql( 132 | 'SELECT a.id, a.answers, a.timestarted, a.timecompleted, '. 133 | 'a.grade, a.userid, a.islast, '.$namefields.' '. 134 | 'FROM {answersheet_attempt} a LEFT JOIN {user} u ON u.id = a.userid '. 135 | 'WHERE answersheetid = :aid '. 136 | ($userid ? 'AND a.userid = :userid ' : ''). 137 | (($displaytype == 'completedattempts') ? 'AND timecompleted IS NOT NULL ' : ''). 138 | (($displaytype == 'lastattempts') ? 'AND islast = 1 ' : ''). 139 | (($displaytype == 'currentattempts') ? 'AND timecompleted IS NULL ' : ''). 140 | 'ORDER BY timestarted, id', 141 | array('aid' => $answersheet->id, 'userid' => $userid)); 142 | $rv = array(); 143 | foreach ($records as $record) { 144 | $rv[] = $record; 145 | } 146 | return $rv; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /mod_form.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * The main answersheet configuration form 19 | * 20 | * It uses the standard core Moodle formslib. For more info about them, please 21 | * visit: http://docs.moodle.org/en/Development:lib/formslib.php 22 | * 23 | * @package mod_answersheet 24 | * @copyright 2015 Marina Glancy 25 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 26 | */ 27 | 28 | defined('MOODLE_INTERNAL') || die(); 29 | 30 | require_once($CFG->dirroot.'/course/moodleform_mod.php'); 31 | 32 | /** 33 | * Module instance settings form 34 | * 35 | * @package mod_answersheet 36 | * @copyright 2015 Marina Glancy 37 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 38 | */ 39 | class mod_answersheet_mod_form extends moodleform_mod { 40 | 41 | /** 42 | * Defines forms elements 43 | */ 44 | public function definition() { 45 | global $CFG; 46 | 47 | $mform = $this->_form; 48 | 49 | // Adding the "general" fieldset, where all the common settings are showed. 50 | $mform->addElement('header', 'general', get_string('general', 'form')); 51 | 52 | // Adding the standard "name" field. 53 | $mform->addElement('text', 'name', get_string('answersheetname', 'answersheet'), array('size' => '64')); 54 | if (!empty($CFG->formatstringstriptags)) { 55 | $mform->setType('name', PARAM_TEXT); 56 | } else { 57 | $mform->setType('name', PARAM_CLEAN); 58 | } 59 | $mform->addRule('name', null, 'required', null, 'client'); 60 | $mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); 61 | 62 | // Adding the standard "intro" and "introformat" fields. 63 | $this->standard_intro_elements(); 64 | 65 | $mform->addElement('header', 'answersheetfieldset', get_string('answersheetfieldset', 'answersheet')); 66 | $mform->setExpanded('answersheetfieldset', true); 67 | 68 | $mform->addElement('editor', 'question_editor', get_string('question', 'answersheet'), 69 | array('rows' => 10), array('maxfiles' => EDITOR_UNLIMITED_FILES, 'context' => $this->context)); 70 | $mform->addHelpButton('question_editor', 'question', 'answersheet'); 71 | 72 | $mform->addElement('text', 'questionscount', get_string('questionscount', 'answersheet')); 73 | $mform->setType('questionscount', PARAM_INT); 74 | $mform->addHelpButton('questionscount', 'questionscount', 'answersheet'); 75 | 76 | $mform->addElement('text', 'questionsoptions', get_string('questionsoptions', 'answersheet')); 77 | $mform->setType('questionsoptions', PARAM_NOTAGS); 78 | $mform->setDefault('questionsoptions', 'A,B,C,D'); 79 | $mform->addHelpButton('questionsoptions', 'questionsoptions', 'answersheet'); 80 | 81 | $mform->addElement('textarea', 'answerslist', get_string('answerslist', 'answersheet')); 82 | $mform->setType('answerslist', PARAM_NOTAGS); 83 | $mform->addHelpButton('answerslist', 'answerslist', 'answersheet'); 84 | 85 | $mform->addElement('editor', 'explanations_editor', get_string('explanations', 'answersheet'), 86 | array('rows' => 10), array('maxfiles' => EDITOR_UNLIMITED_FILES, 'context' => $this->context)); 87 | $mform->addHelpButton('explanations_editor', 'explanations', 'answersheet'); 88 | 89 | // Add standard grading elements. 90 | $this->standard_grading_coursemodule_elements(); 91 | 92 | // Add standard elements, common to all modules. 93 | $this->standard_coursemodule_elements(); 94 | 95 | // Add standard buttons, common to all modules. 96 | $this->add_action_buttons(); 97 | } 98 | 99 | public function data_preprocessing(&$default_values) { 100 | 101 | $context = $this->context; 102 | if ($this->context && $this->context->contextlevel != CONTEXT_MODULE) { 103 | $context = null; 104 | } 105 | $data = file_prepare_standard_editor((object)$default_values, 'explanations', 106 | array('maxfiles' => EDITOR_UNLIMITED_FILES, 'context' => $context), 107 | $context, 108 | 'mod_answersheet', 'explanations', 0); 109 | $data = file_prepare_standard_editor($data, 'question', 110 | array('maxfiles' => EDITOR_UNLIMITED_FILES, 'context' => $context), 111 | $context, 112 | 'mod_answersheet', 'question', 0); 113 | $default_values = (array)$data; 114 | 115 | } 116 | 117 | /** 118 | * Display module-specific activity completion rules. 119 | * Part of the API defined by moodleform_mod 120 | * @return array Array of string IDs of added items, empty array if none 121 | */ 122 | public function add_completion_rules() { 123 | $mform = $this->_form; 124 | 125 | $mform->addElement('advcheckbox', 'completionsubmit', null, 126 | get_string('completionsubmit', 'answersheet')); 127 | 128 | $mform->addElement('advcheckbox', 'completionpass', null, get_string('completionpass', 'answersheet')); 129 | $mform->addHelpButton('completionpass', 'completionpass', 'answersheet'); 130 | 131 | return array('completionsubmit', 'completionpass'); 132 | } 133 | 134 | /** 135 | * Called during validation. Indicates whether a module-specific completion rule is selected. 136 | * 137 | * @param array $data Input data (not yet validated) 138 | * @return bool True if one or more rules is enabled, false if none are. 139 | */ 140 | public function completion_rule_enabled($data) { 141 | return !empty($data['completionpass']) || !empty($data['completionsubmit']); 142 | } 143 | 144 | /** 145 | * Form validation 146 | * 147 | * @param array $data 148 | * @param array $files 149 | * @return array 150 | */ 151 | public function validation($data, $files) { 152 | $errors = parent::validation($data, $files); 153 | 154 | if (empty($data['questionscount']) || $data['questionscount'] <= 0) { 155 | $errors['questionscount'] = get_string('error_questionscount', 'answersheet'); 156 | } 157 | 158 | $options = mod_answersheet_attempt::parse_options($data['questionsoptions']); 159 | if (empty($options) || count($options) != count(array_unique($options)) || 160 | count($options) < 2 || count($options) > 20) { 161 | $errors['questionsoptions'] = get_string('error_questionsoptions', 'answersheet', 20); 162 | } 163 | 164 | if (empty($errors['questionscount']) && empty($errors['questionsoptions'])) { 165 | $answers = mod_answersheet_attempt::parse_answerslist($data['answerslist']); 166 | if (count($answers) != $data['questionscount']) { 167 | $errors['answerslist'] = get_string('error_answerslist_mismatch', 'answersheet'); 168 | } else { 169 | $extraanswers = array_diff(array_unique($answers), $options); 170 | if (!empty($extraanswers)) { 171 | $errors['answerslist'] = get_string('error_answerslist_invalid', 'answersheet', 172 | join(',', $extraanswers)); 173 | } 174 | } 175 | } 176 | return $errors; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /classes/attempt.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * File contains mod_answersheet_attempt class 19 | * 20 | * @package mod_answersheet 21 | * @copyright 2015 Marina Glancy 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die(); 26 | 27 | /** 28 | * mod_answersheet_attempt class allows to deal with one attempt. 29 | * 30 | * @package mod_answersheet 31 | * @copyright 2015 Marina Glancy 32 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 | */ 34 | class mod_answersheet_attempt { 35 | 36 | /** @var stdClass */ 37 | protected $attempt; 38 | /** @var cm_info */ 39 | protected $cm; 40 | /** @var stdClass */ 41 | protected $answersheet; 42 | 43 | /** 44 | * Constructor 45 | * 46 | * @param stdClass $record 47 | * @param cm_info $cm 48 | * @param stdClass $answersheet 49 | */ 50 | protected function __construct($record, $cm, $answersheet) { 51 | $this->attempt = $record; 52 | $this->cm = $cm; 53 | $this->answersheet = $answersheet; 54 | } 55 | 56 | /** 57 | * Magic getter 58 | * 59 | * @param string $name 60 | * @return mixed 61 | */ 62 | public function __get($name) { 63 | if ($name === 'answersheet' || $name === 'cm' || $name === 'attempt') { 64 | return $this->$name; 65 | } else if ($name === 'id') { 66 | return $this->attempt->id; 67 | } 68 | } 69 | 70 | /** 71 | * Retrieves incomplete attempt for the current user 72 | * 73 | * @param cm_info $cm 74 | * @param stdClass $answersheet 75 | * @return mod_answersheet_attempt|null 76 | */ 77 | public static function find_incomplete_attempt($cm, $answersheet) { 78 | global $DB, $USER; 79 | if ($record = $DB->get_record_select('answersheet_attempt', 80 | 'answersheetid = :aid AND userid = :userid AND timecompleted IS NULL', 81 | array('aid' => $answersheet->id, 'userid' => $USER->id))) { 82 | return new self($record, $cm, $answersheet); 83 | } 84 | return null; 85 | } 86 | 87 | /** 88 | * Does this user have completed attempts 89 | * 90 | * @param int $cmid 91 | * @return bool 92 | */ 93 | public static function has_completed_attempts($instanceid) { 94 | global $DB, $USER; 95 | if (!isloggedin() || isguestuser()) { 96 | return false; 97 | } 98 | return $DB->record_exists_select('answersheet_attempt', 99 | 'answersheetid = :aid AND userid = :userid AND timecompleted IS NOT NULL', 100 | array('aid' => $instanceid, 'userid' => $USER->id)); 101 | } 102 | 103 | /** 104 | * Checks if current user is able to start a new attempt 105 | * 106 | * @param cm_info $cm 107 | * @param stdClass $answersheet 108 | * @return bool 109 | */ 110 | public static function can_start($cm, $answersheet) { 111 | if (!has_capability('mod/answersheet:submit', 112 | context_module::instance($cm->id), null/*, false*/)) { 113 | return false; 114 | } 115 | return self::find_incomplete_attempt($cm, $answersheet) ? false : true; 116 | } 117 | 118 | /** 119 | * Starts a new attempt 120 | * 121 | * @param cm_info $cm 122 | * @param stdClass $answersheet 123 | * @return mod_answersheet_attempt 124 | */ 125 | public static function start($cm, $answersheet) { 126 | global $DB, $USER; 127 | $id = $DB->insert_record('answersheet_attempt', array( 128 | 'userid' => $USER->id, 129 | 'answersheetid' => $answersheet->id, 130 | 'timestarted' => time() 131 | )); 132 | $attempt = self::get($id, $cm, $answersheet); 133 | \mod_answersheet\event\attempt_created::create_from_record($attempt->attempt, $cm)->trigger(); 134 | return $attempt; 135 | } 136 | 137 | /** 138 | * Retrieves the attempt by id 139 | * 140 | * @param int $id 141 | * @param cm_info $cm 142 | * @param stdClass $answersheet 143 | * @return mod_answersheet_attempt|null 144 | */ 145 | public static function get($id, $cm, $answersheet) { 146 | global $DB; 147 | if ($record = $DB->get_record('answersheet_attempt', array('id' => $id))) { 148 | return new self($record, $cm, $answersheet); 149 | } 150 | return null; 151 | } 152 | 153 | /** 154 | * Checks if the current user is able to view this attempt 155 | * 156 | * @return bool 157 | */ 158 | public function can_view() { 159 | global $USER; 160 | return $this->attempt->userid === $USER->id || 161 | has_capability('mod/answersheet:viewreports', 162 | context_module::instance($this->cm->id)); 163 | } 164 | 165 | /** 166 | * Converts the grade to the scale string 167 | * 168 | * @param int $scaleid 169 | * @param float $grade 170 | * @return string 171 | */ 172 | public static function get_scale($scaleid, $grade){ 173 | global $DB; 174 | static $scales = array(); 175 | if (!array_key_exists($scaleid, $scales)) { 176 | if ($scale = $DB->get_record('scale', array('id' => $scaleid))) { 177 | $scales[$scaleid] = make_menu_from_list($scale->scale); 178 | } else { 179 | $scales[$scaleid] = null; 180 | } 181 | } 182 | if ($scales[$scaleid]) { 183 | return $scales[$scaleid][$grade]; 184 | } else { 185 | return '-'; 186 | } 187 | } 188 | 189 | /** 190 | * Get the primary grade item for this module instance. 191 | * 192 | * @return stdClass The grade_item record 193 | */ 194 | public static function get_grade_item($course, $instanceid) { 195 | global $CFG; 196 | require_once($CFG->libdir.'/gradelib.php'); 197 | static $items = array(); 198 | if (!array_key_exists($instanceid, $items)) { 199 | $params = array('itemtype' => 'mod', 200 | 'itemmodule' => 'answersheet', 201 | 'iteminstance' => $instanceid, 202 | 'courseid' => $course, 203 | 'itemnumber' => 0); 204 | $items[$instanceid] = grade_item::fetch($params); 205 | } 206 | return $items[$instanceid]; 207 | } 208 | 209 | /** 210 | * Converts the grade from the percentage to the current gradeitem's format 211 | * 212 | * @param stdClass $answersheet 213 | * @param float $floatgrade 214 | * @param bool $human 215 | * @return string|float|int 216 | */ 217 | public static function convert_grade($answersheet, $floatgrade, $human = false) { 218 | if ($answersheet->grade > 0) { 219 | $rv = $floatgrade * $answersheet->grade; 220 | if ($human) { 221 | $rv = round($rv, 2) .' / '.$answersheet->grade; 222 | } 223 | } else { 224 | $gradeitem = self::get_grade_item($answersheet->course, $answersheet->id); 225 | $rv = round($floatgrade * 226 | ($gradeitem->grademax - $gradeitem->grademin) + $gradeitem->grademin, 0); 227 | if ($human) { 228 | $rv = self::get_scale(-$answersheet->grade, $rv); 229 | } 230 | } 231 | return $rv; 232 | } 233 | 234 | /** 235 | * Helper method for updating grades 236 | * 237 | * @param stdClass $answersheet 238 | * @param int $userid 239 | * @return array 240 | */ 241 | public static function get_last_completed_attempt_grade($answersheet, $userid) { 242 | global $DB; 243 | if (!$answersheet->grade) { 244 | // Not graded. 245 | return null; 246 | } 247 | $rs = $DB->get_recordset_sql('SELECT userid AS id, userid, grade AS rawgrade '. 248 | 'FROM {answersheet_attempt} '. 249 | 'WHERE answersheetid=:aid AND timecompleted IS NOT NULL '. 250 | ($userid ? ' AND userid=:userid ' : ''). 251 | 'ORDER BY userid, timestarted DESC, id DESC', 252 | array('userid' => $userid, 'aid' => $answersheet->id)); 253 | $rv = array(); 254 | foreach ($rs as $record) { 255 | // This will return the array with one (last) attempt grade per user. 256 | if (!isset($rv[$record->id])) { 257 | if ($answersheet->grade > 0) { 258 | $record->rawgrade = $record->rawgrade * $answersheet->grade; 259 | } else { 260 | $gradeitem = self::get_grade_item($answersheet->course, $answersheet->id); 261 | $record->rawgrade = round($record->rawgrade * 262 | ($gradeitem->grademax - $gradeitem->grademin) + $gradeitem->grademin, 0); 263 | } 264 | 265 | $rv[$record->id] = $record; 266 | } 267 | } 268 | $rs->close(); 269 | if ($userid && empty($rv)) { 270 | return array($userid => null); 271 | } 272 | return $rv; 273 | } 274 | 275 | /** 276 | * Parses the options list form the module settings 277 | * 278 | * @param string $value 279 | * @return array 280 | */ 281 | public static function parse_options($value) { 282 | return preg_split('/\s*,\s*/', trim($value), -1, PREG_SPLIT_NO_EMPTY); 283 | } 284 | 285 | /** 286 | * Parses the correct answers list form the module settings 287 | * 288 | * @param string $value 289 | * @return array 290 | */ 291 | public static function parse_answerslist($value) { 292 | return preg_split('/\s*[,|\n]\s*/', trim($value), -1, PREG_SPLIT_NO_EMPTY); 293 | } 294 | 295 | /** 296 | * Saves the attempt results 297 | * 298 | * @param array $rawanswers 299 | * @param bool $finish 300 | */ 301 | protected function save($rawanswers, $finish = true) { 302 | global $DB, $CFG; 303 | $answers = array(); 304 | $missed = false; 305 | for ($i=0; $i<$this->answersheet->questionscount; $i++) { 306 | if (isset($rawanswers[$i])) { 307 | $answers[$i] = $rawanswers[$i]; 308 | } else { 309 | $answers[$i] = ''; 310 | $missed = true; 311 | } 312 | } 313 | $this->attempt->answers = join(',', $answers); 314 | $record = array('id' => $this->attempt->id, 315 | 'answers' => $this->attempt->answers); 316 | if ($finish) { 317 | $DB->execute('UPDATE {answersheet_attempt} SET islast = ? '. 318 | 'WHERE answersheetid = ? AND userid = ? AND islast = ?', 319 | array(0, $this->answersheet->id, $this->attempt->userid, 1)); 320 | $record['timecompleted'] = $this->attempt->timecompleted = time(); 321 | $record['grade'] = $this->attempt->grade = self::get_grade($answers); 322 | $record['islast'] = $this->attempt->islast = 1; 323 | } 324 | $DB->update_record('answersheet_attempt', $record); 325 | 326 | if ($finish) { 327 | \mod_answersheet\event\attempt_submitted::create_from_record($this->attempt, $this->cm)->trigger(); 328 | } else { 329 | \mod_answersheet\event\attempt_saved::create_from_record($this->attempt, $this->cm)->trigger(); 330 | } 331 | 332 | if ($finish) { 333 | answersheet_update_grades($this->answersheet, $this->attempt->userid); 334 | 335 | // Update completion state 336 | require_once($CFG->libdir.'/completionlib.php'); 337 | $completion = new completion_info($this->cm->get_course()); 338 | $completion->update_state($this->cm, COMPLETION_COMPLETE); 339 | } 340 | } 341 | 342 | /** 343 | * Calculates the grade from the answers 344 | * 345 | * @param array $answers 346 | * @return float 347 | */ 348 | protected function get_grade($answers) { 349 | $count = 0; 350 | $correctanswers = self::parse_answerslist($this->answersheet->answerslist); 351 | foreach ($correctanswers as $i => $answer) { 352 | $count += (isset($answers[$i]) && ($answers[$i] === $answer)) ? 1 : 0; 353 | } 354 | return 1.0 * $count / $this->answersheet->questionscount; 355 | } 356 | 357 | /** 358 | * Prepares attempt information for display 359 | * 360 | * @return type 361 | */ 362 | protected function attempt_info() { 363 | global $USER, $DB; 364 | $contents = html_writer::start_tag('ul'); 365 | if ($this->attempt->userid != $USER->id) { 366 | $namefields = get_all_user_name_fields(true); 367 | $user = $DB->get_record('user', array('id' => $this->attempt->userid), 368 | $namefields); 369 | $contents .= html_writer::tag('li', get_string('user') . ': '.fullname($user)); 370 | } 371 | $contents .= html_writer::tag('li', get_string('started', 'answersheet') . ': ' . 372 | userdate($this->attempt->timestarted, get_string('strftimedatetime', 'core_langconfig'))); 373 | if ($this->attempt->timecompleted) { 374 | $contents .= html_writer::tag('li', get_string('completed', 'answersheet') . ': ' . 375 | userdate($this->attempt->timecompleted, get_string('strftimedatetime', 'core_langconfig'))); 376 | $contents .= html_writer::tag('li', get_string('grade') . ': ' . 377 | self::convert_grade($this->answersheet, $this->attempt->grade, true)); 378 | $contents .= html_writer::tag('li', get_string('percentage', 'grades') . ': ' . 379 | round($this->attempt->grade * 100, 2).'%'); 380 | } 381 | $contents .= html_writer::end_tag('ul'); 382 | return $contents; 383 | } 384 | 385 | /** 386 | * Displays the attempt (as a form or review) 387 | */ 388 | public function display() { 389 | global $OUTPUT; 390 | 391 | $form = new mod_answersheet_attempt_form(null, (object)array('attempt' => $this)); 392 | $q = preg_split('/,/', $this->attempt->answers); 393 | $form->set_data(array('q' => $q)); 394 | $finish = false; 395 | if ($data = $form->get_data()) { 396 | $finish = isset($data->submitbutton); 397 | self::save(!empty($data->q) ? $data->q : array(), $finish); 398 | $form = new mod_answersheet_attempt_form(null, (object)array('attempt' => $this)); 399 | 400 | if (!$finish) { 401 | redirect($this->cm->url); 402 | } 403 | } 404 | $contents = ''; 405 | 406 | // Display question text. 407 | $options = array('noclean' => true, 'para' => false, 'filter' => true, 'context' => $this->cm->context, 'overflowdiv' => true); 408 | $question = file_rewrite_pluginfile_urls($this->answersheet->question, 'pluginfile.php', $this->cm->context->id, 409 | 'mod_answersheet', 'question', 0); 410 | $question = format_text($question, $this->answersheet->questionformat, $options, null); 411 | $contents .= $OUTPUT->box($question); 412 | 413 | if ($finish) { 414 | $a = (object)[ 415 | 'grade' => self::convert_grade($this->answersheet, $this->attempt->grade, true), 416 | 'percentage' => round($this->attempt->grade * 100, 2) 417 | ]; 418 | $notification = get_string('yourgrade', 'mod_answersheet', $a); 419 | core\notification::add($notification, \core\output\notification::NOTIFY_INFO); 420 | } else { 421 | $contents .= $this->attempt_info(); 422 | } 423 | $contents .= $form->render(); 424 | if ($finish) { 425 | if ($a->percentage < 100 && !empty($this->answersheet->explanations)) { 426 | $options = array('maxfiles' => EDITOR_UNLIMITED_FILES, 'context' => $this->cm->context); 427 | $explanations = file_rewrite_pluginfile_urls($this->answersheet->explanations, 'pluginfile.php', 428 | $this->cm->context->id, 'mod_answersheet', 'explanations', 0); 429 | $contents .= trim(format_text($explanations, $this->answersheet->explanationsformat, $options)); 430 | } 431 | $contents .= $OUTPUT->continue_button($this->cm->url); 432 | } 433 | return $contents; 434 | } 435 | } -------------------------------------------------------------------------------- /lib.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Library of interface functions and constants for module answersheet 19 | * 20 | * All the core Moodle functions, neeeded to allow the module to work 21 | * integrated in Moodle should be placed here. 22 | * 23 | * All the answersheet specific functions, needed to implement all the module 24 | * logic, should go to locallib.php. This will help to save some memory when 25 | * Moodle is performing actions across all modules. 26 | * 27 | * @package mod_answersheet 28 | * @copyright 2015 Marina Glancy 29 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 30 | */ 31 | 32 | defined('MOODLE_INTERNAL') || die(); 33 | 34 | /** 35 | * Example constant, you probably want to remove this :-) 36 | */ 37 | define('NEWMODULE_ULTIMATE_ANSWER', 42); 38 | 39 | /* Moodle core API */ 40 | 41 | /** 42 | * Returns the information on whether the module supports a feature 43 | * 44 | * See {@link plugin_supports()} for more info. 45 | * 46 | * @param string $feature FEATURE_xx constant for requested feature 47 | * @return mixed true if the feature is supported, null if unknown 48 | */ 49 | function answersheet_supports($feature) { 50 | 51 | switch($feature) { 52 | case FEATURE_MOD_INTRO: 53 | return true; 54 | case FEATURE_SHOW_DESCRIPTION: 55 | return true; 56 | case FEATURE_GRADE_HAS_GRADE: 57 | return true; 58 | case FEATURE_BACKUP_MOODLE2: 59 | return true; 60 | case FEATURE_COMPLETION_TRACKS_VIEWS: 61 | return true; 62 | case FEATURE_COMPLETION_HAS_RULES: 63 | return true; 64 | default: 65 | return null; 66 | } 67 | } 68 | 69 | /** 70 | * Saves a new instance of the answersheet into the database 71 | * 72 | * Given an object containing all the necessary data, 73 | * (defined by the form in mod_form.php) this function 74 | * will create a new instance and return the id number 75 | * of the new instance. 76 | * 77 | * @param stdClass $answersheet Submitted data from the form in mod_form.php 78 | * @param mod_answersheet_mod_form $mform The form instance itself (if needed) 79 | * @return int The id of the newly inserted answersheet record 80 | */ 81 | function answersheet_add_instance(stdClass $answersheet, mod_answersheet_mod_form $mform = null) { 82 | global $DB; 83 | 84 | $answersheet->timecreated = time(); 85 | 86 | // You may have to add extra stuff in here. 87 | $answersheet = file_postupdate_standard_editor($answersheet, 'question', 88 | array('maxfiles' => EDITOR_UNLIMITED_FILES), context_module::instance($answersheet->coursemodule), 89 | 'mod_answersheet', 'question', 0); 90 | $answersheet = file_postupdate_standard_editor($answersheet, 'explanations', 91 | array('maxfiles' => EDITOR_UNLIMITED_FILES), context_module::instance($answersheet->coursemodule), 92 | 'mod_answersheet', 'explanations', 0); 93 | 94 | $answersheet->id = $DB->insert_record('answersheet', $answersheet); 95 | 96 | answersheet_grade_item_update($answersheet); 97 | 98 | return $answersheet->id; 99 | } 100 | 101 | /** 102 | * Updates an instance of the answersheet in the database 103 | * 104 | * Given an object containing all the necessary data, 105 | * (defined by the form in mod_form.php) this function 106 | * will update an existing instance with new data. 107 | * 108 | * @param stdClass $answersheet An object from the form in mod_form.php 109 | * @param mod_answersheet_mod_form $mform The form instance itself (if needed) 110 | * @return boolean Success/Fail 111 | */ 112 | function answersheet_update_instance(stdClass $answersheet, mod_answersheet_mod_form $mform = null) { 113 | global $DB; 114 | 115 | $answersheet->timemodified = time(); 116 | $answersheet->id = $answersheet->instance; 117 | 118 | // You may have to add extra stuff in here. 119 | $answersheet = file_postupdate_standard_editor($answersheet, 'question', 120 | array('maxfiles' => EDITOR_UNLIMITED_FILES), context_module::instance($answersheet->coursemodule), 121 | 'mod_answersheet', 'question', 0); 122 | $answersheet = file_postupdate_standard_editor($answersheet, 'explanations', 123 | array('maxfiles' => EDITOR_UNLIMITED_FILES), context_module::instance($answersheet->coursemodule), 124 | 'mod_answersheet', 'explanations', 0); 125 | 126 | $result = $DB->update_record('answersheet', $answersheet); 127 | 128 | answersheet_grade_item_update($answersheet); 129 | 130 | return $result; 131 | } 132 | 133 | /** 134 | * Removes an instance of the answersheet from the database 135 | * 136 | * Given an ID of an instance of this module, 137 | * this function will permanently delete the instance 138 | * and any data that depends on it. 139 | * 140 | * @param int $id Id of the module instance 141 | * @return boolean Success/Failure 142 | */ 143 | function answersheet_delete_instance($id) { 144 | global $DB; 145 | 146 | if (! $answersheet = $DB->get_record('answersheet', array('id' => $id))) { 147 | return false; 148 | } 149 | 150 | // Delete any dependent records here. 151 | 152 | $DB->delete_records('answersheet', array('id' => $answersheet->id)); 153 | $DB->delete_records('answersheet_attempt', array('answersheetid' => $answersheet->id)); 154 | 155 | answersheet_grade_item_delete($answersheet); 156 | 157 | return true; 158 | } 159 | 160 | /** 161 | * Returns a small object with summary information about what a 162 | * user has done with a given particular instance of this module 163 | * Used for user activity reports. 164 | * 165 | * $return->time = the time they did it 166 | * $return->info = a short text description 167 | * 168 | * @param stdClass $course The course record 169 | * @param stdClass $user The user record 170 | * @param cm_info|stdClass $mod The course module info object or record 171 | * @param stdClass $answersheet The answersheet instance record 172 | * @return stdClass|null 173 | */ 174 | function answersheet_user_outline($course, $user, $mod, $answersheet) { 175 | 176 | $return = new stdClass(); 177 | $return->time = 0; 178 | $return->info = ''; 179 | return $return; 180 | } 181 | 182 | /** 183 | * Prints a detailed representation of what a user has done with 184 | * a given particular instance of this module, for user activity reports. 185 | * 186 | * It is supposed to echo directly without returning a value. 187 | * 188 | * @param stdClass $course the current course record 189 | * @param stdClass $user the record of the user we are generating report for 190 | * @param cm_info $mod course module info 191 | * @param stdClass $answersheet the module instance record 192 | */ 193 | function answersheet_user_complete($course, $user, $mod, $answersheet) { 194 | } 195 | 196 | /** 197 | * Given a course and a time, this module should find recent activity 198 | * that has occurred in answersheet activities and print it out. 199 | * 200 | * @param stdClass $course The course record 201 | * @param bool $viewfullnames Should we display full names 202 | * @param int $timestart Print activity since this timestamp 203 | * @return boolean True if anything was printed, otherwise false 204 | */ 205 | function answersheet_print_recent_activity($course, $viewfullnames, $timestart) { 206 | return false; 207 | } 208 | 209 | /** 210 | * Prepares the recent activity data 211 | * 212 | * This callback function is supposed to populate the passed array with 213 | * custom activity records. These records are then rendered into HTML via 214 | * {@link answersheet_print_recent_mod_activity()}. 215 | * 216 | * Returns void, it adds items into $activities and increases $index. 217 | * 218 | * @param array $activities sequentially indexed array of objects with added 'cmid' property 219 | * @param int $index the index in the $activities to use for the next record 220 | * @param int $timestart append activity since this time 221 | * @param int $courseid the id of the course we produce the report for 222 | * @param int $cmid course module id 223 | * @param int $userid check for a particular user's activity only, defaults to 0 (all users) 224 | * @param int $groupid check for a particular group's activity only, defaults to 0 (all groups) 225 | */ 226 | function answersheet_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) { 227 | } 228 | 229 | /** 230 | * Prints single activity item prepared by {@link answersheet_get_recent_mod_activity()} 231 | * 232 | * @param stdClass $activity activity record with added 'cmid' property 233 | * @param int $courseid the id of the course we produce the report for 234 | * @param bool $detail print detailed report 235 | * @param array $modnames as returned by {@link get_module_types_names()} 236 | * @param bool $viewfullnames display users' full names 237 | */ 238 | function answersheet_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) { 239 | } 240 | 241 | /** 242 | * Function to be run periodically according to the moodle cron 243 | * 244 | * This function searches for things that need to be done, such 245 | * as sending out mail, toggling flags etc ... 246 | * 247 | * Note that this has been deprecated in favour of scheduled task API. 248 | * 249 | * @return boolean 250 | */ 251 | function answersheet_cron () { 252 | return true; 253 | } 254 | 255 | /** 256 | * Returns all other caps used in the module 257 | * 258 | * For example, this could be array('moodle/site:accessallgroups') if the 259 | * module uses that capability. 260 | * 261 | * @return array 262 | */ 263 | function answersheet_get_extra_capabilities() { 264 | return array(); 265 | } 266 | 267 | /* Gradebook API */ 268 | 269 | /** 270 | * Is a given scale used by the instance of answersheet? 271 | * 272 | * This function returns if a scale is being used by one answersheet 273 | * if it has support for grading and scales. 274 | * 275 | * @param int $answersheetid ID of an instance of this module 276 | * @param int $scaleid ID of the scale 277 | * @return bool true if the scale is used by the given answersheet instance 278 | */ 279 | function answersheet_scale_used($answersheetid, $scaleid) { 280 | global $DB; 281 | 282 | if ($scaleid and $DB->record_exists('answersheet', array('id' => $answersheetid, 'grade' => -$scaleid))) { 283 | return true; 284 | } else { 285 | return false; 286 | } 287 | } 288 | 289 | /** 290 | * Checks if scale is being used by any instance of answersheet. 291 | * 292 | * This is used to find out if scale used anywhere. 293 | * 294 | * @param int $scaleid ID of the scale 295 | * @return boolean true if the scale is used by any answersheet instance 296 | */ 297 | function answersheet_scale_used_anywhere($scaleid) { 298 | global $DB; 299 | 300 | if ($scaleid and $DB->record_exists('answersheet', array('grade' => -$scaleid))) { 301 | return true; 302 | } else { 303 | return false; 304 | } 305 | } 306 | 307 | /** 308 | * Creates or updates grade item for the given answersheet instance 309 | * 310 | * Needed by {@link grade_update_mod_grades()}. 311 | * 312 | * @param stdClass $answersheet instance object with extra cmidnumber and modname property 313 | * @param bool $reset reset grades in the gradebook 314 | * @return void 315 | */ 316 | function answersheet_grade_item_update(stdClass $answersheet, $reset=false) { 317 | global $CFG; 318 | require_once($CFG->libdir.'/gradelib.php'); 319 | 320 | $item = array(); 321 | $item['itemname'] = clean_param($answersheet->name, PARAM_NOTAGS); 322 | $item['gradetype'] = GRADE_TYPE_VALUE; 323 | 324 | if ($answersheet->grade > 0) { 325 | $item['gradetype'] = GRADE_TYPE_VALUE; 326 | $item['grademax'] = $answersheet->grade; 327 | $item['grademin'] = 0; 328 | } else if ($answersheet->grade < 0) { 329 | $item['gradetype'] = GRADE_TYPE_SCALE; 330 | $item['scaleid'] = -$answersheet->grade; 331 | } else { 332 | $item['gradetype'] = GRADE_TYPE_NONE; 333 | } 334 | 335 | if ($reset) { 336 | $item['reset'] = true; 337 | } 338 | 339 | grade_update('mod/answersheet', $answersheet->course, 'mod', 'answersheet', 340 | $answersheet->id, 0, null, $item); 341 | } 342 | 343 | /** 344 | * Delete grade item for given answersheet instance 345 | * 346 | * @param stdClass $answersheet instance object 347 | * @return grade_item 348 | */ 349 | function answersheet_grade_item_delete($answersheet) { 350 | global $CFG; 351 | require_once($CFG->libdir.'/gradelib.php'); 352 | 353 | return grade_update('mod/answersheet', $answersheet->course, 'mod', 'answersheet', 354 | $answersheet->id, 0, null, array('deleted' => 1)); 355 | } 356 | 357 | /** 358 | * Update answersheet grades in the gradebook 359 | * 360 | * Needed by {@link grade_update_mod_grades()}. 361 | * 362 | * @param stdClass $answersheet instance object with extra cmidnumber and modname property 363 | * @param int $userid update grade of specific user only, 0 means all participants 364 | */ 365 | function answersheet_update_grades(stdClass $answersheet, $userid = 0) { 366 | global $CFG; 367 | require_once($CFG->libdir.'/gradelib.php'); 368 | 369 | // Populate array of grade objects indexed by userid. 370 | $grades = mod_answersheet_attempt::get_last_completed_attempt_grade($answersheet, $userid); 371 | 372 | grade_update('mod/answersheet', $answersheet->course, 'mod', 'answersheet', $answersheet->id, 0, $grades); 373 | } 374 | 375 | /* File API */ 376 | 377 | /** 378 | * Returns the lists of all browsable file areas within the given module context 379 | * 380 | * The file area 'intro' for the activity introduction field is added automatically 381 | * by {@link file_browser::get_file_info_context_module()} 382 | * 383 | * @param stdClass $course 384 | * @param stdClass $cm 385 | * @param stdClass $context 386 | * @return array of [(string)filearea] => (string)description 387 | */ 388 | function answersheet_get_file_areas($course, $cm, $context) { 389 | return array(); 390 | } 391 | 392 | /** 393 | * File browsing support for answersheet file areas 394 | * 395 | * @package mod_answersheet 396 | * @category files 397 | * 398 | * @param file_browser $browser 399 | * @param array $areas 400 | * @param stdClass $course 401 | * @param stdClass $cm 402 | * @param stdClass $context 403 | * @param string $filearea 404 | * @param int $itemid 405 | * @param string $filepath 406 | * @param string $filename 407 | * @return file_info instance or null if not found 408 | */ 409 | function answersheet_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) { 410 | return null; 411 | } 412 | 413 | /** 414 | * Serves the files from the answersheet file areas 415 | * 416 | * @package mod_answersheet 417 | * @category files 418 | * 419 | * @param stdClass $course the course object 420 | * @param stdClass $cm the course module object 421 | * @param context $context the answersheet's context 422 | * @param string $filearea the name of the file area 423 | * @param array $args extra arguments (itemid, path) 424 | * @param bool $forcedownload whether or not force download 425 | * @param array $options additional options affecting the file serving 426 | */ 427 | function answersheet_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload, array $options=array()) { 428 | global $DB, $CFG, $PAGE; 429 | 430 | if ($context->contextlevel != CONTEXT_MODULE || $cm->modname !== 'answersheet') { 431 | send_file_not_found(); 432 | } 433 | 434 | require_login($course, true, $cm); 435 | $itemid = (int)array_shift($args); 436 | 437 | if ($filearea === 'question' && $itemid == 0) { 438 | 439 | $fs = get_file_storage(); 440 | $relativepath = implode('/', $args); 441 | $fullpath = "/$context->id/mod_answersheet/question/$itemid/$relativepath"; 442 | if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { 443 | send_file_not_found(); 444 | } 445 | 446 | send_stored_file($file, null, 0, $forcedownload, $options); 447 | } 448 | 449 | if ($filearea === 'explanations' && $itemid == 0) { 450 | if (!has_any_capability(['moodle/course:manageactivities', 'mod/answersheet:viewreports'], $context)) { 451 | if (!mod_answersheet_attempt::has_completed_attempts($cm->instance)) { 452 | send_file_not_found(); 453 | } 454 | } 455 | 456 | $fs = get_file_storage(); 457 | $relativepath = implode('/', $args); 458 | $fullpath = "/$context->id/mod_answersheet/explanations/$itemid/$relativepath"; 459 | if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { 460 | send_file_not_found(); 461 | } 462 | 463 | send_stored_file($file, null, 0, $forcedownload, $options); 464 | } 465 | 466 | send_file_not_found(); 467 | } 468 | 469 | /* Navigation API */ 470 | 471 | /** 472 | * Extends the global navigation tree by adding answersheet nodes if there is a relevant content 473 | * 474 | * This can be called by an AJAX request so do not rely on $PAGE as it might not be set up properly. 475 | * 476 | * @param navigation_node $navref An object representing the navigation tree node of the answersheet module instance 477 | * @param stdClass $course current course record 478 | * @param stdClass $module current answersheet instance record 479 | * @param cm_info $cm course module information 480 | */ 481 | //function answersheet_extend_navigation(navigation_node $navref, stdClass $course, stdClass $module, cm_info $cm) { 482 | // Delete this function and its docblock, or implement it. 483 | //} 484 | 485 | /** 486 | * Extends the settings navigation with the answersheet settings 487 | * 488 | * This function is called when the context for the page is a answersheet module. This is not called by AJAX 489 | * so it is safe to rely on the $PAGE. 490 | * 491 | * @param settings_navigation $settingsnav complete settings navigation tree 492 | * @param navigation_node $answersheetnode answersheet administration node 493 | */ 494 | //function answersheet_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $answersheetnode=null) { 495 | // Delete this function and its docblock, or implement it. 496 | //} 497 | 498 | /** 499 | * Obtains the automatic completion state for this feedback based on the condition 500 | * in feedback settings. 501 | * 502 | * @param object $course Course 503 | * @param object $cm Course-module 504 | * @param int $userid User ID 505 | * @param bool $type Type of comparison (or/and; can be used as return value if no conditions) 506 | * @return bool True if completed, false if not, $type if conditions not set. 507 | */ 508 | function answersheet_get_completion_state($course, $cm, $userid, $type) { 509 | global $CFG, $DB; 510 | 511 | $answersheet = $DB->get_record('answersheet', array('id' => $cm->instance), '*', MUST_EXIST); 512 | if (!$answersheet->completionsubmit && !$answersheet->completionpass) { 513 | return $type; 514 | } 515 | 516 | // If completion option is enabled, evaluate it and return true/false 517 | if ($answersheet->completionsubmit) { 518 | $params = array('userid' => $userid, 'answersheetid' => $answersheet->id); 519 | if (!$DB->record_exists_select('answersheet_attempt', 520 | 'userid = :userid AND answersheetid = :answersheetid AND timecompleted IS NOT NULL', 521 | $params)) { 522 | return false; 523 | } 524 | } 525 | 526 | // Check for passing grade. 527 | if ($answersheet->completionpass) { 528 | require_once($CFG->libdir . '/gradelib.php'); 529 | $item = grade_item::fetch(array('courseid' => $course->id, 'itemtype' => 'mod', 530 | 'itemmodule' => 'answersheet', 'iteminstance' => $cm->instance, 'outcomeid' => null)); 531 | if ($item) { 532 | $grades = grade_grade::fetch_users_grades($item, array($userid), false); 533 | if (!empty($grades[$userid])) { 534 | return $grades[$userid]->is_passed($item); 535 | } 536 | } 537 | } 538 | 539 | return false; 540 | } --------------------------------------------------------------------------------