diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..143979b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,50 @@ +language: php + +sudo: required + +addons: + firefox: "47.0.1" + postgresql: "9.3" + apt: + packages: + - openjdk-8-jre-headless + +cache: + directories: + - $HOME/.composer/cache + - $HOME/.npm + +php: + - 7.0 + - 7.1 + - 7.2 + +env: + global: + - MOODLE_BRANCH=MOODLE_34_STABLE + matrix: + - DB=pgsql + - DB=mysqli + +before_install: + - phpenv config-rm xdebug.ini + - nvm install 8.9 + - nvm use 8.9 + - cd ../.. + - composer create-project -n --no-dev --prefer-dist moodlerooms/moodle-plugin-ci ci ^2 + - export PATH="$(cd ci/bin; pwd):$(cd ci/vendor/bin; pwd):$PATH" + +install: + - moodle-plugin-ci add-plugin maths/moodle-qbehaviour_adaptivemultipart + - moodle-plugin-ci install + +script: + - moodle-plugin-ci phplint + - moodle-plugin-ci phpmd + - moodle-plugin-ci codechecker + - moodle-plugin-ci validate + - moodle-plugin-ci savepoints + - moodle-plugin-ci mustache + - moodle-plugin-ci grunt + - moodle-plugin-ci phpunit + - moodle-plugin-ci behat diff --git a/README.txt b/README.txt index 28f9815..db45043 100644 --- a/README.txt +++ b/README.txt @@ -1,33 +1,69 @@ -This activity is for Moodle 2.x-versions, it will not work in Moodle 1.9 (or below) +This activity is for Moodle 2.7 and ulterior versions, it will not work with Moodle previous versions. -This module was originally created by Jamie Pratt (e-Mail: me@jamiep.org) with funding from Future University Hakodate http://www.fun.ac.jp/e/ -The module was originally conceived and partially designed by Peter Ruthven-Stuart (http://www.ruthven-stuart.org), Future University - Hakodate. -It has been migrated to Moodle 2.x-versions by gtn gmbh (global training network ltd. - http://gtn-solutions.com, http://www.exabis.at) +This module was originally created by Jamie Pratt (e-Mail: me@jamiep.org) with funding from Future University Hakodate +http://www.fun.ac.jp/e/ +The module was originally conceived and partially designed by Peter Ruthven-Stuart (http://www.ruthven-stuart.org), +Future University - Hakodate. +It has been migrated to Moodle 2.x-versions by gtn gmbh (global training network ltd. - http://gtn-solutions.com, +http://www.exabis.at +It was upgraded to Moodle 2.7 and enhanced by Jean-Michel Vedrine (email vedrine@vedrine.net). * qcreate - Bugs, Feature Requests, and Improvements * -If you have any problems installing this activity or suggestions for improvement please mailto: office@gtn-solutions.com +If you have any problems installing this activity or suggestions for improvement please mailto: vedrine@vedrine.net * qcreate - Description * * qcreate - Disclaimer * -As with any customization, it is recommended that you have a good backup of your Moodle site before attempting to install contributed code. -While those contributing code make every effort to provide the best code that they can, using contributed code nevertheless entails a certain degree of risk as contributed code is not as carefully reviewed and/or tested as the Moodle core code. -Hence, use this block at your own risk. +As with any customization, it is recommended that you have a good backup of your Moodle site before attempting to install +contributed code. +While those contributing code make every effort to provide the best code that they can, using contributed code nevertheless +entails a certain degree of risk as contributed code is not as carefully reviewed and/or tested as the Moodle core code. +Hence, use this plugin at your own risk. * qcreate - History * First official publishing-date: 2007/11/21 09:19:34 jamiesensei Migration to Moodle 2.4 2013/03/28 gtn gmbh +Migration to Moodle 2.7 2014/08/02 Jean-Michel Vedrine -* qcreate - Installation * +QUICK INSTALL +============= -1) Save the zip file somewhere onto your local computer and extract all the files +There are two installation methods that are available. Follow one of these, then log into your Moodle site as an administrator +and visit the notifications page to complete the install. -2) Transfer the folder qcreate to the mod-directory of Moodle +==================== MOST RECOMMENDED METHOD - Git ==================== -3) Log in as 'administrator' and click on the 'Home' link (Moodle 2.x) +If you do not have git installed, please see the below link. Please note, it is not necessary to set up the SSH Keys. +This is only needed if you are going to create a repository of your own on github.com. -That's it! \ No newline at end of file +Information on installing git - http://help.github.com/set-up-git-redirect/ + +Once you have git installed, simply visit the Moodle root directory and clone git://github.com/jmvedrine/qcreate.git +Remember to rename the folder to qcreate if you do not specify this in the clone command + +Eg. Linux command line would be as follow - + +git clone -b git://github.com/jmvedrine/qcreate.git mod/qcreate + +Use git pull to update this repository periodically to ensure you have the latest version. + +==================== Download the qcreate module. ==================== + +Visit https://github.com/jmvedrine/qcreate/ and download the zip, uncompress this zip and extract the folder. +The folder will have a name similar to qcreate-master, you MUST rename this to qcreate. +Place this folder in your mod folder in your Moodle directory. + +nb. The reason this is not the recommended method is due to the fact you have to over-write the contents of this folder +to apply any future updates to the qcreate module. In the above method there is a simple command to update the files. + +IMPORTANT NOTE: +============== +This activity needs that your Moodle cron is working and executed at regular and short intervals. +Since Moodle 2.7 and the introduction of scheduled tasks the recommended interval between cron executions is 1 minute. +See https://docs.moodle.org/27/en/Scheduled_tasks . The creation activity rely on scheduled tasks to upgrade grades and +more importantly to update students capacities on question categories. If the cron is not working properly, you students +will not be able to create any question and they will receive an error message when they try to do so. diff --git a/backup/moodle1/lib.php b/backup/moodle1/lib.php new file mode 100644 index 0000000..82bd3c3 --- /dev/null +++ b/backup/moodle1/lib.php @@ -0,0 +1,155 @@ +. + +/** + * Provides support for the conversion of moodle1 backup to the moodle2 format + * Based off of a template @ http://docs.moodle.org/dev/Backup_1.9_conversion_for_developers + * + * @package mod_qcreate + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Qcreate conversion handler + */ +class moodle1_mod_qcreate_handler extends moodle1_mod_handler { + + /** @var moodle1_file_manager */ + protected $fileman = null; + + /** @var int cmid */ + protected $moduleid = null; + + /** + * Declare the paths in moodle.xml we are able to convert + * + * The method returns list of {@link convert_path} instances. + * For each path returned, the corresponding conversion method must be + * defined. + * + * Note that the path /MOODLE_BACKUP/COURSE/MODULES/MOD/QCREATE does not + * actually exist in the file. The last element with the module name was + * appended by the moodle1_converter class. + * + * @return array of {@link convert_path} instances + */ + public function get_paths() { + return array( + new convert_path( + 'qcreate', '/MOODLE_BACKUP/COURSE/MODULES/MOD/QCREATE', + array( + 'newfields' => array( + 'completionquestions' => 0, + ), + ) + ), + new convert_path('qcreate_requireds', + '/MOODLE_BACKUP/COURSE/MODULES/MOD/QCREATE/REQUIREDS'), + new convert_path('qcreate_required', + '/MOODLE_BACKUP/COURSE/MODULES/MOD/QCREATE/REQUIREDS/REQUIRED'), + new convert_path('qcreate_grades', + '/MOODLE_BACKUP/COURSE/MODULES/MOD/QCREATE/GRADES'), + new convert_path('qcreate_grade', + '/MOODLE_BACKUP/COURSE/MODULES/MOD/QCREATE/GRADE') + ); + } + + /** + * This is executed every time we have one /MOODLE_BACKUP/COURSE/MODULES/MOD/QCREATE + * data available + */ + public function process_qcreate($data) { + global $CFG; + + // Get the course module id and context id. + $instanceid = $data['id']; + $cminfo = $this->get_cminfo($instanceid); + $this->moduleid = $cminfo['id']; + $contextid = $this->converter->get_contextid(CONTEXT_MODULE, $this->moduleid); + + // Get a fresh new file manager for this instance. + $this->fileman = $this->converter->get_file_manager($contextid, 'mod_qcreate'); + + // Convert course files embedded into the intro. + $this->fileman->filearea = 'intro'; + $this->fileman->itemid = 0; + $data['intro'] = moodle1_converter::migrate_referenced_files( + $data['intro'], $this->fileman); + + // Start writing qcreate.xml. + $this->open_xml_writer("activities/qcreate_{$this->moduleid}/qcreate.xml"); + $this->xmlwriter->begin_tag('activity', array('id' => $instanceid, + 'moduleid' => $this->moduleid, 'modulename' => 'qcreate', + 'contextid' => $contextid)); + $this->xmlwriter->begin_tag('qcreate', array('id' => $instanceid)); + + foreach ($data as $field => $value) { + if ($field <> 'id') { + $this->xmlwriter->full_tag($field, $value); + } + } + + return $data; + } + + public function on_qcreate_requireds_start() { + $this->xmlwriter->begin_tag('requireds'); + } + + public function on_qcreate_requireds_end() { + $this->xmlwriter->end_tag('requireds'); + } + + public function process_qcreate_required($data) { + $this->write_xml('required', $data, array('/required/id')); + } + + public function on_qcreate_grades_start() { + $this->xmlwriter->begin_tag('grades'); + } + + public function on_qcreate_grades_end() { + $this->xmlwriter->end_tag('grades'); + } + + public function process_qcreate_grade($data) { + $this->write_xml('grade', $data, array('/grade/id')); + } + + /** + * This is executed when we reach the closing tag of our 'qcreate' path + */ + public function on_qcreate_end() { + // Finish writing qcreate.xml. + $this->xmlwriter->end_tag('qcreate'); + $this->xmlwriter->end_tag('activity'); + $this->close_xml_writer(); + + // Write inforef.xml. + $this->open_xml_writer("activities/qcreate_{$this->moduleid}/inforef.xml"); + $this->xmlwriter->begin_tag('inforef'); + $this->xmlwriter->begin_tag('fileref'); + foreach ($this->fileman->get_fileids() as $fileid) { + $this->write_xml('file', array('id' => $fileid)); + } + $this->xmlwriter->end_tag('fileref'); + $this->xmlwriter->end_tag('inforef'); + $this->close_xml_writer(); + } +} diff --git a/backup/moodle2/backup_qcreate_activity_task.class.php b/backup/moodle2/backup_qcreate_activity_task.class.php new file mode 100644 index 0000000..af4faf1 --- /dev/null +++ b/backup/moodle2/backup_qcreate_activity_task.class.php @@ -0,0 +1,90 @@ +. + +/** + * Defines backup_qcreate_activity_task class + * + * @package mod_qcreate + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/mod/qcreate/backup/moodle2/backup_qcreate_stepslib.php'); + +/** + * Provides the steps to perform one complete backup of the Quiz instance + * + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class backup_qcreate_activity_task extends backup_activity_task { + + /** + * No specific settings for this activity + */ + protected function define_my_settings() { + } + + /** + * Defines backup steps to store the instance data and required questions + */ + protected function define_my_steps() { + // Generate the qcreate.xml file containing all the qcreate information + // and annotating used questions. + $this->add_step(new backup_qcreate_activity_structure_step('qcreate_structure', 'qcreate.xml')); + + // Note: Following steps must be present + // in all the activities using question banks. + + // Process all the annotated questions to calculate the question + // categories needing to be included in backup for this activity + // plus the categories belonging to the activity context itself. + $this->add_step(new backup_calculate_question_categories('activity_question_categories')); + + // Clean backup_temp_ids table from questions. We already + // have used them to detect question_categories and aren't + // needed anymore. + $this->add_step(new backup_delete_temp_questions('clean_temp_questions')); + } + + /** + * Encodes URLs to the index.php and view.php scripts + * + * @param string $content some HTML text that eventually contains URLs to the activity instance scripts + * @return string the content with the URLs encoded + */ + public static function encode_content_links($content) { + global $CFG; + + $base = preg_quote($CFG->wwwroot, '/'); + + // Link to the list of qcreatezes. + $search = "/(".$base."\/mod\/qcreate\/index.php\?id\=)([0-9]+)/"; + $content = preg_replace($search, '$@QCREATEINDEX*$2@$', $content); + + // Link to qcreate view by moduleid. + $search = "/(".$base."\/mod\/qcreate\/view.php\?id\=)([0-9]+)/"; + $content = preg_replace($search, '$@QCREATEVIEWBYID*$2@$', $content); + + // Link to qcreate view by qcreateid. + $search = "/(".$base."\/mod\/qcreate\/view.php\?a\=)([0-9]+)/"; + $content = preg_replace($search, '$@QCREATEVIEWBYA*$2@$', $content); + + return $content; + } +} diff --git a/backup/moodle2/backup_qcreate_stepslib.php b/backup/moodle2/backup_qcreate_stepslib.php new file mode 100644 index 0000000..86ed04d --- /dev/null +++ b/backup/moodle2/backup_qcreate_stepslib.php @@ -0,0 +1,84 @@ +. + +/** + * @package mod_qcreate + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Define all the backup steps that will be used by the backup_qcreate_activity_task + * + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class backup_qcreate_activity_structure_step extends backup_questions_activity_structure_step { + + protected function define_structure() { + + // To know if we are including userinfo. + $userinfo = $this->get_setting_value('userinfo'); + + // Define each element separated. + $qcreate = new backup_nested_element('qcreate', array('id'), array( + 'name', 'intro', 'introformat', 'grade', 'graderatio', 'allowed', 'totalrequired', + 'studentqaccess', 'timesync', 'timeopen', 'timeclose', 'timecreated', + 'timemodified', 'completionquestions', 'sendgradernotifications', 'sendstudentnotifications')); + + $requireds = new backup_nested_element('requireds'); + + $required = new backup_nested_element('required', array('id'), array( + 'qtype', 'no')); + + $grades = new backup_nested_element('grades'); + + $grade = new backup_nested_element('grade', array('id'), array( + 'questionid', 'gradeval', 'gradecomment', 'teacher', 'timemarked')); + + // Build the tree. + $qcreate->add_child($requireds); + $requireds->add_child($required); + + $qcreate->add_child($grades); + $grades->add_child($grade); + + // Define sources. + $qcreate->set_source_table('qcreate', array('id' => backup::VAR_ACTIVITYID)); + + $required->set_source_table('qcreate_required', + array('qcreateid' => backup::VAR_PARENTID)); + + // All the rest of elements only happen if we are including user info. + if ($userinfo) { + $grade->set_source_table('qcreate_grades', array('qcreateid' => backup::VAR_PARENTID)); + } + + // Define source alias. + $grade->set_source_alias('grade', 'gradeval'); + + // Define id annotations. + $grade->annotate_ids('user', 'teacher'); + + // Define file annotations. + $qcreate->annotate_files('mod_qcreate', 'intro', null); // This file area hasn't itemid. + + // Return the root element (qcreate), wrapped into standard activity structure. + return $this->prepare_activity_structure($qcreate); + } +} diff --git a/backup/moodle2/restore_qcreate_activity_task.class.php b/backup/moodle2/restore_qcreate_activity_task.class.php new file mode 100644 index 0000000..1399b5f --- /dev/null +++ b/backup/moodle2/restore_qcreate_activity_task.class.php @@ -0,0 +1,129 @@ +. + +/** + * @package mod_qcreate + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/mod/qcreate/backup/moodle2/restore_qcreate_stepslib.php'); + + +/** + * qcreate restore task that provides all the settings and steps to perform one + * complete restore of the activity + * + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class restore_qcreate_activity_task extends restore_activity_task { + + /** + * Define (add) particular settings this activity can have + */ + protected function define_my_settings() { + // No particular settings for this activity. + } + + /** + * Define (add) particular steps this activity can have + */ + protected function define_my_steps() { + // Qcreate only has one structure step. + $this->add_step(new restore_qcreate_activity_structure_step('qcreate_structure', 'qcreate.xml')); + } + + /** + * Define the contents in the activity that must be + * processed by the link decoder + */ + public static function define_decode_contents() { + $contents = array(); + + $contents[] = new restore_decode_content('qcreate', array('intro'), 'qcreate'); + + return $contents; + } + + /** + * Define the decoding rules for links belonging + * to the activity to be executed by the link decoder + */ + public static function define_decode_rules() { + $rules = array(); + + $rules[] = new restore_decode_rule('QCREATEVIEWBYID', + '/mod/qcreate/view.php?id=$1', 'course_module'); + $rules[] = new restore_decode_rule('QCREATEVIEWBYA', + '/mod/qcreate/view.php?a=$1', 'qcreate'); + $rules[] = new restore_decode_rule('QCREATEINDEX', + '/mod/qcreate/index.php?id=$1', 'course'); + + return $rules; + + } + + /** + * Define the restore log rules that will be applied + * by the {@link restore_logs_processor} when restoring + * qcreate logs. It must return one array + * of {@link restore_log_rule} objects + */ + public static function define_restore_log_rules() { + $rules = array(); + + $rules[] = new restore_log_rule('qcreate', 'add', + 'view.php?id={course_module}', '{qcreate}'); + $rules[] = new restore_log_rule('qcreate', 'update', + 'view.php?id={course_module}', '{qcreate}'); + $rules[] = new restore_log_rule('qcreate', 'view', + 'view.php?id={course_module}', '{qcreate}'); + $rules[] = new restore_log_rule('qcreate', 'preview', + 'view.php?id={course_module}', '{qcreate}'); + $rules[] = new restore_log_rule('qcreate', 'report', + 'report.php?id={course_module}', '{qcreate}'); + $rules[] = new restore_log_rule('qcreate', 'editquestions', + 'view.php?id={course_module}', '{qcreate}'); + $rules[] = new restore_log_rule('qcreate', 'delete attempt', + 'report.php?id={course_module}', '[oldattempt]'); + $rules[] = new restore_log_rule('qcreate', 'manualgrading', + 'report.php?mode=grading&q={qcreate}', '{qcreate}'); + + return $rules; + } + + /** + * Define the restore log rules that will be applied + * by the {@link restore_logs_processor} when restoring + * course logs. It must return one array + * of {@link restore_log_rule} objects + * + * Note this rules are applied when restoring course logs + * by the restore final task, but are defined here at + * activity level. All them are rules not linked to any module instance (cmid = 0) + */ + public static function define_restore_log_rules_for_course() { + $rules = array(); + + $rules[] = new restore_log_rule('qcreate', 'view all', 'index.php?id={course}', null); + + return $rules; + } +} diff --git a/backup/moodle2/restore_qcreate_stepslib.php b/backup/moodle2/restore_qcreate_stepslib.php new file mode 100644 index 0000000..bbef638 --- /dev/null +++ b/backup/moodle2/restore_qcreate_stepslib.php @@ -0,0 +1,124 @@ +. + +/** + * @package mod_qcreate + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +defined('MOODLE_INTERNAL') || die(); + + +/** + * Structure step to restore one qcreate activity + * + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class restore_qcreate_activity_structure_step extends restore_questions_activity_structure_step { + + protected function define_structure() { + + $paths = array(); + $userinfo = $this->get_setting_value('userinfo'); + + $qcreate = new restore_path_element('qcreate', '/activity/qcreate'); + $paths[] = $qcreate; + + $paths[] = new restore_path_element('qcreate_required', + '/activity/qcreate/requireds/required'); + + if ($userinfo) { + $paths[] = new restore_path_element('qcreate_grade', '/activity/qcreate/grades/grade'); + } + + // Return the paths wrapped into standard activity structure. + return $this->prepare_activity_structure($paths); + } + + protected function process_qcreate($data) { + global $CFG, $DB; + + $data = (object)$data; + $oldid = $data->id; + $data->course = $this->get_courseid(); + + $data->timeopen = $this->apply_date_offset($data->timeopen); + $data->timeclose = $this->apply_date_offset($data->timeclose); + $data->timecreated = $this->apply_date_offset($data->timecreated); + $data->timemodified = $this->apply_date_offset($data->timemodified); + + if ($data->grade < 0) { // Scale found, get mapping. + $data->grade = -($this->get_mappingid('scale', abs($data->grade))); + } + + // Supply some items that maybe missing from previous versions. + $qcreateconfig = get_config('qcreate'); + if (!isset($data->completionquestions)) { + $data->completionquestions = 0; + } + if (!isset($data->sendgradernotifications)) { + $data->sendgradernotifications = $qcreateconfig->sendgradernotifications; + } + if (!isset($data->sendstudentnotifications)) { + $data->sendstudentnotifications = $qcreateconfig->sendstudentnotifications; + } + + // Insert the qcreate record. + $newitemid = $DB->insert_record('qcreate', $data); + // Immediately after inserting "activity" record, call this. + $this->apply_activity_instance($newitemid); + } + + protected function process_qcreate_required($data) { + global $DB; + + $data = (object)$data; + $oldid = $data->id; + + $data->qcreateid = $this->get_new_parentid('qcreate'); + + $DB->insert_record('qcreate_required', $data); + } + + protected function process_qcreate_grade($data) { + global $DB; + + $data = (object)$data; + $oldid = $data->id; + $questioncreated = $this->get_mappingid('question_created', $data->questionid); + $data->qcreateid = $this->get_new_parentid('qcreate'); + $data->teacher = $this->get_mappingid('user', $data->teacher); + $data->grade = $data->gradeval; + $data->timemarked = $this->apply_date_offset($data->timemarked); + if ($questioncreated) { + // Adjust questionid. + $data->questionid = $questioncreated; + } + $DB->insert_record('qcreate_grades', $data); + } + + protected function inform_new_usage_id($newusageid) { + + } + protected function after_execute() { + parent::after_execute(); + // Add qcreate related files, no need to match by itemname (just internally handled context). + $this->add_related_files('mod_qcreate', 'intro', null); + } +} diff --git a/backuplib.php b/backuplib.php deleted file mode 100644 index f4d4134..0000000 --- a/backuplib.php +++ /dev/null @@ -1,277 +0,0 @@ -id) - // | - // | - // --------------------------------------------------- - // | | - // qcreate_grades qcreate_required - //(UL,pk->id, fk->qcreateid) (CL,pk->id, fk->qcreateid) - // - // Meaning: pk->primary key field of the table - // fk->foreign key to link with parent - // CL->course level info - // UL->user level info - // - //----------------------------------------------------------- - - //This function executes all the backup procedure about this mod - function qcreate_backup_mods($bf,$preferences) { - - global $CFG; - - $status = true; - - //Iterate over qcreate table - $qcreates = get_records("qcreate","course",$preferences->backup_course,"id"); - if ($qcreates) { - foreach ($qcreates as $qcreate) { - if (backup_mod_selected($preferences,'qcreate',$qcreate->id)) { - $status = $status && qcreate_backup_one_mod($bf,$preferences,$qcreate); - // backup files happens in backup_one_mod now too. - } - } - } - return $status; - } - - function qcreate_backup_one_mod($bf,$preferences,$qcreate) { - if (is_numeric($qcreate)) { - $qcreate = get_record('qcreate','id',$qcreate); - } - - $status = true; - - //Start mod - fwrite($bf,start_tag("MOD",3,true)); - //Print qcreate data - fwrite($bf,full_tag("ID",4,false,$qcreate->id)); - fwrite($bf,full_tag("MODTYPE",4,false, 'qcreate')); - fwrite($bf,full_tag("NAME",4,false,$qcreate->name)); - fwrite($bf,full_tag("GRADE",4,false,$qcreate->grade)); - fwrite($bf,full_tag("GRADERATIO",4,false,$qcreate->graderatio)); - fwrite($bf,full_tag("INTRO",4,false,$qcreate->intro)); - fwrite($bf,full_tag("INTROFORMAT",4,false,$qcreate->introformat)); - fwrite($bf,full_tag("ALLOWED",4,false,$qcreate->allowed)); - fwrite($bf,full_tag("TOTALREQUIRED",4,false,$qcreate->totalrequired)); - fwrite($bf,full_tag("STUDENTQACCESS",4,false,$qcreate->studentqaccess)); - fwrite($bf,full_tag("TIMEOPEN",4,false,$qcreate->timeopen)); - fwrite($bf,full_tag("TIMECLOSE",4,false,$qcreate->timeclose)); - fwrite($bf,full_tag("TIMECREATED",4,false,$qcreate->timecreated)); - fwrite($bf,full_tag("TIMEMODIFIED",4,false,$qcreate->timemodified)); - $status = $status && backup_qcreate_requireds($bf,$preferences,$qcreate->id); - //if we've selected to backup users info, then execute backup_qcreate_grades - if (backup_userdata_selected($preferences,'qcreate',$qcreate->id)) { - $status = $status && backup_qcreate_grades($bf,$preferences,$qcreate->id); - } - //End mod - $status = $status && fwrite($bf,end_tag("MOD",3,true)); - - return $status; - } - - //Backup qcreate_grades contents (executed from qcreate_backup_mods) - function backup_qcreate_grades($bf,$preferences,$qcreate) { - $status = true; - - $qcreate_grades = get_records("qcreate_grades","qcreateid",$qcreate,"id"); - //If there is grades - if ($qcreate_grades) { - //Write start tag - $status = $status && fwrite($bf,start_tag("GRADES",4,true)); - //Iterate over each grade - foreach ($qcreate_grades as $qcreate_grade) { - //Start grade - $status = $status && fwrite($bf,start_tag("GRADE",5,true)); - //Print grade contents - fwrite($bf,full_tag("ID",6,false,$qcreate_grade->id)); - fwrite($bf,full_tag("QUESTIONID",6,false,$qcreate_grade->questionid)); - fwrite($bf,full_tag("GRADE",6,false,$qcreate_grade->grade)); - fwrite($bf,full_tag("GRADECOMMENT",6,false,$qcreate_grade->gradecomment)); - fwrite($bf,full_tag("TEACHER",6,false,$qcreate_grade->teacher)); - fwrite($bf,full_tag("TIMEMARKED",6,false,$qcreate_grade->timemarked)); - //End grade - $status = $status && fwrite($bf,end_tag("GRADE",5,true)); - } - //Write end tag - $status = $status && fwrite($bf,end_tag("GRADES",4,true)); - } - return $status; - } - - //Backup qcreate_requireds contents (executed from qcreate_backup_mods) - function backup_qcreate_requireds($bf,$preferences,$qcreate) { - $status = true; - - $qcreate_requireds = get_records("qcreate_required", "qcreateid", $qcreate, "id"); - //If there is requireds - if ($qcreate_requireds) { - //Write start tag - $status = $status && fwrite($bf,start_tag("REQUIREDS",4,true)); - //Iterate over each required - foreach ($qcreate_requireds as $qcreate_required) { - //Start required - $status = $status && fwrite($bf, start_tag("REQUIRED", 5,true)); - //Print required contents - fwrite($bf, full_tag("ID", 6,false, $qcreate_required->id)); - fwrite($bf, full_tag("QTYPE", 6,false, $qcreate_required->qtype)); - fwrite($bf, full_tag("NO", 6,false, $qcreate_required->no)); - //End required - $status = $status && fwrite($bf, end_tag("REQUIRED", 5,true)); - } - //Write end tag - $status = $status && fwrite($bf, end_tag("REQUIREDS", 4,true)); - } - return $status; - } - - //Return an array of info (name, value) - function qcreate_check_backup_mods($course, $user_data=false, $backup_unique_code, $instances=null) { - if (!empty($instances) && is_array($instances) && count($instances)) { - $info = array(); - foreach ($instances as $id => $instance) { - $info += qcreate_check_backup_mods_instances($instance, $backup_unique_code); - } - return $info; - } - //First the course data - $info[0][0] = get_string("modulenameplural", "qcreate"); - if ($ids = qcreate_ids($course)) { - $info[0][1] = count($ids); - } else { - $info[0][1] = 0; - } - - //Now, if requested, the user_data - if ($user_data) { - $info[1][0] = get_string("grades"); - if ($ids = qcreate_grade_ids_by_course($course)) { - $info[1][1] = count($ids); - } else { - $info[1][1] = 0; - } - } - return $info; - } - - //Return an array of info (name, value) - function qcreate_check_backup_mods_instances($instance, $backup_unique_code) { - $info[$instance->id.'0'][0] = ''.$instance->name.''; - $info[$instance->id.'0'][1] = ''; - if (!empty($instance->userdata)) { - // in this module question categories and questions are user data. - //Categories - $info[$instance->id.'1'][0] = get_string("categories","quiz"); - if ($catids = qcreate_category_ids_by_instance ($instance->id, $backup_unique_code)) { - $info[$instance->id.'1'][1] = count($catids); - } else { - $info[$instance->id.'1'][1] = 0; - } - //Questions - $info[$instance->id.'2'][0] = get_string("questionsinclhidden","quiz"); - if ($ids = qcreate_question_ids_in_cats ($catids, $backup_unique_code)) { - $info[$instance->id.'2'][1] = count($ids); - } else { - $info[$instance->id.'2'][1] = 0; - } - $info[$instance->id.'3'][0] = get_string("grades"); - if ($ids = qcreate_grade_ids_by_instance($instance->id)) { - $info[$instance->id.'3'][1] = count($ids); - } else { - $info[$instance->id.'3'][1] = 0; - } - } - return $info; - } - - function qcreate_category_ids_by_instance ($instanceid, $backup_unique_code){ - - $cm = get_coursemodule_from_instance('qcreate', $instanceid); - $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id); - $cats = get_records('question_categories', 'contextid', $modcontext->id, '', 'id, contextid'); - if ($cats){ - foreach ($cats as $cat){ - backup_putid($backup_unique_code, 'question_categories', $cat->id, 0); - } - return array_keys($cats); - } else { - return array(); - } - } - - function qcreate_question_ids_in_cats ($catids, $backup_unique_code){ - $qs = get_records_select('question', 'category IN ('.join($catids, ', ').')', '', 'id, 0'); - if ($qs){ - foreach (array_keys($qs) as $q){ - backup_putid($backup_unique_code, 'question', $q, 0); - } - return array_keys($qs); - } else { - return array(); - } - } - - //Return a content encoded to support interactivities linking. Every module - //should have its own. They are called automatically from the backup procedure. - function qcreate_encode_content_links($content, $preferences) { - - global $CFG; - - $base = preg_quote($CFG->wwwroot, "/"); - - //Link to the list of qcreates - $buscar="/(".$base."\/mod\/qcreate\/index.php\?id\=)([0-9]+)/"; - $result= preg_replace($buscar, '$@QCREATEINDEX*$2@$', $content); - - //Link to qcreate view by moduleid - $buscar="/(".$base."\/mod\/qcreate\/view.php\?id\=)([0-9]+)/"; - $result= preg_replace($buscar, '$@QCREATEVIEWBYID*$2@$', $result); - - return $result; - } - - // INTERNAL FUNCTIONS. BASED IN THE MOD STRUCTURE - - //Returns an array of qcreates id - function qcreate_ids ($course) { - - global $CFG; - - return get_records_sql ("SELECT qc.id, qc.course - FROM {$CFG->prefix}qcreate qc - WHERE qc.course = '$course'"); - } - - //Returns an array of qcreate_grades id - //only returns grades where question record exists. - function qcreate_grade_ids_by_course ($course) { - - global $CFG; - - return get_records_sql ("SELECT g.id , g.qcreateid - FROM {$CFG->prefix}qcreate_grades g, - {$CFG->prefix}question q, - {$CFG->prefix}qcreate qc - WHERE qc.course = '$course' AND - g.qcreateid = qc.id AND " . - "q.id = g.questionid"); - } - - //Returns an array of qcreate_grades id - //only returns grades where question record exists. - function qcreate_grade_ids_by_instance ($instanceid) { - - global $CFG; - - return get_records_sql ("SELECT g.id , g.qcreateid - FROM {$CFG->prefix}qcreate_grades g, - {$CFG->prefix}question q - WHERE g.qcreateid = $instanceid AND " . - "q.id = g.questionid"); - } -?> diff --git a/classes/analytics/indicator/activity_base.php b/classes/analytics/indicator/activity_base.php new file mode 100644 index 0000000..0aa06b9 --- /dev/null +++ b/classes/analytics/indicator/activity_base.php @@ -0,0 +1,65 @@ +. + +/** + * Activity base class. + * + * @package mod_qcreate + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_qcreate\analytics\indicator; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Activity base class. + * + * @package mod_qcreate + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity { + + /** + * feedback_check_grades + * + * @return bool + */ + protected function feedback_check_grades() { + // We need the grade to be released to the student to consider that feedback has been provided. + return true; + } + + /** + * feedback_viewed_events + * + * @return string[] + */ + protected function feedback_viewed_events() { + return array('\mod_qcreate\event\course_module_viewed'); + } + + /** + * Returns the name of the field that controls activity availability. + * + * @return null|string + */ + protected function get_timeclose_field() { + return 'timeclose'; + } +} diff --git a/classes/analytics/indicator/cognitive_depth.php b/classes/analytics/indicator/cognitive_depth.php new file mode 100644 index 0000000..1d4f549 --- /dev/null +++ b/classes/analytics/indicator/cognitive_depth.php @@ -0,0 +1,79 @@ +. + +/** + * Cognitive depth indicator - qcreate. + * + * @package mod_qcreate + * @copyright 2017 David Monllao {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_qcreate\analytics\indicator; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Cognitive depth indicator - qcreate. + * + * @package mod_qcreate + * @copyright 2017 David Monllao {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class cognitive_depth extends activity_base { + + /** + * Returns the name. + * + * If there is a corresponding '_help' string this will be shown as well. + * + * @return \lang_string + */ + public static function get_name() : \lang_string { + return new \lang_string('indicator:cognitivedepth', 'mod_qcreate'); + } + + public function get_indicator_type() { + return self::INDICATOR_COGNITIVE; + } + + public function get_cognitive_depth_level(\cm_info $cm) { + return self::COGNITIVE_LEVEL_5; + } + + /** + * feedback_submitted_events + * + * @return string[] + */ + protected function feedback_submitted_events() { + return array('\mod_qcreate\event\question_graded'); + } + + /** + * feedback_replied + * + * @param \cm_info $cm + * @param int $contextid + * @param int $userid + * @param int $after + * @return bool + */ + protected function feedback_replied(\cm_info $cm, $contextid, $userid, $after = false) { + // No level 4. + return false; + } +} diff --git a/classes/analytics/indicator/social_breadth.php b/classes/analytics/indicator/social_breadth.php new file mode 100644 index 0000000..484ac72 --- /dev/null +++ b/classes/analytics/indicator/social_breadth.php @@ -0,0 +1,56 @@ +. + +/** + * Social breadth indicator - qcreate. + * + * @package mod_qcreate + * @copyright 2017 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_qcreate\analytics\indicator; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Social breadth indicator - qcreate. + * + * @package mod_qcreate + * @copyright 2017 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class social_breadth extends activity_base { + + /** + * Returns the name. + * + * If there is a corresponding '_help' string this will be shown as well. + * + * @return \lang_string + */ + public static function get_name() : \lang_string { + return new \lang_string('indicator:socialbreadth', 'mod_qcreate'); + } + + public function get_indicator_type() { + return self::INDICATOR_SOCIAL; + } + + public function get_social_breadth_level(\cm_info $cm) { + return self::SOCIAL_LEVEL_2; + } +} diff --git a/classes/event/course_module_instance_list_viewed.php b/classes/event/course_module_instance_list_viewed.php new file mode 100644 index 0000000..e1ae577 --- /dev/null +++ b/classes/event/course_module_instance_list_viewed.php @@ -0,0 +1,39 @@ +. + +/** + * The mod_qcreate instance list viewed event. + * + * @package mod_qcreate + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_qcreate\event; + +defined('MOODLE_INTERNAL') || die(); + +/** + * The mod_qcreate instance list viewed event class. + * + * @package mod_qcreate + * @since Moodle 2.7 + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class course_module_instance_list_viewed extends \core\event\course_module_instance_list_viewed { + // No code required here as the parent class handles it all. +} diff --git a/classes/event/course_module_viewed.php b/classes/event/course_module_viewed.php new file mode 100644 index 0000000..d46e4f0 --- /dev/null +++ b/classes/event/course_module_viewed.php @@ -0,0 +1,49 @@ +. + +/** + * The mod_qcreate course module viewed event. + * + * @package mod_qcreate + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_qcreate\event; + +defined('MOODLE_INTERNAL') || die(); + +/** + * The mod_qcreate course module viewed event class. + * + * @package mod_qcreate + * @since Moodle 2.7 + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class course_module_viewed extends \core\event\course_module_viewed { + + /** + * Init method. + * + * @return void + */ + protected function init() { + $this->data['crud'] = 'r'; + $this->data['edulevel'] = self::LEVEL_PARTICIPATING; + $this->data['objecttable'] = 'qcreate'; + } +} diff --git a/classes/event/edit_page_viewed.php b/classes/event/edit_page_viewed.php new file mode 100644 index 0000000..665de26 --- /dev/null +++ b/classes/event/edit_page_viewed.php @@ -0,0 +1,104 @@ +. + +/** + * The mod_qcreate edit page viewed event. + * + * @package mod_qcreate + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_qcreate\event; + +defined('MOODLE_INTERNAL') || die(); + +/** + * The mod_qcreate edit page viewed event class. + * + * @property-read array $other { + * Extra information about event. + * + * - int qcreateid: the id of the qcreate. + * } + * + * @package mod_qcreate + * @since Moodle 2.7 + * @copyright 2014 Jean-Michel vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class edit_page_viewed extends \core\event\base { + + /** + * Init method. + */ + protected function init() { + $this->data['crud'] = 'r'; + $this->data['edulevel'] = self::LEVEL_TEACHING; + } + + /** + * Returns localised general event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventeditpageviewed', 'mod_qcreate'); + } + + /** + * Returns description of what happened. + * + * @return string + */ + public function get_description() { + return "The user with id '$this->userid' viewed the edit page for the qcreate with " . + "course module id '$this->contextinstanceid'."; + } + + /** + * Returns relevant URL. + * + * @return \moodle_url + */ + public function get_url() { + return new \moodle_url('/mod/qcreate/edit.php', array('cmid' => $this->contextinstanceid)); + } + + /** + * Return the legacy event log data. + * + * @return array + */ + protected function get_legacy_logdata() { + return array($this->courseid, 'qcreate', 'editquestions', 'view.php?id=' . $this->contextinstanceid, + $this->other['qcreateid'], $this->contextinstanceid); + } + + /** + * Custom validation. + * + * @throws \coding_exception + * @return void + */ + protected function validate_data() { + parent::validate_data(); + + if (!isset($this->other['qcreateid'])) { + throw new \coding_exception('The \'qcreateid\' value must be set in other.'); + } + } +} diff --git a/classes/event/overview_viewed.php b/classes/event/overview_viewed.php new file mode 100644 index 0000000..9951593 --- /dev/null +++ b/classes/event/overview_viewed.php @@ -0,0 +1,106 @@ +. + +/** + * The mod_qcreate overview viewed event. + * + * @package mod_qcreate + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_qcreate\event; + +defined('MOODLE_INTERNAL') || die(); + +/** + * The mod_qcreate overview viewed event class. + * + * @property-read array $other { + * Extra information about event. + * + * - int qcreateid: the id of the qcreate. + * } + * + * @package mod_qcreate + * @since Moodle 2.7 + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class overview_viewed extends \core\event\base { + + /** + * Init method. + * + * @return void + */ + protected function init() { + $this->data['crud'] = 'r'; + $this->data['edulevel'] = self::LEVEL_TEACHING; + } + + /** + * Return localised event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventoverviewviewed', 'mod_qcreate'); + } + + /** + * Returns description of what happened. + * + * @return string + */ + public function get_description() { + return "The user with id '$this->userid' viewed the overview for the qcreate with " . + "course module id '$this->contextinstanceid'."; + } + + /** + * Get URL related to the action. + * + * @return \moodle_url + */ + public function get_url() { + return new \moodle_url('/mod/qcreate/overview.php', array('cmid' => $this->contextinstanceid)); + } + + /** + * Return the legacy event log data. + * + * @return array + */ + protected function get_legacy_logdata() { + return array($this->courseid, 'qcreate', 'overview', 'overview.php?cmid=' . $this->contextinstanceid, + $this->other['qcreateid'], $this->contextinstanceid); + } + + /** + * Custom validation. + * + * @throws \coding_exception + * @return void + */ + protected function validate_data() { + parent::validate_data(); + + if (!isset($this->other['qcreateid'])) { + throw new \coding_exception('The \'qcreateid\' value must be set in other.'); + } + } +} diff --git a/classes/event/question_graded.php b/classes/event/question_graded.php new file mode 100644 index 0000000..cf59003 --- /dev/null +++ b/classes/event/question_graded.php @@ -0,0 +1,113 @@ +. + +/** + * The mod_qcreate question graded event. + * + * @package mod_qcreate + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_qcreate\event; + +defined('MOODLE_INTERNAL') || die(); + +/** + * The mod_qcreate question graded event class. + * + * @property-read array $other { + * Extra information about event. + * + * - int qcreateid: the id of the qcreate. + * } + * + * @package core + * @since Moodle 2.7 + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class question_graded extends \core\event\base { + + /** + * Init method. + */ + protected function init() { + $this->data['objecttable'] = 'qcreate_grades'; + $this->data['crud'] = 'c'; + $this->data['edulevel'] = self::LEVEL_TEACHING; + } + + /** + * Returns localised general event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventquestiongraded', 'mod_qcreate'); + } + + /** + * Returns description of what happened. + * + * @return string + */ + public function get_description() { + return "The user with id '$this->userid' graded the question with id '" . $this->other['questionid'] . "' " . + "created by the user with id '$this->relateduserid' for the qcreate with course module id '$this->contextinstanceid'."; + } + + /** + * Returns relevant URL. + * + * @return \moodle_url + */ + public function get_url() { + return new \moodle_url('/grades.php', array('id' => $this->other['qcreateid'], + 'user' => $this->relateduserid)); + } + + /** + * Return the legacy event log data. + * + * @return array + */ + protected function get_legacy_logdata() { + return array($this->courseid, 'qcreate', 'manualgrade', 'comment.php?id=' . $this->other['qcreateid'] . + '&user=' . $this->relateduserid, $this->other['qcreateid'], $this->contextinstanceid); + } + + /** + * Custom validation. + * + * @throws \coding_exception + * @return void + */ + protected function validate_data() { + parent::validate_data(); + + if (!isset($this->relateduserid)) { + throw new \coding_exception('The \'relateduserid\' must be set.'); + } + + if (!isset($this->other['qcreateid'])) { + throw new \coding_exception('The \'qcreateid\' value must be set in other.'); + } + + if (!isset($this->other['questionid'])) { + throw new \coding_exception('The \'questionid\' value must be set in other.'); + } + } +} diff --git a/classes/event/question_regraded.php b/classes/event/question_regraded.php new file mode 100644 index 0000000..8f6a515 --- /dev/null +++ b/classes/event/question_regraded.php @@ -0,0 +1,113 @@ +. + +/** + * The mod_qcreate question regraded event. + * + * @package mod_qcreate + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_qcreate\event; + +defined('MOODLE_INTERNAL') || die(); + +/** + * The mod_qcreate question regraded event class. + * + * @property-read array $other { + * Extra information about event. + * + * - int qcreateid: the id of the qcreate. + * } + * + * @package core + * @since Moodle 2.7 + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class question_regraded extends \core\event\base { + + /** + * Init method. + */ + protected function init() { + $this->data['objecttable'] = 'qcreate_grades'; + $this->data['crud'] = 'c'; + $this->data['edulevel'] = self::LEVEL_TEACHING; + } + + /** + * Returns localised general event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventquestionregraded', 'mod_qcreate'); + } + + /** + * Returns description of what happened. + * + * @return string + */ + public function get_description() { + return "The user with id '$this->userid' regraded the question with id '" . $this->other['questionid'] . "' " . + "created by the user with id '$this->relateduserid' for the qcreate with course module id '$this->contextinstanceid'."; + } + + /** + * Returns relevant URL. + * + * @return \moodle_url + */ + public function get_url() { + return new \moodle_url('/grades.php', array('id' => $this->other['qcreateid'], + 'user' => $this->relateduserid)); + } + + /** + * Return the legacy event log data. + * + * @return array + */ + protected function get_legacy_logdata() { + return array($this->courseid, 'qcreate', 'manualgrade', 'comment.php?id=' . $this->other['qcreateid'] . + '&user=' . $this->relateduserid, $this->other['qcreateid'], $this->contextinstanceid); + } + + /** + * Custom validation. + * + * @throws \coding_exception + * @return void + */ + protected function validate_data() { + parent::validate_data(); + + if (!isset($this->relateduserid)) { + throw new \coding_exception('The \'relateduserid\' must be set.'); + } + + if (!isset($this->other['qcreateid'])) { + throw new \coding_exception('The \'qcreateid\' value must be set in other.'); + } + + if (!isset($this->other['questionid'])) { + throw new \coding_exception('The \'questionid\' value must be set in other.'); + } + } +} diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php new file mode 100644 index 0000000..d444718 --- /dev/null +++ b/classes/privacy/provider.php @@ -0,0 +1,270 @@ +. + +/** + * Privacy Subsystem implementation for mod_qcreate. + * + * @package mod_qcreate + * @copyright 2018 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_qcreate\privacy; + +use \core_privacy\local\request\writer; +use \core_privacy\local\request\transform; +use \core_privacy\local\request\contextlist; +use \core_privacy\local\request\approved_contextlist; +use \core_privacy\local\request\deletion_criteria; +use \core_privacy\local\metadata\collection; +use \core_privacy\local\request\helper; +use \core_privacy\manager; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/mod/qcreate/lib.php'); +require_once($CFG->dirroot . '/mod/qcreate/locallib.php'); + +/** + * Privacy Subsystem implementation for mod_qcreate. + * + * @copyright 2018 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class provider implements + // This plugin has data. + \core_privacy\local\metadata\provider, + + // This plugin currently implements the original plugin_provider interface. + \core_privacy\local\request\plugin\provider { + + /** + * Get the list of contexts that contain user information for the specified user. + * + * @param collection $items The collection to add metadata to. + * @return collection The array of metadata + */ + public static function get_metadata(collection $items) : collection { + // The table 'qcreate' stores a record for each qcreate activity. + // It does not contain user personal data, but data is returned from it for contextual requirements. + + // The table 'qcreate_grades' contains the current grade for each qcreate/user combination. + $items->add_database_table('qcreate_grades', [ + 'qcreateid' => 'privacy:metadata:qcreate_grades:qcreateid', + 'questionid' => 'privacy:metadata:qcreate_grades:questionid', + 'grade' => 'privacy:metadata:qcreate_grades:grade', + 'gradecomment' => 'privacy:metadata:qcreate_grades:gradecomment', + 'teacher' => 'privacy:metadata:qcreate_grades:teacher', + 'timemarked' => 'privacy:metadata:qcreate_grades:timemodified', + ], 'privacy:metadata:qcreate_grades'); + + // The qcreate links to the 'core_question' subsystem for all question functionality. + $items->add_subsystem_link('core_question', [], 'privacy:metadata:core_question'); + + // Although the qcreate supports the core_completion API and defines custom completion items, these will be + // noted by the manager as all activity modules are capable of supporting this functionality. + + return $items; + } + + /** + * Get the list of contexts where the specified user has attempted a qcreate activity, or been involved with manual marking + * and/or grading of a qcreate activity. + * + * @param int $userid The user to search. + * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. + */ + public static function get_contexts_for_userid(int $userid) : contextlist { + + // Select the context of any qcreate attempt where a user has a grade + // (even if not manual graded by a teacher), or is the manual grader of a created question. + $sql = 'SELECT c.id + FROM {question} q + LEFT JOIN {user} u ON u.id = q.createdby + LEFT JOIN {question_categories} qc ON qc.id = q.category + LEFT JOIN {qcreate_grades} g ON g.questionid = q.id + LEFT JOIN {context} c ON c.id = qc.contextid + LEFT JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel + LEFT JOIN {modules} m ON m.id = cm.module AND m.name = :modname + LEFT JOIN {qcreate} a ON a.id = cm.instance + WHERE + g.teacher = :qauserid OR + q.createdby = :qouserid'; + + $params = array( + 'contextlevel' => CONTEXT_MODULE, + 'modname' => 'qcreate', + 'qauserid' => $userid, + 'qouserid' => $userid, + ); + + $resultset = new contextlist(); + $resultset->add_from_sql($sql, $params); + + return $resultset; + } + + /** + * Export all data for all users in the specified context. + * + * @param approved_contextlist $contextlist The approved contexts to export information for. + */ + public static function export_user_data(approved_contextlist $contextlist) { + foreach ($contextlist->get_contexts() as $context) { + // Check that the context is a module context. + if ($context->contextlevel != CONTEXT_MODULE) { + continue; + } + $user = $contextlist->get_user(); + + $qcreatedata = helper::get_context_data($context, $user); + helper::export_context_files($context, $user); + + writer::with_context($context)->export_data([], $qcreatedata); + $qcreateobj = new \qcreate($context, null, null); + + // I need to find out if I'm a student or a teacher. + if ($userids = self::get_graded_users($user->id, $qcreateobj)) { + // Return teacher info. + $currentpath = [get_string('privacy:graderpath', 'mod_qcreate')]; + foreach ($userids as $studentuserid) { + $studentpath = array_merge($currentpath, [$studentuserid->id]); + static::export_grade($qcreateobj, $studentuserid, $context, $studentpath, true); + } + } + $currentpath = [get_string('privacy:studentpath', 'mod_qcreate')]; + $studentpath = array_merge($currentpath, [$user->id]); + static::export_grade($qcreateobj, $user, $context, $studentpath, true); + } + } + + /** + * Delete all data for all users in the specified context. + * + * @param context $context The specific context to delete data for. + */ + public static function delete_data_for_all_users_in_context(\context $context) { + global $DB; + + $cm = get_coursemodule_from_id('qcreate', $context->instanceid); + if (!$cm) { + // Only qcreate module will be handled. + return; + } + $qcreateobj = new \qcreate($context, null, null); + + // This will delete all local grades for this qcreate. + $DB->delete_records('qcreate_grades', array('qcreateid' => $qcreateobj->get_instance()->id)); + + } + + /** + * Delete all user data for the specified user, in the specified contexts. + * + * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. + */ + public static function delete_data_for_user(approved_contextlist $contextlist) { + global $DB; + + $user = $contextlist->get_user(); + + foreach ($contextlist as $context) { + $cm = get_coursemodule_from_id('qcreate', $context->instanceid); + if (!$cm) { + // Only qcreate module will be handled. + continue; + } + $qcreateobj = new \qcreate($context, null, null); + + // This will delete all local grades for this user and this qcreate instance. + $qcreateobj->delete_user_local_grades($user->id); + + // If this user has graded any question we need to anonymise the user id in the grade. + $params = ['grader' => $user->id, 'qcreateid' => $qcreateobj->get_instance()->id]; + $DB->set_field_select('qcreate_grades', 'gradecomment', '', + 'teacher = :grader AND qcreateid = :qcreateid', $params); + $DB->set_field_select('qcreate_grades', 'teacher', 0, + 'teacher = :grader AND qcreateid = :qcreateid', $params); + } + } + + /** + * Find out if this user has graded any users. + * + * @param int $userid The user ID (potential teacher). + * @param qcreate $qcreateobj The qcreate object. + * @return array If successful an array of objects with userids that this user graded, otherwise false. + */ + protected static function get_graded_users(int $userid, \qcreate $qcreateobj) { + $params = ['grader' => $userid, 'qcreateid' => $qcreateobj->get_instance()->id]; + + $sql = "SELECT DISTINCT q.createdby AS id + FROM {qcreate_grades} g + LEFT JOIN {question} q ON q.id = g.questionid + WHERE g.teacher = :grader AND g.qcreateid = :qcreateid"; + + $useridlist = new useridlist($userid, $qcreateobj->get_instance()->id); + $useridlist->add_from_sql($sql, $params); + + $userids = $useridlist->get_userids(); + return ($userids) ? $userids : false; + } + + /** + * Exports qcreate grade data for a user. + * + * @param \qcreate $qcreateobj The qcreate object + * @param \stdClass $user The user object + * @param \context_module $context The context + * @param array $path The path for exporting data + * @param bool|boolean $exportforteacher A flag for if this is exporting data as a teacher. + */ + protected static function export_grade(\qcreate $qcreateobj, \stdClass $user, \context_module $context, array $path, + bool $exportforteacher = false) { + if ($exportforteacher) { + // We need to export all local grades made by this teacher. + $grades = $qcreateobj->get_all_local_grades($user->id, true); + foreach ($grades as $grade) { + self::export_grade_data($grade, $context, $path); + } + } + // Then we need to export local grades for all questions created. + $grades = $qcreateobj->get_all_local_grades($user->id, false); + + foreach ($grades as $grade) { + self::export_grade_data($grade, $context, $path); + } + } + + /** + * Formats and then exports the user's grade data. + * + * @param \stdClass $grade The assign grade object + * @param \context $context The context object + * @param array $currentpath Current directory path that we are exporting to. + */ + protected static function export_grade_data(\stdClass $grade, \context $context, array $currentpath) { + $gradedata = (object)[ + 'timemarked' => transform::datetime($grade->grademodified), + 'teacher' => transform::user($grade->grader), + 'grade' => $grade->bestgrade, + 'question' => $grade->questiongraded, + 'gradecomment' => $grade->teachercomment, + ]; + writer::with_context($context)->export_data( + array_merge($currentpath, [get_string('privacy:gradepath', 'mod_qcreate')]), $gradedata); + } +} diff --git a/classes/privacy/useridlist.php b/classes/privacy/useridlist.php new file mode 100644 index 0000000..21482f0 --- /dev/null +++ b/classes/privacy/useridlist.php @@ -0,0 +1,99 @@ +. + +/** + * This file contains the mod_qcreate useridlist + * + * This is for collecting a list of user IDs + * + * @package mod_qcreate + * @copyright 2018 Jean-Michel Vedrine + * + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_qcreate\privacy; + +defined('MOODLE_INTERNAL') || die(); + +/** + * An object for collecting user IDs related to a teacher. + * + * @copyright 2018 Adrian Greeve + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class useridlist { + + /** @var int The ID of the teacher. */ + protected $teacherid; + + /** @var int The ID of the qcreate object. */ + protected $qcreateid; + + /** @var array A collection of user IDs (students). */ + protected $userids = []; + + /** + * Create this object. + * + * @param int $teacherid The teacher ID. + * @param int $qcreateid The qcreate ID. + */ + public function __construct($teacherid, $qcreateid) { + $this->teacherid = $teacherid; + $this->qcreateid = $qcreateid; + } + + /** + * Returns the teacher ID. + * + * @return int The teacher ID. + */ + public function get_teacherid() { + return $this->teacherid; + } + + /** + * Returns the qcreate ID. + * + * @return int The qcreate ID. + */ + public function get_qcreateid() { + return $this->qcreateid; + } + + /** + * Returns the user IDs. + * + * @return array User IDs. + */ + public function get_userids() { + return $this->userids; + } + + /** + * Add sql and params to return user IDs. + * + * @param string $sql The sql string to return user IDs. + * @param array $params Parameters for the sql statement. + */ + public function add_from_sql($sql, $params) { + global $DB; + $userids = $DB->get_records_sql($sql, $params); + if (!empty($userids)) { + $this->userids = array_merge($this->userids, $userids); + } + } +} diff --git a/classes/search/activity.php b/classes/search/activity.php new file mode 100644 index 0000000..1c8a754 --- /dev/null +++ b/classes/search/activity.php @@ -0,0 +1,46 @@ +. + +/** + * Search area for mod_qcreate activities. + * + * @package mod_qcreate + * @copyright 2015 David Monllao {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_qcreate\search; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Search area for mod_qcreate activities. + * + * @package mod_qcreate + * @copyright 2015 David Monllao {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class activity extends \core_search\base_activity { + + /** + * Returns true if this area uses file indexing. + * + * @return bool + */ + public function uses_file_indexing() { + return true; + } +} diff --git a/classes/task/synchronize_qaccess.php b/classes/task/synchronize_qaccess.php new file mode 100644 index 0000000..28b9f8e --- /dev/null +++ b/classes/task/synchronize_qaccess.php @@ -0,0 +1,64 @@ +. + +/** + * A scheduled task for qcreate activities to synchronize student's question access capabilities. + * + * @package mod_qcreate + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_qcreate\task; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Synchronize students capabilites on questions for each instance of qcreate. + */ +class synchronize_qaccess extends \core\task\scheduled_task { + + /** + * Get a descriptive name for this task (shown to admins). + * + * @return string + */ + public function get_name() { + return get_string('synchronizeqaccesstask', 'mod_qcreate'); + } + + /** + * Do the job. + * Throw exceptions on errors (the job will be retried). + */ + public function execute() { + global $CFG, $DB; + + require_once($CFG->dirroot . '/mod/qcreate/lib.php'); + // Find all qcreate instances. + $sql = "SELECT q.*, cm.id as cmidnumber, q.course as courseid + FROM {qcreate} q, {course_modules} cm, {modules} m + WHERE m.name='qcreate' AND m.id=cm.module AND cm.instance=q.id"; + $qcreates = $DB->get_recordset_sql($sql); + if ($qcreates) { + foreach ($qcreates as $qcreate) { + $context = \context_module::instance($qcreate->cmidnumber); + qcreate_student_q_access_sync($context, $qcreate, false); + } + } + $qcreates->close(); + } + +} diff --git a/classes/task/update_grades.php b/classes/task/update_grades.php new file mode 100644 index 0000000..d5bf7ab --- /dev/null +++ b/classes/task/update_grades.php @@ -0,0 +1,85 @@ +. + +/** + * A scheduled task for qcreate activities to upadte student's grades. + * + * @package mod_qcreate + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_qcreate\task; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Task to update grades for each qcreate instance. + */ +class update_grades extends \core\task\scheduled_task { + + /** + * Get a descriptive name for this task (shown to admins). + * + * @return string + */ + public function get_name() { + return get_string('updategradestask', 'mod_qcreate'); + } + + /** + * Do the job. + * Throw exceptions on errors (the job will be retried). + */ + public function execute() { + global $CFG, $DB; + + require_once($CFG->dirroot . '/mod/qcreate/lib.php'); + // Find all qcreate instances. + $sql = "SELECT q.*, cm.id as cmidnumber, q.course as courseid + FROM {qcreate} q, {course_modules} cm, {modules} m + WHERE m.name='qcreate' AND m.id=cm.module AND cm.instance=q.id"; + $qcreates = $DB->get_recordset_sql($sql); + if ($qcreates) { + foreach ($qcreates as $qcreate) { + $context = \context_module::instance($qcreate->cmidnumber); + // Get allusers that can create questions for this instance. + if ($users = get_users_by_capability($context, 'mod/qcreate:submit', '', '', '', '', '', '', false)) { + $users = array_keys($users); + $sql = 'SELECT q.* FROM {question_categories} qc, {question} q '. + 'LEFT JOIN {qcreate_grades} g ON q.id = g.questionid '. + 'WHERE g.timemarked IS NULL AND q.createdby IN ('.implode(',', $users).') '. + 'AND qc.id = q.category ' . + 'AND q.hidden=\'0\' AND q.parent=\'0\' ' . + 'AND qc.contextid ='.$context->id; + $questionrs = $DB->get_recordset_sql($sql); + $toupdates = array(); + foreach ($questionrs as $question) { + // Process local grades without notification. + qcreate_process_local_grade($qcreate, $question, true, false); + $toupdates[] = $question->createdby; + } + $questionrs->close(); + $toupdates = array_unique($toupdates); + foreach ($toupdates as $toupdate) { + qcreate_update_grades($qcreate, $toupdate); + } + } + } + } + $qcreates->close(); + } + +} diff --git a/db/access.php b/db/access.php index 9cd81c3..c81d41e 100644 --- a/db/access.php +++ b/db/access.php @@ -1,7 +1,28 @@ . + +/** + * Capability definitions for the qcreate module. + * + * For naming conventions, see lib/db/access.php. + * + * @package mod_qcreate + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ defined('MOODLE_INTERNAL') || die(); @@ -9,7 +30,6 @@ 'mod/qcreate:addinstance' => array( 'riskbitmask' => RISK_XSS, - 'captype' => 'write', 'contextlevel' => CONTEXT_COURSE, 'archetypes' => array( @@ -20,7 +40,6 @@ ), 'mod/qcreate:view' => array( - 'captype' => 'read', 'contextlevel' => CONTEXT_MODULE, 'archetypes' => array( @@ -33,7 +52,6 @@ ), 'mod/qcreate:submit' => array( - 'captype' => 'write', 'contextlevel' => CONTEXT_MODULE, 'archetypes' => array( @@ -42,7 +60,6 @@ ), 'mod/qcreate:grade' => array( - 'captype' => 'write', 'contextlevel' => CONTEXT_MODULE, 'archetypes' => array( @@ -50,6 +67,23 @@ 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ) - ) -); + ), + + 'mod/qcreate:receivegradernotifications' => array( + 'captype' => 'read', + 'contextlevel' => CONTEXT_MODULE, + 'archetypes' => array( + 'teacher' => CAP_ALLOW, + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW + ) + ), + 'mod/qcreate:receivestudentnotifications' => array( + 'captype' => 'read', + 'contextlevel' => CONTEXT_MODULE, + 'archetypes' => array( + 'student' => CAP_ALLOW + ) + ), +); diff --git a/db/install.xml b/db/install.xml index 2bf7c53..628092f 100644 --- a/db/install.xml +++ b/db/install.xml @@ -1,26 +1,29 @@ - - +
- - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + @@ -29,31 +32,31 @@
- +
- - - - + + + + - +
- +
- - - - - - - + + + + + + + - - - + + + diff --git a/db/messages.php b/db/messages.php new file mode 100644 index 0000000..7a3b275 --- /dev/null +++ b/db/messages.php @@ -0,0 +1,37 @@ +. + +/** + * Defines message providers (types of message sent) for the qcreate module. + * + * @package mod_qcreate + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$messageproviders = array( + // Notify teacher that a student has created a question. + 'gradernotification' => array( + 'capability' => 'mod/qcreate:receivegradernotifications' + ), + + // Notify student that a question was graded. + 'studentnotification' => array( + 'capability' => 'mod/qcreate:receivestudentnotifications' + ), +); diff --git a/db/tasks.php b/db/tasks.php new file mode 100644 index 0000000..77aa1d3 --- /dev/null +++ b/db/tasks.php @@ -0,0 +1,47 @@ +. + +/** + * Definition of Question creation scheduled tasks. + * + * @package mod_qcreate + * @category task + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$tasks = array( + array( + 'classname' => 'mod_qcreate\task\update_grades', + 'blocking' => 0, + 'minute' => '*/5', + 'hour' => '*', + 'day' => '*', + 'month' => '*', + 'dayofweek' => '*' + ), + array( + 'classname' => 'mod_qcreate\task\synchronize_qaccess', + 'blocking' => 0, + 'minute' => '*/3', + 'hour' => '*', + 'day' => '*', + 'month' => '*', + 'dayofweek' => '*' + ) +); \ No newline at end of file diff --git a/db/upgrade.php b/db/upgrade.php index 2b812ca..9758724 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -1,7 +1,31 @@ -. + +/** + * This file keeps track of upgrades to the qcreate module + * + * For naming conventions, see lib/db/access.php. + * + * @package mod_qcreate + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); -// This file keeps track of upgrades to -// the qcreate module // // Sometimes, changes between versions involve // alterations to database structures and other @@ -15,8 +39,70 @@ // will tell you what you need to do. // // The commands in here will all be database-neutral, -// using the functions defined in lib/ddllib.php +// using the functions defined in lib/ddllib.php. function xmldb_qcreate_upgrade($oldversion=0) { - return true; + global $CFG, $DB, $OUTPUT; + + $dbman = $DB->get_manager(); + + // Moodle v2.2.0 release upgrade line. + // Put any upgrade step following this. + + // Moodle v2.3.0 release upgrade line. + // Put any upgrade step following this. + + // Moodle v2.4.0 release upgrade line. + // Put any upgrade step following this. + + // Moodle v2.5.0 release upgrade line. + // Put any upgrade step following this. + + // Moodle v2.6.0 release upgrade line. + // Put any upgrade step following this. + + // Moodle v2.7.0 release upgrade line. + // Put any upgrade step following this. + + if ($oldversion < 2014060108) { + + // Define field completionquestions to be added to qcreate. + $table = new xmldb_table('qcreate'); + $field = new xmldb_field('completionquestions', XMLDB_TYPE_INTEGER, '10', null, + null, null, '0', 'timemodified'); + + // Conditionally launch add field completionquestions. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Qcreate savepoint reached. + upgrade_mod_savepoint(true, 2014060108, 'qcreate'); + } + + if ($oldversion < 2014082400) { + + // Define field to be added to qcreate. + $table = new xmldb_table('qcreate'); + $field = new xmldb_field('sendgradernotifications', XMLDB_TYPE_INTEGER, '2', null, + XMLDB_NOTNULL, null, '0', 'completionquestions'); + + // Conditionally launch add field. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Define field to be added to qcreate. + $table = new xmldb_table('qcreate'); + $field = new xmldb_field('sendstudentnotifications', XMLDB_TYPE_INTEGER, '2', null, + XMLDB_NOTNULL, null, '0', 'sendgradernotifications'); + + // Conditionally launch add field. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + // Qcreate savepoint reached. + upgrade_mod_savepoint(true, 2014082400, 'qcreate'); + } + return true; } diff --git a/deprecatedlib.php b/deprecatedlib.php deleted file mode 100644 index 8f608e5..0000000 --- a/deprecatedlib.php +++ /dev/null @@ -1,30 +0,0 @@ - $qtype) { - $returnTypes[$name] = $qtype->local_name(); - } - - return $returnTypes; - } -} - -if (!function_exists('questionbank_navigation_tabs')) { - function questionbank_navigation_tabs(&$row, $contexts, $querystring) { - global $CFG, $QUESTION_EDITTABCAPS; - $tabs = array( - 'questions' =>array("$CFG->wwwroot/question/edit.php?$querystring", get_string('questions', 'quiz'), get_string('editquestions', 'quiz')), - 'categories' =>array("$CFG->wwwroot/question/category.php?$querystring", get_string('categories', 'quiz'), get_string('editqcats', 'quiz')), - 'import' =>array("$CFG->wwwroot/question/import.php?$querystring", get_string('import', 'quiz'), get_string('importquestions', 'quiz')), - 'export' =>array("$CFG->wwwroot/question/export.php?$querystring", get_string('export', 'quiz'), get_string('exportquestions', 'quiz'))); - foreach ($tabs as $tabname => $tabparams){ - if ($contexts->have_one_edit_tab_cap($tabname)) { - $row[] = new tabobject($tabname, $tabparams[0], $tabparams[1], $tabparams[2]); - } - } - } -} \ No newline at end of file diff --git a/edit.php b/edit.php index 15ac83f..ea5cb52 100644 --- a/edit.php +++ b/edit.php @@ -1,39 +1,52 @@ -. + /** -* Page to grade questions -* -* -* @version $Id: edit.php,v 1.16 2008/11/12 08:23:00 jamiesensei Exp $ -* @author Martin Dougiamas and many others. -* @license http://www.gnu.org/copyleft/gpl.html GNU Public License -*/ -require_once("../../config.php"); -require_once($CFG->libdir.'/gradelib.php'); -require_once($CFG->libdir.'/tablelib.php'); -require_once($CFG->dirroot.'/mod/qcreate/lib.php'); + * Page to grade created questions. + * + * @package mod_qcreate + * @copyright 2008 Jamie Pratt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or late + */ +require_once(dirname(__FILE__) . '/../../config.php'); +require_once($CFG->libdir . '/gradelib.php'); +require_once($CFG->libdir . '/tablelib.php'); +require_once($CFG->dirroot . '/mod/qcreate/lib.php'); require_once($CFG->dirroot . '/question/editlib.php'); - list($thispageurl, $contexts, $cmid, $cm, $qcreate, $pagevars) = question_edit_setup('questions', '/mod/qcreate/edit.php'); $qcreate->cmidnumber = $cm->id; -require_capability('mod/qcreate:grade', get_context_instance(CONTEXT_MODULE, $cm->id)); -if ($qcreate->graderatio == 100){ - $grading_interface = false; +require_capability('mod/qcreate:grade', context_module::instance($cm->id)); +if ($qcreate->graderatio == 100) { + $gradinginterface = false; } else { - $grading_interface = true; -} + $gradinginterface = true; +} $page = optional_param('page', 0, PARAM_INT); +$gradessubmitted = optional_param('gradessubmitted', 0, PARAM_BOOL); // Grades submitted? -$gradessubmitted = optional_param('gradessubmitted', 0, PARAM_BOOL); // grades submitted? -if ($grading_interface){ +if ($gradinginterface) { $showungraded = optional_param('showungraded', 1, PARAM_BOOL); $showgraded = optional_param('showgraded', 1, PARAM_BOOL); $showneedsregrade = optional_param('showneedsregrade', 1, PARAM_BOOL); } else { $showungraded = true; - $showgraded = true; - $showneedsregrade = true; + $showgraded = true; + $showneedsregrade = true; } @@ -41,18 +54,21 @@ /* first we check to see if the form has just been submitted * to request user_preference updates */ -if (isset($_POST['updatepref'])){ +$updatepref = optional_param('updatepref', 0, PARAM_INT); +if ($updatepref) { $perpage = optional_param('perpage', 10, PARAM_INT); - $perpage = ($perpage <= 0) ? 10 : $perpage ; + $perpage = ($perpage <= 0) ? QCREATE_PER_PAGE : $perpage; + $perpage = ($perpage > QCREATE_MAX_PER_PAGE) ? QCREATE_MAX_PER_PAGE : $perpage; set_user_preference('qcreate_perpage', $perpage); } -/// find out current groups mode + +// Find out current groups mode. $groupmode = groups_get_activity_groupmode($cm); $currentgroup = groups_get_activity_group($cm, true); -/// Get all ppl that are allowed to submit assignments -$context = get_context_instance(CONTEXT_MODULE, $cm->id); -if (!$users = get_users_by_capability($context, 'mod/qcreate:submit', '', '', '', '', $currentgroup, '', false)){ +// Get all ppl that are allowed to submit questions. +$context = context_module::instance($cm->id); +if (!$users = get_users_by_capability($context, 'mod/qcreate:submit', '', '', '', '', $currentgroup, '', false)) { $users = array(); } @@ -62,9 +78,9 @@ $users = array_intersect($users, array_keys($groupingusers)); } -// grades submitted? -if ($gradessubmitted){ - qcreate_process_grades($qcreate, $cm, $users); +// Grades submitted? +if ($gradessubmitted) { + $message = qcreate_process_grades($qcreate, $cm, $users); } /* next we get perpage params @@ -72,46 +88,58 @@ */ $perpage = get_user_preferences('qcreate_perpage', 10); -$grading_info = grade_get_grades($COURSE->id, 'mod', 'qcreate', $qcreate->id); +$gradinginfo = grade_get_grades($COURSE->id, 'mod', 'qcreate', $qcreate->id); -if (!empty($CFG->enableoutcomes) and !empty($grading_info->outcomes)) { - $uses_outcomes = true; +if (!empty($CFG->enableoutcomes) and !empty($gradinginfo->outcomes)) { + $usesoutcomes = true; } else { - $uses_outcomes = false; + $usesoutcomes = false; } -$teacherattempts = true; /// Temporary measure -$strsaveallfeedback = get_string('saveallfeedback', 'assignment'); +$teacherattempts = true; // Temporary measure. +$strsaveallfeedback = get_string('saveallfeedback', 'qcreate'); + +$tabindex = 1; // Tabindex for quick grading tabbing; Not working for dropdowns yet. -$tabindex = 1; //tabindex for quick grading tabbing; Not working for dropdowns yet +// Log this visit. +$params = array( + 'courseid' => $COURSE->id, + 'context' => $context, + 'other' => array( + 'qcreateid' => $qcreate->id + ) +); +$event = \mod_qcreate\event\edit_page_viewed::create($params); +$event->trigger(); -add_to_log($COURSE->id, 'qcreate', 'grade', 'grades.php?id='.$qcreate->id, $qcreate->id, $cm->id); $strqcreate = get_string('modulename', 'qcreate'); $strqcreates = get_string('modulenameplural', 'qcreate'); -$navlinks = array(); -$navlinks[] = array('name' => $strqcreates, 'link' => "index.php?id=$COURSE->id", 'type' => 'activity'); -$navlinks[] = array('name' => format_string($qcreate->name,true), - 'link' => "view.php?id={$cm->id}", - 'type' => 'activityinstance'); -$navlinks[] = array('name' => get_string('grading', 'qcreate'), 'link' => '', 'type' => 'title'); -$navigation = build_navigation($navlinks); $PAGE->set_url($thispageurl); -print_header_simple(format_string($qcreate->name,true), "", $navigation, - '', '', true, update_module_button($cm->id, $COURSE->id, $strqcreate), navmenu($COURSE, $cm)); +// Prepare header. +$title = $COURSE->shortname . ': ' . format_string($qcreate->name); +$PAGE->set_title($title); +$PAGE->set_heading($COURSE->fullname); +$PAGE->set_context($context); +$PAGE->force_settings_menu(); -$mode = 'editq'; -include('tabs.php'); +echo $OUTPUT->header(); +echo $OUTPUT->heading($qcreate->name, 2, null); + +// Grades submitted? +if ($gradessubmitted) { + echo $message; +} -//setting this after tabs.php as these params are just for this page and should not be included in urls for tabs. $thispageurl->params(compact('showgraded', 'showneedsregrade', 'showungraded', 'page')); groups_print_activity_menu($cm, $thispageurl->out()); -if ($grading_interface){ - $tablecolumns = array('picture', 'fullname', 'qname', 'grade', 'status', 'gradecomment', 'timemodified', 'timemarked', 'finalgrade'); +if ($gradinginterface) { + $tablecolumns = array('picture', 'fullname', 'qname', 'grade', 'status', + 'gradecomment', 'timemodified', 'timemarked', 'finalgrade'); $tableheaders = array('', get_string('fullname'), get_string('question'), @@ -121,9 +149,9 @@ get_string('lastmodified'), get_string('marked', 'qcreate'), get_string('finalgrade', 'grades')); - if ($uses_outcomes) { - $tablecolumns[] = 'outcome'; // no sorting based on outcomes column - $tableheaders[] = get_string('outcome', 'grades'); + if ($usesoutcomes) { + $tablecolumns[] = 'outcomes'; // No sorting based on outcomes column. + $tableheaders[] = get_string('outcomes', 'grades'); } } else { $tablecolumns = array('picture', 'fullname', 'qname', 'gradecomment', 'timemodified', 'finalgrade'); @@ -141,9 +169,8 @@ $table->define_headers($tableheaders); $table->define_baseurl($thispageurl->out()); -$table->sortable(true, 'lastname');//sorted by lastname by default +$table->sortable(true, 'lastname');// Sorted by lastname by default. $table->collapsible(true); -$table->initialbars(true); $table->column_suppress('picture'); $table->column_suppress('fullname'); @@ -154,44 +181,42 @@ $table->column_class('gradecomment', 'comment'); $table->column_class('timemodified', 'timemodified'); $table->column_class('finalgrade', 'finalgrade'); -if ($grading_interface){ +if ($gradinginterface) { $table->column_class('grade', 'grade'); $table->column_class('timemarked', 'timemarked'); $table->column_class('status', 'status'); - if ($uses_outcomes) { - $table->column_class('outcome', 'outcome'); + if ($usesoutcomes) { + $table->column_class('outcomes', 'outcome'); } } -$table->set_attribute('cellspacing', '0'); -$table->set_attribute('id', 'attempts'); + +$table->set_attribute('id', 'student_questions'); $table->set_attribute('class', 'grades'); -$table->set_attribute('width', '90%'); -//$table->set_attribute('align', 'center'); -if ($grading_interface){ +if ($gradinginterface) { $table->no_sorting('finalgrade'); - $table->no_sorting('outcome'); + $table->no_sorting('outcomes'); } -// Start working -- this is necessary as soon as the niceties are over +// Start working -- this is necessary as soon as the niceties are over. $table->setup(); -/// Construct the SQL +// Construct the SQL. -if (!empty($users) && ($showungraded || $showgraded || $showneedsregrade)){ +if (!empty($users) && ($showungraded || $showgraded || $showneedsregrade)) { if ($sort = $table->get_sql_sort()) { $sort = ' ORDER BY '.$sort; } $where = $table->get_sql_where(); - if ($where[0]) { + if ($where[0]) { $where[0] .= ' AND '; - } - - //unfortunately we cannot use status in WHERE clause - switch ($showungraded . $showneedsregrade . $showgraded){ + } + + // Unfortunately we cannot use status in WHERE clause. + switch ($showungraded . $showneedsregrade . $showgraded) { case '001': $where[0] .= '(g.timemarked IS NOT NULL) AND (g.timemarked >= q.timemodified ) AND '; break; @@ -210,7 +235,7 @@ case '110': $where[0] .= '((g.timemarked IS NULL) OR g.timemarked < q.timemodified) AND '; break; - case '111': //show everything + case '111': // Show everything. break; } if ($qcreate->allowed != 'ALL') { @@ -219,49 +244,56 @@ $where[0] .= 'q.qtype IN ('.$allowedlist.') AND '; } - $countsql = 'SELECT COUNT(*) FROM '.$CFG->prefix.'user u, '.$CFG->prefix.'question_categories c, '.$CFG->prefix.'question q '. - 'LEFT JOIN '.$CFG->prefix.'qcreate_grades g ON q.id = g.questionid '. - 'WHERE '.$where[0].'q.createdby = u.id AND u.id IN ('.implode(',',$users). + $countsql = 'SELECT COUNT(*) FROM {user} u, {question_categories} c, {question} q '. + 'LEFT JOIN {qcreate_grades} g ON q.id = g.questionid '. + 'WHERE ' . $where[0] . 'q.createdby = u.id AND u.id IN (' . implode(',', $users) . ') AND q.hidden=\'0\' AND q.parent=\'0\' AND q.category = c.id and c.contextid='.$context->id; $answercount = $DB->count_records_sql($countsql, $where[1]); - //complicated status calculation is needed for sorting on status column - $select = 'SELECT q.id AS qid, u.id, u.firstname, u.lastname, u.picture, + $ufields = user_picture::fields('u'); + // Complicated status calculation is needed for sorting on status column. + $select = "SELECT q.id AS qid, $ufields, g.id AS gradeid, g.grade, g.gradecomment, q.timemodified, g.timemarked, q.qtype, q.name AS qname, COALESCE( SIGN(SIGN(g.timemarked) + SIGN(g.timemarked - q.timemodified)) ,-1 - ) AS status '; - $sql = 'FROM '.$CFG->prefix.'user u, '.$CFG->prefix.'question_categories c, '.$CFG->prefix.'question q '. - 'LEFT JOIN '.$CFG->prefix.'qcreate_grades g ON q.id = g.questionid + ) AS status "; + $sql = 'FROM {user} u, {question_categories} c, {question} q '. + 'LEFT JOIN {qcreate_grades} g ON q.id = g.questionid AND g.qcreateid = '.$qcreate->id.' '. - 'WHERE '.$where[0].'q.createdby = u.id AND u.id IN ('.implode(',',$users). + 'WHERE ' . $where[0] . 'q.createdby = u.id AND u.id IN (' . implode(',', $users) . ') AND q.hidden=\'0\' AND q.parent=\'0\' AND q.category = c.id and c.contextid='.$context->id; } else { $answercount = 0; } -if ($grading_interface){ +$table->initialbars($answercount > 20); + +if ($gradinginterface) { echo '
'; echo '
'; - // TODO: echo $thispageurl->hidden_params_out(array('showgraded', 'showneedsregrade', 'showungraded')); - //default value for checkbox when checkbox not checked. + + // Default value for checkbox when checkbox not checked. + echo ''; echo ''; echo ''; echo '
'; echo '
'; print_string('show', 'qcreate'); - $checked = $showgraded?' checked="checked"':''; - echo ''; + $checked = $showgraded ? ' checked="checked"' : ''; + echo ''; echo ''; - $checked = $showneedsregrade?' checked="checked"':''; - echo ''; + $checked = $showneedsregrade ? ' checked="checked"' : ''; + echo ' '; echo ''; - $checked = $showungraded?' checked="checked"':''; - echo ''; + $checked = $showungraded ? ' checked="checked"' : ''; + echo ' '; echo ''; echo '
'; } } else { $teachermodified = '
 
'; - $status = '
 
'; - - if ($final_grade->locked or $final_grade->overridden) { - $grade = '
'.$final_grade->str_grade.'
'; - } else { // allow editing - $menu = html_writer::select(make_grades_menu($qcreate->grade), 'menu['.$answer->qid.']', $answer->grade, get_string('nograde')); - $grade = '
'.$menu.'
'; + $status = ' '; + + if ($finalgradevalue->locked or $finalgradevalue->overridden) { + $grade = '
'.$finalgradevalue->str_grade.'
'; + } else { // Allow editing. + $attributes = array('tabindex' => $tabindex++); + $menu = html_writer::select($grademenu, + 'menu['.$answer->qid.']', $answer->grade, array('-1' => get_string('nograde')), $attributes); + $grade = '
' + .''.$menu.'
'; } - if ($final_grade->locked or $final_grade->overridden) { - $comment = '
'.$final_grade->str_feedback.'
'; + if ($finalgradevalue->locked or $finalgradevalue->overridden) { + $comment = '
'.$finalgradevalue->str_feedback.'
'; } else { $comment = '
' - . '
'; } } - if ($answer->timemarked==0){ + if ($answer->timemarked == 0) { $status = get_string('needsgrading', 'qcreate'); - } else if ($answer->needsregrading){ + $class = "label label-warning"; + } else if ($answer->needsregrading) { $status = get_string('needsregrading', 'qcreate'); + $class = "label label-warning"; } else { $status = get_string('graded', 'qcreate'); + $class = "label label-success"; } - if ($highlight){ - $status = ''.$status.''; - } + $attributes = array('tabindex' => -1); + $status = html_writer::span($status, $class, $attributes); + $status = '
'.$status.'
'; - $finalgrade = ''.$final_grade->str_grade.''; + $finalgradestr = ''.$finalgradevalue->str_grade.''; $outcomes = ''; - if ($uses_outcomes) { - - foreach($grading_info->outcomes as $n=>$outcome) { - $outcomes .= '
'; + if ($usesoutcomes) { + foreach ($gradinginfo->outcomes as $index => $outcome) { + $outcomes .= html_writer::start_tag('div', array('class' => 'outcome')). html_writer::tag('label', $outcome->name ); $options = make_grades_menu(-$outcome->scaleid); if ($outcome->grades[$answer->id]->locked) { $options[0] = get_string('nooutcome', 'grades'); - $outcomes .= ': '.$options[$outcome->grades[$answer->qid]->grade].''; + $outcomes .= ': ' . html_writer::tag('span', $options[$outcome->grades[$answer->qid]->grade], + array(id => "outcome_'.$index.'_'.$answer->qid.'")); } else { $outcomes .= ' '; - $outcomes .= html_writer::select($options, 'outcome_'.$n.'['.$answer->qid.']', - $outcome->grades[$answer->qid]->grade, get_string('nooutcome', 'grades'), array('id'=>'outcome_'.$n.'_'.$answer->qid)); + $outcomes .= html_writer::select($options, 'outcome_'.$index.'['.$answer->qid.']', + $outcome->grades[$answer->qid]->grade, get_string('nooutcome', 'grades'), + array('id' => 'outcome_'.$index.'_'.$answer->qid)); } - $outcomes .= '
'; + $outcomes .= html_writer::end_tag('div'); } } - if ($grading_interface){ - $row = array($picture, fullname($answer), $colquestion, $grade, $status, $comment, $studentmodified, $teachermodified, $finalgrade); + if ($gradinginterface) { + $row = array($picture, $answer->imagealt, $colquestion, $grade, $status, $comment, $studentmodified, + $teachermodified, $finalgradestr); } else { - $row = array($picture, fullname($answer), $colquestion, $comment, $studentmodified, $finalgrade); + $row = array($picture, $answer->imagealt, $colquestion, $comment, $studentmodified, $finalgradestr); } - if ($uses_outcomes) { + if ($usesoutcomes) { $row[] = $outcomes; } $table->add_data($row); - $tableHasData = true; + $tablehasdata = true; } } -$table->print_html(); /// Print the whole table -$tableOutput = ob_get_clean(); +$table->finish_html(); // Print the whole table. +$tableoutput = ob_get_clean(); -if ($tableHasData) { +if ($tablehasdata) { echo ''; echo '
'; echo ''; - // TODO: echo $thispageurl->hidden_params_out(); echo '
'; } -echo $tableOutput; +echo $tableoutput; -if ($tableHasData) { - if ($grading_interface){ - echo '
'; +if ($tablehasdata) { + if ($gradinginterface) { + echo '
'; } else { - echo '
'; + echo '
'; } echo ''; - /// End of fast grading form + // End of fast grading form. } -/// Mini form for setting user preference +// Mini form for setting user preference. echo "\n
"; $form = '
'; -$form .= '
'; -// TODO: $form .= $thispageurl->hidden_params_out(); +$form .= '
'; $form .= ''; $form .= '

'; -$form .= ''; +$form .= ''; $form .= '
'; $form .= ''; $form .= '

'; $form .= '
'; $form .= ''; -$OUTPUT->box($form, 'generalbox boxaligncenter boxwidthnarrow'); -///End of mini form +echo $OUTPUT->box($form, 'generalbox boxaligncenter boxwidthnarrow'); +// End of mini form. + +// Link to the export good page. +echo '
'; +echo $OUTPUT->container_start('exportgoodlink'); +$urlparams = array('cmid' => $cm->id); +$url = new moodle_url('/mod/qcreate/exportgood.php', $urlparams); +echo '' . get_string('exportgood', 'mod_qcreate') . ' '; +echo $OUTPUT->container_end(); +echo '
'; echo $OUTPUT->footer(); diff --git a/export_good_questions_form.php b/export_good_questions_form.php index a69db3b..1cdc0d7 100644 --- a/export_good_questions_form.php +++ b/export_good_questions_form.php @@ -1,44 +1,64 @@ dirroot."/question/export_form.php"); -class question_export__good_questions_form extends question_export_form { - function definition() { +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * This class manage export of good questions form. + * + * @package mod_qcreate + * @copyright 2008 Jamie Pratt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or late + */ + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/question/export_form.php'); + +class qcreate_export_good_questions_form extends question_export_form { + protected function definition() { $mform =& $this->_form; $qcreate = $this->_customdata['qcreate']; - if ($qcreate->graderatio != 100){ - - //-------------------------------------------------------------------------------- - $mform->addElement('header','exportselection', get_string('exportselection','qcreate')); - + if ($qcreate->graderatio != 100) { + $mform->addElement('header', 'exportselection', get_string('exportselection', 'qcreate')); + $menu = make_grades_menu($qcreate->grade); unset($menu[0]); - $menu += array(0 => get_string('allquestions','qcreate')); - $mform->addElement('select','betterthangrade', get_string('betterthangrade','qcreate'), $menu); + $menu += array(0 => get_string('allquestions', 'qcreate')); + $mform->addElement('select', 'betterthangrade', get_string('betterthangrade', 'qcreate'), $menu); $mform->setDefault('betterthangrade', 0); } - $mform->addElement('header','exportnaming', get_string('exportnaming','qcreate')); + $mform->addElement('header', 'exportnaming', get_string('exportnaming', 'qcreate')); - $cbarray3=array(); - $cbarray3[] = &MoodleQuickForm::createElement('checkbox', 'naming[other]', '', get_string('specifictext', 'qcreate')); - $cbarray3[] = &MoodleQuickForm::createElement('text', 'naming[othertext]'); + $cbarray3 = array(); + $cbarray3[] = $mform->createElement('checkbox', 'naming[other]', '', get_string('specifictext', 'qcreate')); + $cbarray3[] = $mform->createElement('text', 'naming[othertext]'); + $mform->setType('naming[othertext]', PARAM_TEXT); $mform->addGroup($cbarray3, 'naming3', '', array(' '), false); $mform->disabledIf('naming3', 'naming[other]'); - $cbarray1=array(); - $cbarray1[] = &MoodleQuickForm::createElement('checkbox', 'naming[firstname]', '', get_string('firstname')); - $cbarray1[] = &MoodleQuickForm::createElement('checkbox', 'naming[lastname]', '', get_string('lastname')); - $cbarray1[] = &MoodleQuickForm::createElement('checkbox', 'naming[username]', '', get_string('username', 'qcreate')); + $cbarray1 = array(); + $cbarray1[] = $mform->createElement('checkbox', 'naming[firstname]', '', get_string('firstname')); + $cbarray1[] = $mform->createElement('checkbox', 'naming[lastname]', '', get_string('lastname')); + $cbarray1[] = $mform->createElement('checkbox', 'naming[username]', '', get_string('username', 'qcreate')); $mform->addGroup($cbarray1, 'naming1', '', array(' '), false); - - $cbarray2=array(); - $cbarray2[] = &MoodleQuickForm::createElement('checkbox', 'naming[activityname]', '', get_string('activityname', 'qcreate')); - $cbarray2[] = &MoodleQuickForm::createElement('checkbox', 'naming[timecreated]', '', get_string('timecreated', 'qcreate')); - $mform->addGroup($cbarray2, 'naming2', '', array(' '), false); + $cbarray2 = array(); + $cbarray2[] = $mform->createElement('checkbox', 'naming[activityname]', '', get_string('activityname', 'qcreate')); + $cbarray2[] = $mform->createElement('checkbox', 'naming[timecreated]', '', get_string('timecreated', 'qcreate')); + $mform->addGroup($cbarray2, 'naming2', '', array(' '), false); parent::definition(); - - - } } -?> diff --git a/exportgood.php b/exportgood.php index 28615f5..216294f 100644 --- a/exportgood.php +++ b/exportgood.php @@ -1,197 +1,122 @@ . + /** * Export questions in the given category and which have been assigned a grade * above a certain level. * - * @author Martin Dougiamas, Howard Miller, Jamie Pratt and many others. - * {@link http://moodle.org} - * @license http://www.gnu.org/copyleft/gpl.html GNU Public License + * @package mod_qcreate + * @copyright 2008 Jamie Pratt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or late */ -require_once("../../config.php"); -require_once($CFG->dirroot."/question/editlib.php"); -require_once("export_good_questions_form.php"); +require_once(dirname(__FILE__) . '/../../config.php'); +require_once($CFG->dirroot . '/question/editlib.php'); +require_once($CFG->dirroot . '/mod/qcreate/export_good_questions_form.php'); -list($thispageurl, $contexts, $cmid, $cm, $qcreate, $pagevars) = question_edit_setup('export', true); +list($thispageurl, $contexts, $cmid, $cm, $qcreate, $pagevars) + = question_edit_setup('export', '/mod/qcreate/exportgood.php', true); +$qcreate->cmidnumber = $cm->id; -if (!has_capability('moodle/question:viewmine', $contexts->lowest()) && !has_capability('moodle/question:viewall', $contexts->lowest())) { - $capabilityname = get_capability_string('moodle/question:viewmine'); - print_error('nopermissions', '', '', $capabilityname); +if (!has_capability('moodle/question:viewmine', $contexts->lowest()) + && !has_capability('moodle/question:viewall', $contexts->lowest())) { + $capabilityname = get_capability_string('moodle/question:viewmine'); + print_error('nopermissions', '', '', $capabilityname); } -// get display strings -$txt = new object; -$txt->category = get_string('category', 'quiz'); -$txt->download = get_string('download', 'quiz'); -$txt->downloadextra = get_string('downloadextra', 'quiz'); -$txt->exporterror = get_string('exporterror', 'quiz'); -$txt->exportname = get_string('exportname', 'quiz'); -$txt->exportquestions = get_string('exportquestions', 'quiz'); -$txt->fileformat = get_string('fileformat', 'quiz'); -$txt->exportcategory = get_string('exportcategory', 'quiz'); -$txt->modulename = get_string('modulename', 'quiz'); -$txt->modulenameplural = get_string('modulenameplural', 'quiz'); -$txt->tofile = get_string('tofile', 'quiz'); - - - -// make sure we are using the user's most recent category choice +// Make sure we are using the user's most recent category choice. if (empty($categoryid)) { - $categoryid = $pagevars['cat']; + $categoryid = $pagevars['cat']; } -// ensure the files area exists for this course -make_upload_directory("$COURSE->id"); list($catid, $catcontext) = explode(',', $pagevars['cat']); -if (!$category = $DB->get_record("question_categories", array("id"=>$catid, 'contextid'=>$catcontext))) { - print_error('nocategory','quiz'); +if (!$category = $DB->get_record("question_categories", array("id" => $catid, 'contextid' => $catcontext))) { + print_error('nocategory', 'quiz'); } -/// Header -$strupdatemodule = has_capability('moodle/course:manageactivities', $contexts->lowest()) - ? update_module_button($cm->id, $COURSE->id, get_string('modulename', $cm->modname)) - : ""; -$navlinks = array(); -$navlinks[] = array('name' => get_string('modulenameplural', $cm->modname), 'link' => "$CFG->wwwroot/mod/{$cm->modname}/index.php?id=$COURSE->id", 'type' => 'activity'); -$navlinks[] = array('name' => format_string($qcreate->name), 'link' => "$CFG->wwwroot/mod/{$cm->modname}/view.php?id={$cm->id}", 'type' => 'title'); -$navlinks[] = array('name' => $txt->exportquestions, 'link' => '', 'type' => 'title'); -$navigation = build_navigation($navlinks); -print_header_simple($txt->exportquestions, '', $navigation, "", "", true, $strupdatemodule); - -$currenttab = 'edit'; -$mode = 'exportgood'; -include($CFG->dirroot."/mod/$cm->modname/tabs.php"); - -$exportfilename = default_export_filename($COURSE, $category); -$export_form = new question_export__good_questions_form($thispageurl, array('contexts'=>array($contexts->lowest()), 'defaultcategory'=>$pagevars['cat'], - 'defaultfilename'=>$exportfilename, 'qcreate'=>$qcreate)); - - -if ($from_form = $export_form->get_data()) { /// Filename - - - if (! is_readable($CFG->dirroot."/question/format/$from_form->format/format.php")) { - error("Format not known ($from_form->format)"); - } - - // load parent class for import/export - require_once($CFG->dirroot."/question/format.php"); - - // and then the class for the selected format - require_once($CFG->dirroot."/question/format/$from_form->format/format.php"); - - $classname = "qformat_$from_form->format"; - $qformat = new $classname(); - $qformat->setContexts($contexts->having_one_edit_tab_cap('export')); - - $questions = get_questions_category($category, true ); - if ($qcreate->graderatio != 100 && $from_form->betterthangrade != 0){ - //filter questions by grade - $qkeys = array(); - foreach ($questions as $question){ - $qkeys[] = $question->id; - } - $questionlist = join($qkeys, ','); - $sql = 'SELECT questionid, grade FROM '.$CFG->prefix.'qcreate_grades '. - 'WHERE questionid IN ('.$questionlist.') AND grade >= '.$from_form->betterthangrade; - if ($goodquestions = get_records_sql($sql)){ - foreach($questions as $zbkey => $question){ - if (!array_key_exists($question->id, $goodquestions)){ - unset($questions[$zbkey]); - } - } - } else { - $a = new object(); - $a->betterthan = $from_form->betterthangrade; - $a->categoryname = $category->name; - notice(get_string('noquestionsabove', 'qcreate', $a)); - } - } - - if (isset($from_form->naming)){ - if (isset($from_form->naming['firstname'])|| - isset($from_form->naming['lastname'])|| - isset($from_form->naming['username'])){ - $useridkeys = array(); - foreach ($questions as $question){ - $useridkeys[] = $question->createdby; - } - $useridlist = join($useridkeys, ','); - if (!$users = get_records_select('user', "id IN ($useridlist)")){ - $users = array(); - } - } - foreach ($questions as $question){ - $prefixes = array(); - if (isset($from_form->naming['other'])&& !empty($from_form->naming['othertext'])){ - $prefixes[] = $from_form->naming['othertext']; - } - if (isset($from_form->naming['firstname'])){ - $prefixes[] = isset($users[$question->createdby])?$users[$question->createdby]->firstname:''; - } - if (isset($from_form->naming['lastname'])){ - $prefixes[] = isset($users[$question->createdby])?$users[$question->createdby]->lastname:''; - } - if (isset($from_form->naming['username'])){ - $prefixes[] = isset($users[$question->createdby])?$users[$question->createdby]->username:''; - } - if (isset($from_form->naming['activityname'])){ - $prefixes[] = $qcreate->name; - } - if (isset($from_form->naming['timecreated'])){ - $prefixes[] = userdate($question->timecreated, get_string('strftimedatetimeshort')); - } - $prefixes[] = $question->name; - $question->name = join($prefixes, '-'); - } - } - - - $qformat->setQuestions($questions); - - $qformat->setCourse($COURSE); - - if (empty($from_form->exportfilename)) { - $from_form->exportfilename = default_export_filename($COURSE, $category); - } - $qformat->setFilename($from_form->exportfilename); - $qformat->setCattofile(!empty($from_form->cattofile)); - $qformat->setContexttofile(!empty($from_form->contexttofile)); - - if (! $qformat->exportpreprocess()) { // Do anything before that we need to - error($txt->exporterror, $thispageurl->out()); - } - - if (! $qformat->exportprocess()) { // Process the export data - error($txt->exporterror, $thispageurl->out()); - } - - if (! $qformat->exportpostprocess()) { // In case anything needs to be done after - error($txt->exporterror, $thispageurl->out()); - } - echo "
"; - - // link to download the finished file - $file_ext = $qformat->export_file_extension(); - if ($CFG->slasharguments) { - $efile = "{$CFG->wwwroot}/file.php/".$qformat->question_get_export_dir()."/$from_form->exportfilename".$file_ext."?forcedownload=1"; - } - else { - $efile = "{$CFG->wwwroot}/file.php?file=/".$qformat->question_get_export_dir()."/$from_form->exportfilename".$file_ext."&forcedownload=1"; - } - echo "

"; - echo "

$txt->downloadextra

"; - - print_continue($thispageurl->out()); - echo $OUTPUT->footer(); - exit; +// Header. +$PAGE->set_url($thispageurl); +$PAGE->set_title(get_string('exportquestions', 'qcreate')); +$PAGE->set_heading($COURSE->fullname); +echo $OUTPUT->header(); + +$exportfilename = question_default_export_filename($COURSE, $category); +$exportform = new qcreate_export_good_questions_form($thispageurl, + array('contexts' => array($contexts->lowest()), 'defaultcategory' => $pagevars['cat'], + 'defaultfilename' => $exportfilename, 'qcreate' => $qcreate)); + +if ($fromform = $exportform->get_data()) { // Filename. + $thiscontext = $contexts->lowest(); + + if (! is_readable($CFG->dirroot."/question/format/$fromform->format/format.php")) { + print_error('unknowformat', '', '', $fromform->format);; + } + $withcategories = 'nocategories'; + if (!empty($fromform->cattofile)) { + $withcategories = 'withcategories'; + } + $withcontexts = 'nocontexts'; + if (!empty($fromform->contexttofile)) { + $withcontexts = 'withcontexts'; + } + $betterthangrade = '0'; + if ($qcreate->graderatio != 100 && !empty($fromform->betterthangrade)) { + $betterthangrade = $fromform->betterthangrade; + } + $naming = ''; + if (isset($fromform->naming['other'])&& !empty($fromform->naming['othertext'])) { + $naming .= '1/' . $fromform->naming['othertext'] . '/'; + } else { + $naming .= '0/none/'; + } + $naming .= isset($fromform->naming['firstname']) ? '1/' : '0/'; + $naming .= isset($fromform->naming['lastname']) ? '1/' : '0/'; + $naming .= isset($fromform->naming['username']) ? '1/' : '0/'; + $naming .= isset($fromform->naming['activityname']) ? '1/' : '0/'; + $naming .= isset($fromform->naming['timecreated']) ? '1' : '0'; + // Load parent class for import/export. + require_once($CFG->dirroot . '/question/format.php'); + + // And then the class for the selected format. + require_once($CFG->dirroot . "/question/format/$fromform->format/format.php"); + + $classname = "qformat_$fromform->format"; + $qformat = new $classname(); + + $filename = question_default_export_filename($COURSE, $category) . + $qformat->export_file_extension(); + $urlbase = "$CFG->httpswwwroot/pluginfile.php"; + $exporturl = moodle_url::make_file_url($urlbase, + "/{$thiscontext->id}/mod_qcreate/export/{$categoryid}/{$fromform->format}/{$withcategories}" . + "/{$withcontexts}/{$betterthangrade}/{$naming}/{$filename}", true); + + echo $OUTPUT->box_start(); + echo get_string('yourfileshoulddownload', 'question', $exporturl->out()); + echo $OUTPUT->box_end(); + + $PAGE->requires->js_function_call('document.location.replace', array($exporturl->out(false)), false, 1); + + echo $OUTPUT->continue_button(new moodle_url('edit.php', $thispageurl->params())); + echo $OUTPUT->footer(); + exit; } -/// Display export form - - -print_heading_with_help($txt->exportquestions, 'export', 'quiz'); +// Display export form. +echo $OUTPUT->heading_with_help(get_string('exportquestions', 'qcreate'), 'exportquestions', 'question'); -$export_form->display(); +$exportform->display(); echo $OUTPUT->footer(); diff --git a/index.php b/index.php index 4aad237..8122db1 100644 --- a/index.php +++ b/index.php @@ -1,89 +1,54 @@ -. + /** * This page lists all the instances of qcreate in a particular course * - * @author - * @version $Id: index.php,v 1.1 2007/09/07 06:31:51 jamiesensei Exp $ - * @package qcreate - **/ - -/// Replace qcreate with the name of your module - - require_once("../../config.php"); - require_once("lib.php"); - - $id = required_param('id', PARAM_INT); // course - - if (! $course = $DB->get_record("course", array("id"=>$id))) { - error("Course ID is incorrect"); - } - - require_login($course->id); - - add_to_log($course->id, "qcreate", "view all", "index.php?id=$course->id", ""); - - -/// Get all required stringsqcreate - - $strqcreates = get_string("modulenameplural", "qcreate"); - $strqcreate = get_string("modulename", "qcreate"); - - -/// Print the header - - $navlinks = array(); - $navlinks[] = array('name' => $strqcreates, 'link' => '', 'type' => 'activity'); - $navigation = build_navigation($navlinks); - - print_header_simple("$strqcreates", "", $navigation, "", "", true, "", navmenu($course)); - -/// Get all the appropriate data - - if (! $qcreates = get_all_instances_in_course("qcreate", $course)) { - notice("There are no qcreates", "../../course/view.php?id=$course->id"); - die; - } - -/// Print the list of instances (your module will probably extend this) - - $timenow = time(); - $strname = get_string("name"); - $strweek = get_string("week"); - $strtopic = get_string("topic"); - - $table = new html_table(); - - if ($course->format == "weeks") { - $table->head = array ($strweek, $strname); - $table->align = array ("center", "left"); - } else if ($course->format == "topics") { - $table->head = array ($strtopic, $strname); - $table->align = array ("center", "left", "left", "left"); - } else { - $table->head = array ($strname); - $table->align = array ("left", "left", "left"); - } - - foreach ($qcreates as $qcreate) { - if (!$qcreate->visible) { - //Show dimmed if the mod is hidden - $link = "coursemodule\">$qcreate->name"; - } else { - //Show normal if the mod is visible - $link = "coursemodule\">$qcreate->name"; - } - - if ($course->format == "weeks" or $course->format == "topics") { - $table->data[] = array ($qcreate->section, $link); - } else { - $table->data[] = array ($link); - } - } - - echo "
"; - - echo html_writer::table($table); - -/// Finish the page - - echo $OUTPUT->footer(); + * @package mod_qcreate + * @copyright 2008 Jamie Pratt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or late + */ + +require_once(dirname(__FILE__) . '/../../config.php'); +require_once($CFG->dirroot . '/mod/qcreate/locallib.php'); + +$id = required_param('id', PARAM_INT); +$course = $DB->get_record('course', array('id' => $id), '*', MUST_EXIST); +require_login($course); +$PAGE->set_url('/mod/qcreate/index.php', array('id' => $id)); +$PAGE->set_pagelayout('incourse'); + +$coursecontext = context_course::instance($id); +$params = array( +'context' => $coursecontext +); +$event = \mod_qcreate\event\course_module_instance_list_viewed::create($params); +$event->trigger(); + +$context = context_course::instance($course->id); +$qcreateobj = new qcreate($context, null, $course); + +// Print the header. +$PAGE->navbar->add($qcreateobj->get_module_name_plural()); +$PAGE->set_title($qcreateobj->get_module_name_plural()); +$PAGE->set_heading($course->fullname); +echo $OUTPUT->header(); +echo $OUTPUT->heading(format_string($qcreateobj->get_module_name_plural())); + +require_capability('mod/qcreate:view', $context); + +// Get the qcreate to render the page. +echo $qcreateobj->view('viewcourseindex'); diff --git a/lang/en/help/qcreate/automaticmanualgrading.html b/lang/en/help/qcreate/automaticmanualgrading.html deleted file mode 100644 index 7413b07..0000000 --- a/lang/en/help/qcreate/automaticmanualgrading.html +++ /dev/null @@ -1,81 +0,0 @@ -

Ratio of Automatic / Manual Grading

- -

Here you specify how the total Grade will be divided: the percentage for the Automatic grade is on the left, and the Manual grade is on the right.

- -

The automatic grade is the grade 'given' by the system for simply making a question. So, for example if you require students to make 10 questions, and you assign 100% for Automatic grading, a student that makes 7 questions will get 70% for the activity. On the other hand, if you asign 50% for Automatic grading, a student that creates 7 questions will get 35% for the activity (7/10 x 50% = 35%).

- -

Note that the automatic grade is no guarantee of quality. The Automatic grade is best used in the following situations:

- -

1) ...when you want to reward students for simply making the correct number of corrections. In this case, it is probably best to keep the weight for the automatic grade low.

- -

2) ...when you are confident that the quality of the questioins your students produce is not in doubt. For example, your students if your students are both experienced at creating questions with Moodle, and experienced at writing questions in general then a high weight or even 100% for the autimatic grade may be appropriate.

- - -

The Manual grade is the grade that you give for each question. So, if you asign 50% for Automatic grading, the other half of the grade will be your manually assigned grade. So, if a student creates 7 questions, they will get 35% Automatic grade, and if you grade each question as perfect they will get a total of 70% (35% automatic + 35% manual). On the other hand, if the questions have problems and you only give 60% for each question of the 7 questions, the student's total grade will be 56%. Note, it is possible to give different grades to each question. The calculation is as follows:

- -

Automatic grade = 7/10 x 50% = 35%

- -

Manual grade = (60% + 60% +60% + 60% + 60% + 60% + 60%)/7 x 35% = 21%

- -

Total = 35% + 21% = 56%

- -

-Another example:
-Here, students are required to make 5 questions. The Ratio of Automatic -/ Manual Grading is 20%/80%.
-A student creates all 5 questions, so gets the full 20% for the automatic grade, but the quality of each question is not perfect. As a result the student's grades could look somehting like this:
-

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

-
Automatic grade (20%)Manual grade (80%)
Question 1100%20%
Question 2100%60%
Question 3100%40%
Question 4100%80%
Question 5100%90%
sub-total100%58%
Total -for activity20%46.4%
= 66.4%
- - - -

\ No newline at end of file diff --git a/lang/en/help/qcreate/grade.html b/lang/en/help/qcreate/grade.html deleted file mode 100644 index c407ff7..0000000 --- a/lang/en/help/qcreate/grade.html +++ /dev/null @@ -1,3 +0,0 @@ -

Grade

- -

This is the total grade for the activity that is reported to the gradebook. It is possible to set 'No grade' so that the the activity is not graded.

\ No newline at end of file diff --git a/lang/en/help/qcreate/gradedquestiontypes.html b/lang/en/help/qcreate/gradedquestiontypes.html deleted file mode 100644 index 8f2dea2..0000000 --- a/lang/en/help/qcreate/gradedquestiontypes.html +++ /dev/null @@ -1,17 +0,0 @@ -

Graded Question Types

- -

Here you specify the type of questions that need to be made. If you select "Allow all question types", then students can make any type of question up to the total specified in "Total Questions Graded".

- -

If your students already know how to make questions, and they know the differences between the question types, it is safe to select "Allow all question types". However, if they are new to question creation, it is probably best to specify here the type of questions you want students to create.

- -

Example 1:

-

If you select '4' in "Total Questions Graded", and check "Allow all question types" in "Graded Question Types", students will be able to create any type of questions; they could be 4 of the same type or a combination of question types. Note, the "Will Grade Minimum Number of Questions of Type" menus do not need to be set at all.

- -

Example 2:

-

If you select '4' in "Total Questions Graded", and in "Graded Question Types" you check "Multiple Choice", students will have to create 4 Multiple choice questions. Again, the "Will Grade Minimum Number of Questions of Type" menus do not need to be set at all.

- -

Example 3:

-

If you select '4' in "Total Questions Graded", and check "Matching" and "Multiple Choice" in "Graded Question Types", students will have to make a total of 4 questions; they could be all Matching, all Multiple Choice, or a combination of the two.

- -

Note, if you want to to specify exactly what type of questions and how many of each type students should create, you need to indicate these parameters in the "Will Grade Minimum Number of Questions of Type" menus. -

\ No newline at end of file diff --git a/lang/en/help/qcreate/index.html b/lang/en/help/qcreate/index.html deleted file mode 100644 index e07e4ca..0000000 --- a/lang/en/help/qcreate/index.html +++ /dev/null @@ -1,11 +0,0 @@ -

Question Creation activity module

- diff --git a/lang/en/help/qcreate/minimumquestions.html b/lang/en/help/qcreate/minimumquestions.html deleted file mode 100644 index 84b01fc..0000000 --- a/lang/en/help/qcreate/minimumquestions.html +++ /dev/null @@ -1,20 +0,0 @@ -

Minimum Questions

- -

In this menu you can specify how many questions of a specific type students should create.

- -

Example:

-

You want students to create a total of 10 questions, 2 matching, 3 multiple choice, 2 Short Answer, and then any combination of these three types.

- -

To do this:

-

1) in the "Total Questions Graded" menu select 10

-

2) in "Graded Question Types" check "Matching", "Multiple Choice" and "Short Answer"

-

3) In the first "Will Grade Minimum Number of Questions of Type" area:

-

...in the Question Type menu select "Matching"

-

...and in Minimum Questions menu select 2

-

4) In the second "Will Grade Minimum Number of Questions of Type" area:

-

...in the Question Type menu select "Multiple Choice"

-

...and in Minimum Questions menu select 3 -

5) In the third "Will Grade Minimum Number of Questions of Type" area:

-

...in the Question Type menu select "Short Answer"

-

...and in Minimum Questions menu select 2 -

\ No newline at end of file diff --git a/lang/en/help/qcreate/mods.html b/lang/en/help/qcreate/mods.html deleted file mode 100644 index 0d9852b..0000000 --- a/lang/en/help/qcreate/mods.html +++ /dev/null @@ -1,9 +0,0 @@ -

qcreate Question Creation activity module

-
-

The Question Creation activity module makes it possible for teachers to let or require students to create questions using the Moodle question creation engine. These creations can subsequently be edited by a teacher and added to Moodle quizzes.

- -

As a non-graded activity, teachers can make it possible for students to make any number of questions. These questions can be imported into quizzes by the teacher. In this way, teachers can encourage students' autonomous learning. Students will create questions about a certain topic or sub-set of knowledge, knowing that their questions will appear in a (graded) quiz.

- -

As a graded activity, teachers can require students to make specific question types and a specific number of these types. Furthermore, these questions can be graded both automatically or manually or a combination of the two. Automatic grading takes no account of the quality of questions, it simply rewards students for creating questions. Manual grading on the other hand is used to evaluate the content and quality of questions.

-
- diff --git a/lang/en/help/qcreate/questiontype.html b/lang/en/help/qcreate/questiontype.html deleted file mode 100644 index ad9c9e5..0000000 --- a/lang/en/help/qcreate/questiontype.html +++ /dev/null @@ -1,17 +0,0 @@ -

Question Type

- -

In this menu you can specify what type of question students should create.

- -

Example:

-

You want students to create a total of 5 questions, 2 matching and 3 multiple choice.

- -

To do this:

-

1) in the "Total Questions Graded" menu select 5

-

2) in "Graded Question Types" check "Matching" and "Multiple Choice"

-

3) In the first "Will Grade Minimum Number of Questions of Type" area:

-

...in the Question Type menu select "Matching"

-

...and in Minimum Questions menu select 2

-

4) In the second "Will Grade Minimum Number of Questions of Type" area:

-

...in the Question Type menu select "Multiple Choice"

-

...and in Minimum Questions menu select 3 -

\ No newline at end of file diff --git a/lang/en/help/qcreate/studentquestionaccess.html b/lang/en/help/qcreate/studentquestionaccess.html deleted file mode 100644 index 809c6e4..0000000 --- a/lang/en/help/qcreate/studentquestionaccess.html +++ /dev/null @@ -1,11 +0,0 @@ -

Student question access

- -

Use this menu to define what access rights students have over the questions they create. There are four types:

- -

1) create only: this is the strictest; once a student clicks on the [Save question] button, they will not be able to edit the question again. - -

2) preview: ???

- -

3) preview and view / save as new: students can do just about everyhing, except edit already created creations. This also means that they can't delete questions they have already made.

- -

4) preview, view / save as new and edit / delete: this is the higest level access that students can have. For an activity in which you want students to go through an ioterative process of question creation, evaluation, and improvemnt, this is proabably the best setting.

diff --git a/lang/en/help/qcreate/timeopen.html b/lang/en/help/qcreate/timeopen.html deleted file mode 100644 index 08baa6e..0000000 --- a/lang/en/help/qcreate/timeopen.html +++ /dev/null @@ -1,5 +0,0 @@ -

Opening and closing the question creation activity

- -

You can specify times when the question creation activity is accessible for people to make questions.

- -

Before the opening time, and after the closing time, students will be unable to create questions.

diff --git a/lang/en/help/qcreate/totalquestionsgraded.html b/lang/en/help/qcreate/totalquestionsgraded.html deleted file mode 100644 index 599cc81..0000000 --- a/lang/en/help/qcreate/totalquestionsgraded.html +++ /dev/null @@ -1,4 +0,0 @@ -

Total Questions Graded

- -

This is the total number of questions that students must create.

-

This number must be equal to or greater than the minimum number of questions required. For example, if you want to specify that students create 2 Matching type questions and 2 Multiple Choice type questions, you will have to select 4 or higher in the Total Questions Graded menu. If you select a number higher than 4, then students will be able to create other questions in excess of the two Matching and two Multiple Choice types. The type of the extra questions created will depend on the types allowed in "Graded Question Types".

\ No newline at end of file diff --git a/lang/en/qcreate.php b/lang/en/qcreate.php index 8f4e810..0882c34 100644 --- a/lang/en/qcreate.php +++ b/lang/en/qcreate.php @@ -1,103 +1,224 @@ . + +/** + * English language strings for the qcreate module + * + * @package mod_qcreate + * @copyright 2008 Jamie Pratt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or late + */ $string['activityclosed'] = 'Activity is closed.'; $string['activitygrade'] = 'You have been awarded a total grade of {$a->grade} / {$a->outof} for this activity.'; $string['activityname'] = 'Activity name'; $string['activityopen'] = 'Activity is open.'; -$string['addminimumquestionshdr'] = 'Will Grade Minimum Number of Questions of Type (optional)'; -$string['allowall'] = 'Allow all question types'; +$string['addminimumquestionshdr'] = 'Required Number of Questions of Type (optional)'; +$string['addmorerequireds'] = 'Add more required question types'; $string['allandother'] = 'To allow all question types, check \'{$a}\' and don\'t check anything else.'; -$string['allquestions'] = '0 - (All questions)'; +$string['allowall'] = 'Allow all question types'; $string['allowedqtypes'] = 'Graded Question Types'; +$string['allowedqtypes_help'] = 'Here you specify the type of questions that are allowed. If you select \'Allow all question types\', then students can make any type of question up to the total specified in \'Total Questions Graded\'.'; +$string['allquestions'] = '0 - (All questions)'; $string['alreadydone'] = 'You\'ve done {$a} questions of this type.'; $string['alreadydoneextra'] = 'You\'ve done {$a} extra questions of this type.'; $string['alreadydoneextraone'] = 'You\'ve done one extra question of this type.'; $string['alreadydoneone'] = 'You\'ve done one question of this type.'; $string['and'] = '{$a} AND'; +$string['andmorenewquestions'] = 'and {$a} more new question(s).'; +$string['attemptqcreatenow'] = 'Attempt question creation now'; +$string['availability'] = 'Availability'; $string['automaticgrade'] = 'You have been awarded an automatic grade of {$a->grade} / {$a->outof} for these questions, since you have done {$a->done} of {$a->required} required questions.'; $string['betterthangrade'] = 'Questions with manual grade equal or better than'; $string['clickhere'] = 'Click here to create a question of \'{$a}\' type.'; $string['clickhereanother'] = 'Click here to create another question of \'{$a}\' type.'; $string['close'] = 'Close activity'; +$string['closebeforeopen'] = 'Could not update the question creation activity. You have specified a close date before the open date.'; +$string['closeon'] = 'Close on'; $string['comma'] = '{$a},'; $string['comment'] = 'Comment'; +$string['completionquestions'] = 'Student must create:'; +$string['completionquestionsdesc'] = 'Student must create at least {$a} question/questions'; +$string['completionquestions_help'] = 'If enabled, this activity is considered complete when the student has created that number of questions (graded or not).'; +$string['completionquestionsgroup'] = 'Require questions'; $string['confirmdeletequestion'] = 'Are you sure you want to delete this question?'; $string['creating'] = 'Creating Questions'; +$string['createdquestions'] = 'Created questions'; +$string['deletegrades'] = 'Delete questions created and manual grades'; $string['donequestionno'] = 'You\'ve completed {$a->done} of {$a->no} questions of type \'{$a->qtypestring}\'. These are listed below.'; -$string['exportgood'] = 'Export Good'; +$string['eventeditpageviewed'] = 'Question creation edit page viewed'; +$string['eventoverviewviewed'] = 'Question creation overview viewed'; +$string['eventquestiongraded'] = 'Question graded'; +$string['eventquestionregraded'] = 'Question regraded'; +$string['exportgood'] = 'Export good questions'; $string['exportgoodquestions'] = 'Export Questions Which Have Been Graded Above a Selected Grade'; $string['exportnaming'] = 'Prefix Exported Question Names With'; +$string['exportquestions'] = 'Export questions to file'; $string['exportselection'] = 'Export only these questions'; $string['extraqdone'] = 'You\'ve done one extra question.'; $string['extraqsdone'] = 'You\'ve done {$a->extraquestionsdone} extra questions.'; $string['extraqgraded'] = 'One question of any of the types below will be graded'; $string['extraqsgraded'] = '{$a->extrarequired} questions of any of the types below will be graded'; +$string['finalgrade'] = 'Final grade for {$a}'; $string['fullstop'] = '{$a}.'; +$string['grade'] = 'Grade'; +$string['grade_help'] = 'This is the total grade for the activity that is reported to the gradebook. It is possible to set \'No grade\' so that the the activity is not graded.'; $string['gradeallautomatic'] = 'Grades are completely automatic, no manual grading.'; $string['gradeallmanual'] = 'Grading is completely manual, no automatic grading.'; -$string['grademixed'] = 'Grading is {$a->automatic}%% automatic, {$a->manual}%% manual.'; +$string['gradeavailabletext'] = '{$a->username} has graded your created question \'{$a->questionname}\' for \'{$a->qcreate}\' + +You can see it on the activity page: + + {$a->url}'; +$string['gradeavailablehtml'] = '{$a->username} has graded your created question +\'{$a->questionname}\' for \'{$a->qcreate}\'

+You can see it on the activity page.'; +$string['gradeavailablesmall'] = '{$a->username} has graded your created question for {$a->qcreate}'; +$string['gradecomment'] = 'Comment for question \'{$a->qname}\' created by {$a->user}'; $string['graded'] = 'Graded'; +$string['grademixed'] = 'Grading is {$a->automatic}%% automatic, {$a->manual}%% manual.'; $string['gradequestions'] = 'Grade questions'; $string['grading'] = 'Grading'; $string['graderatio'] = 'Ratio of Automatic / Manual Grading'; +$string['graderatiois'] = 'Ratio of Automatic / Manual Grading: {$a}'; +$string['graderatio_help'] = 'Here you specify how the total Grade will be divided: the percentage for the Automatic grade is on the left, and the Manual grade is on the right. The automatic grade is the grade \'given\' by the system for simply making a question.'; $string['graderatiooptions'] = '{$a->automatic} / {$a->manual}'; +$string['gradesdeleted'] = 'Questions and manual grades removed'; +$string['indicator:cognitivedepth'] = 'Question creation cognitive'; +$string['indicator:cognitivedepth_help'] = 'This indicator is based on the cognitive depth reached by the student in a Question creation activity.'; +$string['indicator:socialbreadth'] = 'Question creation social'; +$string['indicator:socialbreadth_help'] = 'This indicator is based on the social breadth reached by the student in a Question creation activity.'; $string['intro'] = 'Introduction'; +$string['invalidqcreatezid'] = 'Invalid question creation ID'; $string['manualgrade'] = 'A teacher has awarded you a grade of {$a->grade} / {$a->outof} for the questions you have done.'; $string['marked'] = 'Marked'; +$string['messageprovider:gradernotification'] = 'Created question notification'; +$string['messageprovider:studentnotification'] = 'Graded question notification'; $string['minimumquestions'] = 'Minimum Questions'; +$string['minimumquestions_help'] = 'In this menu you can specify how many questions of a specific type students should create.'; $string['modulename'] = 'Question Creation'; -$string['modulenameplural'] = 'Question Creation'; +$string['modulenameplural'] = 'Question Creations'; +$string['modulename_help'] = 'The question creation activity enables a teacher to ask students to create questions, number of required quesions, avaiblable question types and number of required questions for each question type can be specified.'; +$string['modulename_link'] = 'mod/qcreate/view'; $string['needtoallowqtype'] = 'You need to allow question type \'{$a}\' if you want to require a minimum no of questions of this type need to be created.'; $string['needtoallowatleastoneqtype'] = 'You need to allow at least one question type'; $string['needsgrading'] = 'Needs grading'; $string['needsregrading'] = 'Needs regrading'; +$string['newquestions'] = 'New questions created'; $string['noofquestionstotal'] = 'Total Questions Graded'; +$string['noofquestionstotal_help'] = 'This is the total number of questions that students must create. This number must be equal to or greater than the minimum number of questions required'; +$string['noquestions'] = 'No questions have been created.'; $string['noquestionsabove'] = 'There are no questions with a manual grade set above {$a->betterthan} in category \'{$a->categoryname}\'.'; $string['notgraded'] = 'Not graded yet'; +$string['nothingtosave'] = 'Nothing to save'; +$string['notifications'] = 'Notifications'; $string['nousers'] = 'No users enrolled in this course / in this group.'; $string['open'] = 'Open activity'; +$string['openafterclose'] = 'Could not update the question creation activity. You have specified an open date after the close date.'; +$string['openclosedatesupdated'] = 'Question creation activity open and close dates updated'; +$string['openon'] = 'Open on'; +$string['open_help'] = 'You can specify times when the question creation activity is accessible for people to make questions. Before the opening time, and after the closing time, students will be unable to create questions.'; $string['openmustbemorethanclose'] = 'Time to open activity must be before time to close activity'; $string['overview'] = 'Overview'; $string['pagesize'] = 'Number of questions to show per page'; +$string['pluginadministration'] = 'Question creation Administration'; +$string['pluginname'] = 'Question creation'; +$string['preview'] = 'Preview'; +$string['previewquestion'] = 'Preview question'; +$string['privacy:graderpath'] = 'questionsgraded'; +$string['privacy:gradepath'] = 'grade'; +$string['privacy:metadata:core_question'] = 'The question creation activity stores created questions information in the core_question subsystem'; +$string['privacy:metadata:qcreate_grades'] = 'Question creation local grades'; +$string['privacy:metadata:qcreate_grades:grade'] = 'Question creation local grade'; +$string['privacy:metadata:qcreate_grades:gradecomment'] = 'Question creation teacher comment'; +$string['privacy:metadata:qcreate_grades:qcreateid'] = 'Question creation instance identifier'; +$string['privacy:metadata:qcreate_grades:questionid'] = 'Question graded identifier'; +$string['privacy:metadata:qcreate_grades:teacher'] = 'Grading teacher identifier'; +$string['privacy:metadata:qcreate_grades:timemodified'] = 'Time when the grade or comment was last modified'; +$string['privacy:studentpath'] = 'questionscreated'; $string['qcreate'] = 'qcreate'; +$string['qcreate:addinstance'] = 'Add a question creation instance'; +$string['qcreate:grade'] = 'Grade question'; +$string['qcreate:receivegradernotifications'] = 'Receive grader notifications'; +$string['qcreate:receivestudentnotifications'] = 'Receive student notifications'; +$string['qcreate:submit'] = 'Submit question'; +$string['qcreate:view'] = 'View question creation activity'; +$string['qcreateeventcloses'] = '{$a} closes'; +$string['qcreateeventopens'] = '{$a} opens'; $string['qcreateopens'] = 'Question creation activity opens'; $string['qcreatecloses'] = 'Question creation activity closes'; $string['qsgraded'] = '{$a} question(s) of any of these types will be graded :'; $string['qtype'] = 'Question Type'; -$string['requiredquestions'] = 'Required Questions to Create'; +$string['qtype_help'] = 'In this menu you can specify what type of question students should create.'; +$string['questiongrade'] = 'Grade for question \'{$a->qname}\' created by {$a->user}'; +$string['questions'] = 'question(s) to complete this activity'; +$string['questionscreated'] = '{$a} question(s) created'; +$string['questiontogradetext'] = '{$a->username} has created a new question \'{$a->questionname}\' +for \'{$a->qcreate}\' at {$a->timeupdated} + +It is available here: + + {$a->url}'; +$string['questiontogradehtml'] = '{$a->username} has created a new question \'{$a->questionname}\' +for \'{$a->qcreate}\' at {$a->timeupdated}

+It is available on the web site.'; +$string['questiontogradesmall'] = '{$a->username} has created a new question for {$a->qcreate}.'; +$string['requiredquestions'] = 'Required Questions to Create:'; $string['requiredanyplural'] = '{$a->no} questions of any question type are required'; $string['requiredanysingular'] = 'A question of any question type is required'; $string['requiredplural'] = '{$a->no} questions of \'{$a->qtypestring}\' question type are required'; $string['requiredsingular'] = 'A question of \'{$a->qtypestring}\' question type is required'; $string['saveallfeedback'] = 'Save all my feedback'; $string['saveallfeedbackandgrades'] = 'Save all grades & feedback'; +$string['search:activity'] = 'Question creation activities'; $string['selectone'] = 'Select One...'; +$string['sendstudentnotifications'] = 'Notify students'; +$string['sendstudentnotifications_help'] = 'If enabled, students receive a message when one of their questions is graded.'; +$string['sendgradernotifications'] = 'Notify graders'; +$string['sendgradernotifications_help'] = 'If enabled, graders (usually teachers) receive a message whenever a student create a new question. Message methods are configurable.'; $string['show'] = 'Show '; $string['showgraded'] = 'questions that don\'t need grading'; $string['showneedsregrade'] = 'questions that need regrading'; $string['showungraded'] = 'questions that need grading'; $string['specifictext'] = 'Specific text'; $string['studentqaccess'] = 'To own questions'; +$string['studentqaccess_help'] = 'Use this menu to define what access rights students have over the questions they create.'; $string['studentaccessheader'] = 'Student question access'; $string['studentaccessaddonly'] = 'create only'; $string['studentaccesspreview'] = 'preview'; $string['studentaccesssaveasnew'] = 'preview and view / save as new'; $string['studentaccessedit'] = 'preview, view / save as new and edit / delete'; +$string['studentshavedone'] = 'Students have created {$a} question(s).'; +$string['synchronizeqaccesstask'] = 'Synchronize students questions access capabilities'; $string['timecreated'] = 'Time question created'; $string['timenolimit'] = 'No time limits set.'; $string['timeopen'] = 'Activity opens on {$a->timeopen}'; -$string['timeopened'] = 'Activity opened on $a.'; +$string['timeopened'] = 'Activity opened on {$a}.'; $string['timeclose'] = 'Activity closes on {$a->timeclose}'; -$string['timeclosed'] = 'Activity closed on $a.'; +$string['timeclosed'] = 'Activity closed on {$a}.'; $string['timeopenclose'] = 'Activity is open from {$a->timeopen} to {$a->timeclose}'; -$string['timewillopen'] = 'Activity will open on $a.'; -$string['timewillclose'] = 'Activity will close on $a.'; +$string['timewillopen'] = 'Activity will open on {$a}.'; +$string['timewillclose'] = 'Activity will close on {$a}.'; $string['timing'] = 'Activity timing'; -$string['todoquestionno'] = 'You\'ve still to do $a->stillrequiredno question(s) of type \'{$a->qtypestring}\'.'; +$string['todoquestionno'] = 'You\'ve still to do {$a->stillrequiredno} question(s) of type \'{$a->qtypestring}\'.'; $string['totalgrade'] = 'Total grade'; +$string['totalgradeis'] = 'Total grade: {$a}'; $string['totalrequiredislessthansumoftotalsforeachqtype'] = 'Total required is less than the sum of the totals graded for each question type.
It must be equal or more!'; +$string['youhavedone'] = 'You have created {$a} question(s).'; +$string['updategradestask'] = 'Update question creation activity grades'; $string['username'] = 'User name of creator of question'; -$string['youvesetmorethanonemin'] = 'You\'ve set more than one minimum number of questions for question type \'$a\'!'; -?> +$string['viewgrading'] = 'Grade questions'; +$string['youvesetmorethanonemin'] = 'You\'ve set more than one minimum number of questions for question type \'{$a}\'!'; diff --git a/lang/es/help/qcreate/automaticmanualgrading.html b/lang/es/help/qcreate/automaticmanualgrading.html deleted file mode 100644 index d378538..0000000 --- a/lang/es/help/qcreate/automaticmanualgrading.html +++ /dev/null @@ -1,91 +0,0 @@ -

Ratio calidicación Automática / Manual

- -

Aquí s edebe especificar cómo se calculará la puntuación total: el porcentaje de puntuación Automática es el primero -(izquierda), el de la calificación Manual es el segundo (derecha).

- -

La Puntuación Automática es la nota otorgada por el sistema por simplemente introducir una pregunta. -Depende del número de preguntas realizadas sobre el total requerido. Así, por ejemplo, si requiere que se generen -10 preguntas y especifica un 100% de puntuación automática, un estudiante que crea 7 preguntas recibirá -el 70% de la nota máxima. Por otra parte, si especifica un 50% de puntuación Automática, un estudiante realize las -7 preguntas obtendrá automáticamente el 35% de la nota máxima (7/10 x 50% = 35%).

- -

Obviamente, la nota automática no contieen ninguan garantía de calidad, sólo el número de preguntas realizadas. -La puntuación automática es conveniente en algunas situaciones:

-

1) ...Cuando el énfasis consiste en estimular la participación, más que en generar una evaluación

- -

2) ...Cuando existe confianza en la calidad media de los textos de los estudiantes..

- -

En otros casos es probablemente preferible mantener bajo el porcentaje de puntuación Automática.

- -

La calificación Manual depende de la evaluación que usted, el profesor, realice al revisar explícitamente -cada pregunta introducida, lo que supone una mucho mayor dedicación. Si especifica un 50% de ratio Automática/Manual, -la mitad de la nota final dependerá de su calificación expresa. Obviamente se puede puntuar diferentemente cada pregunta, -la calificación final será calculada a partir de la media de las calificaiones manuales, multiplicada por su porcentaje de ratio. -Así, siguiendo el ejemplo de 10 preguntas requeridas, con una ratio Automática/Manual del 50% y un estudiante que realiza -7 preguntas, su calificación final será calculada de esta manera:

- -

Puntuación Automática = 7/10 x 50% = 35%

- -

Puntuación Manual = (60% + 70% +50% + 80% + 60% + 40% + 60%)/7 x 35% = 21%

- -

Total = 35% + 21% = 56%

- -

-Otro ejemplo:
-Aquí lso estudaintes deben crear al menos 5 preguntas. La ratio Automática/Manual es 20%/80%.
-Un estudiante introduce las 5 preguntas, por lo tanto recibe el 20% de la nota final automáticamente. Pero -la caliad de cada pregunta introducida es dispar. Como resultado la calificación del estudiante podría -aparecer como algo parecido a esto::
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

-
Calificación Automática(20%)Calificación Manual(80%)
Pregunta 1100%20%
Pregunta 2100%60%
Pregunta 3100%40%
Pregunta 4100%80%
Pregunta 5100%90%
sub-total100%58%
Total -for activity20%46.4%
= 66.4%
- -

\ No newline at end of file diff --git a/lang/es/help/qcreate/grade.html b/lang/es/help/qcreate/grade.html deleted file mode 100644 index 28f0e88..0000000 --- a/lang/es/help/qcreate/grade.html +++ /dev/null @@ -1,4 +0,0 @@ -

Calificación

- -

Esta es la Calificación total que se transladará al libro de notas. Es posible especificar "Sin calificación", -de forma que la actividad no será evaluable.

\ No newline at end of file diff --git a/lang/es/help/qcreate/gradedquestiontypes.html b/lang/es/help/qcreate/gradedquestiontypes.html deleted file mode 100644 index c5da62d..0000000 --- a/lang/es/help/qcreate/gradedquestiontypes.html +++ /dev/null @@ -1,28 +0,0 @@ -

Tipos de preguntas requeridos

- -

Aquí puede especificar los tipos (formatos) de preguntas que deben ser introducidas. Si selecciona "Permitir todos los tipos" -entonces los estudiantes podrán realizar cualquier tipo hasta el número de preguntas total especificado.

- -

Si sus estudiantes ya conocen cómo usar el interfaz para crear preguntas y conocen las diferencias entre los distintos -tipos de preguntas entonces está justificado y es seguro seleccionar "Permitir todos los tipos". Sin embargo, si éste no es el -caso, probablemente es mejor especificar aquí un tipo (formato) concreto de pregunat que deseda que realicen los estudiantes. -Se pueden ir introduciendo los distintos tipos por etapas.

- -

Ejemplo 1:

-

Especifica '4' como "Total de preguntas requeridas" y en "Tipos de preguntas requeridos" marca "Opción múltiple". -Los estudiantes tendrán que introducir preguntas forzosamente del tipo "opción múltiple", no tendrán posibilidad de -escoger otro tipo. Los menús de más abajo de "Nº mínimo de preguntas a calificar del Tipo" no necesitan ser utilizados.

- -

Ejemplo 2:

-

Especifica '4' como "Total de preguntas requeridas" y en "Tipos de preguntas requeridos" marca "Opción múltiple" -y "Emparejamiento". Los estudiantes podrán realizar todas de tipo "Opción múltiple" o todas de tipo "Emparejamiento", -o una mezcla unas de un tipo y otras del otro tipo.
Si desea especificar exactamente cuántas preguntas de cada tipo -deben ser introducidas debe utilizar los menús "Nº mínimo de preguntas a calificar del Tipo" de más abajo. -Puede añadir más si lo necesita.

- -

Ejemplo 3:

-

Especifica '4' como "Total de preguntas requeridas" y marca "Permitir todos los tipos de preguntas" en -"Tipos de preguntas requeridas", los estudiantes podrán crear preguntas de cualquier tipo. Pueden ser todas del mismo -tipo o cualquier combinación de tipos. Tenga en cuenta que los menús "Nº mínimo de preguntas a calificar del Tipo" -pueden usarse para especificar una combinación mínima de tipos de preguntas. O pueden deshabilitarse para no exigir -ningún tipo en particular.

diff --git a/lang/es/help/qcreate/index.html b/lang/es/help/qcreate/index.html deleted file mode 100644 index 363c201..0000000 --- a/lang/es/help/qcreate/index.html +++ /dev/null @@ -1,11 +0,0 @@ -

Módulo de actividad Crear Preguntas

- diff --git a/lang/es/help/qcreate/minimumquestions.html b/lang/es/help/qcreate/minimumquestions.html deleted file mode 100644 index 4a94733..0000000 --- a/lang/es/help/qcreate/minimumquestions.html +++ /dev/null @@ -1,21 +0,0 @@ -

Nº mínimo de preguntas

- -

En eset menú puede especificar cuantas preguntas de un tipo (formato) concreto debería introducir cada estudiante.

- -

Ejemplo: -

Usted desea que cada estudiante introduzcan un total de 10 preguntas, 3 de "Opción múltiple", 2 de "Respuesta corta", -2 de ""Emparejamiento" y además otras 3 de cualquier otra combinación de esso tipos.

- -

Configurelo de esta forma:

-

1) en el menú "Total de preguntas requeridas" seleccione 10

-

2) en "Tipos de preguntas requeridos" marque las tres casillas "Opción múltiple", "Respuesta corta" y "Emparejamiento"

-

3) En el primer bloque "Nº mínimo de preguntas a calificar del Tipo" :

-

...en el menú Tipo de Pregunta seleccione "Opción múltiple"

-

...y en Preguntas mínimas seleccione 3

-

4) En el segundo bloque "Nº mínimo de preguntas a calificar del Tipo":

-

...en el menú Tipo de Pregunta seleccione "Respuesta corta"

-

...y en Preguntas mínimas seleccione 2 -

5) En el tercer bloque "Nº mínimo de preguntas a calificar del Tipo" :

-

...en el menú Tipo de Pregunta seleccione "Emparejamiento"

-

...y en Preguntas mínimas seleccione 2 -

\ No newline at end of file diff --git a/lang/es/help/qcreate/mods.html b/lang/es/help/qcreate/mods.html deleted file mode 100644 index 17b04b7..0000000 --- a/lang/es/help/qcreate/mods.html +++ /dev/null @@ -1,23 +0,0 @@ -

qcreate Módulo de actividad Crear Preguntas

-
-

El módulo de actividad Crear Preguntas hace posible a los profesores el permitir alos estudiantes que generen -preguntas de examen usando el interfaz de creación y el motor de almacenamiento de preguntas de Moodle. Las preguntas -generadas pueden ser evaluadas por el profesor con lo que la actividad se convierte en otra forma de valorar las habilidades -de los estudiantes.

- -

Además, si así se juzga oportuno, las preguntas deseadas pueden transladarse a otras categorías y usarse -en los Cuestionarios de Moodle junto con las generadas por los profesores. Esta adición NO es automática, sino -que debe ser realizada manualmente por el profesor.

- -

Usándola como actividad calificable los profesors pueden establecer un Tema y especificar un número -mínimo de preguntas de formatos concretos a ser generadas por los estudiantes. Esta preguntas son evaluadas -en una combinación de puntuación automática y revisión manual por el profesor considerando así bien la adquisición -de conocimientos sobre el tema de las preguntas mostrado por los estudiantes, bien su habilidad en la tarea -concreta de generar instrumentos para evaluar a otros..

- -

Usándola como actividad no evaluable los profesores pueden promocionar el aprendizaje autónomo. -Los estudiantes son "recompensados" por revisar un tema o campo de conocimientos lo suficiente como -para generar preguntas sobre ese tema con la ventaja de que sus propias preguntas aparezcan en los Cuestionarios usados.

- -
- diff --git a/lang/es/help/qcreate/questiontype.html b/lang/es/help/qcreate/questiontype.html deleted file mode 100644 index bfd3fba..0000000 --- a/lang/es/help/qcreate/questiontype.html +++ /dev/null @@ -1,17 +0,0 @@ -

Tipo de pregunta

- -

En este menú puede especificar qué tipo (formato) de pregunta concreto desea que generen los estudiantes.

- -

Ejemplo: -

Usted queire que los estudiantes introduzcan un de 5 preguntas, 3 de Opción múltiple y 2 de Emparejamiento.

- -

Configurelo de esta forma:

-

1) en el menú "Total de preguntas requeridas" seleccione 5

-

2) en "Tipos de preguntas requeridos" marque las dos casillas "Opción múltiple" y "Emparejamiento"

-

3) En el primer bloque "Nº mínimo de preguntas a calificar del Tipo" :

-

...en el menú Tipo de Pregunta seleccione "Opción múltiple"

-

...y en Preguntas mínimas seleccione 3

-

4) En el segundo bloque "Nº mínimo de preguntas a calificar del Tipo" :

-

...en el menú Tipo de Pregunta seleccione "Emparejamiento"

-

...y en Preguntas mínimas seleccione 2 -

diff --git a/lang/es/help/qcreate/studentquestionaccess.html b/lang/es/help/qcreate/studentquestionaccess.html deleted file mode 100644 index 6e7ca39..0000000 --- a/lang/es/help/qcreate/studentquestionaccess.html +++ /dev/null @@ -1,24 +0,0 @@ -

Acceso de los estudiantes a las preguntas

- -

De forma genérica, dentro de este módulo los estudiantes sólo tienen acceso a sus propias preguntas, nunca a -las generadas por sus compañeros.

- -

Este menú especifica el grado de acceso que desea otorgar a cada estudiante a las preguntas que él mismo -ha generado previamente. Hay cuatro tipos o grados de acceso:

- -

1) Sólo crear: -
Este es el modo más limitado. Una vez que el estudiante termina de editar y guarda el texto -de una pregunta no podrá verla ni editarla más, queda fuera de su alcance.

- -

2) Previsualizar: -
Puede ver la pregunta introducida tal y como se mostraría en pantalla en un Cuestionario

- -

3) Previsualizar / Guardar como nueva: -
Además de ver las preguntas puede "copiar" una para usarla como base para generar una nueva - pregunta. Sin embargo, el estudiante no puede editar o borrar las preguntas ya generadas y guardadas.

- -

4) Previsualizar / Guardar como nueva / Editar / Borrar: -
Este es el nivel de acceso más alto que puede otorgar a un estudiante. El estudiante puede editar y cambiar -las preguntas generadas anteriormente, así como borrar aquellas que no considere satisfactorias.
-Si usted desea una actividad en la que los estudiantes puedan desarrollar intaractivamente su habilidad -para crear, valorar y mejorar las preguntas generadas este es el nivel de acceso adecuado.

diff --git a/lang/es/help/qcreate/timeopen.html b/lang/es/help/qcreate/timeopen.html deleted file mode 100644 index ddb5582..0000000 --- a/lang/es/help/qcreate/timeopen.html +++ /dev/null @@ -1,7 +0,0 @@ -

Abrir y Cerrar la actividad de Creación de Preguntas

- -

Puede especificar el día y la hora en la que los estudiantes pueden empezar a introducir texto para generar preguntas.
-Igualmente, el momento en el que la actividad terminará

- -

Antes del momento de apertura, o después del cierre, la actividad de creación de preguntas no estará disponible. -Se podrá revisar lo existentes pero NO generar contenido nuevo.

diff --git a/lang/es/help/qcreate/totalquestionsgraded.html b/lang/es/help/qcreate/totalquestionsgraded.html deleted file mode 100644 index 01a6efd..0000000 --- a/lang/es/help/qcreate/totalquestionsgraded.html +++ /dev/null @@ -1,9 +0,0 @@ -

Total de preguntas requeridas

- -

Este es el número totas de preguntas (de cualquier tipo) que desea que cada estudiante genere usando esta actividad. -Eset numero debe ser igual o mayor que la suma de los números mínimos de preguntas de cada tipo especificado -(si se especifica alguno). El tipo (formato) de preguntas que será posible generar depende las otras opciones.

- -

El total de preguntas requeridas se emplea para calcular la fracción de calificación automática (si se usa esta opción) -pero no limita el número de preguntas que los estudiantes pueden introducir y almacenar. Los estudiantes siempre pueden -generar preguntas "extra" por encima de este número total de preguntas, sin límite alguno.

\ No newline at end of file diff --git a/lang/es/qcreate.php b/lang/es/qcreate.php deleted file mode 100644 index 3f3bf0b..0000000 --- a/lang/es/qcreate.php +++ /dev/null @@ -1,85 +0,0 @@ -grade} / {$a->outof} por esta actividad.'; -$string['addminimumquestionshdr'] = 'Nº mínimo de preguntas a calificar del Tipo :'; -$string['allowall'] = 'Permitir todos los tipos de preguntas'; -$string['allandother'] = 'Para permitir todos los tipos de preguntas, marque \'{$a}\' y nada más.'; -$string['allquestions'] = '0 - (Todas las preguntas)'; -$string['allowedqtypes'] = 'Tipos de preguntas requeridos'; -$string['alreadydone'] = 'Ha introducido {$a} preguntas de este tipo.'; -$string['alreadydoneextra'] = 'Ha introducido {$a} preguntas extra de este tipo.'; -$string['alreadydoneextraone'] = 'Ha introducido una pregunta extra de este tipo.'; -$string['alreadydoneone'] = 'Ha introducido una pregunta de este tipo.'; -$string['and'] = '{$a} y'; -$string['automaticgrade'] = 'Ha recibido una calificación automática de {$a->grade} / {$a->outof} por estas preguntas, ya que ha introducido {$a->done} de {$a->required} preguntas requeridas.'; -$string['betterthangrade'] = 'Preguntas con calificación igual o mayor que'; -$string['clickhere'] = 'Pinche aquí para crear una pregunta del tipo \'{$a}\'.'; -$string['clickhereanother'] = 'Pinche aquí para crear otra pregunta del tipo \'{$a}\'.'; -$string['close'] = 'Cerrar la actividad'; -$string['comma'] = '{$a},'; -$string['comment'] = 'Comentario'; -$string['creating'] = 'Creando Preguntas'; -$string['donequestionno'] = 'Ha completado {$a->done} de {$a->no} preguntas del tipo \'{$a->qtypestring}\'. Se listan debajo.'; -$string['exportgood'] = 'Exportar válidas'; -$string['exportgoodquestions'] = 'Exportar las preguntas con calificación mayor que un valor dado'; -$string['exportselection'] = 'Exportar sólo estas preguntas'; -$string['extraqdone'] = 'Ha introducido una pregunta extra.'; -$string['extraqsdone'] = 'Ha introducido {$a->extraquestionsdone} preguntas extra.'; -$string['extraqgraded'] = 'Una pregunta de estos tipos será evaluada'; -$string['extraqsgraded'] = '{$a->extrarequired} preguntas de estos tipos será evaluada'; -$string['fullstop'] = '{$a}.'; -$string['gradeallautomatic'] = 'Calificaciones completamente automáticas. No hay calificación manual.'; -$string['gradeallmanual'] = 'Calificaciones completamente manuales. No hay calificación automática.'; -$string['grademixed'] = 'Calificaciones {$a->automatic}%% automáticas, {$a->manual}%% manual.'; -$string['graded'] = 'Calificado'; -$string['gradequestions'] = 'Califique las preguntas'; -$string['grading'] = 'Calificar'; -$string['graderatio'] = 'Ratio de calificación Automática / Manual'; -$string['graderatiooptions'] = '{$a->automatic} / {$a->manual}'; -$string['intro'] = 'Introducción'; -$string['manualgrade'] = 'Un profesor ha otrogado una calificación de of {$a->grade} / {$a->outof} por las preguntas introducidas.'; -$string['marked'] = 'Evaluado'; -$string['minimumquestions'] = 'Preguntas mínimas'; -$string['modulename'] = 'Crear preguntas'; -$string['modulenameplural'] = 'Actividades Crear preguntas'; -$string['needtoallowqtype'] = 'Debe permitir el tipo de pregunta \'{$a}\' si desea requerir la creación de un número mínimo de preguntas de este tipo.'; -$string['needtoallowatleastoneqtype'] = 'Necesita permitir al menos un tipo de pregunta'; -$string['needsgrading'] = 'Necesita calificación'; -$string['needsregrading'] = 'necesita re-calificación'; -$string['noofquestionstotal'] = 'Total de preguntas requeridas'; -$string['notgraded'] = 'Aún no calificada'; -$string['nousers'] = 'No hay usuarios matriculados en este curso o grupo.'; -$string['open'] = 'Abrir la actividad'; -$string['openmustbemorethanclose'] = 'El momento de abrir la actividad debe ser anterior en el tiempo al de cierre'; -$string['overview'] = 'Presentación'; -$string['pagesize'] = 'Número de preguntas a mostrar por página'; -$string['qcreate'] = 'qcreate'; -$string['qsgraded'] = '{$a} pregunta(s) de cualquiera de estos tipos será evaluada:'; -$string['qtype'] = 'Tipo de Pregunta'; -$string['requiredquestions'] = 'Preguntas a crear requeridas'; -$string['requiredanyplural'] = 'Se requieren {$a->no} preguntas de cualquier tipo'; -$string['requiredanysingular'] = 'Se requiere una pregunta de cualquier tipo'; -$string['requiredplural'] = 'Se requieren {$a->no} preguntas del tipo \'{$a->qtypestring}\' '; -$string['requiredsingular'] = 'Se requiere una pregunta del tipo \'{$a->qtypestring}\' '; -$string['saveallfeedback'] = 'Guardar comentarios'; -$string['saveallfeedbackandgrades'] = 'Guardar calificaciones y comentarios'; -$string['selectone'] = 'Escoja uno ...'; -$string['show'] = 'Mostrar '; -$string['showgraded'] = 'preguntas que no requieren evaluación'; -$string['showneedsregrade'] = 'preguntas que requieren re-evaluación'; -$string['showungraded'] = 'preguntas que requieren evaluación'; -$string['studentqaccess'] = 'Con las preguntas propias'; -$string['studentaccessheader'] = 'Accesoa las preguntas por estudiantes'; -$string['studentaccessaddonly'] = 'Sólo crear'; -$string['studentaccesspreview'] = 'Previsualizar'; -$string['studentaccesssaveasnew'] = 'Previsualizar / Guardar como nueva'; -$string['studentaccessedit'] = 'Previsualizar / Guardar como nueva / Editar / Borrar'; -$string['timenolimit'] = 'No hay límite de tiempo definido.'; -$string['timeopen'] = 'La actividad comienza en {$a->timeopen}'; -$string['timeclose'] = 'La actividad termina en {$a->timeclose}'; -$string['timeopenclose'] = 'La actividad está abierta desde el {$a->timeopen} al {$a->timeclose}'; -$string['timing'] = 'Temporalización de la actividad'; -$string['todoquestionno'] = 'Todavía debe introducir {$a->stillrequiredno} preguntas(s) del tipo \'{$a->qtypestring}\'.'; -$string['totalgrade'] = 'Calificación total'; -$string['totalrequiredislessthansumoftotalsforeachqtype'] = 'El total requerido es menor que la suma de los mínimos especificados para cada tipo de pregunta requerid.
¡Debe ser igual o mayor!'; -$string['youvesetmorethanonemin'] = 'Ha especificado más de un mímimo de cuestiones de tipo \'{$a}\'!'; -?> diff --git a/lib.php b/lib.php index 11cafd8..f596501 100644 --- a/lib.php +++ b/lib.php @@ -1,4 +1,27 @@ -. + +/** + * @package mod_qcreate + * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + /** * Library of functions and constants for module qcreate * This file should have two well differenced parts: @@ -13,336 +36,750 @@ * help to save some memory when Moodle is performing * actions across all modules. */ + /** * The options used when popping up a question preview window in Javascript. */ define('QCREATE_EDIT_POPUP_OPTIONS', 'scrollbars=yes,resizable=yes,width=800,height=540'); +/** Set QCREATE_PER_PAGE to 0 if you wish to display all questions on the edit page */ +define('QCREATE_PER_PAGE', 10); + +define('QCREATE_MAX_PER_PAGE', 100); + /** - * If start and end date for the quiz are more than this many seconds apart - * they will be represented by two separate events in the calendar + * Event types. */ -define("QCREATE_MAX_EVENT_LENGTH", 5*24*60*60); // 5 days maximum - -require_once(dirname(__FILE__).'/deprecatedlib.php'); +define('QCREATE_EVENT_TYPE_OPEN', 'open'); +define('QCREATE_EVENT_TYPE_CLOSE', 'close'); /** - * Given an object containing all the necessary data, - * (defined by the form in mod.html) this function - * will create a new instance and return the id number - * of the new instance. + * Returns the information on whether the module supports a feature * - * @param object $instance An object from the form in mod.html - * @return int The id of the newly inserted qcreate record - **/ -function qcreate_add_instance($qcreate) { - global $DB; - $qcreate->timecreated = time(); - - $qcreate->allowed = join(array_keys($qcreate->allowed), ','); - if ($qcreate->id = $DB->insert_record("qcreate", $qcreate)){ - $qtypemins = array_filter($qcreate->qtype); - if (count($qtypemins)){ - foreach ($qtypemins as $key => $qtypemin){ - $toinsert = new stdClass(); - $toinsert->no = $qcreate->minimumquestions[$key]; - $toinsert->qtype = $qtypemin; - $toinsert->qcreateid = $qcreate->id; - $toinsert->timemodified = time(); - $DB->insert_record('qcreate_required', $toinsert); - } - } - qcreate_after_add_or_update($qcreate); + * @see plugin_supports() in lib/moodlelib.php + * @param string $feature FEATURE_xx constant for requested feature + * @return mixed true if the feature is supported, null if unknown + */ +function qcreate_supports($feature) { + switch($feature) { + case FEATURE_GROUPS: + return true; + case FEATURE_GROUPINGS: + return true; + case FEATURE_MOD_INTRO: + return true; + case FEATURE_COMPLETION_TRACKS_VIEWS: + return true; + case FEATURE_COMPLETION_HAS_RULES: + return true; + case FEATURE_GRADE_HAS_GRADE: + return true; + case FEATURE_BACKUP_MOODLE2: + return true; + case FEATURE_GRADE_OUTCOMES: + return true; + case FEATURE_SHOW_DESCRIPTION: + return true; + case FEATURE_USES_QUESTIONS: + return true; + + default: + return null; } - return $qcreate->id; } + /** - * Called from cron and update_instance. Not called from add_instance as the contexts are not set up yet. + * Obtains the automatic completion state for this qcreate based on any conditions + * in qcreate settings. + * + * @global object + * @global object + * @param object $course Course + * @param object $cm Course-module + * @param int $userid User ID + * @param bool $type Type of comparison (or/and; can be used as return value if no conditions) + * @return bool True if completed, false if not. (If no conditions, then return + * value depends on comparison type) */ -function qcreate_student_q_access_sync($qcreate, $cmcontext=null, $course=null, $forcesync= false){ - global $DB; - - //check if a check is needed - $timenow = time(); - $activityopen = ($qcreate->timeopen == 0 ||($qcreate->timeopen < $timenow)) && - ($qcreate->timeclose == 0 ||($qcreate->timeclose > $timenow)); - $activitywasopen = ($qcreate->timeopen == 0 ||($qcreate->timeopen < $qcreate->timesync)) && - ($qcreate->timeclose == 0 ||($qcreate->timeclose > $qcreate->timesync)); - $needsync = (empty($qcreate->timesync) || //no sync has happened yet - ($activitywasopen != $activityopen)); - if ($forcesync || $needsync){ - if ($cmcontext == null){ - $cm = get_coursemodule_from_instance('qcreate', $qcreate->id); - $cmcontext = get_context_instance(CONTEXT_MODULE, $cm->id); - } - if ($course == null){ - $course = get_record('course', 'id', $qcreate->course); - } +function qcreate_get_completion_state($course, $cm, $userid, $type) { + global $CFG, $DB; + require_once($CFG->dirroot . '/mod/qcreate/locallib.php'); - $studentrole = get_archetype_roles('student'); - $studentrole = reset($studentrole); + $context = context_module::instance($cm->id); + $qcreateobj = new qcreate($context, $cm, $course); - if ($activityopen){ - $capabilitiestoassign = array ( - 0=> array('moodle/question:add'=> 1, 'moodle/question:usemine'=> -1, 'moodle/question:viewmine'=> -1, 'moodle/question:editmine'=> -1), - 1=> array('moodle/question:add'=> 1, 'moodle/question:usemine'=> 1, 'moodle/question:viewmine'=> -1, 'moodle/question:editmine'=> -1), - 2=> array('moodle/question:add'=> 1, 'moodle/question:usemine'=> 1, 'moodle/question:viewmine'=> 1, 'moodle/question:editmine'=> -1), - 3=> array('moodle/question:add'=> 1, 'moodle/question:usemine'=> 1, 'moodle/question:viewmine'=> 1, 'moodle/question:editmine'=> 1)); - foreach ($capabilitiestoassign[$qcreate->studentqaccess] as $capability => $permission) { - assign_capability($capability, $permission, $studentrole->id, $cmcontext->id, true); - } + $result = $type; // Default return value. + + if ($qcreateobj->get_instance()->completionquestions) { + $value = $qcreateobj->get_instance()->completionquestions <= $qcreateobj->count_user_questions($userid); + if ($type == COMPLETION_AND) { + $result = $result && $value; } else { - $capabilitiestounassign = array ( - 'moodle/question:add', 'moodle/question:usemine', 'moodle/question:viewmine', 'moodle/question:editmine'); - foreach ($capabilitiestounassign as $capability) { - unassign_capability($capability, $studentrole->id, $cmcontext->id); - } + $result = $result || $value; } - $DB->set_field('qcreate', 'timesync', $timenow, array('id'=>$qcreate->id)); - } + return $result; } /** - * Given an object containing all the necessary data, - * (defined by the form in mod.html) this function - * will update an existing instance with new data. + * Adds a qcreate instance * - * @param object $instance An object from the form in mod.html - * @return boolean Success/Fail - **/ -function qcreate_update_instance($qcreate) { - global $COURSE, $DB; + * This is done by calling the add_instance() method of the qcreate type class + * @param stdClass $data + * @param mod_qcreate_mod_form $form + * @return int The instance id of the new qcreate + */ +function qcreate_add_instance(stdClass $data, $form = null) { + global $CFG; + require_once($CFG->dirroot . '/mod/qcreate/locallib.php'); - $qcreate->timemodified = time(); - $qcreate->id = $qcreate->instance; + $qcreateobj = new qcreate(context_module::instance($data->coursemodule), null, null); + return $qcreateobj->add_instance($data, true); +} - $DB->delete_records('qcreate_required', array('qcreateid'=>$qcreate->id)); +/** + * Update an qcreate instance + * + * This is done by calling the update_instance() method of the qcreate type class + * @param stdClass $data he data that came from the form + * @param stdClass $form - unused + * @return object + */ +function qcreate_update_instance(stdClass $data, $form) { + global $CFG; + require_once($CFG->dirroot . '/mod/qcreate/locallib.php'); - $qtypemins = array_filter($qcreate->qtype); - if (count($qtypemins)){ - foreach ($qtypemins as $key => $qtypemin){ - $toinsert = new stdClass(); - $toinsert->no = $qcreate->minimumquestions[$key]; - $toinsert->qtype = $qtypemin; - $toinsert->qcreateid = $qcreate->id; - $toinsert->timemodified = time(); - $DB->insert_record('qcreate_required', $toinsert); - } - } - $qcreate->allowed = join(array_keys($qcreate->allowed), ','); + $qcreateobj = new qcreate(context_module::instance($data->coursemodule), null, null); + return $qcreateobj->update_instance($data); +} + +/** + * Given an ID of an instance of this module, + * this function will permanently delete the instance + * and any data that depends on it. + * + * @param int $id Id of the module instance + * @return boolean Success/Failure + **/ +function qcreate_delete_instance($id) { + global $CFG; + require_once($CFG->dirroot . '/mod/qcreate/locallib.php'); - $toreturn = $DB->update_record("qcreate", $qcreate); - - $qcreate = $DB->get_record('qcreate', array('id'=>$qcreate->id)); + $cm = get_coursemodule_from_instance('qcreate', $id, 0, false, MUST_EXIST); + $context = context_module::instance($cm->id); - qcreate_student_q_access_sync($qcreate, null, $COURSE, true); + $qcreateobj = new qcreate($context, null, null); + return $qcreateobj->delete_instance(); +} - qcreate_after_add_or_update($qcreate); - return $toreturn; +/** + * This function is used by the reset_course_userdata function in moodlelib. + * This function will remove all grades from the specified qcreate + * and clean up any related data. + * + * @param $data the data submitted from the reset course. + * @return array status array + */ +function qcreate_reset_userdata($data) { + global $CFG, $DB; + require_once($CFG->dirroot . '/mod/qcreate/locallib.php'); + + $status = array(); + $params = array('courseid' => $data->courseid); + $sql = "SELECT a.id FROM {qcreate} a WHERE a.course=:courseid"; + $course = $DB->get_record('course', array('id' => $data->courseid), '*', MUST_EXIST); + if ($qcreates = $DB->get_records_sql($sql, $params)) { + foreach ($qcreates as $qcreate) { + $cm = get_coursemodule_from_instance('qcreate', + $qcreate->id, + $data->courseid, + false, + MUST_EXIST); + $context = context_module::instance($cm->id); + $qcreateobj = new qcreate($context, $cm, $course); + $status = array_merge($status, $qcreateobj->reset_userdata($data)); + } + } + return $status; } + /** - * This function is called at the end of qcreate_add_instance - * and qcreate_update_instance, to do the common processing. + * This standard function will check all instances of this module + * and make sure there are up-to-date events created for each of them. + * If courseid = 0, then every qcreate event in the site is checked, else + * only qcreate events belonging to the course specified are checked. + * This function is used, in its new format, by restore_refresh_events() * - * @param object $qcreate the qcreate object. + * @param int $courseid + * @param int|stdClass $instance qcreate module instance or ID. + * @param int|stdClass $cm Course module object or ID (not used in this module). + * @return bool */ -function qcreate_after_add_or_update($qcreate) { - global $COURSE, $DB; +function qcreate_refresh_events($courseid = 0, $instance = null, $cm = null) { + global $DB; - // Update the events relating to this qcreate. - // This is slightly inefficient, deleting the old events and creating new ones. However, - // there are at most two events, and this keeps the code simpler. - if ($events = $DB->get_records('event', array('modulename' => 'qcreate', 'instance' => $qcreate->id))) { - foreach($events as $event) { - delete_event($event->id); + // If we have instance information then we can just update the one event instead of updating all events. + if (isset($instance)) { + if (!is_object($instance)) { + $instance = $DB->get_record('qcreate', array('id' => $instance), '*', MUST_EXIST); } + qcreate_update_events($instance); + return true; } - $event = new stdClass; - $event->description = $qcreate->intro; - $event->courseid = $qcreate->course; - $event->groupid = 0; - $event->userid = 0; - $event->modulename = 'qcreate'; - $event->instance = $qcreate->id; - $event->timestart = $qcreate->timeopen; - $event->timeduration = $qcreate->timeclose - $qcreate->timeopen; - $event->visible = instance_is_visible('qcreate', $qcreate); - $event->eventtype = 'open'; - - if ($qcreate->timeclose and $qcreate->timeopen and $event->timeduration <= QCREATE_MAX_EVENT_LENGTH) { - // Single event for the whole qcreate. - $event->name = $qcreate->name; - add_event($event); - } else { - // Separate start and end events. - $event->timeduration = 0; - if ($qcreate->timeopen) { - $event->name = $qcreate->name.' ('.get_string('qcreateopens', 'qcreate').')'; - add_event($event); - unset($event->id); // So we can use the same object for the close event. + if ($courseid == 0) { + if (!$qcreates = $DB->get_records('qcreate')) { + return true; } - if ($qcreate->timeclose) { - $event->name = $qcreate->name.' ('.get_string('qcreatecloses', 'qcreate').')'; - $event->timestart = $qcreate->timeclose; - $event->eventtype = 'close'; - add_event($event); + } else { + if (!$qcreates = $DB->get_records('qcreate', array('course' => $courseid))) { + return true; } } - //update related grade item - qcreate_grade_item_update($qcreate); + foreach ($qcreates as $qcreate) { + qcreate_update_events($qcreate); + } + return true; } + /** - * Given an ID of an instance of this module, - * this function will permanently delete the instance - * and any data that depends on it. + * This actually updates the activity calendar events. * - * @param int $id Id of the module instance - * @return boolean Success/Failure - **/ -function qcreate_delete_instance($id) { + * @param stdClass $qcreate qcreate object (from DB). + * @param stdClass $course Course object. + * @param stdClass $cm Course module object. + */ +function qcreate_update_events($qcreate, $course = null, $cm = null) { + global $DB, $CFG; + require_once($CFG->dirroot . '/mod/qcreate/locallib.php'); - if (! $qcreate = get_record("qcreate", "id", "$id")) { - return false; + if (!isset($course)) { + // Get course and course module for the qcreate. + list($course, $cm) = get_course_and_cm_from_instance($qcreate->id, 'qcreate', $qcreate->course); } + // Refresh the qcreate's calendar events. + $context = context_module::instance($cm->id); + $qcreateobj = new qcreate($context, $cm, $course); + $qcreateobj->update_calendar($cm->id); +} +/** + * Prints qcreate summaries on MyMoodle Page + * @param array $courses + * @param array $htmlarray + */ +function qcreate_print_overview($courses, &$htmlarray) { + global $USER, $CFG; - $result = true; + debugging('The function qcreate_print_overview() is now deprecated.', DEBUG_DEVELOPER); - if (! delete_records("qcreate_grades", "qcreateid", "$qcreate->id")) { - $result = false; + // These next 6 Lines are constant in all modules (just change module name). + if (empty($courses) || !is_array($courses) || count($courses) == 0) { + return array(); } - if (! delete_records("qcreate_required", "qcreateid", "$qcreate->id")) { - $result = false; + if (!$qcreates = get_all_instances_in_courses('qcreate', $courses)) { + return; } - if (! delete_records("qcreate", "id", "$qcreate->id")) { - $result = false; + // Fetch some language strings outside the main loop. + $strqcreate = get_string('modulename', 'qcreate'); + + // We want to list qcreates that are currently availables. + // I know this is different from lesson and quiz. See MDL-10568. + $now = time(); + $strnoquestions = get_string('noquestions', 'qcreate'); + foreach ($qcreates as $qcreate) { + if (($qcreate->timeopen == 0 ||($qcreate->timeopen < $now)) && + ($qcreate->timeclose == 0 ||($qcreate->timeclose > $now))) { + // Give a link to the qcreate, and the deadline. + $str = '
' . + ''; + $str .= '
' . qcreate_time_status($qcreate) . '
'; + + // Now provide more information depending on the uers's role. + $context = context_module::instance($qcreate->coursemodule); + if (has_capability('mod/qcreate:grade', $context)) { + // For teacher-like people, show a summary of the number questions created. + // The $qcreate objects returned by get_all_instances_in_course have the necessary $cm + // fields set to make the following call work. + $str .= '
' . + get_string('studentshavedone', 'qcreate', qcreate_get_qestions_number(0, $qcreate)) . '
'; + } else if (has_capability('mod/qcreate:view', $context)) { // Student + // For student-like people, tell them how many questions they have created. + if (isset($USER->id)) { + $str .= '
' . + get_string('youhavedone', 'qcreate', qcreate_get_qestions_number($USER->id, $qcreate)) . '
'; + } else { + $str .= '
' . $strnoquestions . '
'; + } + } else { + // For ayone else, there is no point listing this qcreate, so stop processing. + continue; + } + + // Add the output for this qcreate to the rest. + $str .= '
'; + if (empty($htmlarray[$qcreate->course]['qcreate'])) { + $htmlarray[$qcreate->course]['qcreate'] = $str; + } else { + $htmlarray[$qcreate->course]['qcreate'] .= $str; + } + } } +} - return $result; +/** + * Removes all grades from gradebook + * + * @param int $courseid The ID of the course to reset + * @param string $type Optional type of qcreate (not used here) + */ +function qcreate_reset_gradebook($courseid, $type='') { + global $CFG, $DB; + + $params = array('moduletype' => 'qcreate', 'courseid' => $courseid); + $sql = 'SELECT q.*, cm.idnumber as cmidnumber, q.course as courseid + FROM {qcreate} q, {course_modules} cm, {modules} m + WHERE m.name=:moduletype AND m.id=cm.module AND cm.instance=q.id AND q.course=:courseid'; + + if ($qcreates = $DB->get_records_sql($sql, $params)) { + foreach ($qcreates as $qcreate) { + qcreate_grade_item_update($qcreate, 'reset'); + } + } } /** - * Return a small object with summary information about what a - * user has done with a given particular instance of this module - * Used for user activity reports. - * $return->time = the time they did it - * $return->info = a short text description + * Implementation of the function for printing the form elements that control + * whether the course reset functionality affects the qcreate. * - * @return null - * @todo Finish documenting this function - **/ + * + * @param $mform form passed by reference + */ +function qcreate_reset_course_form_definition(&$mform) { + $mform->addElement('header', 'qcreateheader', get_string('modulenameplural', 'qcreate')); + $mform->addElement('advcheckbox', 'reset_qcreate', get_string('deletegrades', 'qcreate')); +} + +/** + * Course reset form defaults. + * + * + * @param stdClass $course + * @return array + */ +function qcreate_reset_course_form_defaults($course) { + return array('reset_qcreate' => 1); +} + +/** + * Used by course/user.php to display this module's user activity outline. + * @param object $course as this is a standard function this is required but not used here + * @param object $user user object + * @param object $mod not used here + * @param object $qcreate qcreate object + * @return object A standard object with 2 variables: info (grade for this user) and + * time (last modified) + */ function qcreate_user_outline($course, $user, $mod, $qcreate) { - return null; + global $DB, $CFG; + + require_once($CFG->libdir . '/gradelib.php'); + $result = new stdClass(); + $result->info = get_string('questionscreated', 'qcreate', qcreate_get_qestions_number($user->id, $qcreate)); + $grades = grade_get_grades($course->id, 'mod', 'qcreate', $qcreate->id, $user->id); + + if (empty($grades->items[0]->grades)) { + return null; + } else { + $grade = reset($grades->items[0]->grades); + $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade; + } + $result->time = $grade->dategraded; + + return $result; } /** - * Print a detailed representation of what a user has done with + * Print a detailed representation of what a user has done with * a given particular instance of this module, for user activity reports. * - * @return boolean - * @todo Finish documenting this function - **/ -function qcreate_user_complete($course, $user, $mod, $qcreate) { + * @param object $course + * @param object $user + * @param object $coursemodule + * @param object $qcreate + * @return bool + */ +function qcreate_user_complete($course, $user, $coursemodule, $qccreate) { + global $CFG; + require_once($CFG->dirroot . '/mod/qcreate/locallib.php'); + + $context = context_module::instance($coursemodule->id); + $qcreateobj = new qcreate($context, $coursemodule, $course); + echo $qcreateobj->view_student_summary($user, false); return true; } /** - * Given a course and a time, this module should find recent activity - * that has occurred in qcreate activities and print it out. - * Return true if there was output, or false is there was none. + * Print recent activity from all qcreates in a given course * - * @uses $CFG - * @return boolean - * @todo Finish documenting this function - **/ -function qcreate_print_recent_activity($course, $isteacher, $timestart) { - global $CFG; + * This is used by the recent activity block + * @param mixed $course the course to print activity for + * @param bool $viewfullnames boolean to determine whether to show full names or not + * @param int $timestart the time the rendering started + * @return bool true if activity was printed, false otherwise. + */ +function qcreate_print_recent_activity($course, $viewfullnames, $timestart) { + global $CFG, $DB, $OUTPUT; + if (!defined('QCREATE_RECENT_ACTIVITY_LIMIT')) { + define('QCREATE_RECENT_ACTIVITY_LIMIT', 20); + } + $modinfo = get_fast_modinfo($course); + $ids = array(); + $params = array(); + + foreach ($modinfo->cms as $cm) { + if ($cm->modname != 'qcreate') { + continue; + } + if (!$cm->uservisible) { + continue; + } + $modcontext = context_module::instance($cm->id); + $ids[$cm->instance] = $modcontext->id; + } + + if (!$ids) { + return false; + } + + // Generate list of question categories ids for all qcreates in the course. + $qcatids = array(); + foreach ($ids as $qcreateinstanceid => $qcatid) { + $qcatids[] = ' qc.contextid = :qccontid'.$qcreateinstanceid.' '; + $params['qccontid'.$qcreateinstanceid] = $qcatid; + } - return false; // True if anything was printed, otherwise false + if (count($qcatids) > 0) { + $qcatsql = 'AND ('. implode($qcatids, ' OR ') .') '; + } else { + $qcatsql = ''; + } + + $params['timestart'] = $timestart; + + // Generate list of created questions for all qcreate in the course. + $userfields = user_picture::fields('u', null, 'userid'); + $sql = 'SELECT q.id, q.name AS qname, qc.id as qcat, q.timemodified, g.grade as rawgrade, a.name AS aname, a.id as aid, ' . + $userfields . + ' FROM {question} q + LEFT JOIN {user} u ON u.id = q.createdby + LEFT JOIN {question_categories} qc ON qc.id = q.category + LEFT JOIN {qcreate_grades} g ON g.questionid = q.id + LEFT JOIN {context} c ON c.id = qc.contextid + LEFT JOIN {course_modules} cm ON cm.id = c.instanceid + LEFT JOIN {qcreate} a ON a.id = cm.instance + WHERE q.timecreated > :timestart ' . + $qcatsql . + ' ORDER BY q.timecreated ASC'; + + if ($questions = $DB->get_records_sql($sql, $params)) { + echo $OUTPUT->heading(get_string('newquestions', 'qcreate').':', 3); + $strftimerecent = get_string('strftimerecent'); + $questioncount = 0; + foreach ($questions as $question) { + if ($questioncount < QCREATE_RECENT_ACTIVITY_LIMIT) { + $urlparams = array('a' => $question->aid); + $link = new moodle_url($CFG->wwwroot.'/mod/qcreate/view.php', $urlparams); + print_recent_activity_note($question->timemodified, + $question, + $question->aname, + $link, + false, + $viewfullnames); + $questioncount += 1; + } else { + $numnewquestions = count($questions); + echo '
' . + get_string('andmorenewquestions', 'qcreate', $numnewquestions - QCREATE_RECENT_ACTIVITY_LIMIT) . + '
'; + break; + } + } + return true; + } + return false; // True if anything was printed, otherwise false. } /** - * Function to be run periodically according to the moodle cron - * This function searches for things that need to be done, such - * as sending out mail, toggling flags etc ... + * Returns all questions created since a given time. * - * @uses $CFG - * @return boolean - * @todo Finish documenting this function - **/ -function qcreate_cron () { - global $CFG, $DB; - $sql = "SELECT q.*, cm.id as cmidnumber, q.course as courseid - FROM {$CFG->prefix}qcreate q, {$CFG->prefix}course_modules cm, {$CFG->prefix}modules m - WHERE m.name='qcreate' AND m.id=cm.module AND cm.instance=q.id"; - $rs = $DB->get_recordset_sql($sql); - foreach ($rs as $qcreate) { - $context = get_context_instance(CONTEXT_MODULE, $qcreate->cmidnumber); - if ($users = get_users_by_capability($context, 'mod/qcreate:submit', '', '', '', '', '', '', false)){ - $users = array_keys($users); - $sql = 'SELECT q.* FROM '.$CFG->prefix.'question_categories qc, '.$CFG->prefix.'question q '. - 'LEFT JOIN '.$CFG->prefix.'qcreate_grades g ON q.id = g.questionid '. - 'WHERE g.timemarked IS NULL AND q.createdby IN ('.implode(',',$users).') '. - 'AND qc.id = q.category ' . - 'AND q.hidden=\'0\' AND q.parent=\'0\' ' . - 'AND qc.contextid ='.$context->id; - $questionrs = $DB->get_recordset_sql($sql); - $toupdates = array(); - foreach ($questionrs as $question) { - qcreate_process_local_grade($qcreate, $question, true); - $toupdates[] = $question->createdby; + * @param array $activities The activity information is returned in this array + * @param int $index The current index in the activities array + * @param int $timestart The earliest activity to show + * @param int $courseid Limit the search to this course + * @param int $cmid The course module id + * @param int $userid Optional user id + * @param int $groupid Optional group id + * @return void + */ +function qcreate_get_recent_mod_activity(&$activities, + &$index, + $timestart, + $courseid, + $cmid, + $userid=0, + $groupid=0) { + global $CFG, $COURSE, $USER, $DB; + + if ($COURSE->id == $courseid) { + $course = $COURSE; + } else { + $course = $DB->get_record('course', array('id' => $courseid)); + } + + $modinfo = get_fast_modinfo($course); + + $cm = $modinfo->get_cm($cmid); + $modcontext = context_module::instance($cm->id); + $params = array(); + if ($userid) { + $userselect = 'AND u.id = :userid'; + $params['userid'] = $userid; + } else { + $userselect = ''; + } + + if ($groupid) { + $groupselect = 'AND gm.groupid = :groupid'; + $groupjoin = 'JOIN {groups_members} gm ON gm.userid=u.id'; + $params['groupid'] = $groupid; + } else { + $groupselect = ''; + $groupjoin = ''; + } + + $params['contextid'] = $modcontext->id; + $params['timestart'] = $timestart; + + $userfields = user_picture::fields('u', null, 'userid'); + + $sql = 'SELECT q.id, q.qtype AS qtype, q.name AS qname, qc.id as qcat, q.timecreated, g.grade as rawgrade, a.grade, ' . + $userfields . + ' FROM {question} q + LEFT JOIN {user} u ON u.id = q.createdby + LEFT JOIN {question_categories} qc ON qc.id = q.category + LEFT JOIN {qcreate_grades} g ON g.questionid = q.id + LEFT JOIN {qcreate} a ON a.id = g.qcreateid + ' . + $groupjoin . + ' WHERE q.timecreated > :timestart AND + qc.contextid = :contextid + ' . $userselect . ' ' . $groupselect . + ' ORDER BY q.timecreated ASC'; + + if (!$questions = $DB->get_records_sql($sql, $params)) { + return; + } + + $groupmode = groups_get_activity_groupmode($cm, $course); + $cmcontext = context_module::instance($cm->id); + $grader = has_capability('moodle/grade:viewall', $cmcontext); + $accessallgroups = has_capability('moodle/site:accessallgroups', $cmcontext); + $viewfullnames = has_capability('moodle/site:viewfullnames', $cmcontext); + + $show = array(); + foreach ($questions as $question) { + if ($question->userid == $USER->id) { + $show[] = $question; + continue; + } + // A graded question may be considered private - + // only graders will see it if specified. + if (!$grader) { + continue; + } + + if ($groupmode == SEPARATEGROUPS and !$accessallgroups) { + if (isguestuser()) { + // Shortcut - guest user does not belong into any group. + continue; + } + + // This will be slow - show only users that share group with me in this cm. + if (!$modinfo->get_groups($cm->groupingid)) { + continue; } - $questionrs->close(); - $toupdates = array_unique($toupdates); - foreach ($toupdates as $toupdate){ - qcreate_update_grades($qcreate, $toupdate); + $usersgroups = groups_get_all_groups($course->id, $question->userid, $cm->groupingid); + if (is_array($usersgroups)) { + $usersgroups = array_keys($usersgroups); + $intersect = array_intersect($usersgroups, $modinfo->get_groups($cm->groupingid)); + if (empty($intersect)) { + continue; + } } } - qcreate_student_q_access_sync($qcreate); + $show[] = $question; } - $rs->close(); + if (empty($show)) { + return; + } - return true; + if ($grader) { + require_once($CFG->libdir . '/gradelib.php'); + $userids = array(); + foreach ($show as $id => $question) { + $userids[] = $question->userid; + } + $grades = grade_get_grades($courseid, 'mod', 'qcreate', $cm->instance, $userids); + } + + $aname = format_string($cm->name, true); + foreach ($show as $question) { + $activity = new stdClass(); + + $activity->type = 'qcreate'; + $activity->cmid = $cm->id; + $activity->name = $aname; + $activity->sectionnum = $cm->sectionnum; + $activity->timestamp = $question->timecreated; + $activity->user = new stdClass(); + if ($grader) { + if ($question->rawgrade) { + $activity->grade = get_string('grade').': '.$question->rawgrade . '/' . $question->grade; + } else { + $activity->grade = get_string('notgraded', 'qcreate'); + } + } + + $userfields = explode(',', user_picture::fields()); + foreach ($userfields as $userfield) { + if ($userfield == 'id') { + // Aliased in SQL above. + $activity->user->{$userfield} = $question->userid; + } else { + $activity->user->{$userfield} = $question->{$userfield}; + } + } + $activity->user->fullname = fullname($question, $viewfullnames); + + $activities[$index++] = $activity; + } + + return; } /** - * Must return an array of grades for a given instance of this module, - * indexed by user. It also returns a maximum allowed grade. + * Print recent activity from all qcreates in a given course * - * Example: - * $return->grades = array of grades; - * $return->maxgrade = maximum allowed grade; - * - * return $return; + * This is used by course/recent.php + * @param stdClass $activity + * @param int $courseid + * @param bool $detail + * @param array $modnames + */ +function qcreate_print_recent_mod_activity($activity, $courseid, $detail, $modnames) { + global $CFG, $OUTPUT; + + echo ''; + + echo '
'; + echo $OUTPUT->user_picture($activity->user); + echo ''; + + if ($detail) { + $modname = $modnames[$activity->type]; + echo '
'; + echo ''; + echo ''; + echo $activity->name; + echo ''; + echo '
'; + } + + if (isset($activity->grade)) { + echo '
'; + echo $activity->grade; + echo '
'; + } + + echo ''; + + echo '
'; +} + +/** + * Function to be run periodically according to the scheduled task manager + * This function synchronize students access to questions according + * to the instance settings and to the open/closed status of the instance. * - * @param int $qcreateid ID of an instance of this module - * @return mixed Null or object with an array of grades and with the maximum grade **/ -function qcreate_grades($qcreateid) { - return NULL; +function qcreate_student_q_access_sync($cmcontext, $qcreate, $forcesync= false) { + global $DB; + + // Check if a check is needed. + $timenow = time(); + $activityopen = ($qcreate->timeopen == 0 ||($qcreate->timeopen < $timenow)) && + ($qcreate->timeclose == 0 ||($qcreate->timeclose > $timenow)); + $activitywasopen = ($qcreate->timeopen == 0 ||($qcreate->timeopen < $qcreate->timesync)) && + ($qcreate->timeclose == 0 ||($qcreate->timeclose > $qcreate->timesync)); + $needsync = (empty($qcreate->timesync) || // No sync has happened yet. + ($activitywasopen != $activityopen)); + + if ($forcesync || $needsync) { + $studentrole = get_archetype_roles('student'); + $studentrole = reset($studentrole); + + if ($activityopen) { + $capabilitiestoassign = array ( + 0 => array('moodle/question:add' => CAP_ALLOW, 'moodle/question:usemine' => CAP_PREVENT, + 'moodle/question:viewmine' => CAP_PREVENT, 'moodle/question:editmine' => CAP_PREVENT), + 1 => array('moodle/question:add' => CAP_ALLOW, 'moodle/question:usemine' => CAP_ALLOW, + 'moodle/question:viewmine' => CAP_PREVENT, 'moodle/question:editmine' => CAP_PREVENT), + 2 => array('moodle/question:add' => CAP_ALLOW, 'moodle/question:usemine' => CAP_ALLOW, + 'moodle/question:viewmine' => CAP_ALLOW, 'moodle/question:editmine' => CAP_PREVENT), + 3 => array('moodle/question:add' => CAP_ALLOW, 'moodle/question:usemine' => CAP_ALLOW, + 'moodle/question:viewmine' => CAP_ALLOW, 'moodle/question:editmine' => CAP_ALLOW)); + foreach ($capabilitiestoassign[$qcreate->studentqaccess] as $capability => $permission) { + assign_capability($capability, $permission, $studentrole->id, $cmcontext->id, true); + } + } else { + $capabilitiestounassign = array ( + 'moodle/question:add', 'moodle/question:usemine', 'moodle/question:viewmine', 'moodle/question:editmine'); + foreach ($capabilitiestounassign as $capability) { + unassign_capability($capability, $studentrole->id, $cmcontext->id); + } + } + $DB->set_field('qcreate', 'timesync', $timenow, array('id' => $qcreate->id)); + } } /** - * Must return an array of user records (all data) who are participants - * for a given instance of qcreate. Must include every user involved - * in the instance, independient of his role (student, teacher, admin...) - * See other modules as example. + * Returns all other caps used in the module * - * @param int $qcreateid ID of an instance of this module - * @return mixed boolean/array of students - **/ -function qcreate_get_participants($qcreateid) { - return false; + * @example return array('moodle/site:accessallgroups'); + * @return array + */ +function qcreate_get_extra_capabilities() { + global $CFG; + require_once($CFG->libdir . '/questionlib.php'); + $caps = question_get_all_capabilities(); + $caps[] = 'moodle/site:accessallgroups'; + return $caps; } /** * This function returns if a scale is being used by one qcreate - * it it has support for grading and scales. Commented code should be + * if it has support for grading and scales. Commented code should be * modified if necessary. See forum, glossary or journal modules * as reference. * @@ -350,41 +787,56 @@ function qcreate_get_participants($qcreateid) { * @return mixed * @todo Finish documenting this function **/ -function qcreate_scale_used ($qcreateid,$scaleid) { +function qcreate_scale_used ($qcreateid, $scaleid) { + global $DB; + $return = false; + $rec = $DB->get_record('qcreate', array('id' => $qcreateid, 'grade' => -$scaleid)); - //$rec = get_record("qcreate","id","$qcreateid","scale","-$scaleid"); - // - //if (!empty($rec) && !empty($scaleid)) { - // $return = true; - //} + if (!empty($rec) && !empty($scaleid)) { + $return = true; + } return $return; } -////////////////////////////////////////////////////////////////////////////////////// -/// Any other qcreate functions go here. Each of them must have a name that -/// starts with qcreate_ -/// Remember (see note in first lines) that, if this section grows, it's HIGHLY -/// recommended to move all funcions below to a new "localib.php" file. +/** + * Checks if scale is being used by any instance of qcreate + * + * This is used to find out if scale used anywhere + * @param int $scaleid + * @return boolean True if the scale is used by any qcreate + */ +function qcreate_scale_used_anywhere($scaleid) { + global $DB; + + if ($scaleid and $DB->record_exists('qcreate', array('grade' => -$scaleid))) { + return true; + } else { + return false; + } +} /** - * Create one or all grade items for given qcreate + * Create/update grade item for given qcreate * - * @param object $qcreate object with extra cmidnumber - * @return int 0 if ok, error code otherwise + * @param stdClass $qcreate qcreate object with extra cmidnumber + * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook + * @return int 0 if ok */ -function qcreate_grade_item_update($qcreate) { +function qcreate_grade_item_update($qcreate, $grades=null) { global $CFG; - if (!function_exists('grade_update')) { //workaround for buggy PHP versions - require_once($CFG->libdir.'/gradelib.php'); - } + require_once($CFG->libdir . '/gradelib.php'); if (!isset($qcreate->courseid)) { $qcreate->courseid = $qcreate->course; } - $params = array('itemname'=>$qcreate->name, 'idnumber'=>$qcreate->cmidnumber); + if (array_key_exists('cmidnumber', $qcreate)) { // It may not be always present. + $params = array('itemname' => $qcreate->name, 'idnumber' => $qcreate->cmidnumber); + } else { + $params = array('itemname' => $qcreate->name); + } if ($qcreate->grade > 0) { $params['gradetype'] = GRADE_TYPE_VALUE; @@ -397,90 +849,106 @@ function qcreate_grade_item_update($qcreate) { } else { $params['gradetype'] = GRADE_TYPE_NONE; } + + if ($grades === 'reset') { + $params['reset'] = true; + $grades = null; + } $params['itemnumber'] = 0; - return grade_update('mod/qcreate', $qcreate->courseid, 'mod', 'qcreate', $qcreate->id, 0, NULL, $params); + return grade_update('mod/qcreate', $qcreate->courseid, 'mod', 'qcreate', $qcreate->id, 0, $grades, $params); } + /** * Process submitted grades. * @param opbject qcreate the qcreate object with cmidnumber set to $cm->id * @param object cm coursemodule object * @param array users array of ids of users who can take part in this activity. */ -function qcreate_process_grades($qcreate, $cm, $users){ - global $USER, $DB; - ///do the fast grading stuff +function qcreate_process_grades($qcreate, $cm, $users) { + global $DB, $OUTPUT; + + // Do the fast grading stuff. $grading = false; $commenting = false; $qids = array(); - if (isset($_POST['gradecomment'])) { + $submitcomments = optional_param_array('gradecomment', 0, PARAM_RAW); + $submittedgrades = optional_param_array('menu', 0, PARAM_INT); + if ($submitcomments) { $commenting = true; - //process array of submitted comments - $submitcomments = optional_param_array('gradecomment', 0, PARAM_RAW); - $qids = array_keys($_POST['gradecomment']); + // Process array of submitted comments. + $qids = array_keys($submitcomments); } - if (isset($_POST['menu'])) { + if ($submittedgrades) { $grading = true; - //process array of submitted grades - $submittedgrades = optional_param_array('menu', 0, PARAM_INT); - $qids = array_unique(array_merge($qids, array_keys($_POST['menu']))); + // Process array of submitted grades. + $qids = array_unique(array_merge($qids, array_keys($submittedgrades))); } if (!$qids) { - return; + $message = $OUTPUT->notification(get_string('nothingtosave', 'qcreate'), 'notifyproblem'); + return $message; } - //get the cleaned keys which are the questions ids - $qids = clean_param_array($qids, PARAM_INT); - if ($qids){ + // Get the cleaned keys which are the questions ids. + $qids = clean_param_array($qids, PARAM_INT); + if ($qids) { $toupdates = array(); $questions = $DB->get_records_select('question', 'id IN ('.implode(',', $qids).') AND '. 'createdby IN ('.implode(',', $users).')'); - foreach ($qids as $qid){ - //test that qid is a question created by one of the users we can grade - if (isset($questions[$qid])){ + foreach ($qids as $qid) { + // Test that qid is a question created by one of the users we can grade. + if (isset($questions[$qid])) { $question = $questions[$qid]; - //TODO fix outcomes - //qcreate_process_outcomes($qcreate, $id); + // TODO fix outcomes, + // and call qcreate_process_outcomes. if ($grading) { $submittedgrade = $submittedgrades[$qid]; } else { - $submittedgrade = -1; //not graded + $submittedgrade = -1; // Not graded. } if ($commenting) { $submitcomment = $submitcomments[$qid]; } else { - $submitcomment = ''; //no comment + $submitcomment = ''; // No comment. } - - if (qcreate_process_local_grade($qcreate, $question, false, $submittedgrade, $submitcomment)){ + + if (qcreate_process_local_grade($qcreate, $question, false, true, $submittedgrade, $submitcomment)) { $toupdates[] = $question->createdby; } } } $toupdates = array_unique($toupdates); - foreach ($toupdates as $toupdate){ + foreach ($toupdates as $toupdate) { qcreate_update_grades($qcreate, $toupdate); } - } - $message = notify(get_string('changessaved'), 'notifysuccess', 'center', true); + $message = $OUTPUT->notification(get_string('changessaved'), 'notifysuccess'); return $message; } -function qcreate_process_local_grade($qcreate, $question, $forcenewgrade = false, $submittedgrade=-1, $submittedcomment=''){ - global $USER, $DB; - if ($forcenewgrade || !$grade = qcreate_get_grade($qcreate, $question->id)) { - $grade = qcreate_prepare_new_grade($qcreate, $question); +/** + * Process submitted grades. + * @param opbject qcreate the qcreate object with cmidnumber set to $cm->id + * @param object question with id and createdby + * @param array users array of ids of users who can take part in this activity. + */ +function qcreate_process_local_grade($qcreate, $question, + $forcenewgrade = false, $notifystudent = false, $submittedgrade = -1, $submittedcomment = '') { + global $CFG, $USER, $DB; + require_once($CFG->dirroot . '/mod/qcreate/locallib.php'); + + $context = context_module::instance($qcreate->cmidnumber); + $qcreateobj = new qcreate($context, null, null); + if ($forcenewgrade || !$grade = qcreate_get_local_grade($qcreate, $question->id)) { + $grade = qcreate_prepare_new_local_grade($qcreate, $question); $newgrade = true; } else { $newgrade = false; } - unset($grade->data1); // Don't need to update this. - unset($grade->data2); // Don't need to update this. - //for fast grade, we need to check if any changes take place + // For fast grade, we need to check if any changes take place. $updatedb = false; $updatedb = $updatedb || ($grade->grade != $submittedgrade); @@ -492,65 +960,95 @@ function qcreate_process_local_grade($qcreate, $question, $forcenewgrade = false $grade->userid = $question->createdby; $grade->teacher = $USER->id; - if ($grade->grade != -1){ + if ($grade->grade != -1) { $grade->timemarked = time(); } else { $grade->timemarked = 0; } - //if it is not an update, we don't change the last modified time etc. - //this will also not write into database if no gradecomment and grade is entered. + // If it is not an update, we don't change the last modified time etc. + // This will also not write into database if no gradecomment and grade is entered. - if ($forcenewgrade || $updatedb){ + if ($forcenewgrade || $updatedb) { if ($newgrade) { if (!$sid = $DB->insert_record('qcreate_grades', $grade)) { return false; } $grade->id = $sid; + + $params = array( + 'context' => context_module::instance($qcreate->cmidnumber), + 'objectid' => $grade->id, + 'relateduserid' => $grade->userid, + 'other' => array( + 'qcreateid' => $qcreate->id, + 'questionid' => $question->id, + ) + ); + $event = \mod_qcreate\event\question_graded::create($params); + $event->add_record_snapshot('qcreate_grades', $grade); + $event->trigger(); + } else { if (!$DB->update_record('qcreate_grades', $grade)) { return false; } - } - // triger grade event - //add to log only if updating - add_to_log($qcreate->course, 'qcreate', 'update grades', - 'grades.php?id='.$qcreate->id.'&user='.$grade->userid, - $grade->userid, $qcreate->cmidnumber); + $params = array( + 'context' => context_module::instance($qcreate->cmidnumber), + 'objectid' => $grade->id, + 'relateduserid' => $grade->userid, + 'other' => array( + 'qcreateid' => $qcreate->id, + 'questionid' => $question->id, + ) + ); + $event = \mod_qcreate\event\question_regraded::create($params); + $event->add_record_snapshot('qcreate_grades', $grade); + $event->trigger(); + + } + if ($notifystudent) { + $qcreateobj->notify_student_question_graded($question); + } } return $updatedb; } -function qcreate_process_outcomes($qcreate, $userid) { - global $CFG, $COURSE; - - if (empty($CFG->enableoutcomes)) { - return; +/** + * Return the number of question created by a particular user for a qceate activity + * + * @param $integer userid id of the user, 0 means all users + * @param object $qcreate object + * @return integer + */ +function qcreate_get_qestions_number($userid, $qcreate) { + global $DB; + + $cm = get_coursemodule_from_instance('qcreate', $qcreate->id); + $modcontext = context_module::instance($cm->id); + + $params = array(); + $whereqtype = ''; + $whereuser = ''; + if ($qcreate->allowed != 'ALL') { + $allowedparts = explode(',', $qcreate->allowed); + $allowedlist = "'".join("','", $allowedparts)."'"; + $whereqtype = 'q.qtype IN ('.$allowedlist.') AND '; } - require_once($CFG->libdir.'/gradelib.php'); - - if (!$formdata = data_submitted()) { - return; + if ($userid) { + $params['userid'] = $userid; + $whereuser = 'q.createdby = :userid AND '; } - $data = array(); - $grading_info = grade_get_grades($COURSE->id, 'mod', 'qcreate', $qcreate->id, $userid); - - if (!empty($grading_info->outcomes)) { - foreach($grading_info->outcomes as $n=>$old) { - $name = 'outcome_'.$n; - if (isset($formdata->{$name}[$userid]) and $old->grades[$userid]->grade != $formdata->{$name}[$userid]) { - $data[$n] = $formdata->{$name}[$userid]; - } - } - } - if (count($data) > 0) { - grade_update_outcomes('mod/qcreate', $COURSE->id, 'mod', 'qcreate', $qcreate->id, $userid, $data); - } + $params['contextid'] = $modcontext->id; + $countsql = 'SELECT COUNT(q.id) FROM {question} q,{question_categories} c '. + 'WHERE ' . $whereqtype . $whereuser . + 'q.hidden=\'0\' AND q.parent=\'0\' AND q.category = c.id and c.contextid = :contextid'; + return $DB->count_records_sql($countsql, $params); } /** * Load the local grade object for a particular user @@ -560,20 +1058,22 @@ function qcreate_process_outcomes($qcreate, $userid) { * @param $createnew boolean optional Defaults to false. If set to true a new grade object will be created in the database * @return object The grade */ -function qcreate_get_grade($qcreate, $qid, $createnew=false) { - global $DB; - - $grade = $DB->get_record('qcreate_grades', array('qcreateid'=>$qcreate->id, 'questionid'=>$qid)); +function qcreate_get_local_grade($qcreate, $qid, $createnew = false) { + global $DB; + + $grade = $DB->get_record_sql( + 'SELECT * FROM {qcreate_grades} WHERE qcreateid=? AND questionid=? ORDER BY timemarked DESC LIMIT 1', + array($qcreate->id, $qid)); if ($grade || !$createnew) { return $grade; } - $newgrade = qcreate_prepare_new_grade($qcreate, $qid); - if (!insert_record("qcreate_grades", $newgrade)) { - error("Could not insert a new empty grade"); + $newgrade = qcreate_prepare_new_local_grade($qcreate, $qid); + if (!$DB->insert_record('qcreate_grades', $newgrade)) { + print_error('Could not insert a new empty grade'); } - return get_record('qcreate_grades', 'qcreate', $qcreate->id, 'questionid', $qid); + return $DB->get_record('qcreate_grades', array('qcreateid' => $qcreate->id, 'questionid' => $qid)); } /** @@ -583,79 +1083,89 @@ function qcreate_get_grade($qcreate, $qid, $createnew=false) { * @param $userid int The userid for which we want a grade object * @return object The grade */ -function qcreate_prepare_new_grade($qcreate, $question) { - $grade = new Object; +function qcreate_prepare_new_local_grade($qcreate, $question) { + $grade = new stdClass(); $grade->qcreateid = $qcreate->id; $grade->questionid = $question->id; - $grade->numfiles = 0; - $grade->data1 = ''; - $grade->data2 = ''; $grade->grade = -1; $grade->gradecomment = ''; $grade->teacher = 0; $grade->timemarked = 0; - $grade->mailed = 0; return $grade; } + /** - * Update grades. + * Update grades in the gradebook. * * @param object $qcreate null means all qcreates * @param int $userid specific user only, 0 mean all + * @param bool $nullifnone If true and the user has no grade then a grade item with rawgrade == null will be inserted */ -function qcreate_update_grades($qcreate=null, $userid=0) { +function qcreate_update_grades($qcreate=null, $userid=0, $nullifnone = true) { global $CFG, $DB; - if (!function_exists('grade_update')) { //workaround for buggy PHP versions - require_once($CFG->libdir.'/gradelib.php'); - } + require_once($CFG->libdir . '/gradelib.php'); if ($qcreate != null) { - if ($gradesbyuserids = qcreate_get_user_grades($qcreate, $userid)) { - foreach ($gradesbyuserids as $userid => $gradesbyuserid){ - qcreate_grade_item_update($qcreate); - grade_update('mod/qcreate', $qcreate->courseid, 'mod', 'qcreate', $qcreate->id, 0, $gradesbyuserid); - } + if ($qcreate->grade == 0) { + qcreate_grade_item_update($qcreate); + } else if ($grades = qcreate_get_user_grades($qcreate, $userid)) { + qcreate_grade_item_update($qcreate, $grades); + + } else if ($userid && $nullifnone) { + $grade = new stdClass(); + $grade->userid = $userid; + $grade->rawgrade = null; + qcreate_grade_item_update($qcreate, $grade); + + } else { + qcreate_grade_item_update($qcreate); } } else { - $sql = "SELECT a.*, cm.idnumber as cmidnumber, a.course as courseid - FROM {$CFG->prefix}qcreate a, {$CFG->prefix}course_modules cm, {$CFG->prefix}modules m - WHERE m.name='qcreate' AND m.id=cm.module AND cm.instance=a.id"; + $sql = "SELECT q.*, cm.id as cmidnumber, q.course as courseid + FROM {qcreate} q, {course_modules} cm, {modules} m + WHERE m.name='qcreate' AND m.id=cm.module AND cm.instance=q.id"; $rs = $DB->get_recordset_sql($sql); - foreach ($rs as $qcreate) { + foreach ($rs as $qcreate) { qcreate_grade_item_update($qcreate); if ($qcreate->grade != 0) { - qcreate_update_grades($qcreate); + qcreate_update_grades($qcreate, $userid); } } $rs->close(); } } + /** * Return local grades for given user or all users. * * @param object $qcreate - * @param int $userid optional user id, 0 means all users + * @param mixed $userid optional user id or array of userids, 0 means all users * @return array array of grades, false if none */ function qcreate_get_user_grades($qcreate, $userid=0) { - global $CFG, $DB; - if (is_array($userid)){ - $user = "u.id IN (".implode(',', $userid).") AND"; - } else if ($userid){ - $user = "u.id = $userid AND"; + global $DB; + + $cm = get_coursemodule_from_instance('qcreate', $qcreate->id, 0, false, MUST_EXIST); + $context = context_module::instance($cm->id); + if (is_array($userid)) { + $user = "u.id IN (".implode(',', $userid).") AND"; + } else if ($userid) { + $user = "u.id = $userid AND"; } else { - $user = ''; - } - $modulecontext = get_context_instance(CONTEXT_MODULE, $qcreate->cmidnumber); - $sql = "SELECT q.id, u.id AS userid, g.grade AS rawgrade, g.gradecomment AS feedback, g.teacher AS usermodified, q.qtype AS qtype - FROM {$CFG->prefix}user u, {$CFG->prefix}question_categories qc, {$CFG->prefix}question q - LEFT JOIN {$CFG->prefix}qcreate_grades g ON g.questionid = q.id - WHERE $user u.id = q.createdby AND qc.id = q. category AND qc.contextid={$modulecontext->id} - ORDER BY rawgrade DESC"; + $user = ''; + } + + $sql = "SELECT q.id, u.id AS userid, g.grade AS rawgrade, g.gradecomment AS feedback, + g.teacher AS usermodified, q.qtype AS qtype + FROM {user} u, {question_categories} qc, {question} q + LEFT JOIN {qcreate_grades} g ON g.questionid = q.id + WHERE $user u.id = q.createdby AND qc.id = q. category AND qc.contextid={$context->id} + ORDER BY rawgrade DESC"; $localgrades = $DB->get_records_sql($sql); + $gradesbyuserids = array(); - foreach($localgrades as $k=>$v) { - if (!isset($gradesbyuserids[$v->userid])){ + foreach ($localgrades as $k => $v) { + if (!isset($gradesbyuserids[$v->userid])) { $gradesbyuserids[$v->userid] = array(); } if ($v->rawgrade == -1) { @@ -664,7 +1174,7 @@ function qcreate_get_user_grades($qcreate, $userid=0) { $gradesbyuserids[$v->userid][$k] = $v; } $aggregategradebyuserids = array(); - foreach ($gradesbyuserids as $userid => $gradesbyuserid){ + foreach ($gradesbyuserids as $userid => $gradesbyuserid) { $aggregategradebyuserids[$userid] = qcreate_grade_aggregate($gradesbyuserid, $qcreate); } return $aggregategradebyuserids; @@ -673,54 +1183,58 @@ function qcreate_get_user_grades($qcreate, $userid=0) { * @param array gradesforuser an array of objects from local grades tables * @return aggregated grade */ -function qcreate_grade_aggregate($gradesforuser, $qcreate){ +function qcreate_grade_aggregate($gradesforuser, $qcreate) { $aggregated = new stdClass(); $aggregated->rawgrade = 0; $aggregated->usermodified = 0; $requireds = qcreate_required_qtypes($qcreate); - //need to make sure that we grade required questions and then any extra. - //grades are sorted for descending raw grade + + // Need to make sure that we grade required questions and then any extra. + // Grades are sorted for descending raw grade. $counttotalrequired = $qcreate->totalrequired; - if ($requireds){ - foreach ($requireds as $required){ - foreach ($gradesforuser as $key => $gradeforuser){ - if ($gradeforuser->qtype == $required->qtype){ - $aggregated->rawgrade += ($gradeforuser->rawgrade / $qcreate->totalrequired); - $aggregated->userid = $gradeforuser->userid; - unset($gradesforuser[$key]); - $required->no--; - $counttotalrequired--; - if ($required->no == 0){ - //go on to the next required type - break; - } - } - } - } - } - if ($counttotalrequired != 0){ - //now grade the remainder of the questions - if ($qcreate->allowed != 'ALL'){ + if ($requireds) { + foreach ($requireds as $required) { + foreach ($gradesforuser as $key => $gradeforuser) { + if ($gradeforuser->qtype == $required->qtype) { + $aggregated->rawgrade += ($gradeforuser->rawgrade / $qcreate->totalrequired); + $aggregated->userid = $gradeforuser->userid; + unset($gradesforuser[$key]); + $required->no--; + $counttotalrequired--; + if ($required->no == 0) { + // Go on to the next required type. + break; + } + } + } + } + } + + if ($counttotalrequired != 0) { + // Now grade the remainder of the questions. + if ($qcreate->allowed != 'ALL') { $allowall = false; $allowed = explode(',', $qcreate->allowed); } else { $allowall = true; } - foreach ($gradesforuser as $key => $gradeforuser){ - if ($allowall || in_array($gradeforuser->qtype, $allowed)){ + + foreach ($gradesforuser as $key => $gradeforuser) { + if ($allowall || in_array($gradeforuser->qtype, $allowed)) { $aggregated->rawgrade += ($gradeforuser->rawgrade / $qcreate->totalrequired); $aggregated->userid = $gradeforuser->userid; $counttotalrequired--; - if ($counttotalrequired == 0){ + if ($counttotalrequired == 0) { break; } } } } + $totalrequireddone = $qcreate->totalrequired - $counttotalrequired; - $aggregated->rawgrade = $aggregated->rawgrade * ((100 - $qcreate->graderatio) / 100) + - (($totalrequireddone*$qcreate->grade / $qcreate->totalrequired) * ($qcreate->graderatio/ 100)); + $aggregated->rawgrade = $aggregated->rawgrade * ((100 - $qcreate->graderatio) / 100) + ( + ($totalrequireddone * $qcreate->grade / $qcreate->totalrequired) * ($qcreate->graderatio / 100)); return $aggregated; } @@ -731,17 +1245,282 @@ function qcreate_grade_aggregate($gradesforuser, $qcreate){ * @param object qcreate the qcreate object * @return array an array of objects */ -function qcreate_required_qtypes($qcreate){ - global $DB; - +function qcreate_required_qtypes($qcreate) { + global $DB; + static $requiredcache = array(); - if (!isset($requiredcache[$qcreate->id])){ - $requiredcache[$qcreate->id] = $DB->get_records('qcreate_required', array('qcreateid'=>$qcreate->id), 'qtype', 'qtype, no, id'); + if (!isset($requiredcache[$qcreate->id])) { + $requiredcache[$qcreate->id] = $DB->get_records('qcreate_required', + array('qcreateid' => $qcreate->id), 'qtype', 'qtype, no, id'); } return $requiredcache[$qcreate->id]; } -function qcreate_time_status($qcreate){ +/** + * Extends the settings navigation with the qcreate settings + * + * This function is called when the context for the page is a qcreate module. This is not called by AJAX + * so it is safe to rely on the $PAGE. + * + * @param settings_navigation $settingsnav {@link settings_navigation} + * @param navigation_node $qcreatenode {@link navigation_node} + */ +function qcreate_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $qcreatenode=null) { + global $PAGE, $CFG; + require_once($CFG->libdir . '/questionlib.php'); + + $keys = $qcreatenode->get_children_key_list(); + $beforekey = null; + $i = array_search('modedit', $keys); + if ($i === false and array_key_exists(0, $keys)) { + $beforekey = $keys[0]; + } else if (array_key_exists($i + 1, $keys)) { + $beforekey = $keys[$i + 1]; + } + if (has_capability('mod/qcreate:grade', $PAGE->cm->context)) { + $node = navigation_node::create(get_string('grading', 'qcreate'), + new moodle_url('/mod/qcreate/edit.php', array('cmid' => $PAGE->cm->id)), + navigation_node::TYPE_SETTING, null, 'mod_qcreate_edit'); + $qcreatenode->add_node($node, $beforekey); + $node = navigation_node::create(get_string('exportgood', 'qcreate'), + new moodle_url('/mod/qcreate/exportgood.php', array('cmid' => $PAGE->cm->id)), + navigation_node::TYPE_SETTING, null, 'mod_qcreate_exportgood'); + $qcreatenode->add_node($node, $beforekey); + question_extend_settings_navigation($settingsnav, $PAGE->cm->context)->trim_if_empty(); + } +} + +/** + * Add a get_coursemodule_info function in case any qcreate type wants to add 'extra' information + * for the course (see resource). + * + * Given a course_module object, this function returns any "extra" information that may be needed + * when printing this activity in a course listing. See get_array_of_activities() in course/lib.php. + * + * @param stdClass $coursemodule The coursemodule object (record). + * @return cached_cm_info An object on information that the courses + * will know about (most noticeably, an icon). + */ +function qcreate_get_coursemodule_info($coursemodule) { + global $DB; + + $dbparams = ['id' => $coursemodule->instance]; + $fields = 'id, name, intro, introformat, completionquestions'; + if (!$qcreate = $DB->get_record('qcreate', $dbparams, $fields)) { + return false; + } + + $result = new cached_cm_info(); + $result->name = $qcreate->name; + + if ($coursemodule->showdescription) { + // Convert intro to html. Do not filter cached version, filters run at display time. + $result->content = format_module_intro('qcreate', $qcreate, $coursemodule->id, false); + } + + // Populate the custom completion rules as key => value pairs, but only if the completion mode is 'automatic'. + if ($coursemodule->completion == COMPLETION_TRACKING_AUTOMATIC) { + $result->customdata['customcompletionrules']['completionquestions'] = $qcreate->completionquestions; + } + + return $result; +} +/** + * Callback which returns human-readable strings describing the active completion custom rules for the module instance. + * + * @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules'] + * @return array $descriptions the array of descriptions for the custom rules. + */ +function mod_qcreate_get_completion_active_rule_descriptions($cm) { + // Values will be present in cm_info, and we assume these are up to date. + if (empty($cm->customdata['customcompletionrules']) + || $cm->completion != COMPLETION_TRACKING_AUTOMATIC) { + return []; + } + + $descriptions = []; + foreach ($cm->customdata['customcompletionrules'] as $key => $val) { + switch ($key) { + case 'completionquestions': + if (empty($val)) { + continue; + } + $descriptions[] = get_string('completionquestionsdesc', 'qcreate', $val); + break; + default: + break; + } + } + return $descriptions; +} +/** + * Serves the files from the qcreate file areas + * + * @package mod_qcreate + * @category files + * + * @param stdClass $course the course object + * @param stdClass $cm the course module object + * @param stdClass $context the qcreate's context + * @param string $filearea the name of the file area + * @param array $args extra arguments (itemid, path) + * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving + */ +function qcreate_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload, array $options=array()) { + global $DB, $CFG; + // Special case, sending a question bank export. + if ($filearea === 'export') { + list($context, $course, $cm) = get_context_info_array($context->id); + require_login($course, false, $cm); + + require_once($CFG->dirroot . '/question/editlib.php'); + $contexts = new question_edit_contexts($context); + // Check export capability. + $contexts->require_one_edit_tab_cap('export'); + $categoryid = (int)array_shift($args); + $format = array_shift($args); + $cattofile = array_shift($args); + $contexttofile = array_shift($args); + $betterthangrade = (int)array_shift($args); + $namingother = (bool)array_shift($args); + $namingtext = array_shift($args); + $namingfirstname = (bool)array_shift($args); + $naminglastname = (bool)array_shift($args); + $namingusername = (bool)array_shift($args); + $namingactivityname = (bool)array_shift($args); + $namingtimecreated = (bool)array_shift($args); + $filename = array_shift($args); + + if ($namingactivityname) { + $qcreate = $DB->get_record('qcreate', array('id' => $cm->instance)); + } + + // Load parent class for import/export. + require_once($CFG->dirroot . '/question/format.php'); + require_once($CFG->dirroot . '/question/editlib.php'); + require_once($CFG->dirroot . '/question/format/' . $format . '/format.php'); + + $classname = 'qformat_' . $format; + if (!class_exists($classname)) { + send_file_not_found(); + } + $qformat = new $classname(); + + if (!$category = $DB->get_record('question_categories', array('id' => $categoryid))) { + send_file_not_found(); + } + + $qformat->setContexts($contexts->having_one_edit_tab_cap('export')); + $qformat->setCourse($course); + + $questions = get_questions_category($category, true ); + if ($betterthangrade > 0) { + // Filter questions by grade. + $qkeys = array(); + foreach ($questions as $question) { + $qkeys[] = $question->id; + } + $questionlist = join($qkeys, ','); + $sql = 'SELECT questionid, grade FROM {qcreate_grades} '. + 'WHERE questionid IN ('.$questionlist.') AND grade >= '.$betterthangrade; + if ($goodquestions = $DB->get_records_sql($sql)) { + foreach ($questions as $zbkey => $question) { + if (!array_key_exists($question->id, $goodquestions)) { + unset($questions[$zbkey]); + } + } + } else { + send_file_not_found(); + print_error('noquestionsabove', 'qcreate', $thispageurl->out()); + } + } + + if ($namingfirstname||$naminglastname||$namingusername + ||$namingother||$namingactivityname||$namingtimecreated) { + if ($namingfirstname||$naminglastname||$namingusername) { + $useridkeys = array(); + foreach ($questions as $question) { + $useridkeys[] = $question->createdby; + } + $useridlist = join($useridkeys, ','); + if (!$users = $DB->get_records_select('user', "id IN ($useridlist)")) { + $users = array(); + } + } + foreach ($questions as $question) { + $prefixes = array(); + if ($namingother && !empty($namingtext)) { + $prefixes[] = $namingtext; + } + if ($namingfirstname) { + $prefixes[] = isset($users[$question->createdby]) ? $users[$question->createdby]->firstname : ''; + } + if ($naminglastname) { + $prefixes[] = isset($users[$question->createdby]) ? $users[$question->createdby]->lastname : ''; + } + if ($namingusername) { + $prefixes[] = isset($users[$question->createdby]) ? $users[$question->createdby]->username : ''; + } + if ($namingactivityname) { + $prefixes[] = $qcreate->name; + } + if ($namingtimecreated) { + $prefixes[] = userdate($question->timecreated, get_string('strftimedatetimeshort')); + } + $prefixes[] = $question->name; + $question->name = join($prefixes, '-'); + } + } + $qformat->setQuestions($questions); + + if ($cattofile == 'withcategories') { + $qformat->setCattofile(true); + } else { + $qformat->setCattofile(false); + } + + if ($contexttofile == 'withcontexts') { + $qformat->setContexttofile(true); + } else { + $qformat->setContexttofile(false); + } + + if (!$qformat->exportpreprocess()) { + send_file_not_found(); + print_error('exporterror', 'question', $thispageurl->out()); + } + + // Export data to moodle file pool. + if (!$content = $qformat->exportprocess()) { + send_file_not_found(); + } + + send_file($content, $filename, 0, 0, true, true, $qformat->mime_type()); + } + + if ($context->contextlevel != CONTEXT_MODULE) { + send_file_not_found(); + } + + require_login($course, true, $cm); + + send_file_not_found(); +} + +function qcreate_question_pluginfile($course, $context, $component, + $filearea, $qubaid, $slot, $args, $forcedownload, array $options=array()) { + $fs = get_file_storage(); + $relativepath = implode('/', $args); + $fullpath = "/$context->id/$component/$filearea/$relativepath"; + if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { + send_file_not_found(); + } + + send_stored_file($file, 0, 0, $forcedownload, $options); +} + +function qcreate_time_status($qcreate) { $timenow = time(); $available = ($qcreate->timeopen < $timenow && ($timenow < $qcreate->timeclose || !$qcreate->timeclose)); @@ -751,19 +1530,19 @@ function qcreate_time_status($qcreate){ $string = get_string("activityclosed", "qcreate"); } $string = "$string"; - if (!$qcreate->timeopen && !$qcreate->timeclose){ + if (!$qcreate->timeopen && !$qcreate->timeclose) { return $string.' '.get_string('timenolimit', 'qcreate'); } - if ($qcreate->timeopen){ + if ($qcreate->timeopen) { if ($timenow < $qcreate->timeopen) { $string .= ' '.get_string("timewillopen", "qcreate", userdate($qcreate->timeopen)); } else { - $string .= ' '.get_string("timeopened", "qcreate", userdate($qcreate->timeclose)); + $string .= ' '.get_string("timeopened", "qcreate", userdate($qcreate->timeopen)); } } - if ($qcreate->timeclose){ + if ($qcreate->timeclose) { if ($timenow < $qcreate->timeclose) { - $string .= ' '.get_string("timewillclose", "qcreate", userdate($qcreate->timeopen)); + $string .= ' '.get_string("timewillclose", "qcreate", userdate($qcreate->timeclose)); } else { $string .= ' '.get_string("timeclosed", "qcreate", userdate($qcreate->timeclose)); } @@ -771,4 +1550,248 @@ function qcreate_time_status($qcreate){ return $string; } -?> +/** + * Check if the module has any update that affects the current user since a given time. + * + * @param cm_info $cm course module data + * @param int $from the time to check updates from + * @param array $filter if we need to check only specific updates + * @return stdClass an object with the different type of areas indicating if they were updated or not + * @since Moodle 3.2 + */ +function qcreate_check_updates_since(cm_info $cm, $from, $filter = array()) { + global $DB, $USER, $CFG; + require_once($CFG->dirroot . '/mod/qcreate/locallib.php'); + + $updates = course_check_module_updates_since($cm, $from, array(), $filter); + + // Check if questions have been modified or added. + $context = $cm->context; + $updates->questions = (object) array('updated' => false); + $qcreateobj = new qcreate($context, null, null); + $instance = $qcreateobj->get_instance(); + + list($whereqtype, $params) = $qcreateobj->get_allowed_qtypes_where(); + $params['time1'] = $from; + $params['time2'] = $from; + if (has_capability('mod/qcreate:grade', $qcreateobj->get_context())) { + // Teacher should see questions from all users. + $whereuser = '(q.timemodified > :time1 OR q.timecreated > :time2) AND '; + } else { + $whereuser = 'q.createdby = :userid AND (q.timemodified > :time1 OR q.timecreated > :time2) AND '; + $params['userid'] = $USER->id; + } + $params['contextid'] = $qcreateobj->get_context()->id; + + $questionsql = "SELECT q.*, c.id as cid, c.name as cname, g.grade, g.gradecomment, g.id as gid + FROM {question_categories} c, {question} q + LEFT JOIN {qcreate_grades} g ON q.id = g.questionid + AND g.qcreateid = {$instance->id} + WHERE $whereqtype $whereuser c.contextid = :contextid AND c.id = q.category AND q.hidden='0' AND q.parent='0'"; + $questions = $DB->get_records_sql($questionsql, $params); + if (!empty($questions)) { + $updates->questions->updated = true; + $updates->questions->itemids = array_keys($questions); + } + + // Check for new grades. + // TODO : what should see teachers here ? + $updates->grades = (object) array('updated' => false); + $params['userid'] = $USER->id; + $params['time1'] = $from; + $params['contextid'] = $context->id; + $sql = "SELECT g.id, g.grade AS rawgrade, g.gradecomment AS feedback, + g.teacher AS usermodified, q.qtype AS qtype + FROM {user} u, {question_categories} qc, {question} q + LEFT JOIN {qcreate_grades} g ON g.questionid = q.id + WHERE u.id = :userid AND u.id = q.createdby AND qc.id = q.category AND qc.contextid= :contextid + AND timemarked > :time1 + ORDER BY rawgrade DESC"; + $grades = $DB->get_records_sql($sql, $params); + if (!empty($grades)) { + $updates->grades->updated = true; + $updates->grades->itemids = array_keys($grades); + } + + return $updates; +} + +/** + * This function receives a calendar event and returns the action associated with it, or null if there is none. + * + * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event + * is not displayed on the block. + * + * @param calendar_event $event + * @param \core_calendar\action_factory $factory + * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default). + * @return \core_calendar\local\event\entities\action_interface|null + */ +function mod_qcreate_core_calendar_provide_event_action(calendar_event $event, + \core_calendar\action_factory $factory, + $userid = 0) { + + global $CFG, $USER; + + require_once($CFG->dirroot . '/mod/qcreate/locallib.php'); + + if (empty($userid)) { + $userid = $USER->id; + } + + $cm = get_fast_modinfo($event->courseid, $userid)->instances['qcreate'][$event->instance]; + $context = context_module::instance($cm->id); + $qcreateobj = new qcreate($context, $cm, null); + $instance = $qcreateobj->get_instance(); + + // Check they have capabilities allowing them to view the qcreate. + if (!has_any_capability(array('mod/qcreate:view', 'mod/qcreate:submit'), + $qcreateobj->get_context(), $userid)) { + return null; + } + + // Check if qcreate is closed, if so don't display it. + if (!empty($instance->timeclose) && $instance->timeclose <= time()) { + return null; + } + + $name = get_string('attemptqcreatenow', 'qcreate'); + $url = new \moodle_url('/mod/qcreate/view.php', [ + 'id' => $cm->id + ]); + $itemcount = 1; + $actionable = true; + + // Check if the qcreate is not currently actionable. + if (!empty($instance->timeopen) && $instance->timeopen > time()) { + $actionable = false; + } + + return $factory->create_instance( + $name, + $url, + $itemcount, + $actionable + ); +} + +/** + * This function calculates the minimum and maximum cutoff values for the timestart of + * the given event. + * + * It will return an array with two values, the first being the minimum cutoff value and + * the second being the maximum cutoff value. Either or both values can be null, which + * indicates there is no minimum or maximum, respectively. + * + * If a cutoff is required then the function must return an array containing the cutoff + * timestamp and error string to display to the user if the cutoff value is violated. + * + * A minimum and maximum cutoff return value will look like: + * [ + * [1505704373, 'The due date must be after the sbumission start date'], + * [1506741172, 'The due date must be before the cutoff date'] + * ] + * + * If the event does not have a valid timestart range then [false, false] will + * be returned. + * + * @param calendar_event $event The calendar event to get the time range for + * @param stdClass $instance The module instance to get the range from + * @return array + */ +function mod_qcreate_core_calendar_get_valid_event_timestart_range(\calendar_event $event, \stdClass $qcreate) { + global $CFG, $DB; + require_once($CFG->dirroot . '/mod/qcreate/locallib.php'); + + $mindate = null; + $maxdate = null; + + if ($event->eventtype == QCREATE_EVENT_TYPE_OPEN) { + if (!empty($qcreate->timeclose)) { + $maxdate = [ + $qcreate->timeclose, + get_string('openafterclose', 'qcreate') + ]; + } + } else if ($event->eventtype == QCREATE_EVENT_TYPE_CLOSE) { + if (!empty($qcreate->timeopen)) { + $mindate = [ + $qcreate->timeopen, + get_string('closebeforeopen', 'qcreate') + ]; + } + } + + return [$mindate, $maxdate]; +} +/** + * This function will update the qcreate module according to the + * event that has been modified. + * + * @throws \moodle_exception + * @param \calendar_event $event + * @param stdClass $instance The module instance to get the range from + */ +function mod_qcreate_core_calendar_event_timestart_updated(\calendar_event $event, \stdClass $qcreate) { + global $CFG, $DB; + + require_once($CFG->dirroot . '/mod/qcreate/locallib.php'); + if (!in_array($event->eventtype, [QCREATE_EVENT_TYPE_OPEN, QCREATE_EVENT_TYPE_CLOSE])) { + // This isn't an event that we care about so we can ignore it. + return; + } + + $courseid = $event->courseid; + $modulename = $event->modulename; + $instanceid = $event->instance; + $modified = false; + $closedatechanged = false; + + // Something weird going on. The event is for a different module so + // we should ignore it. + if (empty($event->instance) || $event->modulename != 'qcreate') { + return; + } + + if ($qcreate->id != $instanceid) { + // The provided qcreate instance doesn't match the event so + // there is nothing to do here. + return; + } + + $coursemodule = get_fast_modinfo($courseid)->instances[$modulename][$instanceid]; + $context = context_module::instance($coursemodule->id); + + // The user does not have the capability to modify this activity. + if (!has_capability('moodle/course:manageactivities', $context)) { + return; + } + + if ($event->eventtype == QCREATE_EVENT_TYPE_OPEN) { + // If the event is for the qcreate activity opening then we should + // set the start time of the qcreate activity to be the new start + // time of the event. + if ($qcreate->timeopen != $event->timestart) { + $qcreate->timeopen = $event->timestart; + $modified = true; + } + } else if ($event->eventtype == QCREATE_EVENT_TYPE_CLOSE) { + // If the event is for the qcreate activity closing then we should + // set the end time of the qcreate activity to be the new start + // time of the event. + if ($qcreate->timeclose != $event->timestart) { + $qcreate->timeclose = $event->timestart; + $modified = true; + $closedatechanged = true; + } + } + + if ($modified) { + $qcreate->timemodified = time(); + $DB->update_record('qcreate', $qcreate); + + qcreate_update_events($qcreate); + $event = \core\event\course_module_updated::create_from_cm($coursemodule, $context); + $event->trigger(); + } +} diff --git a/locallib.php b/locallib.php index 9dda47d..9b23b7e 100644 --- a/locallib.php +++ b/locallib.php @@ -1,381 +1,1848 @@ libdir.'/questionlib.php'); -require_once($CFG->libdir.'/gradelib.php'); +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir . '/questionlib.php'); +require_once($CFG->libdir . '/gradelib.php'); +require_once($CFG->dirroot . '/mod/qcreate/renderable.php'); /** - * Function that can be used in various parts of the quiz code. - * @param object $quiz - * @param integer $cmid - * @param object $question - * @param string $returnurl url to return to after action is done. - * @return string html for a number of icons linked to action pages for a - * question - preview and edit / view icons depending on user capabilities. + * Standard base class for mod_qcreate. + * + * @package mod_qcreate + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -function qcreate_question_action_icons($cmid, $question, $returnurl){ - global $CFG, $COURSE, $OUTPUT; - static $stredit = null; - static $strview = null; - static $strpreview = null; - static $strdelete = null; - if ($stredit === null){ - $stredit = get_string('edit'); - $strview = get_string('view'); - $strpreview = get_string('preview', 'quiz'); - $strdelete = get_string("delete"); - } - $html =''; - if (($question->qtype != 'random')){ - if (question_has_capability_on($question, 'use', $question->cid)){ - $url = '/question/preview.php?id=' . $question->id . '&courseid=' .$COURSE->id; - $action = new popup_action('click', $url, 'questionpreview', question_preview_popup_params()); - $html .= $OUTPUT->action_link($url, "pix_url('t/preview')."\" class=\"iconsmall\" alt=\"$strpreview\" />", $action, array('title' => $strpreview)); - } - } - $questionparams = array('returnurl' => $returnurl->out_as_local_url(), 'cmid'=>$cmid, 'id' => $question->id); - $questionurl = new moodle_url("$CFG->wwwroot/question/question.php", $questionparams); - if (question_has_capability_on($question, 'edit', $question->cid) || - question_has_capability_on($question, 'move', $question->cid)) { - $html .= "out()."\">" . - "pix_url('t/edit')."\" class=\"iconsmall\" alt=\"$stredit\" />" . - ""; - } elseif (question_has_capability_on($question, 'view', $question->cid)){ - $html .= "out(false, array('id'=>$question->id))."\">" . - "pix_url('i/info')."\" alt=\"$strview\" />" . - ""; - } - if (question_has_capability_on($question, 'edit', $question->cid)) { - $html .= "id\">" . - "pix_url('t/delete')."\" alt=\"$strdelete\" />"; +class qcreate { + + /** @var stdClass the qcreate record that contains the global settings for this qcreate instance */ + private $instance; + + /** @var stdClass the grade_item record for this qcreate instance's primary grade item. */ + private $gradeitem; + + /** @var context the context of the course module for this qcreate instance + * (or just the course if we are creating a new one) + */ + private $context; + + /** @var stdClass the course this qcreate instance belongs to */ + private $course; + + /** @var stdClass the admin config for all qcreate instances */ + private $adminconfig; + + /** @var qcreate_renderer the custom renderer for this module */ + private $output; + + /** @var stdClass the course module for this qcreate instance */ + private $coursemodule; + + /** @var array cache for things like the coursemodule name or the scale menu - + * only lives for a single request. + */ + private $cache; + + /** @var string modulename prevents excessive calls to get_string */ + private static $modulename = null; + + /** @var string modulenameplural prevents excessive calls to get_string */ + private static $modulenameplural = null; + + /** + * Constructor for the base qcreate class. + * + * @param mixed $coursemodulecontext context|null the course module context + * (or the course context if the coursemodule has not been + * created yet). + * @param mixed $coursemodule the current course module if it was already loaded, + * otherwise this class will load one from the context as required. + * @param mixed $course the current course if it was already loaded, + * otherwise this class will load one from the context as required. + */ + public function __construct($coursemodulecontext, $coursemodule, $course) { + $this->context = $coursemodulecontext; + $this->coursemodule = $coursemodule; + $this->course = $course; + + // Temporary cache only lives for a single request - used to reduce db lookups. + $this->cache = array(); + } + + /** + * Set the submitted form data. + * + * @param stdClass $data The form data (instance) + */ + public function set_instance(stdClass $data) { + $this->instance = $data; } - return $html; -} -function qcreate_required_q_list($requireds, $cat, $thisurl, $qcreate, $cm, $modulecontext){ - global $CFG, $DB, $USER, $COURSE, $OUTPUT; + /** + * Set the context. + * + * @param context $context The new context + */ + public function set_context(context $context) { + $this->context = $context; + } - $qtypemenu = question_type_menu(); + /** + * Set the course data. + * + * @param stdClass $course The course data + */ + public function set_course(stdClass $course) { + $this->course = $course; + } - if ($qcreate->graderatio == 100){ - $showmanualgrades = false; - } else { - $showmanualgrades = true; - } - - $questionurl = new moodle_url($CFG->wwwroot.'/question/question.php'); - $questionurl->params(array('cmid'=>$cm->id, 'returnurl'=>$thisurl->out_as_local_url())); - - - $questionsql = "SELECT q.*, c.id as cid, c.name as cname, g.grade, g.gradecomment, g.id as gid - FROM {$CFG->prefix}question_categories c, {$CFG->prefix}question q - LEFT JOIN {$CFG->prefix}qcreate_grades g ON q.id = g.questionid - AND g.qcreateid = {$qcreate->id} - WHERE c.contextid = {$modulecontext->id} AND c.id = q.category AND q.hidden='0' AND q.parent='0' - AND q.createdby=".$USER->id; - if ($qcreate->allowed != 'ALL'){ - //wrap question type names in inverted commas. - $allowedlistparts = explode(',', $qcreate->allowed); - $allowedlist = ('\''.join($allowedlistparts, '\', \'').'\''); - $questionsql .= " AND q.qtype IN ($allowedlist)"; - } - - $questions = $DB->get_records_sql($questionsql); - $activityopen = qcreate_activity_open($qcreate); - - echo $OUTPUT->heading(get_string('requiredquestions', 'qcreate')); - $qtyperequired = 0; - $qtypedone = 0; - $qtypeqs = qcreate_questions_of_type($questions); - $content = "\n\t\t
    "; - if ($requireds){ - $i = 1; - $grammarised = qcreate_proper_grammar($requireds); - $punctuated = qcreate_proper_punctuation($grammarised); - foreach ($requireds as $qtype => $required){ - $qtyperequired += $required->no; - if (!empty($qtypeqs[$qtype])){ - $requireds[$qtype]->done = (count($qtypeqs[$qtype]) > $required->no) - ? $required->no:count($qtypeqs[$qtype]); - $requireds[$qtype]->stillrequiredno = $required->no - $requireds[$qtype]->done; - //sub list of questions done of each question type - $questionlist = "\n\t\t\t\t
      "; - $i = 0; - while (($i < $required->no) && ($qtypeq = array_shift($qtypeqs[$qtype]))){ - $questionlistitem = question_item_html($qtypeq, $questionurl, $thisurl, $qcreate, $cm, $showmanualgrades); - $questionlist .= "\n\t\t\t\t\t
    • $questionlistitem
    • "; - $i++; + /** + * Display the activity, used by view.php + * + * The activity is displayed differently depending on your role, + * the settings for the activity and the status of the activity. + * + * @param string $action The current action if any. + * @return string - The page output. + */ + public function view($action='') { + $o = ''; + $mform = null; + $notices = array(); + $nextpageparams = array(); + + if (!empty($this->get_course_module()->id)) { + $nextpageparams['id'] = $this->get_course_module()->id; + } + + if ($action == 'viewcourseindex') { + $o .= $this->view_course_index(); + } else if ($action == 'overview') { + $o .= $this->view_overview_page(); + } else if ($action == 'view') { + $o .= $this->view_student_page(); + } else if ($action == 'confirmdelete') { + $o .= $this->view_delete_confirm($mform); + } + return $o; + } + + /** + * Add this instance to the database. + * + * @param stdClass $formdata The data submitted from the form + * @return mixed false if an error occurs or the int id of the new instance + */ + public function add_instance(stdClass $formdata) { + global $DB; + + $adminconfig = $this->get_admin_config(); + $err = ''; + + // Add the database record. + $update = new stdClass(); + $update->name = $formdata->name; + $update->timemodified = time(); + $update->timecreated = time(); + $update->course = $formdata->course; + $update->courseid = $formdata->course; + $update->grade = $formdata->grade; + $update->graderatio = $formdata->graderatio; + $update->intro = $formdata->intro; + $update->introformat = $formdata->introformat; + $update->allowed = join(array_keys($formdata->allowed), ','); + $update->totalrequired = $formdata->totalrequired; + $update->studentqaccess = $formdata->studentqaccess; + $update->timesync = 0; + $update->timeopen = $formdata->timeopen; + $update->timeclose = $formdata->timeclose; + if (isset($formdata->completionquestions)) { + $update->completionquestions = $formdata->completionquestions; + } else { + $update->completionquestions = 0; + } + $update->sendgradernotifications = $adminconfig->sendgradernotifications; + if (isset($formdata->sendgradernotifications)) { + $update->sendgradernotifications = $formdata->sendgradernotifications; + } + $update->sendstudentnotifications = $adminconfig->sendstudentnotifications; + if (isset($formdata->sendstudentnotifications)) { + $update->sendstudentnotifications = $formdata->sendstudentnotifications; + } + $returnid = $DB->insert_record('qcreate', $update); + + // Now save the requireds. + $qtypemins = array_filter($formdata->qtype); + if (count($qtypemins)) { + foreach ($qtypemins as $key => $qtypemin) { + $toinsert = new stdClass(); + $toinsert->no = $formdata->minimumquestions[$key]; + $toinsert->qtype = $qtypemin; + $toinsert->qcreateid = $returnid; + $DB->insert_record('qcreate_required', $toinsert); + } + } + + // We need to use context now, so we need to make sure all needed info is already in db. + $DB->set_field('course_modules', 'instance', $returnid, array('id' => $formdata->coursemodule)); + + $context = context_module::instance($formdata->coursemodule); + $contexts = array($context); + question_make_default_categories($contexts); + + $this->update_calendar($formdata->coursemodule); + $this->update_gradebook(false, $formdata->coursemodule); + + $this->instance = $DB->get_record('qcreate', array('id' => $returnid), '*', MUST_EXIST); + $requireds = $DB->get_records('qcreate_required', + array('qcreateid' => $returnid), 'qtype', 'qtype, no, id'); + $this->instance->requiredqtypes = $requireds; + + // Cache the course record. + $this->course = $DB->get_record('course', array('id' => $formdata->course), '*', MUST_EXIST); + + return $returnid; + } + + /** + * Delete all grades from the gradebook for this qcreate activity. + * + * @return bool + */ + protected function delete_grades() { + global $CFG; + + $result = grade_update('mod/qcreate', + $this->get_course()->id, + 'mod', + 'qcreate', + $this->get_instance()->id, + 0, + null, + array('deleted' => 1)); + return $result == GRADE_UPDATE_OK; + } + + /** + * Delete this instance from the database. + * + * @return bool false if an error occurs + */ + public function delete_instance() { + global $DB; + $result = true; + + // Delete files associated with this qcreate. + $fs = get_file_storage(); + if (! $fs->delete_area_files($this->context->id) ) { + $result = false; + } + + // Delete_records will throw an exception if it fails - so no need for error checking here. + $DB->delete_records('qcreate_grades', array('qcreateid' => $this->get_instance()->id)); + $DB->delete_records('qcreate_required', array('qcreateid' => $this->get_instance()->id)); + + // Delete items from the gradebook. + if (! $this->delete_grades()) { + $result = false; + } + + $events = $DB->get_records('event', array('modulename' => 'qcreate', 'instance' => $this->get_instance()->id)); + foreach ($events as $event) { + $event = calendar_event::load($event); + $event->delete(); + } + + // Delete the instance. + $DB->delete_records('qcreate', array('id' => $this->get_instance()->id)); + + return $result; + } + + public function get_required_qtypes() { + return $this->get_instance()->requiredqtypes; + } + + public function get_allowed_qtypes_list() { + return $this->get_instance()->allowed; + } + + public function get_allowed_qtypes_array() { + return explode(',', $this->get_instance()->allowed); + } + + public function get_allowed_qtypes_where() { + global $DB; + if ($this->get_instance()->allowed != 'ALL') { + list($sql, $params) = $DB->get_in_or_equal($this->get_allowed_qtypes_array(), SQL_PARAMS_NAMED); + $sql = 'q.qtype ' . $sql . ' AND '; + return array($sql, $params); + } else { + return array('', array()); + } + + } + + /** + * Actual implementation of the reset course functionality, delete all the + * qcreate questions and local grades for course $data->courseid. + * + * @param stdClass $data the data submitted from the reset course. + * @return array status array + */ + public function reset_userdata($data) { + global $CFG, $DB; + + $componentstr = get_string('modulenameplural', 'qcreate'); + $status = array(); + + $fs = get_file_storage(); + if (!empty($data->reset_qcreate)) { + $qcreatessql = 'SELECT a.id + FROM {qcreate} a + WHERE a.course=:course'; + $params = array('course' => $data->courseid); + + if ($categoriesmods = $DB->get_records('question_categories', + array('contextid' => $this->get_context()->id), 'parent', 'id, parent, name, contextid')) { + foreach ($categoriesmods as $category) { + + // Delete all questions. + if ($questions = $DB->get_records('question', + array('category' => $category->id), '', 'id,qtype')) { + foreach ($questions as $question) { + question_delete_question($question->id); + } + $DB->delete_records('question', array('category' => $category->id)); + } } - $questionlist .= "\n\t\t\t\t
    "; - } else { - $requireds[$qtype]->done = 0; - $requireds[$qtype]->stillrequiredno = $required->no; - $questionlist = ''; } - $qtypedone += $requireds[$qtype]->done; - //one item list with one link to create question - $linklist = "\n\t\t\t\t\t\t\t\t"; + } + // Updating dates - shift may be negative too. + if ($data->timeshift) { + shift_course_mod_dates('qcreate', + array('timeopen', 'timeclose'), + $data->timeshift, + $data->courseid, $this->get_instance()->id); + $status[] = array('component' => $componentstr, + 'item' => get_string('datechanged'), + 'error' => false); + } - if (isset($qtypeqs[$qtype])){ - //top level list - $requirementslist = "\n\t\t\t\t\t\t
      \n\t\t\t\t\t\t\t
    • "; - $requirementslist .= get_string('donequestionno', 'qcreate', $required); + return $status; + } - $requirementslist .= "$questionlist
    • "; + /** + * Update the gradebook information for this qcreate activity. + * + * @param bool $reset If true, will reset all grades in the gradbook for this qcreate activity + * @param int $coursemoduleid This is required because it might not exist in the database yet + * @return bool + */ + public function update_gradebook($reset, $coursemoduleid) { + global $CFG; - if ($requireds[$qtype]->stillrequiredno > 0){ - $requirementslist .= "\n\t\t\t\t\t\t\t
    • "; - $requirementslist .= get_string('todoquestionno', 'qcreate', $required); - $requirementslist .= "$linklist"; - $requirementslist .= "\n\t\t\t\t\t\t
    • "; - } + require_once($CFG->dirroot.'/mod/qcreate/lib.php'); + $qcreate = clone $this->get_instance(); + $qcreate->cmidnumber = $coursemoduleid; + + $param = null; + if ($reset) { + $param = 'reset'; + } + + return qcreate_grade_item_update($qcreate, $param); + } - $requirementslist .= "\n\t\t\t\t\t\t\t
    "; - $content .= "\n\t\t\t
  • {$punctuated[$qtype]}$requirementslist
  • "; + /** + * Update the calendar entries for this qcreate activity. + * + * @param int $coursemoduleid - Required to pass this in because it might + * not exist in the database yet. + * @return bool + */ + public function update_calendar($coursemoduleid) { + global $DB, $CFG; + require_once($CFG->dirroot.'/calendar/lib.php'); + + // Special case for add_instance as the coursemodule has not been set yet. + $instance = $this->get_instance(); + $cm = get_coursemodule_from_instance('qcreate', $instance->id); + + // Load the old events relating to this qcreate. + $conds = array('modulename' => 'qcreate', + 'instance' => $instance->id); + $oldevents = $DB->get_records('event', $conds, 'id ASC'); + + // Qcreate start calendar event. + if (isset($instance->timeopen) && $instance->timeopen > 0) { + $event = new stdClass(); + $event->eventtype = QCREATE_EVENT_TYPE_OPEN; + $event->type = CALENDAR_EVENT_TYPE_ACTION;; + $event->name = $event->name = get_string('qcreateeventopens', 'qcreate', $instance->name); + $event->courseid = $instance->course; + $event->groupid = 0; + $event->userid = 0; + $event->modulename = 'qcreate'; + $event->instance = $instance->id; + $event->description = format_module_intro('qcreate', $instance, $cm->id); + $event->timestart = $instance->timeopen; + $event->timesort = $instance->timeopen; + $event->visible = instance_is_visible('qcreate', $instance); + $event->timeduration = 0; + if ($oldevent = array_shift($oldevents)) { + $event->id = $oldevent->id; } else { - $content .= "\n\t\t\t
  • {$punctuated[$qtype]}$linklist
  • "; + unset($event->id); } + // The method calendar_event::create will reuse a db record if the id field is set. + calendar_event::create($event, false); + } + + // Qcreate close calendar event. + if (isset($instance->timeclose) && $instance->timeclose > 0) { + $event = new stdClass(); + $event->eventtype = QCREATE_EVENT_TYPE_CLOSE; + $event->type = CALENDAR_EVENT_TYPE_ACTION;; + $event->name = $event->name = get_string('qcreateeventcloses', 'qcreate', $instance->name); + $event->courseid = $instance->course; + $event->groupid = 0; + $event->userid = 0; + $event->modulename = 'qcreate'; + $event->instance = $instance->id; + $event->description = format_module_intro('qcreate', $instance, $cm->id); + $event->timestart = $instance->timeclose; + $event->timesort = $instance->timeclose; + $event->visible = instance_is_visible('qcreate', $instance); + $event->timeduration = 0; + if ($oldevent = array_shift($oldevents)) { + $event->id = $oldevent->id; + } else { + unset($event->id); + } + // The method calendar_event::create will reuse a db record if the id field is set. + calendar_event::create($event, false); + } + + // Delete any leftover events. + foreach ($oldevents as $badevent) { + $badevent = calendar_event::load($badevent); + $badevent->delete(); } - $content .= "\n\t\t"; } - if ($qtyperequired < $qcreate->totalrequired){ - if ($qcreate->allowed != 'ALL'){ - $qtypesallowed = explode(',', $qcreate->allowed); + + + /** + * Update this instance in the database. + * + * @param stdClass $formdata - the data submitted from the form + * @return bool false if an error occurs + */ + public function update_instance($formdata) { + global $DB; + + $adminconfig = $this->get_admin_config(); + + $update = new stdClass(); + $update->id = $formdata->instance; + $update->name = $formdata->name; + $update->timemodified = time(); + $update->course = $formdata->course; + $update->courseid = $formdata->course; + $update->grade = $formdata->grade; + $update->graderatio = $formdata->graderatio; + $update->intro = $formdata->intro; + $update->introformat = $formdata->introformat; + $update->allowed = join(array_keys($formdata->allowed), ','); + $update->totalrequired = $formdata->totalrequired; + $update->studentqaccess = $formdata->studentqaccess; + $update->timeopen = $formdata->timeopen; + $update->timeclose = $formdata->timeclose; + if (isset($formdata->completionquestions)) { + $update->completionquestions = $formdata->completionquestions; } else { - $qtypesallowed = array_keys($qtypemenu); + $update->completionquestions = 0; } - $extraquestionsdone = 0; - foreach ($qtypeqs as $qtypeq){ - if (is_array($qtypeq)){ - $extraquestionsdone += count($qtypeq); - } + $update->sendgradernotifications = $adminconfig->sendgradernotifications; + if (isset($formdata->sendgradernotifications)) { + $update->sendgradernotifications = $formdata->sendgradernotifications; } - $extraquestionlinklist = '
      '; - - foreach ($qtypesallowed as $qtypeallowed){ - $countqtypes = isset($qtypeqs[$qtypeallowed])?count($qtypeqs[$qtypeallowed]):0; - $extraqcreateurl = $questionurl->out(false, array('qtype'=>$qtypeallowed, 'category'=>$cat->id)); - $extraquestionlinklist .= "
    • "; - if ($activityopen){ - $extraquestionlinklist .= "{$qtypemenu[$qtypeallowed]}"; - } else { - $extraquestionlinklist .= $qtypemenu[$qtypeallowed]; + $update->sendstudentnotifications = $adminconfig->sendstudentnotifications; + if (isset($formdata->sendstudentnotifications)) { + $update->sendstudentnotifications = $formdata->sendstudentnotifications; + } + $result = $DB->update_record('qcreate', $update); + $this->instance = $DB->get_record('qcreate', array('id' => $update->id), '*', MUST_EXIST); + + $DB->delete_records('qcreate_required', array('qcreateid' => $update->id)); + // TODO re-use old records. + $qtypemins = array_filter($formdata->qtype); + if (count($qtypemins)) { + foreach ($qtypemins as $key => $qtypemin) { + $toinsert = new stdClass(); + $toinsert->no = $formdata->minimumquestions[$key]; + $toinsert->qtype = $qtypemin; + $toinsert->qcreateid = $update->id; + $DB->insert_record('qcreate_required', $toinsert); } - if (isset($requireds[$qtypeallowed]) && $countqtypes){ - if ($countqtypes==1){ - $extraquestionlinklist .= ' '.get_string('alreadydoneextraone', 'qcreate', $countqtypes); + } + $requireds = $DB->get_records('qcreate_required', + array('qcreateid' => $update->id), 'qtype', 'qtype, no, id'); + $this->instance->requiredqtypes = $requireds; + $this->update_calendar($this->get_course_module()->id); + $this->update_gradebook(false, $this->get_course_module()->id); + qcreate_student_q_access_sync($this->get_context(), $this->get_instance(), true); + return $result; + } + + /** + * Get the name of the current module. + * + * @return string the module name (Assignment) + */ + protected function get_module_name() { + if (isset(self::$modulename)) { + return self::$modulename; + } + self::$modulename = get_string('modulename', 'qcreate'); + return self::$modulename; + } + + /** + * Get the plural name of the current module. + * + * @return string the module name plural (Question Creations) + */ + public function get_module_name_plural() { + if (isset(self::$modulenameplural)) { + return self::$modulenameplural; + } + self::$modulenameplural = get_string('modulenameplural', 'qcreate'); + return self::$modulenameplural; + } + + /** + * Has this qcreate been constructed from an instance? + * + * @return bool + */ + public function has_instance() { + return $this->instance || $this->get_course_module(); + } + + /** + * Get the settings for the current instance of this qcreate activity + * + * @return stdClass The settings + */ + public function get_instance() { + global $DB; + if ($this->instance) { + return $this->instance; + } + if ($this->get_course_module()) { + $qcreateid = $this->get_course_module()->instance; + $params = array('id' => $qcreateid); + $this->instance = $DB->get_record('qcreate', $params, '*', MUST_EXIST); + $params = array('qcreateid' => $qcreateid); + $requireds = $DB->get_records('qcreate_required', + $params, 'qtype', 'qtype, no, id'); + $this->instance->requiredqtypes = $requireds; + } + if (!$this->instance) { + throw new coding_exception('Improper use of the qcreate class. ' . + 'Cannot load the qcreate record.'); + } + return $this->instance; + } + + /** + * Get the primary grade item for this qcreate instance. + * + * @return stdClass The grade_item record + */ + public function get_grade_item() { + if ($this->gradeitem) { + return $this->gradeitem; + } + $instance = $this->get_instance(); + $params = array('itemtype' => 'mod', + 'itemmodule' => 'qcreate', + 'iteminstance' => $instance->id, + 'courseid' => $instance->course, + 'itemnumber' => 0); + $this->gradeitem = grade_item::fetch($params); + if (!$this->gradeitem) { + throw new coding_exception('Improper use of the qcreate class. ' . + 'Cannot load the grade item.'); + } + return $this->gradeitem; + } + + /** + * Get the question category + * + * @return object question_category + */ + public function get_question_category() { + return question_get_default_category($this->get_context()->id); + } + + /** + * Get the context of the current course. + * + * @return mixed context|null The course context + */ + public function get_course_context() { + if (!$this->context && !$this->course) { + throw new coding_exception('Improper use of the qcreate class. ' . + 'Cannot load the course context.'); + } + if ($this->context) { + return $this->context->get_course_context(); + } else { + return context_course::instance($this->course->id); + } + } + + + /** + * Get the current course module. + * + * @return mixed stdClass|null The course module + */ + public function get_course_module() { + if ($this->coursemodule) { + return $this->coursemodule; + } + if (!$this->context) { + return null; + } + + if ($this->context->contextlevel == CONTEXT_MODULE) { + $this->coursemodule = get_coursemodule_from_id('qcreate', + $this->context->instanceid, + 0, + false, + MUST_EXIST); + return $this->coursemodule; + } + return null; + } + + /** + * Get context module. + * + * @return context + */ + public function get_context() { + return $this->context; + } + + /** + * Get the current course. + * + * @return mixed stdClass|null The course + */ + public function get_course() { + global $DB; + + if ($this->course) { + return $this->course; + } + + if (!$this->context) { + return null; + } + $params = array('id' => $this->get_course_context()->instanceid); + $this->course = $DB->get_record('course', $params, '*', MUST_EXIST); + + return $this->course; + } + + /** + * Return a grade in user-friendly form, whether it's a scale or not. + * + * @param mixed $grade int|null + * @param boolean $editing Are we allowing changes to this grade? + * @param int $userid The user id the grade belongs to + * @return string User-friendly representation of grade + */ + public function display_grade($grade, $editing, $userid=0) { + global $DB; + + static $scalegrades = array(); + + $o = ''; + + if ($this->get_instance()->grade >= 0) { + // Normal number. + if ($editing && $this->get_instance()->grade > 0) { + if ($grade < 0) { + $displaygrade = ''; } else { - $extraquestionlinklist .= ' '.get_string('alreadydoneextra', 'qcreate', $countqtypes); + $displaygrade = format_float($grade, 2); } - } else if ($countqtypes) { - if ($countqtypes==1){ - $extraquestionlinklist .= ' '.get_string('alreadydoneone', 'qcreate', $countqtypes); + $o .= ''; + $o .= ''; + $o .= ' / ' . format_float($this->get_instance()->grade, 2); + return $o; + } else { + if ($grade == -1 || $grade === null) { + $o .= '-'; } else { - $extraquestionlinklist .= ' '.get_string('alreadydone', 'qcreate', $countqtypes); + $item = $this->get_grade_item(); + $o .= grade_format_gradevalue($grade, $item); + if ($item->get_displaytype() == GRADE_DISPLAY_TYPE_REAL) { + // If displaying the raw grade, also display the total value. + $o .= ' / ' . format_float($this->get_instance()->grade, 2); + } } + return $o; } - if ($countqtypes){ - $extraquestionlinklist .= "
        "; - foreach ($qtypeqs[$qtypeallowed] as $qtypeq){ - $extraquestionlinklist .= "
      • ".question_item_html($qtypeq, $questionurl, $thisurl, $qcreate, $cm, $showmanualgrades)."
      • "; + } else { + // Scale. + if (empty($this->cache['scale'])) { + if ($scale = $DB->get_record('scale', array('id' => -($this->get_instance()->grade)))) { + $this->cache['scale'] = make_menu_from_list($scale->scale); + } else { + $o .= '-'; + return $o; } - $extraquestionlinklist .= "
      "; } + if ($editing) { + $o .= ''; + $o .= ''; + return $o; + } else { + $scaleid = (int)$grade; + if (isset($this->cache['scale'][$scaleid])) { + $o .= $this->cache['scale'][$scaleid]; + return $o; + } + $o .= '-'; + return $o; + } + } + } - $extraquestionlinklist .= "
    • "; + public function questions_of_type($questions) { + $questionsofqtype = array(); + if ($questions) { + foreach ($questions as $key => $question) { + $questionsofqtype[$question->qtype][] = $question; + } } - $extraquestionlinklist .= '
    '; + return $questionsofqtype; + } - $a= new object(); - $a->extraquestionsdone = $extraquestionsdone; - $a->extrarequired = $qcreate->totalrequired - $qtyperequired; - $content .= '
  • '; - if ($a->extraquestionsdone == 1){ - $content .= get_string('extraqdone', 'qcreate', $a); - } else { - $content .= get_string('extraqsdone', 'qcreate', $a); + public static function qtype_menu() { + $types = question_bank::get_creatable_qtypes(); + $returntypes = array(); + + foreach ($types as $name => $qtype) { + if ($name != 'randomsamatch') { + $returntypes[$name] = $qtype->local_name(); + } } - if ($a->extrarequired == 1){ - $content .= ' '.get_string('extraqgraded', 'qcreate', $a); - } else { - $content .= ' '.get_string('extraqsgraded', 'qcreate', $a); - } - $content .= ''; - $content .= $extraquestionlinklist.'
  • '; - } - $grading_info = grade_get_grades($COURSE->id, 'mod', 'qcreate', $qcreate->id, $USER->id); - $gradeforuser = $grading_info->items[0]->grades[$USER->id]; - if (!empty($gradeforuser->dategraded)){ - - $fullgrade = new object(); - $fullgrade->grade = (float)$gradeforuser->str_grade; - $fullgrade->outof = (float)$grading_info->items[0]->grademax; - $content .= '
  • '.get_string('activitygrade', 'qcreate', $fullgrade); - if (!empty($qcreate->graderatio)){ - $automaticgrade = new object(); - $automaticquestiongrade = $grading_info->items[0]->grademax * ($qcreate->graderatio / 100) / $qcreate->totalrequired; - $automaticgrade->outof = $grading_info->items[0]->grademax * ($qcreate->graderatio / 100); - $automaticgrade->done = ($extraquestionsdone + $qtypedone); - $automaticgrade->required = $qcreate->totalrequired; - if ($automaticgrade->done < $automaticgrade->required){ - $automaticgrade->grade = $automaticgrade->done * $automaticquestiongrade; + return $returntypes; + } + + /** + * Return a summary of the student activity for this qcreate + * the created questions summary and the grading summary. + * + * @param stdClass $user the user to print the report for + * @param bool $showlinks - Return plain text or links to the questions + * @return string - the html summary + */ + public function view_student_summary($user, $showlinks) { + global $CFG, $DB, $PAGE, $OUTPUT; + require_once($CFG->libdir.'/gradelib.php'); + require_once($CFG->dirroot.'/grade/grading/lib.php'); + + $instance = $this->get_instance(); + + $o = ''; + + $gradinginfo = grade_get_grades($this->get_course()->id, + 'mod', + 'qcreate', + $instance->id, + $user->id); + + $gradingitem = null; + $gradebookgrade = null; + if (isset($gradinginfo->items[0])) { + $gradingitem = $gradinginfo->items[0]; + $gradebookgrade = $gradingitem->grades[$user->id]; + } + + // Only show the grade if it is not hidden in gradebook. + if (!$gradebookgrade->hidden) { + $gradefordisplay = $this->display_grade($gradebookgrade->grade, false); + $gradeddate = $gradebookgrade->dategraded; + if (isset($grade->grader)) { + $grader = $DB->get_record('user', array('id' => $grade->grader)); + } + } + + if (!empty($gradefordisplay)) { + $o .= get_string('grade').': '.$gradefordisplay; + } + return $o; + } + + /** + * Load a count of created questions in the current module that require grading or regrading. + * This means the question modification time is more recent than the + * qcreate_grades timemarked time. + * + * @return int number of matching submissions + */ + public function count_questionss_need_grading() { + global $DB; + + $currentgroup = groups_get_activity_group($this->get_course_module(), true); + list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/qcreate:submit', $currentgroup, true); + + $params['qcreateid'] = $this->get_instance()->id; + + $sql = 'TODO'; + + return $DB->count_records_sql($sql, $params); + } + + /** + * Count the number of questions created by an user + * + * @ param int $userid id of the user, 0 means all users + * + * @return int number of questions + */ + public function count_user_questions($userid=0) { + global $DB; + + list($whereqtype, $params) = $this->get_allowed_qtypes_where(); + $whereuser = ''; + if ($userid) { + $params['userid'] = $userid; + $whereuser = 'q.createdby = :userid AND '; + } + + $params['contextid'] = $this->get_context()->id; + $countsql = 'SELECT COUNT(q.id) FROM {question} q,{question_categories} c '. + 'WHERE ' . $whereqtype . $whereuser . + 'q.hidden=\'0\' AND q.parent=\'0\' AND q.category = c.id and c.contextid = :contextid'; + return $DB->count_records_sql($countsql, $params); + } + + /** + * Update a local grade. + * + * @param stdClass $grade a grade record keyed on id + * @return bool true for success + */ + public function update_local_grade($grade) { + global $DB; + + if ($grade->grade && $grade->grade != -1) { + if ($this->get_instance()->grade > 0) { + if (!is_numeric($grade->grade)) { + return false; + } else if ($grade->grade > $this->get_instance()->grade) { + return false; + } else if ($grade->grade < 0) { + return false; + } } else { - $automaticgrade->grade = $automaticgrade->outof; + // This is a scale. + if ($scale = $DB->get_record('scale', array('id' => -($this->get_instance()->grade)))) { + $scaleoptions = make_menu_from_list($scale->scale); + if (!array_key_exists((int) $grade->grade, $scaleoptions)) { + return false; + } + } } - $content .= '
    • '.get_string('automaticgrade', 'qcreate', $automaticgrade).'
    • '; - if ($showmanualgrades){ - $manualgrade = new object(); - $manualgrade->grade = $gradeforuser->grade - $automaticgrade->grade; - $manualgrade->outof = $grading_info->items[0]->grademax - $automaticgrade->outof; - $content .= '
    • '.get_string('manualgrade', 'qcreate', $manualgrade).'
    • '; + } + + $result = $DB->update_record('qcreate_grades', $grade); + return $result; + } + + + + /** + * View a summary listing of all qcreate activities in the current course. + * + * @return string + */ + private function view_course_index() { + global $USER; + + $o = ''; + + $course = $this->get_course(); + + if (!$cms = get_coursemodules_in_course('qcreate', $course->id)) { + $o .= $this->get_renderer()->notification(get_string('thereareno', 'moodle', $this->get_module_name_plural())); + $o .= $this->get_renderer()->continue_button(new moodle_url('/course/view.php', array('id' => $course->id))); + return $o; + } + + $strsectionname = ''; + $usesections = course_format_uses_sections($course->format); + $modinfo = get_fast_modinfo($course); + + if ($usesections) { + $strsectionname = get_string('sectionname', 'format_'.$course->format); + $sections = $modinfo->get_section_info_all(); + } + $courseindexsummary = new qcreate_course_index_summary($usesections, $strsectionname); + + $timenow = time(); + + $currentsection = ''; + foreach ($modinfo->instances['qcreate'] as $cm) { + if (!$cm->uservisible) { + continue; + } + + $sectionname = ''; + if ($usesections && $cm->sectionnum) { + $sectionname = get_section_name($course, $sections[$cm->sectionnum]); } - echo '
    '; + + $submitted = ''; + $context = context_module::instance($cm->id); + + $qcreateobj = new qcreate($context, $cm, $course); + + $timeopen = $qcreateobj->get_instance()->timeopen; + $timeclose = $qcreateobj->get_instance()->timeclose; + + if (has_capability('mod/qcreate:grade', $context)) { + $questionsnumber = $qcreateobj->count_user_questions(); + + } else if (has_capability('mod/qcreate:submit', $context)) { + $questionsnumber = $qcreateobj->count_user_questions($USER->id); + } + $gradinginfo = grade_get_grades($course->id, 'mod', 'qcreate', $cm->instance, $USER->id); + if (isset($gradinginfo->items[0]->grades[$USER->id]) && + !$gradinginfo->items[0]->grades[$USER->id]->hidden ) { + $grade = $gradinginfo->items[0]->grades[$USER->id]->str_grade; + } else { + $grade = '-'; + } + + $courseindexsummary->add_qcreate_info($cm->id, $cm->name, $sectionname, + $timeopen, $timeclose, $questionsnumber, $grade); + } - $content .= '
  • '; - $content .= '
'; + + $o .= $this->get_renderer()->render($courseindexsummary); + $o .= $this->view_footer(); + + return $o; } - echo $OUTPUT->box($content, 'generalbox boxaligncenter boxwidthwide'); -} -function qcreate_teacher_overview($requireds, $qcreate){ - global $CFG, $OUTPUT; - $qtypemenu = question_type_menu(); - echo '
'; - echo '

'.get_string('requiredquestions', 'qcreate').' :

'; - echo '
'; - $content = "\n\t\t
    "; - $qtyperequired =0; - if ($requireds){ - $grammarised = qcreate_proper_grammar($requireds); - foreach ($requireds as $qtype => $required){ - $qtyperequired += $required->no; - } - } - if ($qtyperequired < $qcreate->totalrequired){ - $a= new object(); - $a->extrarequired = $qcreate->totalrequired - $qtyperequired; - if ($a->extrarequired == 1){ - $grammarised['extras'] = get_string('extraqgraded', 'qcreate', $a); + /** + * View overview page. + * + * @return string + */ + private function view_overview_page() { + global $CFG, $DB, $USER, $PAGE; + + $instance = $this->get_instance(); + + $o = ''; + $o .= $this->get_renderer()->render(new qcreate_header($instance, + $this->get_context(), + true, + $this->get_course_module()->id)); + + $o .= $this->get_renderer()->render(new qcreate_teacher_overview($instance, + $this->get_context(), + $this->get_course_module()->id, + $this->is_activity_open())); + + $o .= $this->view_footer(); + + $params = array( + 'courseid' => $this->get_course()->id, + 'context' => $this->get_context(), + 'other' => array( + 'qcreateid' => $this->get_instance()->id + ) + ); + $event = \mod_qcreate\event\overview_viewed::create($params); + $event->trigger(); + + return $o; + } + + /** + * Student viewpage. + * + * @return string + */ + private function view_student_page() { + global $CFG, $DB, $USER, $PAGE; + + $instance = $this->get_instance(); + $cat = $this->get_question_category()->id; + $cm = $this->get_course_module(); + + list($whereqtype, $params) = $this->get_allowed_qtypes_where(); + $params['userid'] = $USER->id; + $whereuser = 'q.createdby = :userid AND '; + $params['contextid'] = $this->get_context()->id; + + $questionsql = "SELECT q.*, c.id as cid, c.name as cname, g.grade, g.gradecomment, g.id as gid + FROM {question_categories} c, {question} q + LEFT JOIN {qcreate_grades} g ON q.id = g.questionid + AND g.qcreateid = {$instance->id} + WHERE $whereqtype $whereuser c.contextid = :contextid AND c.id = q.category AND q.hidden='0' AND q.parent='0'"; + $questions = $DB->get_records_sql($questionsql, $params); + + // Calculate number of question done and number of questions still to do + // For each required qtype. + $qtyperequired = 0; // Total of required questions. + $qtypedone = 0; + $requiredquestions = array(); + $extraquestions = array(); + $qtypeqs = $this->questions_of_type($questions); + if ($instance->requiredqtypes) { + foreach ($instance->requiredqtypes as $qtype => $required) { + $i = 1; + $qtyperequired += $required->no; + if (!empty($qtypeqs[$qtype])) { + $instance->requiredqtypes[$qtype]->done + = (count($qtypeqs[$qtype]) > $required->no) ? $required->no : count($qtypeqs[$qtype]); + $instance->requiredqtypes[$qtype]->stillrequiredno + = $instance->requiredqtypes[$qtype]->no - $instance->requiredqtypes[$qtype]->done; + while (($i <= $required->no) && ($qtypeq = array_shift($qtypeqs[$qtype]))) { + $requiredquestions[$qtype][] = $qtypeq; + $i++; + } + } else { + $instance->requiredqtypes[$qtype]->done = 0; + $instance->requiredqtypes[$qtype]->stillrequiredno = $required->no; + $requiredquestions[$qtype] = array(); + } + $qtypedone += $instance->requiredqtypes[$qtype]->done; + } + } + + // Calculate number of extra questions done for each allowed qtype. + $extras = array(); + // If some extras questions are graded. + $extraquestionsgraded = $qtyperequired < (int)$instance->totalrequired ? $instance->totalrequired - $qtyperequired : 0; + if ($instance->allowed != 'ALL') { + $qtypesallowed = explode(',', $instance->allowed); } else { - $grammarised['extras'] = get_string('extraqsgraded', 'qcreate', $a); + $qtypesallowed = array_keys(self::qtype_menu()); } + $extraquestionsdone = 0; + foreach ($qtypesallowed as $qtypeallowed) { + $countqtypes = isset($qtypeqs[$qtypeallowed]) ? count($qtypeqs[$qtypeallowed]) : 0; + $extraquestionsdone += $countqtypes; - $a= new object(); - $a->extrarequired = $qcreate->totalrequired - $qtyperequired; + if ($countqtypes) { + // There are extra questions for this qtype. + $extras[$qtypeallowed] = $countqtypes; + $extraquestions[$qtypeallowed] = $qtypeqs[$qtypeallowed]; + } else { + $extraquestions[$qtypeallowed] = array(); + } + } - if ($qcreate->allowed != 'ALL'){ - $qtypesallowed = explode(',', $qcreate->allowed); - } else { - $qtypesallowed = array_keys($qtypemenu); + // Grade infos. + $gradinginfo = grade_get_grades($this->get_course()->id, 'mod', 'qcreate', $this->get_instance()->id, $USER->id); + $gradeforuser = $gradinginfo->items[0]->grades[$USER->id]; + $grademax = $gradinginfo->items[0]->grademax; + $studentgrade = new stdClass(); + if (!empty($gradeforuser->dategraded)) { + $fullgrade = new stdClass(); + $fullgrade->grade = (float)$gradeforuser->str_grade; + $fullgrade->outof = (float)$gradinginfo->items[0]->grademax; + $studentgrade->fullgrade = $fullgrade; + if (!empty($this->get_instance()->graderatio)) { + $automaticgrade = new stdClass(); + $automaticquestiongrade = $gradinginfo->items[0]->grademax * ( + $this->get_instance()->graderatio / 100) / $this->get_instance()->totalrequired; + $automaticgrade->outof = $gradinginfo->items[0]->grademax * ($this->get_instance()->graderatio / 100); + $automaticgrade->done = ($extraquestionsdone + $qtypedone); + $automaticgrade->required = $this->get_instance()->totalrequired; + if ($automaticgrade->done < $automaticgrade->required) { + $automaticgrade->grade = $automaticgrade->done * $automaticquestiongrade; + } else { + $automaticgrade->grade = $automaticgrade->outof; + } + $studentgrade->automaticgrade = $automaticgrade; + if ($this->get_instance()->graderatio != 100) { + $manualgrade = new stdClass(); + $manualgrade->grade = $gradeforuser->grade - $automaticgrade->grade; + $manualgrade->outof = $gradinginfo->items[0]->grademax - $automaticgrade->outof; + $studentgrade->manualgrade = $manualgrade; + } + } } - $allowedqtypelist = '
      '; - foreach ($qtypesallowed as $qtypeallowed){ - $allowedqtypelist .= "
    • {$qtypemenu[$qtypeallowed]}
    • "; + $o = ''; + $o .= $this->get_renderer()->render(new qcreate_header($instance, + $this->get_context(), + true, + $this->get_course_module()->id)); + + $o .= $this->get_renderer()->render(new qcreate_student_view($instance, + $cm, + $cat, + $requiredquestions, + $extraquestions, + $qtyperequired, + $extraquestionsgraded, + $extraquestionsdone, + $extras, + $studentgrade, + $qtypesallowed, + $this->get_context(), + $this->get_course_module()->id, + $this->is_activity_open())); + + $o .= $this->view_footer(); + + // Write log entry. + $params = array( + 'objectid' => $instance->id, + 'context' => $this->get_context() + ); + $event = \mod_qcreate\event\course_module_viewed::create($params); + $event->add_record_snapshot('qcreate', $instance); + $event->trigger(); + + return $o; + } + + /** + * Display a continue page after grading. + * + * @param string $message - The message to display. + * @return string + */ + protected function view_savegrading_result($message) { + $o = ''; + $o .= $this->get_renderer()->render(new qcreate_header($this->get_instance(), + $this->get_context(), + $this->show_intro(), + $this->get_course_module()->id, + get_string('savegradingresult', 'qcreate'))); + $gradingresult = new assign_gradingmessage(get_string('savegradingresult', 'qcreate'), + $message, + $this->get_course_module()->id); + $o .= $this->get_renderer()->render($gradingresult); + $o .= $this->view_footer(); + return $o; + } + + /** + * Show a confirmation page to make sure they want to delete a created question. + * + * @return string + */ + protected function view_delete_confirm() { + + $o = ''; + $header = new qcreate_header($this->get_instance(), + $this->get_context(), + false, + $this->get_course_module()->id); + $o .= $this->get_renderer()->render($header); + $delete = optional_param('delete', 0, PARAM_INT); // Question id to delete. + $urlparams = array('id' => $this->get_course_module()->id, + 'action' => 'view', + 'confirm' => 1, + 'delete' => $delete, + 'sesskey' => sesskey()); + $confirmurl = new moodle_url('/mod/qcreate/view.php', $urlparams); + + $urlparams = array('id' => $this->get_course_module()->id); + $cancelurl = new moodle_url('/mod/qcreate/view.php', $urlparams); + + $o .= $this->get_renderer()->confirm(get_string('confirmdeletequestion', 'qcreate'), + $confirmurl, + $cancelurl); + $o .= $this->view_footer(); + + return $o; + } + /** + * Display the page footer. + * + * @return string + */ + protected function view_footer() { + // When viewing the footer during PHPUNIT tests a set_state error is thrown. + if (!PHPUNIT_TEST) { + return $this->get_renderer()->render_footer(); } - $allowedqtypelist .= '
    '; + return ''; + } + /** + * Does this user have grade permission for this qcreate activity? + * + * @return bool + */ + public function can_grade() { + // Permissions check. + if (!has_capability('mod/qcreate:grade', $this->context)) { + return false; + } + + return true; } - $punctuateds = qcreate_proper_punctuation($grammarised); - foreach ($punctuateds as $key => $punctuated){ - $content .= "\n\t\t\t
  • {$punctuated}"; - if ($key == 'extras'){ - $content .= $allowedqtypelist; + + /** + * Load and cache the admin config for this module. + * + * @return stdClass the plugin config + */ + public function get_admin_config() { + if ($this->adminconfig) { + return $this->adminconfig; } - $content .= "
  • "; + $this->adminconfig = get_config('qcreate'); + return $this->adminconfig; } - $content .= "\n\t\t
"; + /** + * Lazy load the page renderer and expose the renderer to plugins. + * + * @return qcreate_renderer + */ + public function get_renderer() { + global $PAGE; + if ($this->output) { + return $this->output; + } + $this->output = $PAGE->get_renderer('mod_qcreate'); + return $this->output; + } - echo $OUTPUT->box($content, 'generalbox boxaligncenter boxwidthwide'); -} + /** + * This will retrieve a grade object from the db, optionally creating it if required. + * + * @param int $userid The user we are grading + * @param bool $create If true the grade will be created if it does not exist + * @return stdClass The grade record + */ + public function get_local_grade($userid, $create, $questionid) { + global $DB, $USER; + // If the userid is not null then use userid. + if (!$userid) { + $userid = $USER->id; + } + $params = array('qcreateid' => $this->get_instance()->id, 'userid' => $userid, 'questionid' => $questionid); + $grades = $DB->get_records('qcreate_grades', $params, 'timemarked DESC', '*', 0, 1); -function qcreate_activity_open($qcreate){ - $timenow = time(); - return ($qcreate->timeopen == 0 ||($qcreate->timeopen < $timenow)) && - ($qcreate->timeclose == 0 ||($qcreate->timeclose > $timenow)); -} + if ($grades) { + return reset($grades); + } + if ($create) { + $grade = new stdClass(); + $grade->qcreateid = $this->get_instance()->id; + $grade->userid = $userid; + $grade->timemarked = time(); + $grade->grade = -1; + $grade->teacher = $USER->id; -function question_item_html($question, $questionurl, $thisurl, $qcreate, $cm, $showgrades = true){ - global $CFG; - $activityopen = qcreate_activity_open($qcreate); - if ($activityopen && (question_has_capability_on($question, 'edit', $question->cid) - || question_has_capability_on($question, 'move', $question->cid) - || question_has_capability_on($question, 'view', $question->cid))){ - $questionlistitem = "out(false, array('id'=>$question->id))."\">"; - $questionlistitem .= $question->name.""; - } else { - $questionlistitem = $question->name; + $gid = $DB->insert_record('qcreate_grades', $grade); + $grade->id = $gid; + return $grade; + } + return false; + } + + /** + * Get the local grades for an user. + * If isgrader is set to false, it will return local grades for + * all question created in this qcreate. + * If isgrader is set to true it will return local grades given + * by this grader. + * + * @param int $userid id of user. + * @return array $grades All grade records for this user. + */ + + public function get_all_local_grades($userid, $isgrader = false) { + global $DB; + + $params = array('qcreateid' => $this->get_instance()->id, 'userid' => $userid); + $userwhere = $isgrader ? "qg.teacher = :userid" : "q.createdby = :userid"; + $sql = "SELECT + q.*, + qg.id AS hasgrade, + qg.grade AS bestgrade, + qg.questionid AS questiongraded, + qg.timemarked AS grademodified, + qg.teacher AS grader, + qg.gradecomment AS teachercomment + FROM {question} q + LEFT JOIN {user} u ON u.id = q.createdby + LEFT JOIN {question_categories} qc ON qc.id = q.category + LEFT JOIN {qcreate_grades} qg ON qg.questionid = q.id + WHERE $userwhere AND qg.qcreateid = :qcreateid;"; + + // Fetch the questions created. + $grades = $DB->get_records_sql($sql, $params); + + return $grades; + } + + public function delete_user_local_grades($userid) { + global $DB; + $cat = $this->get_question_category()->id; + $questions = $DB->get_records('question', array('category' => $cat, 'createdby' => $userid), '', 'id'); + if ($questions) { + list($qids, $params) = $DB->get_in_or_equal(array_keys($questions), SQL_PARAMS_NAMED); + $DB->delete_records_select('qcreate_grades', 'questionid ' . $qids, $params); + } + } + /** + * This will retrieve a grade object from the db. + * + * @param int $gradeid The id of the grade + * @return stdClass The grade record + */ + protected function get_grade($gradeid) { + global $DB; + + $params = array('qcreateid' => $this->get_instance()->id, 'id' => $gradeid); + return $DB->get_record('qcreate_grades', $params, '*', MUST_EXIST); + } + + /** + * See if this qcreate activity has a grade yet. + * + * @param int $userid + * @return bool + */ + protected function is_graded($userid) { + $grade = $this->get_user_grade($userid, false); + if ($grade) { + return ($grade->grade !== null && $grade->grade >= 0); + } + return false; } - if ($showgrades){ - $questionlistitem .= " "; - if ($question->gid && $question->grade != -1){ - $questionlistitem .= "({$question->grade}/{$qcreate->grade})"; + + /** + * Is this qcreate activity open? + * @return bool + */ + public function is_activity_open($timenow = null) { + + if (!$timenow) { + $timenow = time(); + } + $qcreate = $this->get_instance(); + return ($qcreate->timeopen == 0 ||($qcreate->timeopen < $timenow)) && + ($qcreate->timeclose == 0 ||($qcreate->timeclose > $timenow)); + } + + /** + * Returns a list of teachers that should be grading given userid created questions. + * + * @param int $userid The userid to grade + * @return array + */ + protected function get_graders($userid) { + // Potential graders should be active users only. + $potentialgraders = get_enrolled_users($this->context, "mod/qcreate:grade", null, 'u.*', null, null, null, true); + + $graders = array(); + if (groups_get_activity_groupmode($this->get_course_module()) == SEPARATEGROUPS) { + if ($groups = groups_get_all_groups($this->get_course()->id, $userid, $this->get_course_module()->groupingid)) { + foreach ($groups as $group) { + foreach ($potentialgraders as $grader) { + if ($grader->id == $userid) { + // Do not send self. + continue; + } + if (groups_is_member($group->id, $grader->id)) { + $graders[$grader->id] = $grader; + } + } + } + } else { + // User not in group, try to find graders without group. + foreach ($potentialgraders as $grader) { + if ($grader->id == $userid) { + // Do not send self. + continue; + } + if (!groups_has_membership($this->get_course_module(), $grader->id)) { + $graders[$grader->id] = $grader; + } + } + } } else { - $questionlistitem .= "(".get_string('notgraded', 'qcreate').")"; + foreach ($potentialgraders as $grader) { + if ($grader->id == $userid) { + // Do not send self. + continue; + } + // Must be enrolled. + if (is_enrolled($this->get_course_context(), $grader->id)) { + $graders[$grader->id] = $grader; + } + } } - if ($question->gradecomment != ''){ - $questionlistitem .='"'.$question->gradecomment.'"'; + return $graders; + } + + /** + * Format a notification for plain text. + * + * @param string $messagetype + * @param stdClass $info + * @param stdClass $course + * @param stdClass $context + * @param string $modulename + * @param string $qcreatename + */ + protected static function format_notification_message_text($messagetype, + $info, + $course, + $context, + $modulename, + $qcreatename) { + $formatparams = array('context' => $context->get_course_context()); + $posttext = format_string($course->shortname, true, $formatparams) . + ' -> ' . + $modulename . + ' -> ' . + format_string($qcreatename, true, $formatparams) . "\n"; + $posttext .= '---------------------------------------------------------------------' . "\n"; + $posttext .= get_string($messagetype . 'text', 'qcreate', $info)."\n"; + $posttext .= "\n---------------------------------------------------------------------\n"; + return $posttext; + } + + /** + * Format a notification for HTML. + * + * @param string $messagetype + * @param stdClass $info + * @param stdClass $course + * @param stdClass $context + * @param string $modulename + * @param stdClass $coursemodule + * @param string $qcreatename + */ + protected static function format_notification_message_html($messagetype, + $info, + $course, + $context, + $modulename, + $coursemodule, + $qcreatename) { + global $CFG; + $formatparams = array('context' => $context->get_course_context()); + $posthtml = '

' . + '' . + format_string($course->shortname, true, $formatparams) . + ' ->' . + '' . + $modulename . + ' ->' . + '' . + format_string($qcreatename, true, $formatparams) . + '

'; + $posthtml .= '
'; + $posthtml .= '

' . get_string($messagetype . 'html', 'qcreate', $info) . '

'; + $posthtml .= '

'; + return $posthtml; + } + + /** + * Message someone about something (static so it can be called from cron). + * + * @param stdClass $userfrom + * @param stdClass $userto + * @param string $messagetype + * @param string $eventtype + * @param int $updatetime + * @param stdClass $coursemodule + * @param stdClass $context + * @param stdClass $course + * @param string $modulename + * @param string $qcreatename + * @param bool $blindmarking + * @param int $uniqueidforuser + * @return void + */ + public static function send_qcreate_notification($userfrom, + $userto, + $messagetype, + $eventtype, + $questionname, + $updatetime, + $coursemodule, + $context, + $course, + $modulename, + $qcreatename) { + global $CFG; + + $info = new stdClass(); + $info->username = fullname($userfrom, true); + $info->questionname = $questionname; + $info->qcreate = format_string($qcreatename, true, array('context' => $context)); + $info->url = $CFG->wwwroot.'/mod/qcreate/view.php?id='.$coursemodule->id; + $info->timeupdated = userdate($updatetime, get_string('strftimerecentfull')); + + $postsubject = get_string($messagetype . 'small', 'qcreate', $info); + $posttext = self::format_notification_message_text($messagetype, + $info, + $course, + $context, + $modulename, + $qcreatename); + $posthtml = ''; + if ($userto->mailformat == 1) { + $posthtml = self::format_notification_message_html($messagetype, + $info, + $course, + $context, + $modulename, + $coursemodule, + $qcreatename); } - $questionlistitem .= "
"; + + $eventdata = new \core\message\message(); + $eventdata->courseid = $course->id; + $eventdata->modulename = 'qcreate'; + $eventdata->userfrom = $userfrom; + $eventdata->userto = $userto; + $eventdata->subject = $postsubject; + $eventdata->fullmessage = $posttext; + $eventdata->fullmessageformat = FORMAT_PLAIN; + $eventdata->fullmessagehtml = $posthtml; + $eventdata->smallmessage = $postsubject; + + $eventdata->name = $eventtype; + $eventdata->component = 'mod_qcreate'; + $eventdata->notification = 1; + $eventdata->contexturl = $info->url; + $eventdata->contexturlname = $info->qcreate; + + message_send($eventdata); } - if ($activityopen){ - $questionlistitem .= qcreate_question_action_icons($cm->id, $question, $thisurl); + + /** + * Message someone about something. + * + * @param stdClass $userfrom + * @param stdClass $userto + * @param string $messagetype + * @param string $eventtype + * @param int $updatetime + * @return void + */ + public function send_notification($userfrom, + $userto, + $messagetype, + $eventtype, + $questionname, + $updatetime) { + self::send_qcreate_notification($userfrom, + $userto, + $messagetype, + $eventtype, + $questionname, + $updatetime, + $this->get_course_module(), + $this->get_context(), + $this->get_course(), + $this->get_module_name(), + $this->get_instance()->name); } - return $questionlistitem; -} -function qcreate_proper_grammar($arrayitems){ - $qtypemenu = question_type_menu(); - $grammarised = array(); - foreach ($arrayitems as $key => $arrayitem){ - if ($arrayitem->no > 1){ - $arrayitem->qtypestring = $qtypemenu[$arrayitem->qtype]; - $grammarised[$key] = get_string('requiredplural', 'qcreate', $arrayitem); + + /** + * Notify student that one of his questions was graded. + * + * @param stdClass $question + * @return void + */ + public function notify_student_question_graded(stdClass $question) { + global $DB, $USER; + + if (!$this->get_instance()->sendstudentnotifications) { + // No need to do anything. + return; + } + + $user = $DB->get_record('user', array('id' => $question->createdby), '*', MUST_EXIST); + $this->send_notification($USER, + $user, + 'gradeavailable', + 'studentnotification', + $question->name, + $question->timemodified); + } + + /** + * Send notifications to graders upon student submissions. + * + * @param stdClass $question + * @return void + */ + public function notify_graders(stdClass $question) { + global $DB, $USER; + + $instance = $this->get_instance(); + + if (!$instance->sendgradernotifications) { + // No need to do anything. + return; + } + + if ($question->createdby) { + $user = $DB->get_record('user', array('id' => $question->createdby), '*', MUST_EXIST); } else { - $arrayitem->qtypestring = $qtypemenu[$arrayitem->qtype]; - $grammarised[$key] = get_string('requiredsingular', 'qcreate', $arrayitem); + $user = $USER; } + $graders = $this->get_graders($user->id); + foreach ($graders as $grader) { + $this->send_notification($user, + $grader, + 'questiontograde', + 'gradernotification', + $question->name, + $question->timemodified); + } + } - return $grammarised; -} -function qcreate_proper_punctuation($arrayitems){ - $i = 1; - $listitems = array(); - foreach ($arrayitems as $key => $arrayitem){ - //all but last and last but one items - if ($i < (count($arrayitems)-1)){ - $listitems[$key] = get_string('comma', 'qcreate', $arrayitem); + + /** + * Save outcomes submitted from grading form. + * + * @param int $userid + * @param stdClass $formdata + * @param int $sourceuserid The user ID under which the outcome data is accessible. This is relevant + * for an outcome set to a user but applied to an entire group. + */ + protected function process_outcomes($userid, $formdata, $sourceuserid = null) { + global $CFG, $USER; + + if (empty($CFG->enableoutcomes)) { + return; } - //last but one item - if ($i == (count($arrayitems)-1)){ - $listitems[$key] = get_string('and', 'qcreate', $arrayitem); + + require_once($CFG->libdir.'/gradelib.php'); + + $data = array(); + $gradinginfo = grade_get_grades($this->get_course()->id, + 'mod', + 'qcreate', + $this->get_instance()->id, + $userid); + + if (!empty($gradinginfo->outcomes)) { + foreach ($gradinginfo->outcomes as $index => $oldoutcome) { + $name = 'outcome_'.$index; + $sourceuserid = $sourceuserid !== null ? $sourceuserid : $userid; + if (isset($formdata->{$name}[$sourceuserid]) && + $oldoutcome->grades[$userid]->grade != $formdata->{$name}[$sourceuserid]) { + $data[$index] = $formdata->{$name}[$sourceuserid]; + } + } } - if ($i == (count($arrayitems))){ - //last item - $listitems[$key] = get_string('fullstop', 'qcreate', $arrayitem); + if (count($data) > 0) { + grade_update_outcomes('mod/qcreate', + $this->course->id, + 'mod', + 'qcreate', + $this->get_instance()->id, + $userid, + $data); } - $i++; } - return $listitems; } -function qcreate_questions_of_type($questions){ - $questionsofqtype = array(); - if ($questions){ - foreach ($questions as $key => $question){ - $questionsofqtype[$question->qtype][] = $question; + +/** + * @param int $cmid the course_module object for this qcreate. + * @param object $question the question. + * @param string $returnurl url to return to after action is done. + * @return string html for a number of icons linked to action pages for a + * question - preview, edit / view and delete icons depending on user capabilities. + */ +function qcreate_question_action_icons($cmid, $question, $returnurl) { + + $html = qcreate_question_preview_button($question); + $html .= qcreate_question_edit_button($cmid, $question, $returnurl); + $html .= qcreate_question_delete_button($cmid, $question, $returnurl); + return $html; +} + +/** + * @param int $cmid the course_module.id for this qcreate. + * @param object $question the question. + * @param string $returnurl url to return to after action is done. + * @param string $contentaftericon some HTML content to be added inside the link, after the icon. + * @return the HTML for an edit icon, view icon, or nothing for a question + * (depending on permissions). + */ +function qcreate_question_edit_button($cmid, $question, $returnurl, $contentaftericon = '') { + global $CFG, $OUTPUT; + + // Minor efficiency saving. Only get strings once, even if there are a lot of icons on one page. + static $stredit = null; + static $strview = null; + if ($stredit === null) { + $stredit = get_string('edit'); + $strview = get_string('view'); + } + + // What sort of icon should we show? + $action = ''; + if (!empty($question->id) && + (question_has_capability_on($question, 'edit', $question->category) || + question_has_capability_on($question, 'move', $question->category))) { + $action = $stredit; + $icon = 't/edit'; + } else if (!empty($question->id) && + question_has_capability_on($question, 'view', $question->category)) { + $action = $strview; + $icon = 'i/info'; + } + + // Build the icon. + if ($action) { + if ($returnurl instanceof moodle_url) { + $returnurl = $returnurl->out_as_local_url(false); } + $questionparams = array('returnurl' => $returnurl, 'cmid' => $cmid, 'id' => $question->id); + $questionurl = new moodle_url("$CFG->wwwroot/question/question.php", $questionparams); + return '' . + $OUTPUT->pix_icon($icon, $action) . $contentaftericon . + ''; + } else if ($contentaftericon) { + return '' . $contentaftericon . ''; + } else { + return ''; } - return $questionsofqtype; } -?> + + +/** + * @param int $cmid the course_module.id for this qcreate. + * @param object $question the question. + * @param string $returnurl url to return to after action is done. + * @return the HTML for a delete icon, or nothing for a question + * (depending on permissions). + */ +function qcreate_question_delete_button($cmid, $question, $returnurl) { + global $CFG, $OUTPUT; + + // Minor efficiency saving. Only get strings once, even if there are a lot of icons on one page. + static $strdelete = null; + if ($strdelete === null) { + $strdelete = get_string('delete'); + } + + if (!empty($question->id) && + (question_has_capability_on($question, 'edit', $question->category))) { + + // Build the icon. + $action = $strdelete; + $icon = 't/delete'; + + return ''. + $OUTPUT->pix_icon($icon, $action) . ''; + } else { + return ''; + } +} + +/** + * @param object $qcreate the qcreate settings + * @param object $question the question + * @return the HTML for a preview question icon. + */ +function qcreate_question_preview_button($question) { + global $CFG, $OUTPUT; + // Minor efficiency saving. Only get strings once, even if there are a lot of icons on one page. + static $strpreview = null; + if ($strpreview === null) { + $strpreview = get_string('preview', 'qcreate'); + } + + if (!question_has_capability_on($question, 'use', $question->category)) { + return ''; + } + + $url = question_preview_url($question->id); + + // Build the icon. + $image = $OUTPUT->pix_icon('t/preview', $strpreview); + + $action = new popup_action('click', $url, 'questionpreview', + question_preview_popup_params()); + + return $OUTPUT->action_link($url, $image, $action, array('title' => $strpreview, 'class' => 'iconsmall')); +} diff --git a/mod_form.php b/mod_form.php index a6dcf13..13fd47c 100644 --- a/mod_form.php +++ b/mod_form.php @@ -1,176 +1,283 @@ . + +/** + * Instance add/edit form + * + * @package mod_qcreate + * @copyright 2007 Jamie Pratt me@jamiep.org + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ if (!defined('MOODLE_INTERNAL')) { - die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page + die('Direct access to this script is forbidden.'); // It must be included from a Moodle page. } -require_once ($CFG->dirroot.'/mod/qcreate/locallib.php'); -require_once ($CFG->dirroot.'/course/moodleform_mod.php'); -require_once ($CFG->libdir.'/questionlib.php'); - +require_once($CFG->dirroot . '/mod/qcreate/locallib.php'); +require_once($CFG->dirroot . '/course/moodleform_mod.php'); +require_once($CFG->libdir . '/questionlib.php'); class mod_qcreate_mod_form extends moodleform_mod { - - private $_requireds; + + private $_requireds; protected function definition() { global $COURSE, $DB; $mform =& $this->_form; - $this->_requireds = $DB->get_records('qcreate_required', array('qcreateid'=>$this->_instance), 'qtype', 'qtype, no, id'); - + $qcreateconfig = get_config('qcreate'); + $qcreate = new stdClass(); + $qcreate->id = $this->_instance; + if (!empty($this->_instance)) { + $this->_requireds = qcreate_required_qtypes($qcreate); + } else { + $this->_requireds = array(); + } -//------------------------------------------------------------------------------- - /// Adding the "general" fieldset, where all the common settings are showed + // Adding the "general" fieldset, where all the common settings are showed. $mform->addElement('header', 'general', get_string('general', 'form')); - /// Adding the standard "name" field - $mform->addElement('text', 'name', get_string('name'), array('size'=>'64')); - $mform->setType('name', PARAM_TEXT); + // Adding the standard "name" field. + $mform->addElement('text', 'name', get_string('name'), array('size' => '64')); + if (!empty($CFG->formatstringstriptags)) { + $mform->setType('name', PARAM_TEXT); + } else { + $mform->setType('name', PARAM_CLEANHTML); + } $mform->addRule('name', null, 'required', null, 'client'); - /// Adding the optional "intro" and "introformat" pair of fields - $this->add_intro_editor(false, get_string('intro', 'qcreate')); - // TODO: $mform->setHelpButton('intro', array('writing', 'richtext'), false, 'editorhelpbutton'); + $mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); - // TODO: $mform->addElement('format', 'introformat', get_string('format')); + // Introduction. + $this->standard_intro_elements(get_string('intro', 'qcreate')); -//------------------------------------------------------------------------------- - $mform->addElement('header', 'timinghdr', get_string('timing', 'form')); - $mform->addElement('date_time_selector', 'timeopen', get_string('open', 'qcreate'), array('optional'=>true)); - // TODO: $mform->setHelpButton('timeopen', array('timeopen', get_string('open', 'qcreate'), 'qcreate')); + // Open and close time. + $mform->addElement('header', 'timinghdr', get_string('availability', 'qcreate')); + $mform->addElement('date_time_selector', 'timeopen', get_string('open', 'qcreate'), array('optional' => true)); + $mform->addHelpButton('timeopen', 'open', 'qcreate'); + $mform->addElement('date_time_selector', 'timeclose', get_string('close', 'qcreate'), array('optional' => true)); - $mform->addElement('date_time_selector', 'timeclose', get_string('close', 'qcreate'), array('optional'=>true)); - // TODO: $mform->setHelpButton('timeclose', array('timeopen', get_string('close', 'qcreate'), 'qcreate')); - -//------------------------------------------------------------------------------- + // Grading. $mform->addElement('header', 'gradeshdr', get_string('grading', 'qcreate')); $gradeoptions = array(); $gradeoptions[0] = get_string('nograde'); - for ($i=100; $i>=1; $i--) { + for ($i = 100; $i >= 1; $i--) { $gradeoptions[$i] = $i; } $mform->addElement('select', 'grade', get_string('grade'), $gradeoptions); $mform->setDefault('grade', 100); - // TODO: $mform->setHelpButton('grade', array('grade', get_string('grade'), 'qcreate')); + $mform->addHelpButton('grade', 'grade', 'qcreate'); $graderatiooptions = array(); - foreach (array(100, 90, 80, 67, 60, 50, 40, 33, 30, 20, 10, 0) - as $graderatiooption){ - $a = new object(); + foreach (array(100, 90, 80, 67, 60, 50, 40, 33, 30, 20, 10, 0) as $graderatiooption) { + $a = new stdClass(); $a->automatic = ($graderatiooption).'%'; $a->manual = (100 - ($graderatiooption)).'%'; $graderatiooptions[$graderatiooption] = get_string('graderatiooptions', 'qcreate', $a); } $mform->addElement('select', 'graderatio', get_string('graderatio', 'qcreate'), $graderatiooptions); $mform->setDefault('graderatio', 50); - // TODO: $mform->setHelpButton('graderatio', array('automaticmanualgrading', get_string('graderatio', 'qcreate'), 'qcreate')); + $mform->addHelpButton('graderatio', 'graderatio', 'qcreate'); + $mform->setExpanded('gradeshdr', true); $allowedgroup = array(); $allowedgroup[] =& $mform->createElement('checkbox', "ALL", '', get_string('allowall', 'qcreate')); - $mform->setDefault("allowed[ALL]", 1); - $qtypemenu = question_type_menu(); - foreach ($qtypemenu as $qtype => $qtypestring){ - $allowedgroup[] =& $mform->createElement('checkbox', "$qtype", '', $qtypestring); + + $qtypemenu = qcreate::qtype_menu(); + $allowedqtypes = array(); + foreach ($qtypemenu as $qtype => $name) { + $allowedgroup[] =& $mform->createElement('checkbox', $qtype, '', $name); + $allowedqtypes[$qtype] = $name; + } + if (question_bank::is_qtype_installed('multichoice')) { + $mform->setDefault("allowed[multichoice]", 1); + } else { + $mform->setDefault("allowed[ALL]", 1); } $mform->addGroup($allowedgroup, 'allowed', get_string('allowedqtypes', 'qcreate')); $mform->disabledIf('allowed', "allowed[ALL]", 'checked'); - // TODO: $mform->setHelpButton('allowed', array('gradedquestiontypes', get_string('allowedqtypes', 'qcreate'), 'qcreate')); + $mform->addHelpButton('allowed', 'allowedqtypes', 'qcreate'); - for ($i= 1; $i<=20; $i++){ + for ($i = 1; $i <= 20; $i++) { $noofquestionsmenu[$i] = $i; } $mform->addElement('select', 'totalrequired', get_string('noofquestionstotal', 'qcreate'), $noofquestionsmenu); - // TODO: $mform->setHelpButton('totalrequired', array('totalquestionsgraded', get_string('noofquestionstotal', 'qcreate'), 'qcreate')); - + $mform->addHelpButton('totalrequired', 'noofquestionstotal', 'qcreate'); -//------------------------------------------------------------------------------- - $repeatarray=array(); - $repeatarray[] =& $mform->createElement('header', 'addminimumquestionshdr', get_string('addminimumquestionshdr', 'qcreate')); - $qtypeselect = array(''=>get_string('selectone', 'qcreate')) + $qtypemenu; + $mform->addElement('header', 'addminimumquestionshdr', get_string('addminimumquestionshdr', 'qcreate')); + $repeatarray = array(); + $qtypeselect = array('' => get_string('selectone', 'qcreate')) + $allowedqtypes; $repeatarray[] =& $mform->createElement('select', 'qtype', get_string('qtype', 'qcreate'), $qtypeselect); - $repeatarray[] =& $mform->createElement('select', 'minimumquestions', get_string('minimumquestions', 'qcreate'), $noofquestionsmenu); + $repeatarray[] =& $mform->createElement('select', 'minimumquestions', get_string('minimumquestions', 'qcreate'), + $noofquestionsmenu); $requiredscount = count($this->_requireds); - $repeats = $this->_requireds ? $requiredscount+2 : 4; - $repeats = $this->repeat_elements($repeatarray, $repeats, array(), 'minrepeats', 'addminrepeats', 2); + $repeats = $this->_requireds ? $requiredscount + 2 : 4; + $repeats = $this->repeat_elements($repeatarray, $repeats, array(), 'minrepeats', 'addminrepeats', 2, + get_string('addmorerequireds', 'qcreate'), true); - for ($i=0; $i<$repeats; $i++) { - // TODO: $mform->setHelpButton("qtype[$i]", array('questiontype', get_string('qtype', 'qcreate'), 'qcreate')); - // TODO: $mform->setHelpButton("minimumquestions[$i]", array('minimumquestions', get_string('minimumquestions', 'qcreate'), 'qcreate')); + for ($i = 0; $i < $repeats; $i++) { + $mform->addHelpButton("qtype[$i]", 'qtype', 'qcreate'); + $mform->addHelpButton("minimumquestions[$i]", 'minimumquestions', 'qcreate'); $mform->disabledIf("minimumquestions[$i]", "qtype[$i]", 'eq', ''); } -//------------------------------------------------------------------------------- - /// Adding the "general" fieldset, where all the common settings are showed + + // Sudents access rights on their own questions. $mform->addElement('header', 'studentaccessheader', get_string('studentaccessheader', 'qcreate')); - $studentqaccessmenu = array(0=>get_string('studentaccessaddonly', 'qcreate'), - 1=>get_string('studentaccesspreview', 'qcreate'), - 2=>get_string('studentaccesssaveasnew', 'qcreate'), - 3=>get_string('studentaccessedit', 'qcreate')); + $studentqaccessmenu = array(0 => get_string('studentaccessaddonly', 'qcreate'), + 1 => get_string('studentaccesspreview', 'qcreate'), + 2 => get_string('studentaccesssaveasnew', 'qcreate'), + 3 => get_string('studentaccessedit', 'qcreate')); $mform->addElement('select', 'studentqaccess', get_string('studentqaccess', 'qcreate'), $studentqaccessmenu); - // TODO: $mform->setHelpButton('studentqaccess', array('studentquestionaccess', get_string('studentqaccess', 'qcreate'), 'qcreate')); + $mform->addHelpButton('studentqaccess', 'studentqaccess', 'qcreate'); + + // Notifications. + $mform->addElement('header', 'notifications', get_string('notifications', 'qcreate')); -//------------------------------------------------------------------------------- - // add standard elements, common to all modules + $name = get_string('sendgradernotifications', 'qcreate'); + $mform->addElement('selectyesno', 'sendgradernotifications', $name); + $mform->addHelpButton('sendgradernotifications', 'sendgradernotifications', 'qcreate'); + $mform->setDefault('sendgradernotifications', $qcreateconfig->sendgradernotifications); + + $name = get_string('sendstudentnotifications', 'qcreate'); + $mform->addElement('selectyesno', 'sendstudentnotifications', $name); + $mform->addHelpButton('sendstudentnotifications', 'sendstudentnotifications', 'qcreate'); + $mform->setDefault('sendstudentnotifications', $qcreateconfig->sendstudentnotifications); + + // Add standard elements, common to all modules. $this->standard_coursemodule_elements(); -//------------------------------------------------------------------------------- - // add standard buttons, common to all modules + + // Add standard buttons, common to all modules. $this->add_action_buttons(); } - function data_preprocessing(&$default_values){ + public function data_preprocessing(&$defaultvalues) { $i = 0; - if ($this->_requireds){ - foreach ($this->_requireds as $qtype => $required){ - $default_values["minimumquestions[$i]"] = $required->no; - $default_values["qtype[$i]"] = $qtype; + if ($this->_requireds) { + foreach ($this->_requireds as $qtype => $required) { + $defaultvalues["minimumquestions[$i]"] = $required->no; + $defaultvalues["qtype[$i]"] = $qtype; $i++; } } - if (isset($default_values['allowed'])){ - $enabled = explode(',', $default_values['allowed']); - $qtypemenu = question_type_menu(); - foreach (array_keys($qtypemenu) as $qtype){ - $default_values["allowed[$qtype]"] = (array_search($qtype, $enabled)!==FALSE)?1:0; + if (isset($defaultvalues['allowed'])) { + $enabled = explode(',', $defaultvalues['allowed']); + $qtypemenu = qcreate::qtype_menu(); + foreach ($qtypemenu as $qtype => $notused) { + $defaultvalues["allowed[$qtype]"] = (array_search($qtype, $enabled) !== false) ? 1 : 0; } - $default_values["allowed[ALL]"] = (array_search('ALL', $enabled)!==FALSE)?1:0; + $defaultvalues["allowed[ALL]"] = (array_search('ALL', $enabled) !== false) ? 1 : 0; + } + + // Set up the completion checkboxes which aren't part of standard data. + // We also make the default value (if you turn on the checkbox) for those + // numbers to be 1, this will not apply unless checkbox is ticked. + $defaultvalues['completionquestionsenabled'] + = !empty($defaultvalues['completionquestions']) ? 1 : 0; + if (empty($defaultvalues['completionquestions'])) { + $defaultvalues['completionquestions'] = 1; } } - public function validation($data, $files){ + public function validation($data, $files) { $errors = array(); - if (!isset($data['allowed'])){ - $errors['allowed']=get_string('needtoallowatleastoneqtype', 'qcreate'); + if (!isset($data['allowed'])) { + $errors['allowed'] = get_string('needtoallowatleastoneqtype', 'qcreate'); } - $qtypemenu = question_type_menu(); $totalrequired = 0; - if (isset($data['qtype'])){ - foreach ($data['qtype'] as $key => $qtype){ - if ($qtype!=''){ + if (isset($data['qtype'])) { + foreach ($data['qtype'] as $key => $qtype) { + if ($qtype != '') { $chkqtypes[$key] = $qtype; $keysforthisqtype = array_keys($chkqtypes); - if (count(array_keys($data['qtype'], $qtype)) > 1){ - $errors["qtype[$key]"]=get_string('morethanonemin', 'qcreate', $qtypemenu[$qtype]); + if (count(array_keys($data['qtype'], $qtype)) > 1) { + $errors["qtype[$key]"] = get_string('morethanonemin', 'qcreate', question_bank::get_qtype_name($qtype)); - } elseif (!isset($data['allowed'][$qtype]) && !isset($data['allowed']['ALL'])){ - $errors['allowed']=get_string('needtoallowqtype', 'qcreate', $qtypemenu[$qtype]); - $errors["qtype[$key]"]=get_string('needtoallowqtype', 'qcreate', $qtypemenu[$qtype]); + } else if (!isset($data['allowed'][$qtype]) && !isset($data['allowed']['ALL'])) { + $errors['allowed'] = get_string('needtoallowqtype', 'qcreate', question_bank::get_qtype_name($qtype)); + $errors["qtype[$key]"] = get_string('needtoallowqtype', 'qcreate', question_bank::get_qtype_name($qtype)); } $totalrequired += $data['minimumquestions'][$key]; } } } - if ($totalrequired > $data['totalrequired']){ - $errors['totalrequired']=get_string('totalrequiredislessthansumoftotalsforeachqtype', 'qcreate'); + if ($totalrequired > $data['totalrequired']) { + $errors['totalrequired'] = get_string('totalrequiredislessthansumoftotalsforeachqtype', 'qcreate'); } - if (isset($data['allowed']['ALL']) && (count($data['allowed']) > 1)){ - $errors['allowed']=get_string('allandother', 'qcreate'); + if (isset($data['allowed']['ALL']) && (count($data['allowed']) > 1)) { + $errors['allowed'] = get_string('allandother', 'qcreate'); } - if (($data['timeclose'] !=0) && ($data['timeopen'] !=0) && ($data['timeclose'] <= $data['timeopen'])){ - $errors['timeopen']=get_string('openmustbemorethanclose', 'qcreate'); + if (($data['timeclose'] != 0) && ($data['timeopen'] != 0) && ($data['timeclose'] <= $data['timeopen'])) { + $errors['timeopen'] = get_string('openmustbemorethanclose', 'qcreate'); } return $errors; } + + /** + * Display module-specific activity completion rules. + * Part of the API defined by moodleform_mod + * @return array Array of string IDs of added items, empty array if none + */ + public function add_completion_rules() { + $mform = $this->_form; + + $group = array(); + $group[] =& $mform->createElement('checkbox', 'completionquestionsenabled', + '', get_string('completionquestions', 'qcreate')); + $group[] =& $mform->createElement('text', 'completionquestions', '', array('size' => 3)); + $group[] =& $mform->createElement('static', 'staticthing', '', get_string('questions', 'qcreate') ); + $mform->setType('completionquestions', PARAM_INT); + $mform->addGroup($group, 'completionquestionsgroup', get_string('completionquestionsgroup', 'qcreate'), array(' '), false); + $mform->addHelpButton('completionquestionsgroup', 'completionquestions', 'qcreate'); + $mform->disabledIf('completionquestions', 'completionquestionsenabled', 'notchecked'); + + return array('completionquestionsgroup'); + } + + /** + * Called during validation. Indicates whether a module-specific completion rule is selected. + * + * @param array $data Input data (not yet validated) + * @return bool True if one or more rules is enabled, false if none are. + */ + public function completion_rule_enabled($data) { + return (!empty($data['completionquestionsenabled']) && $data['completionquestions'] != 0); + + } + + /** + * Allows module to modify the data returned by form get_data(). + * This method is also called in the bulk activity completion form. + * + * Only available on moodleform_mod. + * + * @param stdClass $data the form data to be modified. + */ + public function data_postprocessing($data) { + parent::data_postprocessing($data); + // Set up completion section even if checkbox is not ticked. + if (!empty($data->completionunlocked)) { + // Turn off completion settings if the checkboxes aren't ticked. + $autocompletion = !empty($data->completion) && $data->completion == COMPLETION_TRACKING_AUTOMATIC; + if (empty($data->completionquestionsenabled) || !$autocompletion) { + $data->completionquestions = 0; + } + } + } + } diff --git a/overview.php b/overview.php index d1e173b..154e923 100644 --- a/overview.php +++ b/overview.php @@ -1,14 +1,28 @@ . + /** * This page prints an overview of a particular instance of qcreate for someone with grading permission * - * @author - * @version $Id: overview.php,v 1.3 2008/12/01 13:18:25 jamiesensei Exp $ - * @package qcreate + * @package mod_qcreate + * @copyright 2008 Jamie Pratt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or late **/ - -require_once("../../config.php"); +require_once(dirname(__FILE__) . '/../../config.php'); require_once($CFG->dirroot.'/mod/qcreate/lib.php'); require_once($CFG->dirroot.'/mod/qcreate/locallib.php'); require_once($CFG->dirroot . '/question/editlib.php'); @@ -16,73 +30,17 @@ list($thispageurl, $contexts, $cmid, $cm, $qcreate, $pagevars) = question_edit_setup('questions', true); $qcreate->cmidnumber = $cm->id; -require_capability('mod/qcreate:grade', get_context_instance(CONTEXT_MODULE, $cm->id)); - -$requireds = $DB->get_records('qcreate_required', array('qcreateid'=>$qcreate->id), 'qtype', 'qtype, no, id'); - +require_capability('mod/qcreate:grade', context_module::instance($cm->id)); -$modulecontext = get_context_instance(CONTEXT_MODULE, $cm->id); +$modulecontext = context_module::instance($cm->id); +$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST); - -require_login($COURSE->id); +require_login($course->id); require_capability('mod/qcreate:grade', $modulecontext); +$PAGE->set_url('/mod/qcreate/overview.php', array('cmid' => $cm->id)); -add_to_log($COURSE->id, "qcreate", "overview", "overview.php?id=$cm->id", "$qcreate->id"); - -/// Print the page header -$strqcreates = get_string("modulenameplural", "qcreate"); -$strqcreate = get_string("modulename", "qcreate"); - -$navlinks = array(); -$navlinks[] = array('name' => $strqcreates, 'link' => "index.php?id=$COURSE->id", 'type' => 'activity'); -$navlinks[] = array('name' => format_string($qcreate->name), 'link' => '', 'type' => 'activityinstance'); - -$navigation = build_navigation($navlinks); - -$PAGE->set_url("/mod/qcreate/overview.php?id=$cm->id"); - -print_header_simple(format_string($qcreate->name), "", $navigation, "", "", true, - update_module_button($cm->id, $COURSE->id, $strqcreate), navmenu($COURSE, $cm)); -$mode = 'overview'; - -include('tabs.php'); - -echo $OUTPUT->box(format_text($qcreate->intro, $qcreate->introformat), 'generalbox', 'intro'); - -$qcreatetime = new object(); -$qcreatetime->timeopen = userdate($qcreate->timeopen); -$qcreatetime->timeclose = userdate($qcreate->timeclose); -/* if ($qcreate->timeopen == 0 AND $qcreate->timeclose ==0 ){ - $timestring = get_string('timenolimit', 'qcreate'); -} else if ($qcreate->timeopen != 0 AND $qcreate->timeclose ==0 ) { - $timestring = get_string('timeopen', 'qcreate', $qcreatetime); - -} else if ($qcreate->timeopen == 0 AND $qcreate->timeclose !=0 ) { - $timestring = get_string('timeclose', 'qcreate', $qcreatetime); -} else { - $timestring = get_string('timeopenclose', 'qcreate', $qcreatetime); -}*/ -$timestring = qcreate_time_status($qcreate); - -if ($qcreate->graderatio == 100){ - $gradestring = get_string('gradeallautomatic', 'qcreate'); -} else if ($qcreate->graderatio == 0){ - $gradestring = get_string('gradeallmanual', 'qcreate'); -} else { - $gradeobj = new object(); - $gradeobj->automatic = $qcreate->graderatio; - $gradeobj->manual = 100 - $qcreate->graderatio; - $gradestring = get_string('grademixed', 'qcreate', $gradeobj); -} - -echo '
'; -echo '

'.$timestring.'

'; -echo '

'.get_string('totalgrade', 'qcreate').' : '.$qcreate->grade.'

'; -echo '

'.get_string('graderatio', 'qcreate').' : '.$gradestring.'

'; -echo '
'; - - -qcreate_teacher_overview($requireds, $qcreate); +$qcreateobj = new qcreate($modulecontext, $cm, $course); -echo $OUTPUT->footer(); +// Get the qcreate class to render the overview page. +echo $qcreateobj->view(optional_param('action', 'overview', PARAM_TEXT)); diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..b2c5907 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + . + + + + + . + + . + + + + + diff --git a/pix/icon.gif b/pix/icon.gif deleted file mode 100644 index 13261a9..0000000 Binary files a/pix/icon.gif and /dev/null differ diff --git a/pix/icon.png b/pix/icon.png index b11d1be..1f81fa9 100644 Binary files a/pix/icon.png and b/pix/icon.png differ diff --git a/pix/icon.svg b/pix/icon.svg new file mode 100644 index 0000000..68a6033 --- /dev/null +++ b/pix/icon.svg @@ -0,0 +1,211 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/renderable.php b/renderable.php new file mode 100644 index 0000000..5ccfd66 --- /dev/null +++ b/renderable.php @@ -0,0 +1,252 @@ +. + +/** + * This file contains the definition for the renderable classes for the qcreatement + * + * @package mod_qcreate + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Renderable course index summary + * @package mod_qcreate + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class qcreate_course_index_summary implements renderable { + /** @var array qcreates - A list of course module info and question counts */ + public $qcreates = array(); + /** @var boolean usesections - Does this course format support sections? */ + public $usesections = false; + /** @var string courseformat - The current course format name */ + public $courseformatname = ''; + + /** + * constructor + * + * @param boolean $usesections - True if this course format uses sections + * @param string $courseformatname - The id of this course format + */ + public function __construct($usesections, $courseformatname) { + $this->usesections = $usesections; + $this->courseformatname = $courseformatname; + } + + /** + * Add a row of data to display on the course index page + * + * @param int $cmid - The course module id for generating a link + * @param string $cmname - The course module name for generating a link + * @param string $sectionname - The name of the course section (only if $usesections is true) + * @param int $timeopen - The due date for the qcreate - may be 0 if no timeopen + * @param int $timeclose - The due date for the qcreate - may be 0 if no timeclose + * @param int $questions - Number of created questions (for user or for all users) + * @param string $gradeinfo - The current users grade if they have been graded and it is not hidden. + */ + public function add_qcreate_info($cmid, $cmname, $sectionname, $timeopen, $timeclose, $questions, $gradeinfo) { + $this->qcreates[] = array('cmid' => $cmid, + 'cmname' => $cmname, + 'sectionname' => $sectionname, + 'timeopen' => $timeopen, + 'timeclose' => $timeclose, + 'questions' => $questions, + 'gradeinfo' => $gradeinfo); + } +} + +/** + * Renderable header + * @package mod_qcreate + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class qcreate_header implements renderable { + /** @var stdClass the qcreate record */ + public $qcreate = null; + /** @var mixed context|null the context record */ + public $context = null; + /** @var bool $showintro - show or hide the intro */ + public $showintro = false; + /** @var int coursemoduleid - The course module id */ + public $coursemoduleid = 0; + /** @var string $subpage optional subpage (extra level in the breadcrumbs) */ + public $subpage = ''; + /** @var string $preface optional preface (text to show before the heading) */ + public $preface = ''; + + /** + * Constructor + * + * @param stdClass $qcreate - the qcreate database record + * @param mixed $context context|null the course module context + * @param bool $showintro - show or hide the intro + * @param int $coursemoduleid - the course module id + * @param string $subpage - an optional sub page in the navigation + * @param string $preface - an optional preface to show before the heading + */ + public function __construct(stdClass $qcreate, + $context, + $showintro, + $coursemoduleid, + $subpage='', + $preface='') { + $this->qcreate = $qcreate; + $this->context = $context; + $this->showintro = $showintro; + $this->coursemoduleid = $coursemoduleid; + $this->subpage = $subpage; + $this->preface = $preface; + } +} + +/** + * Implements a renderable message notification + * @package mod_qcreate + * @copyright 2018 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class qcreate_gradingmessage implements renderable { + /** @var string $heading is the heading to display to the user */ + public $heading = ''; + /** @var string $message is the message to display to the user */ + public $message = ''; + /** @var int $coursemoduleid */ + public $coursemoduleid = 0; + /** @var int $gradingerror should be set true if there was a problem grading */ + public $gradingerror = null; + + /** + * Constructor + * @param string $heading This is the heading to display + * @param string $message This is the message to display + * @param bool $gradingerror Set to true to display the message as an error. + * @param int $coursemoduleid + * @param int $page This is the current quick grading page + */ + public function __construct($heading, $message, $coursemoduleid, $gradingerror = false, $page = null) { + $this->heading = $heading; + $this->message = $message; + $this->coursemoduleid = $coursemoduleid; + $this->gradingerror = $gradingerror; + $this->page = $page; + } + +} + +/** + * Renderable overview + * @package mod_qcreate + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class qcreate_teacher_overview implements renderable { + /** @var stdClass the qcreate record */ + public $qcreate = null; + /** @var mixed context|null the context record */ + public $context = null; + /** @var int coursemoduleid - The course module id */ + public $coursemoduleid = 0; + public $available; + public $timenow; + public $required; + public $allowed; + + /** + * Constructor + * + * @param stdClass $qcreate - the qcreate database record + * @param mixed $context context|null the course module context + * @param int $coursemoduleid - the course module id + */ + public function __construct(stdClass $qcreate, + $context, + $coursemoduleid, + $available) { + $this->qcreate = $qcreate; + $this->context = $context; + $this->coursemoduleid = $coursemoduleid; + $this->available = $available; + $this->timenow = time(); + } +} + +/** + * Renderable view page + * @package mod_qcreate + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class qcreate_student_view implements renderable { + /** @var stdClass the qcreate record */ + public $qcreate = null; + public $cm; + public $cat; + public $requiredquestions; + public $extraquestions; + public $qtyperequired; + public $extraquestionsgraded; + public $extraquestionsdone; + public $extras; + public $studentgrade; + public $qtypesallowed; + /** @var mixed context|null the context record */ + public $context = null; + /** @var int coursemoduleid - The course module id */ + public $coursemoduleid = 0; + public $available; + public $timenow; + + /** + * Constructor + * + * @param stdClass $qcreate - the qcreate database record + * @param mixed $context context|null the course module context + * @param int $coursemoduleid - the course module id + */ + public function __construct(stdClass $qcreate, + $cm, + $cat, + $requiredquestions, + $extraquestions, + $qtyperequired, + $extraquestionsgraded, + $extraquestionsdone, + $extras, + $studentgrade, + $qtypesallowed, + $context, + $coursemoduleid, + $available) { + $this->qcreate = $qcreate; + $this->cm = $cm; + $this->cat = $cat; + $this->requiredquestions = $requiredquestions; + $this->extraquestions = $extraquestions; + $this->extraquestionsgraded = $extraquestionsgraded; + $this->extraquestionsdone = $extraquestionsdone; + $this->extras = $extras; + $this->studentgrade = $studentgrade; + $this->qtypesallowed = $qtypesallowed; + $this->context = $context; + $this->coursemoduleid = $coursemoduleid; + $this->available = $available; + $this->timenow = time(); + } +} \ No newline at end of file diff --git a/renderer.php b/renderer.php new file mode 100644 index 0000000..7b3f14f --- /dev/null +++ b/renderer.php @@ -0,0 +1,553 @@ +. + +/** + * This file contains a renderer for the qcreatement class + * + * @package mod_qcreate + * @copyright 2014 Jean-Michel vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/mod/qcreate/locallib.php'); + +/** + * A custom renderer class that extends the plugin_renderer_base and is used by the qcreate module. + * + * @package mod_qcreate + * @copyright 2012 NetSpot {@link http://www.netspot.com.au} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_qcreate_renderer extends plugin_renderer_base { + + /** + * Utility function to add a row of data to a table with 2 columns. Modified + * the table param and does not return a value + * + * @param html_table $table The table to append the row of data to + * @param string $first The first column text + * @param string $second The second column text + * @return void + */ + private function add_table_row_tuple(html_table $table, $first, $second) { + $row = new html_table_row(); + $cell1 = new html_table_cell($first); + $cell2 = new html_table_cell($second); + $row->cells = array($cell1, $cell2); + $table->data[] = $row; + } + + /** + * Render a grading message notification + * @param qcreate_gradingmessage $result The result to render + * @return string + */ + public function render_qcreate_gradingmessage(qcreate_gradingmessage $result) { + $urlparams = array('id' => $result->coursemoduleid, 'action' => 'grading'); + $url = new moodle_url('/mod/qcreate/view.php', $urlparams); + $classes = $result->gradingerror ? 'notifyproblem' : 'notifysuccess'; + + $o = ''; + $o .= $this->output->heading($result->heading, 4); + $o .= $this->output->notification($result->message, $classes); + $o .= $this->output->continue_button($url); + return $o; + } + + /** + * Render a course index summary + * + * @param qcreate_course_index_summary $indexsummary + * @return string + */ + public function render_qcreate_course_index_summary(qcreate_course_index_summary $indexsummary) { + $o = ''; + + $strplural = get_string('modulenameplural', 'qcreate'); + $strsectionname = $indexsummary->courseformatname; + $stropen = get_string('openon', 'qcreate'); + $strclose = get_string('closeon', 'qcreate'); + $strqcreated = get_string('createdquestions', 'qcreate'); + $strgrade = get_string('grade'); + + $table = new html_table(); + if ($indexsummary->usesections) { + $table->head = array ($strsectionname, $strplural, $stropen, $strclose, $strqcreated, $strgrade); + $table->align = array ('left', 'left', 'left', 'left', 'center', 'right'); + } else { + $table->head = array ($strplural, $strqcreated, $stropen, $strclose, $strgrade); + $table->align = array ('left', 'left', 'left', 'center', 'right'); + } + $table->data = array(); + + $currentsection = ''; + foreach ($indexsummary->qcreates as $info) { + $params = array('id' => $info['cmid']); + $link = html_writer::link(new moodle_url('/mod/qcreate/view.php', $params), + $info['cmname']); + + $printsection = ''; + if ($indexsummary->usesections) { + if ($info['sectionname'] !== $currentsection) { + if ($info['sectionname']) { + $printsection = $info['sectionname']; + } + if ($currentsection !== '') { + $table->data[] = 'hr'; + } + $currentsection = $info['sectionname']; + } + } + + $open = $info['timeopen'] ? userdate($info['timeopen']) : '-'; + $close = $info['timeclose'] ? userdate($info['timeclose']) : '-'; + + if ($indexsummary->usesections) { + $row = array($printsection, $link, $open, $close, $info['questions'], $info['gradeinfo']); + } else { + $row = array($link, $open, $close, $info['questions'], $info['gradeinfo']); + } + $table->data[] = $row; + } + + $o .= html_writer::table($table); + + return $o; + } + + /** + * Page is done - render the footer. + * + * @return void + */ + public function render_footer() { + return $this->output->footer(); + } + + /** + * Render the header. + * + * @param qcreate_header $header + * @return string + */ + public function render_qcreate_header(qcreate_header $header) { + $o = ''; + + if ($header->subpage) { + $this->page->navbar->add($header->subpage); + } + + $this->page->set_title(get_string('pluginname', 'qcreate')); + $this->page->set_heading($this->page->course->fullname); + + $o .= $this->output->header(); + $heading = format_string($header->qcreate->name, false, array('context' => $header->context)); + $o .= $this->output->heading($heading); + if ($header->preface) { + $o .= $header->preface; + } + + if ($header->showintro) { + $o .= $this->output->box_start('generalbox boxaligncenter', 'intro'); + $o .= format_module_intro('qcreate', $header->qcreate, $header->coursemoduleid); + $o .= $this->output->box_end(); + } + + return $o; + } + + /** + * Render the time status. + * + * @param bool available is the qcreate available + * @param int timeopen + * @param int timeclose + * @return string + */ + public function time_status($available, $timenow, $timeopen, $timeclose) { + if ($available) { + $openstring = get_string('activityopen', 'qcreate'); + } else { + $openstring = get_string('activityclosed', 'qcreate'); + } + $string = html_writer::tag('em', $openstring); + if (!$timeopen && !$timeclose) { + return $string.' '.get_string('timenolimit', 'qcreate'); + } + if ($timeopen) { + if ($timenow < $timeopen) { + $string .= ' '.get_string('timewillopen', 'qcreate', userdate($timeopen)); + } else { + $string .= ' '.get_string('timeopened', 'qcreate', userdate($timeopen)); + } + } + if ($timeclose) { + if ($timenow < $timeclose) { + $string .= ' '.get_string('timewillclose', 'qcreate', userdate($timeclose)); + } else { + $string .= ' '.get_string('timeclosed', 'qcreate', userdate($timeclose)); + } + } + return $string; + } + + public function gradingstring($graderatio) { + if ($graderatio == 100) { + $gradestring = get_string('gradeallautomatic', 'qcreate'); + } else if ($graderatio == 0) { + $gradestring = get_string('gradeallmanual', 'qcreate'); + } else { + $gradeobj = new stdClass(); + $gradeobj->automatic = $graderatio; + $gradeobj->manual = 100 - $graderatio; + $gradestring = get_string('grademixed', 'qcreate', $gradeobj); + } + return $gradestring; + } + + public function proper_grammar($arrayitems) { + $grammarised = array(); + foreach ($arrayitems as $key => $arrayitem) { + if ($arrayitem->no > 1) { + $arrayitem->qtypestring = question_bank::get_qtype_name($arrayitem->qtype); + $grammarised[$key] = get_string('requiredplural', 'qcreate', $arrayitem); + } else { + $arrayitem->qtypestring = question_bank::get_qtype_name($arrayitem->qtype); + $grammarised[$key] = get_string('requiredsingular', 'qcreate', $arrayitem); + } + } + return $grammarised; + } + + public function proper_punctuation($arrayitems) { + $i = 1; + $listitems = array(); + foreach ($arrayitems as $key => $arrayitem) { + // All but last and last but one items. + if ($i < (count($arrayitems) - 1)) { + $listitems[$key] = get_string('comma', 'qcreate', $arrayitem); + } + // Last but one item. + if ($i == (count($arrayitems) - 1)) { + $listitems[$key] = get_string('and', 'qcreate', $arrayitem); + } + if ($i == (count($arrayitems))) { + // Last item. + $listitems[$key] = get_string('fullstop', 'qcreate', $arrayitem); + } + $i++; + } + return $listitems; + } + + private function teacher_required_questions($requireds, $totalrequired) { + $qtyperequired = 0; + if ($requireds) { + $grammarised = $this->proper_grammar($requireds); + foreach ($requireds as $qtype => $required) { + $qtyperequired += $required->no; + } + } + if ($qtyperequired < $totalrequired) { + $a = new stdClass(); + $a->extrarequired = $totalrequired - $qtyperequired; + if ($a->extrarequired == 1) { + $grammarised['extras'] = get_string('extraqgraded', 'qcreate', $a); + } else { + $grammarised['extras'] = get_string('extraqsgraded', 'qcreate', $a); + } + } + $punctuateds = $this->proper_punctuation($grammarised); + return $punctuateds; + } + + private function allowed_qtypes_list ($allowedqtypes) { + if ($allowedqtypes != 'ALL') { + $qtypesallowed = explode(',', $allowedqtypes); + } else { + $qtypesallowed = array_keys(qcreate::qtype_menu()); + } + $allowedqtypelist = html_writer::start_tag('ul'); + foreach ($qtypesallowed as $qtypeallowed) { + $allowedqtypelist .= '
  • ' . question_bank::get_qtype_name($qtypeallowed) . '
  • '; + } + $allowedqtypelist .= html_writer::end_tag('ul'); + return $allowedqtypelist; + } + + /** + * Render the teacher overview. + * + * @param qcreate_header $header + * @return string + */ + public function render_qcreate_teacher_overview(qcreate_teacher_overview $overview) { + $o = ''; + $o .= $this->output->box_start('generalbox boxaligncenter', 'status'); + $o .= $this->output->container(format_string($this->time_status($overview->available, $overview->timenow, + $overview->qcreate->timeopen, $overview->qcreate->timeclose))); + $o .= $this->output->box_end(); + $o .= $this->output->box_start('generalbox boxaligncenter', 'grade'); + $o .= $this->output->container(format_string(get_string('totalgradeis', 'qcreate', $overview->qcreate->grade))); + $o .= $this->output->box_end(); + $o .= $this->output->box_start('generalbox boxaligncenter', 'grading'); + $o .= $this->output->container(format_string($this->gradingstring($overview->qcreate->graderatio))); + $o .= $this->output->box_end(); + $o .= $this->output->box_start('generalbox boxaligncenter boxwidthwide', 'questions_overview'); + $o .= $this->output->heading(get_string('requiredquestions', 'qcreate'), 3); + $o .= html_writer::start_tag('ul'); + $requireds = $this->teacher_required_questions($overview->qcreate->requiredqtypes, + $overview->qcreate->totalrequired); + if ($requireds) { + foreach ($requireds as $key => $punctuated) { + $o .= html_writer::start_tag('li') . $punctuated; + if ($key == 'extras') { + $o .= $this->allowed_qtypes_list ($overview->qcreate->allowed); + } + $o .= html_writer::end_tag('li'); + } + } + + $o .= $this->output->box_end(); + + // Link to the grading page. + $o .= '
    '; + $o .= $this->output->container_start('gradinglink'); + $urlparams = array('cmid' => $overview->coursemoduleid); + $url = new moodle_url('/mod/qcreate/edit.php', $urlparams); + $o .= '' . get_string('viewgrading', 'mod_qcreate') . ' '; + $o .= $this->output->container_end(); + $o .= '
    '; + return $o; + } + + /** + * Render the student view. + * + * @param qcreate_header $header + * @return string + */ + public function render_qcreate_student_view(qcreate_student_view $studentview) { + $o = ''; + $o .= $this->output->box_start('generalbox boxaligncenter', 'status'); + $o .= $this->output->container(format_string($this->time_status($studentview->available, $studentview->timenow, + $studentview->qcreate->timeopen, $studentview->qcreate->timeclose))); + $o .= $this->output->box_end(); + $o .= $this->output->box_start('generalbox boxaligncenter boxwidthwide', 'required_questions'); + $o .= $this->output->heading(get_string('requiredquestions', 'qcreate'), 3); + + // Render required questions. + $requireds = $studentview->qcreate->requiredqtypes; + if ($requireds) { + $grammarised = $this->proper_grammar($requireds); + $punctuateds = $this->proper_punctuation($grammarised); + $o .= html_writer::start_tag('ul', array('id' => 'requiredqlist')); + foreach ($requireds as $qtype => $required) { + $o .= $this->student_required_view($studentview->cm , $required, $studentview->cat, $punctuateds[$qtype], + $studentview->requiredquestions[$qtype], $studentview->qcreate->grade, $studentview->available); + } + } + + // Render extras questions. + if ($studentview->extraquestionsgraded) { + $a = new stdClass(); + $a->extraquestionsdone = $studentview->extraquestionsdone; + $a->extrarequired = $studentview->extraquestionsgraded; + $o .= html_writer::start_tag('li') . html_writer::start_tag('strong'); + if ($a->extraquestionsdone == 1) { + $o .= get_string('extraqdone', 'qcreate', $a); + } else { + $o .= get_string('extraqsdone', 'qcreate', $a); + } + if ($a->extrarequired == 1) { + $o .= ' '.get_string('extraqgraded', 'qcreate', $a); + } else { + $o .= ' '.get_string('extraqsgraded', 'qcreate', $a); + } + $o .= html_writer::end_tag('strong'); + $o .= html_writer::start_tag('ul'); + foreach ($studentview->qtypesallowed as $qtypeallowed) { + $hasrequireds = isset($requireds[$qtypeallowed]); + $o .= $this->student_extra_view($studentview->cm , $qtypeallowed, $hasrequireds, + $studentview->cat, $studentview->extraquestions[$qtypeallowed], + $studentview->qcreate->grade, $studentview->available); + } + $o .= html_writer::end_tag('ul'); + $o .= html_writer::end_tag('li'); + } + + // Render grade. + $o .= $this->render_grade($studentview->studentgrade, $studentview->qcreate->graderatio); + + $o .= html_writer::end_tag('ul'); + $o .= $this->output->box_end(); + return $o; + } + + public function student_required_view($cm, $required, $cat, $punctuated, $questions, $maxgrade, $available) { + global $CFG; + $o = ''; + $linklist = ''; + if ($available) { + // One item list with one link to create question. + $linklist .= html_writer::start_tag('ul') . html_writer::start_tag('li'); + $link = new moodle_url($CFG->wwwroot . '/question/question.php'); + $returnurl = new moodle_url($CFG->wwwroot . '/mod/qcreate/view.php', array('id' => $cm->id, 'qaction' => 'add')); + $link->params(array('cmid' => $cm->id, 'qtype' => $required->qtype, 'category' => $cat, + 'returnurl' => $returnurl->out_as_local_url(true))); + if (count($questions)) { + $linklist .= html_writer::link($link, get_string('clickhereanother', 'qcreate', $required->qtypestring)); + } else { + $linklist .= html_writer::link($link, get_string('clickhere', 'qcreate', $required->qtypestring)); + } + $linklist .= html_writer::end_tag('li') . html_writer::end_tag('ul'); + } + + if (count($questions)) { + // Top level list. + $questionlist = $this->student_questionlist_view($questions, $cm, $maxgrade, $available); + $requirementslist = html_writer::start_tag('ul') . html_writer::start_tag('li'); + $requirementslist .= get_string('donequestionno', 'qcreate', $required); + + $requirementslist .= $questionlist . html_writer::end_tag('li'); + + if ($required->stillrequiredno > 0) { + $requirementslist .= html_writer::start_tag('li'); + $requirementslist .= get_string('todoquestionno', 'qcreate', $required); + $requirementslist .= $linklist; + $requirementslist .= html_writer::end_tag('li'); + } + + $requirementslist .= html_writer::end_tag('ul'); + $o .= html_writer::start_tag('li') . + $punctuated . $requirementslist . html_writer::end_tag('li'); + } else { + $o .= html_writer::start_tag('li') . + $punctuated . $linklist . html_writer::end_tag('li'); + } + return $o; + } + + public function student_questionlist_view($questions, $cm, $maxgrade, $available) { + $o = ''; + $o .= html_writer::start_tag('ul'); + foreach ($questions as $question) { + $o .= html_writer::start_tag('li'); + $o .= $this->student_question_view($question, $cm, true, $maxgrade, $available); + $o .= html_writer::end_tag('li'); + } + $o .= html_writer::end_tag('ul'); + return $o; + } + + public function student_question_view($question, $cm, $showgrades, $maxgrade, $available) { + global $CFG; + $o = ''; + $actionicons = ''; + if ($available && (question_has_capability_on($question, 'edit', $question->cid) + || question_has_capability_on($question, 'move', $question->cid) + || question_has_capability_on($question, 'view', $question->cid))) { + $link = new moodle_url($CFG->wwwroot . '/question/question.php'); + + $returnurl = new moodle_url($CFG->wwwroot . '/mod/qcreate/view.php', array('id' => $cm->id, 'qaction' => 'edit')); + $link->params(array('cmid' => $cm->id, 'id' => $question->id, 'returnurl' => $returnurl->out_as_local_url(true))); + $o .= html_writer::link($link, $question->name); + $actionicons = ' ' . qcreate_question_action_icons($cm->id, $question, $returnurl); + } else { + $o = $question->name; + } + if ($showgrades) { + $o .= ' ' . html_writer::start_tag('em'); + if ($question->gid && $question->grade != -1) { + $o .= "({$question->grade}/{$maxgrade})"; + } else { + $o .= "(".get_string('notgraded', 'qcreate').")"; + } + if ($question->gradecomment != '') { + $o .= '"'.$question->gradecomment.'"'; + } + $o .= html_writer::end_tag('em'); + } + + $o .= $actionicons; + + return $o; + } + + public function student_extra_view($cm, $qtypeallowed, $hasrequireds, $cat, $questions, $maxgrade, $available) { + global $CFG; + $o = ''; + $o .= html_writer::start_tag('ul') . html_writer::start_tag('li'); + if ($available) { + // One item list with one link to create question. + $link = new moodle_url($CFG->wwwroot . '/question/question.php'); + $returnurl = new moodle_url($CFG->wwwroot . '/mod/qcreate/view.php', array('id' => $cm->id, 'qaction' => 'add')); + $link->params(array('cmid' => $cm->id, 'qtype' => $qtypeallowed, 'category' => $cat, + 'returnurl' => $returnurl->out_as_local_url(true))); + $o .= html_writer::link($link, question_bank::get_qtype_name($qtypeallowed)); + } else { + $o .= question_bank::get_qtype_name($qtypeallowed); + } + $extrascount = count($questions); + if ($hasrequireds && $extrascount) { + if ($extrascount == 1) { + $o .= ' '.get_string('alreadydoneextraone', 'qcreate', $extrascount); + } else { + $o .= ' '.get_string('alreadydoneextra', 'qcreate', $extrascount); + } + } else if ($extrascount) { + if ($extrascount == 1) { + $o .= ' '.get_string('alreadydoneone', 'qcreate', $extrascount); + } else { + $o .= ' '.get_string('alreadydone', 'qcreate', $extrascount); + } + } + if ($extrascount) { + $o .= html_writer::start_tag('ul'); + foreach ($questions as $question) { + $o .= html_writer::start_tag('li'); + $o .= $this->student_question_view($question, $cm, true, $maxgrade, $available); + $o .= html_writer::end_tag('li'); + } + $o .= html_writer::end_tag('ul'); + } + $o .= html_writer::end_tag('li') . html_writer::end_tag('ul'); + return $o; + } + + public function render_grade($studentgrade, $graderatio) { + $o = ''; + if (count((array)$studentgrade)) { + $o .= html_writer::start_tag('li') . html_writer::start_tag('em'); + $o .= get_string('activitygrade', 'qcreate', $studentgrade->fullgrade); + if (!empty($graderatio)) { + $o .= html_writer::start_tag('ul'); + $o .= html_writer::tag('li', + html_writer::tag('em', get_string('automaticgrade', 'qcreate', $studentgrade->automaticgrade))); + if ($graderatio != 100) { + $o .= html_writer::tag('li', + html_writer::tag('em', get_string('manualgrade', 'qcreate', $studentgrade->manualgrade))); + } + $o .= html_writer::end_tag('ul'); + } + $o .= html_writer::end_tag('em') . html_writer::end_tag('li'); + } + return $o; + } +} + diff --git a/restorelib.php b/restorelib.php deleted file mode 100644 index 0a90737..0000000 --- a/restorelib.php +++ /dev/null @@ -1,398 +0,0 @@ -id) - // | - // | - // --------------------------------------------------- - // | | - // qcreate_grades qcreate_required - //(UL, pk->id, fk->qcreateid) (CL, pk->id, fk->qcreateid) - // - // Meaning: pk->primary key field of the table - // fk->foreign key to link with parent - // CL->course level info - // UL->user level info - // - //----------------------------------------------------------- - - //This function executes all the restore procedure about this mod - function qcreate_restore_mods($mod, $restore) { - - global $CFG; - - $status = true; - - //Get record from backup_ids - $data = backup_getid($restore->backup_unique_code, $mod->modtype, $mod->id); - - if ($data) { - //Now get completed xmlized object - $info = $data->info; - //if necessary, write to restorelog and adjust date/time fields - if ($restore->course_startdateoffset) { - restore_log_date_changes('Qcreate', $restore, $info['MOD']['#'], array('TIMEOPEN', 'TIMECLOSE')); - } - - //Now, build the QCREATE record structure - $qcreate = new object(); - $qcreate->course = $restore->course_id; - $qcreate->name = backup_todb($info['MOD']['#']['NAME']['0']['#']); - $qcreate->grade = backup_todb($info['MOD']['#']['GRADE']['0']['#']); - $qcreate->graderatio = backup_todb($info['MOD']['#']['GRADERATIO']['0']['#']); - $qcreate->intro = backup_todb($info['MOD']['#']['INTRO']['0']['#']); - $qcreate->introformat = backup_todb($info['MOD']['#']['INTROFORMAT']['0']['#']); - $qcreate->allowed = backup_todb($info['MOD']['#']['ALLOWED']['0']['#']); - $qcreate->totalrequired = backup_todb($info['MOD']['#']['TOTALREQUIRED']['0']['#']); - $qcreate->studentqaccess = backup_todb($info['MOD']['#']['STUDENTQACCESS']['0']['#']); - $qcreate->timesync = 0; - $qcreate->timeopen = backup_todb($info['MOD']['#']['TIMEOPEN']['0']['#']); - $qcreate->timeclose = backup_todb($info['MOD']['#']['TIMECLOSE']['0']['#']); - $qcreate->timecreated = backup_todb($info['MOD']['#']['TIMECREATED']['0']['#']); - $qcreate->timemodified = backup_todb($info['MOD']['#']['TIMEMODIFIED']['0']['#']); - - //We have to recode the grade field if it is <0 (scale) - if ($qcreate->grade < 0) { - $scale = backup_getid($restore->backup_unique_code, "scale", abs($qcreate->grade)); - if ($scale) { - $qcreate->grade = -($scale->new_id); - } - } - - - //The structure is equal to the db, so insert the qcreate - $newid = insert_record("qcreate", $qcreate); - - //Do some output - if (!defined('RESTORE_SILENTLY')) { - echo "
  • ".get_string("modulename", "qcreate")." \"".format_string(stripslashes($qcreate->name), true)."\"
  • "; - } - backup_flush(300); - - if ($newid) { - //We have the newid, update backup_ids - backup_putid($restore->backup_unique_code, $mod->modtype, - $mod->id, $newid); - $status = qcreate_requireds_restore_mods($mod->id, $newid, $info, $restore) && $status; - //Now check if want to restore user data and do it. - if (restore_userdata_selected($restore, 'qcreate', $mod->id)) { - //Restore qcreate_grades - $status = qcreate_grades_restore_mods($mod->id, $newid, $info, $restore) && $status; - } - } else { - $status = false; - } - } else { - $status = false; - } - - return $status; - } - - //This function restores the qcreate_grades - function qcreate_grades_restore_mods($old_qcreate_id, $new_qcreate_id, $info, $restore) { - - global $CFG; - - $status = true; - - //Get the submissions array - it might not be present - if (isset($info['MOD']['#']['GRADES']['0']['#']['GRADE'])) { - $grades = $info['MOD']['#']['GRADES']['0']['#']['GRADE']; - } else { - $grades = array(); - } - - //Iterate over grades - for($i = 0; $i < sizeof($grades); $i++) { - $sub_info = $grades[$i]; - //traverse_xmlize($sub_info); //Debug - //print_object ($GLOBALS['traverse_array']); //Debug - //$GLOBALS['traverse_array']=""; //Debug - - //We'll need this later!! - $oldid = backup_todb($sub_info['#']['ID']['0']['#']); - - //Now, build the QCREATE_SUBMISSIONS record structure - $grade = new object(); - $grade->qcreateid = $new_qcreate_id; - $grade->questionid = backup_todb($sub_info['#']['QUESTIONID']['0']['#']); - $grade->grade = backup_todb($sub_info['#']['GRADE']['0']['#']); - $grade->gradecomment = backup_todb($sub_info['#']['GRADECOMMENT']['0']['#']); - $grade->teacher = backup_todb($sub_info['#']['TEACHER']['0']['#']); - $grade->timemarked = backup_todb($sub_info['#']['TIMEMARKED']['0']['#']); - - //We have to recode the questionid field - $question = backup_getid($restore->backup_unique_code, "question", $grade->questionid); - if ($question) { - $grade->questionid = $question->new_id; - } else { - continue; // skip this grade, the question associated has not been restored - } - - //We have to recode the teacher field - $user = backup_getid($restore->backup_unique_code, "user", $grade->teacher); - if ($user) { - $grade->teacher = $user->new_id; - } - - //The structure is equal to the db, so insert the qcreate_submission - $newid = insert_record("qcreate_grades", $grade); - - //Do some output - if (($i+1) % 50 == 0) { - if (!defined('RESTORE_SILENTLY')) { - echo "."; - if (($i+1) % 1000 == 0) { - echo "
    "; - } - } - backup_flush(300); - } - - if ($newid) { - //We have the newid, update backup_ids - backup_putid($restore->backup_unique_code, "qcreate_grades", $oldid, - $newid); - - - } else { - $status = false; - } - } - - return $status; - } - - //This function restores the qcreate_requireds - function qcreate_requireds_restore_mods($old_qcreate_id, $new_qcreate_id, $info, $restore) { - - global $CFG; - - $status = true; - - //Get the requireds array - it might not be present - if (isset($info['MOD']['#']['REQUIREDS']['0']['#']['REQUIRED'])) { - $requireds = $info['MOD']['#']['REQUIREDS']['0']['#']['REQUIRED']; - } else { - $requireds = array(); - } - - //Iterate over requireds - for($i = 0; $i < sizeof($requireds); $i++) { - $sub_info = $requireds[$i]; - //traverse_xmlize($sub_info); //Debug - //print_object ($GLOBALS['traverse_array']); //Debug - //$GLOBALS['traverse_array']=""; //Debug - - //We'll need this later!! - $oldid = backup_todb($sub_info['#']['ID']['0']['#']); - - //Now, build the QCREATE_REQUIRED record structure - $required = new object(); - $required->qcreateid = $new_qcreate_id; - $required->qtype = backup_todb($sub_info['#']['QTYPE']['0']['#']); - $required->no = backup_todb($sub_info['#']['NO']['0']['#']); - - - //The structure is equal to the db, so insert the qcreate_submission - $newid = insert_record("qcreate_required", $required); - - //Do some output - if (($i+1) % 50 == 0) { - if (!defined('RESTORE_SILENTLY')) { - echo "."; - if (($i+1) % 1000 == 0) { - echo "
    "; - } - } - backup_flush(300); - } - - if ($newid) { - //We have the newid, update backup_ids - backup_putid($restore->backup_unique_code, "qcreate_required", $oldid, - $newid); - - - } else { - $status = false; - } - } - - return $status; - } - - - - //Return a content decoded to support interactivities linking. Every module - //should have its own. They are called automatically from - //qcreate_decode_content_links_caller() function in each module - //in the restore process - function qcreate_decode_content_links($content, $restore) { - - global $CFG; - - $result = $content; - - //Link to the list of qcreates - - $searchstring='/\$@(QCREATEINDEX)\*([0-9]+)@\$/'; - //We look for it - preg_match_all($searchstring, $content, $foundset); - //If found, then we are going to look for its new id (in backup tables) - if ($foundset[0]) { - //print_object($foundset); //Debug - //Iterate over foundset[2]. They are the old_ids - foreach($foundset[2] as $old_id) { - //We get the needed variables here (course id) - $rec = backup_getid($restore->backup_unique_code, "course", $old_id); - //Personalize the searchstring - $searchstring='/\$@(QCREATEINDEX)\*('.$old_id.')@\$/'; - //If it is a link to this course, update the link to its new location - if($rec->new_id) { - //Now replace it - $result= preg_replace($searchstring, $CFG->wwwroot.'/mod/qcreate/index.php?id='.$rec->new_id, $result); - } else { - //It's a foreign link so leave it as original - $result= preg_replace($searchstring, $restore->original_wwwroot.'/mod/qcreate/index.php?id='.$old_id, $result); - } - } - } - - //Link to qcreate view by moduleid - - $searchstring='/\$@(QCREATEVIEWBYID)\*([0-9]+)@\$/'; - //We look for it - preg_match_all($searchstring, $result, $foundset); - //If found, then we are going to look for its new id (in backup tables) - if ($foundset[0]) { - //print_object($foundset); //Debug - //Iterate over foundset[2]. They are the old_ids - foreach($foundset[2] as $old_id) { - //We get the needed variables here (course_modules id) - $rec = backup_getid($restore->backup_unique_code, "course_modules", $old_id); - //Personalize the searchstring - $searchstring='/\$@(QCREATEVIEWBYID)\*('.$old_id.')@\$/'; - //If it is a link to this course, update the link to its new location - if($rec->new_id) { - //Now replace it - $result= preg_replace($searchstring, $CFG->wwwroot.'/mod/qcreate/view.php?id='.$rec->new_id, $result); - } else { - //It's a foreign link so leave it as original - $result= preg_replace($searchstring, $restore->original_wwwroot.'/mod/qcreate/view.php?id='.$old_id, $result); - } - } - } - - return $result; - } - - //This function makes all the necessary calls to xxxx_decode_content_links() - //function in each module, passing them the desired contents to be decoded - //from backup format to destination site/course in order to mantain inter-activities - //working in the backup/restore process. It's called from restore_decode_content_links() - //function in restore process - function qcreate_decode_content_links_caller($restore) { - global $CFG; - $status = true; - - if ($qcreates = get_records_sql("SELECT a.id, a.intro - FROM {$CFG->prefix}qcreate a - WHERE a.course = $restore->course_id")) { - //Iterate over each qcreate->description - $i = 0; //Counter to send some output to the browser to avoid timeouts - foreach ($qcreates as $qcreate) { - //Increment counter - $i++; - $content = $qcreate->intro; - $result = restore_decode_content_links_worker($content, $restore); - if ($result != $content) { - //Update record - $qcreate->intro = addslashes($result); - $status = update_record("qcreate", $qcreate); - if (debugging()) { - if (!defined('RESTORE_SILENTLY')) { - echo '

    '.s($content).'
    changed to
    '.s($result).'

    '; - } - } - } - //Do some output - if (($i+1) % 5 == 0) { - if (!defined('RESTORE_SILENTLY')) { - echo "."; - if (($i+1) % 100 == 0) { - echo "
    "; - } - } - backup_flush(300); - } - } - } - return $status; - } - - - - //This function returns a log record with all the necessay transformations - //done. It's used by restore_log_module() to restore modules log. - function qcreate_restore_logs($restore, $log) { - - $status = false; - - //Depending of the action, we recode different things - switch ($log->action) { - case "add": - if ($log->cmid) { - //Get the new_id of the module (to recode the info field) - $mod = backup_getid($restore->backup_unique_code, $log->module, $log->info); - if ($mod) { - $log->url = "view.php?id=".$log->cmid; - $log->info = $mod->new_id; - $status = true; - } - } - break; - case "update": - if ($log->cmid) { - //Get the new_id of the module (to recode the info field) - $mod = backup_getid($restore->backup_unique_code, $log->module, $log->info); - if ($mod) { - $log->url = "view.php?id=".$log->cmid; - $log->info = $mod->new_id; - $status = true; - } - } - break; - case "view": - if ($log->cmid) { - //Get the new_id of the module (to recode the info field) - $mod = backup_getid($restore->backup_unique_code, $log->module, $log->info); - if ($mod) { - $log->url = "view.php?id=".$log->cmid; - $log->info = $mod->new_id; - $status = true; - } - } - break; - case "view all": - $log->url = "index.php?id=".$log->course; - $status = true; - break; - default: - if (!defined('RESTORE_SILENTLY')) { - echo "action (".$log->module."-".$log->action.") unknown. Not restored
    "; //Debug - } - break; - } - - if ($status) { - $status = $log; - } - return $status; - } -?> diff --git a/settings.php b/settings.php new file mode 100644 index 0000000..c4d76b7 --- /dev/null +++ b/settings.php @@ -0,0 +1,44 @@ +. + +/** + * Administration settings definitions for the qcreate module. + * + * @package mod_qcreate + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +defined('MOODLE_INTERNAL') || die(); + +if ($ADMIN->fulltree) { + $name = new lang_string('sendgradernotifications', 'mod_qcreate'); + $description = new lang_string('sendgradernotifications_help', 'mod_qcreate'); + $setting = new admin_setting_configcheckbox('qcreate/sendgradernotifications', + $name, + $description, + 0); + $settings->add($setting); + + $name = new lang_string('sendstudentnotifications', 'mod_qcreate'); + $description = new lang_string('sendstudentnotifications_help', 'mod_qcreate'); + $setting = new admin_setting_configcheckbox('qcreate/sendstudentnotifications', + $name, + $description, + 0); + $settings->add($setting); +} diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..ef46e69 --- /dev/null +++ b/styles.css @@ -0,0 +1,12 @@ +#page-mod-qcreate-view ul#requiredqlist li { + font-weight: bold; +} + +#page-mod-qcreate-view ul#requiredqlist ul li { + font-weight: normal; +} + +#page-mod-qcreate-edit table#mod-qcreate-grades { + border-spacing: 0; + width: 100%; +} diff --git a/styles.php b/styles.php deleted file mode 100644 index 6babd35..0000000 --- a/styles.php +++ /dev/null @@ -1,3 +0,0 @@ -ul#requiredqlist li { font-weight:bold } - -ul#requiredqlist ul li { font-weight:normal } diff --git a/tabs.php b/tabs.php deleted file mode 100644 index 7e0a9d2..0000000 --- a/tabs.php +++ /dev/null @@ -1,45 +0,0 @@ -wwwroot/mod/qcreate/overview.php?".$thispageurl->get_query_string(), - get_string('overview', 'qcreate'), get_string('overview', 'qcreate')); -if ($contexts->have_one_edit_tab_cap('questions')) { - $row[] = new tabobject('editq', "$CFG->wwwroot/mod/qcreate/edit.php?".$thispageurl->get_query_string(), - get_string('grading', 'qcreate'), get_string('gradequestions', 'qcreate')); -} -questionbank_navigation_tabs($row, $contexts, $thispageurl->get_query_string()); - -// no export tab here -foreach ($row as $key => $tab){ - if ($tab->id == 'export'){ - unset($row[$key]); - } -} -/* -if ($contexts->have_one_edit_tab_cap('export')) { - $row[] = new tabobject('exportgood', "$CFG->wwwroot/mod/qcreate/exportgood.php?".$thispageurl->get_query_string(), - get_string('export', 'quiz'), get_string('exportgoodquestions', 'qcreate')); -} -*/ -$tabs[] = $row; - -print_tabs($tabs, $mode); - -?> diff --git a/tests/base_test.php b/tests/base_test.php new file mode 100644 index 0000000..1c4691d --- /dev/null +++ b/tests/base_test.php @@ -0,0 +1,229 @@ +. + +/** + * Base class for unit tests for mod_qcreate. + * + * @package mod_qcreate + * @category phpunit + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->dirroot . '/mod/qcreate/locallib.php'); + +/** + * Unit tests for (some of) mod/qcreate/locallib.php. + * + * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_qcreate_base_testcase extends advanced_testcase { + + /** @const Default number of students to create */ + const DEFAULT_STUDENT_COUNT = 3; + /** @const Default number of teachers to create */ + const DEFAULT_TEACHER_COUNT = 2; + /** @const Default number of editing teachers to create */ + const DEFAULT_EDITING_TEACHER_COUNT = 2; + /** @const Optional extra number of students to create */ + const EXTRA_STUDENT_COUNT = 40; + /** @const Optional number of suspended students */ + const EXTRA_SUSPENDED_COUNT = 10; + /** @const Optional extra number of teachers to create */ + const EXTRA_TEACHER_COUNT = 5; + /** @const Optional extra number of editing teachers to create */ + const EXTRA_EDITING_TEACHER_COUNT = 5; + /** @const Number of groups to create */ + const GROUP_COUNT = 6; + + /** @var stdClass $course New course created to hold the qcreate activities */ + protected $course = null; + + /** @var array $teachers List of DEFAULT_TEACHER_COUNT teachers in the course*/ + protected $teachers = null; + + /** @var array $editingteachers List of DEFAULT_EDITING_TEACHER_COUNT editing teachers in the course */ + protected $editingteachers = null; + + /** @var array $students List of DEFAULT_STUDENT_COUNT students in the course*/ + protected $students = null; + + /** @var array $extrateachers List of EXTRA_TEACHER_COUNT teachers in the course*/ + protected $extrateachers = null; + + /** @var array $extraeditingteachers List of EXTRA_EDITING_TEACHER_COUNT editing teachers in the course*/ + protected $extraeditingteachers = null; + + /** @var array $extrastudents List of EXTRA_STUDENT_COUNT students in the course*/ + protected $extrastudents = null; + + /** @var array $extrasuspendedstudents List of EXTRA_SUSPENDED_COUNT students in the course*/ + protected $extrasuspendedstudents = null; + + /** @var array $groups List of 10 groups in the course */ + protected $groups = null; + + /** + * Setup function - we will create a course and add a qcreate instance to it. + */ + protected function setUp() { + global $DB; + + $this->resetAfterTest(true); + + $this->course = $this->getDataGenerator()->create_course(); + $this->teachers = array(); + for ($i = 0; $i < self::DEFAULT_TEACHER_COUNT; $i++) { + array_push($this->teachers, $this->getDataGenerator()->create_user()); + } + + $this->editingteachers = array(); + for ($i = 0; $i < self::DEFAULT_EDITING_TEACHER_COUNT; $i++) { + array_push($this->editingteachers, $this->getDataGenerator()->create_user()); + } + + $this->students = array(); + for ($i = 0; $i < self::DEFAULT_STUDENT_COUNT; $i++) { + array_push($this->students, $this->getDataGenerator()->create_user()); + } + + $this->groups = array(); + for ($i = 0; $i < self::GROUP_COUNT; $i++) { + array_push($this->groups, $this->getDataGenerator()->create_group(array('courseid' => $this->course->id))); + } + + $teacherrole = $DB->get_record('role', array('shortname' => 'teacher')); + foreach ($this->teachers as $i => $teacher) { + $this->getDataGenerator()->enrol_user($teacher->id, + $this->course->id, + $teacherrole->id); + groups_add_member($this->groups[$i % self::GROUP_COUNT], $teacher); + } + + $editingteacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); + foreach ($this->editingteachers as $i => $editingteacher) { + $this->getDataGenerator()->enrol_user($editingteacher->id, + $this->course->id, + $editingteacherrole->id); + groups_add_member($this->groups[$i % self::GROUP_COUNT], $editingteacher); + } + + $studentrole = $DB->get_record('role', array('shortname' => 'student')); + foreach ($this->students as $i => $student) { + $this->getDataGenerator()->enrol_user($student->id, + $this->course->id, + $studentrole->id); + groups_add_member($this->groups[$i % self::GROUP_COUNT], $student); + } + } + + /* + * For tests that make sense to use alot of data, create extra students/teachers. + */ + protected function create_extra_users() { + global $DB; + $this->extrateachers = array(); + for ($i = 0; $i < self::EXTRA_TEACHER_COUNT; $i++) { + array_push($this->extrateachers, $this->getDataGenerator()->create_user()); + } + + $this->extraeditingteachers = array(); + for ($i = 0; $i < self::EXTRA_EDITING_TEACHER_COUNT; $i++) { + array_push($this->extraeditingteachers, $this->getDataGenerator()->create_user()); + } + + $this->extrastudents = array(); + for ($i = 0; $i < self::EXTRA_STUDENT_COUNT; $i++) { + array_push($this->extrastudents, $this->getDataGenerator()->create_user()); + } + + $this->extrasuspendedstudents = array(); + for ($i = 0; $i < self::EXTRA_SUSPENDED_COUNT; $i++) { + array_push($this->extrasuspendedstudents, $this->getDataGenerator()->create_user()); + } + + $teacherrole = $DB->get_record('role', array('shortname' => 'teacher')); + foreach ($this->extrateachers as $i => $teacher) { + $this->getDataGenerator()->enrol_user($teacher->id, + $this->course->id, + $teacherrole->id); + groups_add_member($this->groups[$i % self::GROUP_COUNT], $teacher); + } + + $editingteacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); + foreach ($this->extraeditingteachers as $i => $editingteacher) { + $this->getDataGenerator()->enrol_user($editingteacher->id, + $this->course->id, + $editingteacherrole->id); + groups_add_member($this->groups[$i % self::GROUP_COUNT], $editingteacher); + } + + $studentrole = $DB->get_record('role', array('shortname' => 'student')); + foreach ($this->extrastudents as $i => $student) { + $this->getDataGenerator()->enrol_user($student->id, + $this->course->id, + $studentrole->id); + if ($i < (self::EXTRA_STUDENT_COUNT / 2)) { + groups_add_member($this->groups[$i % self::GROUP_COUNT], $student); + } + } + + foreach ($this->extrasuspendedstudents as $i => $suspendedstudent) { + $this->getDataGenerator()->enrol_user($suspendedstudent->id, + $this->course->id, + $studentrole->id, 'manual', 0, 0, ENROL_USER_SUSPENDED); + if ($i < (self::EXTRA_SUSPENDED_COUNT / 2)) { + groups_add_member($this->groups[$i % self::GROUP_COUNT], $suspendedstudent); + } + } + } + + protected function create_instance($params=array()) { + $generator = $this->getDataGenerator()->get_plugin_generator('mod_qcreate'); + $params['course'] = $this->course->id; + + $instance = $generator->create_instance($params); + $cm = get_coursemodule_from_instance('qcreate', $instance->id); + $context = context_module::instance($cm->id); + return new testable_qcreate($context, $cm, $this->course); + } + + public function test_create_instance() { + $this->assertNotEmpty($this->create_instance()); + } + +} + +/** + * Test subclass that makes all the protected methods we want to test public. + * and add utility methods. + */ +class testable_qcreate extends qcreate { + + public function testable_delete_grades() { + return parent::delete_grades(); + } + + public function testable_get_graders($userid) { + // Changed method from protected to public. + return parent::get_graders($userid); + } +} diff --git a/tests/behat/behat_mod_qcreate.php b/tests/behat/behat_mod_qcreate.php new file mode 100644 index 0000000..dbf7f89 --- /dev/null +++ b/tests/behat/behat_mod_qcreate.php @@ -0,0 +1,61 @@ +. + +/** + * Steps definitions related to mod_qcreate. + * + * @package mod_qcreate + * @category test + * @copyright 2017 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. + +require_once(__DIR__ . '/../../../../lib/behat/behat_base.php'); +require_once(__DIR__ . '/../../../../question/tests/behat/behat_question_base.php'); + +use Behat\Gherkin\Node\TableNode as TableNode; + +use Behat\Mink\Exception\ExpectationException as ExpectationException; + +/** + * Steps definitions related to mod_qcreate. + * + * @copyright 2017 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class behat_mod_qcreate extends behat_question_base { + + /** + * Adds a question to the existing Question creation activity with filling the form. + * + * The form for creating a question should be on one page. + * + * @When /^I add a "(?P(?:[^"]|\\")*)" question to the "(?P(?:[^"]|\\")*)" qcreate with:$/ + * @param string $questiontype + * @param string $qcreatename + * @param TableNode $questiondata with data for filling the add question form + */ + public function i_add_question_to_the_qcreate_with($questiontype, $qcreatename, TableNode $questiondata) { + $qcreatename = $this->escape($qcreatename); + $questiontype = $this->escape($questiontype); + $this->execute('behat_general::click_link', $qcreatename); + $this->execute('behat_general::click_link', $questiontype); + $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $questiondata); + $this->execute("behat_forms::press_button", 'id_submitbutton'); + } +} diff --git a/tests/behat/qcreate_add.feature b/tests/behat/qcreate_add.feature new file mode 100644 index 0000000..57d4c6c --- /dev/null +++ b/tests/behat/qcreate_add.feature @@ -0,0 +1,31 @@ +@mod @mod_qcreate +Feature: A teacher can create a Question Creation activity + In order to test my student ability to create questions + As a teacher + I need to create a Question Creation activity + + Scenario: Create a qcreate activity + Given the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Terry1 | Teacher1 | teacher1@example.com | + | student1 | Sam1 | Student1 | student1@example.com | + And the following "courses" exist: + | fullname | shortname | category | + | Course 1 | C1 | 0 | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + | student1 | C1 | student | + When I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + And I add a "Question Creation" to section "1" and I fill the form with: + | Name | Question Creation 001 | + | Introduction | Question Creation introduction | + And I am on "Course 1" course homepage + And I follow "Question Creation 001" + Then I should see "Question Creation 001" + And I should see "Question Creation introduction" + And I should see "Activity is open. No time limits set." + And I should see "Grading is 50%% automatic, 50%% manual." + And I navigate to "Grading" in current page administration + And I should see "Nothing to display" diff --git a/tests/behat/qcreate_backup_restore.feature b/tests/behat/qcreate_backup_restore.feature new file mode 100644 index 0000000..f2d8948 --- /dev/null +++ b/tests/behat/qcreate_backup_restore.feature @@ -0,0 +1,32 @@ +@mod @mod_qcreate +Feature: Backup and Restore of Question creation activities + In order to reuse my Question creation activities + As a admin + I need to be able to backup and restore them + + Background: + Given the following "courses" exist: + | fullname | shortname | category | + | Course 1 | C1 | 0 | + And the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Terry1 | Teacher1 | teacher1@example.com | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + And I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + And I add a "Question Creation" to section "1" and I fill the form with: + | Name | Question Creation 001 | + | Introduction | Question Creation introduction | + And I log out + And I log in as "admin" + + @javascript + Scenario: Backup and restore in a new course + When I backup "Course 1" course using this options: + | Confirmation | Filename | test_backup.mbz | + And I restore "test_backup.mbz" backup into a new course using this options: + | Schema | Course name | Course 2 | + Then I should see "Course 2" + And I should see "Question Creation 001" diff --git a/tests/behat/qcreate_export_good.feature b/tests/behat/qcreate_export_good.feature new file mode 100644 index 0000000..d647b94 --- /dev/null +++ b/tests/behat/qcreate_export_good.feature @@ -0,0 +1,80 @@ +@mod @mod_qcreate +Feature: Export good questions of a qcreate activity + As a teacher + In order to re-use questions created by my students in a Question creation activity + I need to export them + + @javascript @_file_upload + Scenario: Teacher export good questions + Given the following "courses" exist: + | fullname | shortname | category | groupmode | + | Course 1 | C1 | 0 | 1 | + And the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + | student1 | Student | 1 | student1@example.com | + | student2 | Student | 2 | student2@example.com | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + | student1 | C1 | student | + | student2 | C1 | student | + When I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + And I add a "Question Creation" to section "1" and I fill the form with: + | Name | Question Creation 001 | + | Introduction | Question Creation introduction | + | To own questions | preview and view / save as new | + | Total Questions Graded | 2 | + And I log out + And I run the scheduled task "mod_qcreate\task\synchronize_qaccess" + And I log in as "student1" + And I am on "Course 1" course homepage + And I add a "Multiple choice" question to the "Question Creation 001" qcreate with: + | Question name | Multi-choice-001 | + | Question text | Find the capital of France. | + | General feedback | Paris is the capital of France | + | Choice 1 | Tokyo | + | Choice 2 | London | + | Choice 3 | Paris | + | id_fraction_0 | None | + | id_fraction_1 | None | + | id_fraction_2 | 100% | + And I add a "Multiple choice" question to the "Question Creation 001" qcreate with: + | Question name | Multi-choice-002 | + | Question text | What\'s between orange and green in the spectrum? | + | General feedback | The odd numbers are One and Three | + | Choice 1 | Red | + | Choice 2 | Yellow | + | Choice 3 | Blue | + | id_fraction_0 | None | + | id_fraction_1 | 100% | + | id_fraction_2 | None | + And I log out + And I log in as "student2" + And I am on "Course 1" course homepage + And I add a "Multiple choice" question to the "Question Creation 001" qcreate with: + | Question name | Multi-choice-003 | + | Question text | Find the capital of England | + | General feedback | London is the capital of England | + | Choice 1 | Tokyo | + | Choice 2 | London | + | Choice 3 | Paris | + | id_fraction_0 | None | + | id_fraction_1 | 100% | + | id_fraction_2 | None | + And I log out + And I log in as "teacher1" + And I am on "Course 1" course homepage + And I follow "Question Creation 001" + And I navigate to "Grading" in current page administration + And I set the field "Grade for question 'Multi-choice-001' created by Student 1" to "80 / 100" + And I set the field "Grade for question 'Multi-choice-002' created by Student 1" to "50 / 100" + And I set the field "Grade for question 'Multi-choice-003' created by Student 2" to "90 / 100" + And I press "Save all grades & feedback" + And I navigate to "Export good questions" in current page administration + And I set the field "id_betterthangrade" to "70 / 100" + And I set the field "id_format_xml" to "1" + And I press "Export questions to file" + Then following "click here" should download between "2900" and "3000" bytes + And I log out diff --git a/tests/behat/qcreate_grade_question.feature b/tests/behat/qcreate_grade_question.feature new file mode 100644 index 0000000..d7cc33e --- /dev/null +++ b/tests/behat/qcreate_grade_question.feature @@ -0,0 +1,62 @@ +@mod @mod_qcreate +Feature: Test grading a question in a qcreate activity + As a teacher + In order to evaluate my students in a Question creation activity + I need to grade questions created by my students + + Background: + Given the following "courses" exist: + | fullname | shortname | category | groupmode | + | Course 1 | C1 | 0 | 1 | + And the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + | student1 | Student | 1 | student1@example.com | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + | student1 | C1 | student | + + @javascript @_switch_window + Scenario: Teacher grade student question + When I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + And I add a "Question Creation" to section "1" and I fill the form with: + | Name | Question Creation 001 | + | Introduction | Question Creation introduction | + | To own questions | preview and view / save as new | + | Total Questions Graded | 1 | + And I log out + # We need to run the task to update students capabilities on questions + And I run the scheduled task "mod_qcreate\task\synchronize_qaccess" + And I log in as "student1" + And I am on "Course 1" course homepage + And I add a "Multiple choice" question to the "Question Creation 001" qcreate with: + | Question name | Multi-choice-001 | + | Question text | Find the capital cities in Europe. | + | General feedback | Paris and London | + | One or multiple answers? | Multiple answers allowed | + | Choice 1 | Tokyo | + | Choice 2 | Spain | + | Choice 3 | London | + | Choice 4 | Barcelona | + | Choice 5 | Paris | + | id_fraction_0 | None | + | id_fraction_1 | None | + | id_fraction_2 | 50% | + | id_fraction_3 | None | + | id_fraction_4 | 50% | + And I log out + And I log in as "teacher1" + And I am on "Course 1" course homepage + And I follow "Question Creation 001" + And I navigate to "Grading" in current page administration + And "Student 1" row "Status" column of "student_questions" table should contain "Needs grading" + And I should see "Multi-choice-001" + And I set the field "Grade for question 'Multi-choice-001' created by Student 1" to "80 / 100" + And I set the field "Comment for question 'Multi-choice-001' created by Student 1" to "Feedback from teacher." + And I press "Save all grades & feedback" + Then I should see "Feedback from teacher." + And I should see "80 / 100" + And I should not see "Needs grading" + And "Student 1" row "Status" column of "student_questions" table should contain "Graded" diff --git a/tests/behat/qcreate_grading.feature b/tests/behat/qcreate_grading.feature new file mode 100644 index 0000000..55dc3c1 --- /dev/null +++ b/tests/behat/qcreate_grading.feature @@ -0,0 +1,91 @@ +@mod @mod_qcreate +Feature: Test grading several questions in a qcreate activity + As a teacher + In order to evaluate my students in a Question creation activity + I need to grade questions created by my students + + Background: + Given the following "courses" exist: + | fullname | shortname | category | groupmode | + | Course 1 | C1 | 0 | 1 | + And the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + | student1 | Student | 1 | student1@example.com | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + | student1 | C1 | student | + + @javascript @_switch_window + Scenario: Teacher grade student questions + When I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + And I add a "Question Creation" to section "1" and I fill the form with: + | Name | Question Creation 001 | + | Introduction | Question Creation introduction | + | To own questions | preview and view / save as new | + | Total Questions Graded | 2 | + And I log out + # We need to run the task to update students capabilities on questions + And I run the scheduled task "mod_qcreate\task\synchronize_qaccess" + And I log in as "student1" + And I am on "Course 1" course homepage + And I add a "Multiple choice" question to the "Question Creation 001" qcreate with: + | Question name | Multi-choice-001 | + | Question text | Find the capital cities in Europe. | + | General feedback | Paris and London | + | One or multiple answers? | Multiple answers allowed | + | Choice 1 | Tokyo | + | Choice 2 | Spain | + | Choice 3 | London | + | Choice 4 | Barcelona | + | Choice 5 | Paris | + | id_fraction_0 | None | + | id_fraction_1 | None | + | id_fraction_2 | 50% | + | id_fraction_3 | None | + | id_fraction_4 | 50% | + And I should see "Not graded yet" + And I add a "Multiple choice" question to the "Question Creation 001" qcreate with: + | Question name | Multi-choice-002 | + | Question text | Which are the odd numbers? | + | General feedback | The odd numbers are One and Three | + | One or multiple answers? | Multiple answers allowed | + | Choice 1 | One | + | Choice 2 | Two | + | Choice 3 | Three | + | Choice 4 | Four | + | id_fraction_0 | 50% | + | id_fraction_1 | None | + | id_fraction_2 | 50% | + | id_fraction_3 | None | + And I log out + And I log in as "teacher1" + And I am on "Course 1" course homepage + And I follow "Question Creation 001" + And I navigate to "Grading" in current page administration + And "Student 1" row "Status" column of "student_questions" table should contain "Needs grading" + And I should see "Multi-choice-001" + And I set the field "Grade for question 'Multi-choice-001' created by Student 1" to "80 / 100" + And I set the field "Comment for question 'Multi-choice-001' created by Student 1" to "Feedback from teacher." + And I press "Save all grades & feedback" + Then I should see "Changes saved" + And I should see "Feedback from teacher." + And I should see "80 / 100" + And I should see "Graded" + And I set the field "Grade for question 'Multi-choice-002' created by Student 1" to "50 / 100" + And I set the field "Comment for question 'Multi-choice-002' created by Student 1" to "You can do better." + And I press "Save all grades & feedback" + And I should see "Changes saved" + And "Student 1" row "Final grade" column of "student_questions" table should contain "82.50" + And I log out + And I log in as "student1" + And I am on "Course 1" course homepage + And I follow "Question Creation 001" + And I should see "You have been awarded a total grade of 82.5 / 100 for this activity." + And I should see "You have been awarded an automatic grade of 50 / 50 for these questions, since you have done 2 of 2 required questions." + And I should see "A teacher has awarded you a grade of 32.5 / 50 for the questions you have done." + And I follow "Grades" in the user menu + And I click on "Course 1" "link" in the "region-main" "region" + And I should see "82.50" diff --git a/tests/behat/qcreate_question.feature b/tests/behat/qcreate_question.feature new file mode 100644 index 0000000..2d7906b --- /dev/null +++ b/tests/behat/qcreate_question.feature @@ -0,0 +1,81 @@ +@mod @mod_qcreate +Feature: Test creating a question in a qcreate activity + As a student + In order to get a grade in a Question creation activity + I need to be able to create a question + + Background: + Given the following "courses" exist: + | fullname | shortname | category | groupmode | + | Course 1 | C1 | 0 | 1 | + And the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + | student1 | Student | 1 | student1@example.com | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + | student1 | C1 | student | + + @javascript @_switch_window + Scenario: Student create a Multiple choice question + When I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + And I add a "Question Creation" to section "1" and I fill the form with: + | Name | Question Creation 001 | + | Introduction | Question Creation introduction | + | To own questions | preview and view / save as new | + | Total Questions Graded | 2 | + And I log out + # We need to run the task to update students capabilities on questions + And I run the scheduled task "mod_qcreate\task\synchronize_qaccess" + And I log in as "student1" + And I am on "Course 1" course homepage + And I add a "Multiple choice" question to the "Question Creation 001" qcreate with: + | Question name | Multi-choice-001 | + | Question text | Find the capital cities in Europe. | + | General feedback | Paris and London | + | One or multiple answers? | Multiple answers allowed | + | Choice 1 | Tokyo | + | Choice 2 | Spain | + | Choice 3 | London | + | Choice 4 | Barcelona | + | Choice 5 | Paris | + | id_fraction_0 | None | + | id_fraction_1 | None | + | id_fraction_2 | 50% | + | id_fraction_3 | None | + | id_fraction_4 | 50% | + Then I should see "You've done one extra question." + And I should see "2 questions of any of the types below will be graded" + And I should see "Multi-choice-001" + And I should see "Not graded yet" + And I should see "You have been awarded a total grade of 25 / 100 for this activity" + And I should see "You have been awarded an automatic grade of 25 / 50 for these questions, since you have done 1 of 2 required questions." + And I should see "A teacher has awarded you a grade of 0 / 50 for the questions you have done." + And I add a "Multiple choice" question to the "Question Creation 001" qcreate with: + | Question name | Multi-choice-002 | + | Question text | Which are the odd numbers? | + | General feedback | The odd numbers are One and Three | + | One or multiple answers? | Multiple answers allowed | + | Choice 1 | One | + | Choice 2 | Two | + | Choice 3 | Three | + | Choice 4 | Four | + | id_fraction_0 | 50% | + | id_fraction_1 | None | + | id_fraction_2 | 50% | + | id_fraction_3 | None | + And I should see "You've done 2 extra questions." + And I should see "You've done 2 questions of this type." + And I should see "Multi-choice-002" + And I should see "Not graded yet" + And I should see "You have been awarded a total grade of 50 / 100 for this activity" + And I should see "You have been awarded an automatic grade of 50 / 50 for these questions, since you have done 2 of 2 required questions." + And I should see "A teacher has awarded you a grade of 0 / 50 for the questions you have done." + And I click on "Preview" "link" in the "Multi-choice-002" "list_item" + And I switch to "questionpreview" window + And I should see "Marked out of 1.00" + And I should see "Technical information" + And I should see "Attempt options" + And I should see "Display options" diff --git a/tests/events_test.php b/tests/events_test.php new file mode 100644 index 0000000..ccc4dc2 --- /dev/null +++ b/tests/events_test.php @@ -0,0 +1,111 @@ +. + +/** + * Question creation events tests. + * + * @package mod_qcreate + * @category phpunit + * @copyright 2013 Adrian Greeve + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; + +/** + * Unit tests for qcreate events. + * + * @package mod_qcreate + * @category phpunit + * @copyright 2013 Adrian Greeve + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_qcreate_events_testcase extends advanced_testcase { + + /** + * Test the edit page viewed event. + * + * There is no external API for updating a qcreate, so the unit test will simply + * create and trigger the event and ensure the event data is returned as expected. + */ + public function test_edit_page_viewed() { + $this->resetAfterTest(); + + $this->setAdminUser(); + $course = $this->getDataGenerator()->create_course(); + $qcreate = $this->getDataGenerator()->create_module('qcreate', array('course' => $course->id)); + + $params = array( + 'courseid' => $course->id, + 'context' => context_module::instance($qcreate->cmid), + 'other' => array( + 'qcreateid' => $qcreate->id + ) + ); + $event = \mod_qcreate\event\edit_page_viewed::create($params); + $legacy = array($course->id, 'qcreate', 'editquestions', 'view.php?id=' . $qcreate->cmid, $qcreate->id, $qcreate->cmid); + + // Trigger and capture the event. + $sink = $this->redirectEvents(); + $event->trigger(); + $events = $sink->get_events(); + $event = reset($events); + + // Check that the event data is valid. + $this->assertInstanceOf('\mod_qcreate\event\edit_page_viewed', $event); + $this->assertEquals(context_module::instance($qcreate->cmid), $event->get_context()); + $this->assertEventLegacyLogData($legacy, $event); + $this->assertEventContextNotUsed($event); + } + + /** + * Test the overview viewed event. + * + * There is no external API for viewing overview of a qcreate, so the unit test will simply + * create and trigger the event and ensure the event data is returned as expected. + */ + public function test_overview_viewed() { + $this->resetAfterTest(); + + $this->setAdminUser(); + $course = $this->getDataGenerator()->create_course(); + $qcreate = $this->getDataGenerator()->create_module('qcreate', array('course' => $course->id)); + + $params = array( + 'courseid' => $course->id, + 'context' => context_module::instance($qcreate->cmid), + 'other' => array( + 'qcreateid' => $qcreate->id + ) + ); + $event = \mod_qcreate\event\overview_viewed::create($params); + $legacy = array($course->id, 'qcreate', 'overview', 'overview.php?cmid=' . $qcreate->cmid, $qcreate->id, $qcreate->cmid); + + // Trigger and capture the event. + $sink = $this->redirectEvents(); + $event->trigger(); + $events = $sink->get_events(); + $event = reset($events); + + // Check that the event data is valid. + $this->assertInstanceOf('\mod_qcreate\event\overview_viewed', $event); + $this->assertEquals(context_module::instance($qcreate->cmid), $event->get_context()); + $this->assertEventLegacyLogData($legacy, $event); + $this->assertEventContextNotUsed($event); + } +} diff --git a/tests/generator/lib.php b/tests/generator/lib.php new file mode 100644 index 0000000..93604bf --- /dev/null +++ b/tests/generator/lib.php @@ -0,0 +1,68 @@ +. + +defined('MOODLE_INTERNAL') || die(); + +/** + * Question creation module test data generator class + * + * @package mod_qcreate + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_qcreate_generator extends testing_module_generator { + /** + * Creates new qcreate module instance. + * + * @param array|stdClass $record data for module being generated. Requires 'course' key + * (an id or the full object). Also can have any fields from add module form. + * @param null|array $options general options for course module. Since 2.6 it is + * possible to omit this argument by merging options into $record + * @return stdClass record from module-defined table with additional field + * cmid (corresponding id in course_modules table) + */ + public function create_instance($record = null, array $options = null) { + global $CFG; + require_once($CFG->dirroot . '/mod/qcreate/locallib.php'); + // Ensure the record can be modified without affecting calling code. + $record = (object)(array)$record; + + $defaultqcreatesettings = array( + 'grade' => 100, + 'graderatio' => 50, + 'allowed' => array('ALL' => 1), + 'qtype' => array(), + 'totalrequired' => 1, + 'studentqaccess' => 3, + 'timeopen' => 0, + 'timeclose' => 0, + 'timesync' => 0, + 'timecreated' => time(), + 'timemodified' => time(), + 'completionquestions' => 1, + 'sendgradernotifications' => 0, + 'sendstudentnotifications' => 0 + ); + + foreach ($defaultqcreatesettings as $name => $value) { + if (!isset($record->{$name})) { + $record->{$name} = $value; + } + } + + return parent::create_instance($record, (array)$options); + } +} diff --git a/tests/generator_test.php b/tests/generator_test.php new file mode 100644 index 0000000..3bb4984 --- /dev/null +++ b/tests/generator_test.php @@ -0,0 +1,70 @@ +. + +/** + * PHPUnit data generator tests + * + * @package mod_qcreate + * @category phpunit + * @copyright 2012 Matt Petro + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + + +/** + * PHPUnit data generator testcase + * + * @package mod_qcreate + * @category phpunit + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_qcreate_generator_testcase extends advanced_testcase { + public function test_create_instance() { + global $DB; + $this->resetAfterTest(); + $this->setAdminUser(); + + $course = $this->getDataGenerator()->create_course(); + + $this->assertFalse($DB->record_exists('qcreate', array('course' => $course->id))); + + $generator = $this->getDataGenerator()->get_plugin_generator('mod_qcreate'); + $this->assertInstanceOf('mod_qcreate_generator', $generator); + $this->assertEquals('qcreate', $generator->get_modulename()); + + $qcreate = $generator->create_instance(array('course' => $course->id)); + $this->assertEquals(1, $DB->count_records('qcreate', array('course' => $course->id))); + $this->assertTrue($DB->record_exists('qcreate', array('course' => $course->id, 'id' => $qcreate->id))); + $this->assertEquals(1, $DB->count_records('qcreate')); + + $cm = get_coursemodule_from_instance('qcreate', $qcreate->id); + $this->assertEquals($qcreate->id, $cm->instance); + $this->assertEquals('qcreate', $cm->modname); + $this->assertEquals($course->id, $cm->course); + + $context = context_module::instance($cm->id); + $this->assertEquals($qcreate->cmid, $context->instanceid); + $this->assertTrue($DB->record_exists('question_categories', array('contextid' => $context->id))); + + $params = array('course' => $course->id, 'name' => 'One more qcreate'); + $qcreate = $this->getDataGenerator()->create_module('qcreate', $params); + $this->assertEquals(2, $DB->count_records('qcreate', array('course' => $course->id))); + $this->assertEquals('One more qcreate', $DB->get_field_select('qcreate', 'name', 'id = :id', array('id' => $qcreate->id))); + } +} diff --git a/tests/lib_test.php b/tests/lib_test.php new file mode 100644 index 0000000..335e450 --- /dev/null +++ b/tests/lib_test.php @@ -0,0 +1,786 @@ +. + +/** + * Unit tests for (some of) mod/qcreate/lib.php. + * + * @package mod_qcreate + * @category phpunit + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->dirroot . '/mod/qcreate/lib.php'); +require_once($CFG->dirroot . '/mod/qcreate/locallib.php'); +require_once($CFG->dirroot . '/mod/qcreate/tests/base_test.php'); + +/** + * Unit tests for (some of) mod/qcreate/lib.php. + * + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_qcreate_lib_testcase extends mod_qcreate_base_testcase { + + protected function setUp() { + parent::setUp(); + + // Add additional default data. + + } + + public function test_qcreate_print_overview() { + global $DB; + $courses = $DB->get_records('course', array('id' => $this->course->id)); + $qcreate = $this->create_instance(); + + // Create a question as student0. + $this->setUser($this->students[0]); + $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); + $standardq = $questiongenerator->create_question('shortanswer', null, + array('category' => $qcreate->get_question_category()->id)); + + // Check the overview as the different users. + $overview = array(); + qcreate_print_overview($courses, $overview); + $this->assertDebuggingCalledCount(1); + $this->assertEquals(count($overview), 1); + + $this->setUser($this->teachers[0]); + $overview = array(); + qcreate_print_overview($courses, $overview); + $this->assertDebuggingCalledCount(1); + $this->assertEquals(count($overview), 1); + + $this->setUser($this->editingteachers[0]); + $overview = array(); + qcreate_print_overview($courses, $overview); + $this->assertDebuggingCalledCount(1); + $this->assertEquals(1, count($overview)); + } + + public function test_print_recent_activity() { + $this->setUser($this->editingteachers[0]); + $qcreate = $this->create_instance(); + + // Create a question as student0. + $this->setUser($this->students[0]); + $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); + $standardq = $questiongenerator->create_question('shortanswer', null, + array('category' => $qcreate->get_question_category()->id)); + + $this->expectOutputRegex('/New questions created:/'); + qcreate_print_recent_activity($this->course, true, time() - 3600); + } + + public function test_qcreate_get_recent_mod_activity() { + $this->setUser($this->editingteachers[0]); + $qcreate = $this->create_instance(); + + // Create a question as student0. + $this->setUser($this->students[0]); + $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); + $standardq = $questiongenerator->create_question('shortanswer', null, + array('category' => $qcreate->get_question_category()->id)); + + $activities = array(); + $index = 0; + + $activity = new stdClass(); + $activity->type = 'activity'; + + $activity->cmid = $qcreate->get_course_module()->id; + $activities[$index++] = $activity; + + qcreate_get_recent_mod_activity( $activities, + $index, + time() - 3600, + $this->course->id, + $qcreate->get_course_module()->id); + + $this->assertEquals("qcreate", $activities[1]->type); + } + + public function test_qcreate_user_complete() { + global $PAGE; + + $this->setUser($this->editingteachers[0]); + $qcreate = $this->create_instance(); + + $PAGE->set_url(new moodle_url('/mod/qcreate/view.php', array('id' => $qcreate->get_course_module()->id))); + + $this->expectOutputRegex('/Grade: -/'); + qcreate_user_complete($this->course, $this->students[0], $qcreate->get_course_module(), $qcreate->get_instance()); + + // Create a question as student0. + $this->setUser($this->students[0]); + $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); + $standardq = $questiongenerator->create_question('shortanswer', null, + array('category' => $qcreate->get_question_category()->id)); + + $this->expectOutputRegex('/Grade: -/'); + + qcreate_user_complete($this->course, $this->students[0], $qcreate->get_course_module(), $qcreate->get_instance()); + } + + public function test_qcreate_get_completion_state() { + $qcreate = $this->create_instance(); + + $this->setUser($this->students[0]); + $result = qcreate_get_completion_state($this->course, $qcreate->get_course_module(), $this->students[0]->id, false); + $this->assertFalse($result); + + // Create a question as student0. + $this->setUser($this->students[0]); + $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); + $standardq = $questiongenerator->create_question('shortanswer', null, + array('category' => $qcreate->get_question_category()->id)); + + $result = qcreate_get_completion_state($this->course, $qcreate->get_course_module(), $this->students[0]->id, false); + + $this->assertTrue($result); + } + + /** + * Tests for mod_qcreate_refresh_events. + */ + public function test_qcreate_refresh_events() { + global $DB; + $this->resetAfterTest(); + $this->setAdminUser(); + + $timeopen = time(); + // 7 days duration. + $timeclose = time() + DAYSECS * 7; + $newtimeclose = $timeclose + DAYSECS; + + // Create a course. + $course = $this->getDataGenerator()->create_course(); + + // Create a qcreate. + $qcreate = $this->getDataGenerator()->create_module('qcreate', array('course' => $course->id, + 'timeopen' => $timeopen, 'timeclose' => $timeclose)); + + // Make sure the calendar events for qcreate 1 matches the initial parameters. + $this->assertTrue(qcreate_refresh_events($course->id)); + $eventparams = array('modulename' => 'qcreate', 'instance' => $qcreate->id, 'eventtype' => 'open'); + $openevent = $DB->get_record('event', $eventparams, '*', MUST_EXIST); + $this->assertEquals($openevent->timestart, $timeopen); + + $eventparams = array('modulename' => 'qcreate', 'instance' => $qcreate->id, 'eventtype' => 'close'); + $closeevent = $DB->get_record('event', $eventparams, '*', MUST_EXIST); + $this->assertEquals($closeevent->timestart, $timeclose); + + // In case the course ID is passed as a numeric string. + $this->assertTrue(qcreate_refresh_events('' . $course->id)); + // Course ID not provided. + $this->assertTrue(qcreate_refresh_events()); + $eventparams = array('modulename' => 'qcreate'); + $events = $DB->get_records('event', $eventparams); + foreach ($events as $event) { + if ($event->modulename === 'qcreate' && $event->instance === $qcreate->id && $event->eventtype === 'open') { + $this->assertEquals($event->timestart, $timeopen); + } + if ($event->modulename === 'qcreate' && $event->instance === $qcreate->id && $event->eventtype === 'close') { + $this->assertEquals($event->timestart, $timeclose); + } + } + // Manually update qcreate 1's close time. + $DB->update_record('qcreate', (object)['id' => $qcreate->id, 'timeclose' => $newtimeclose]); + + // Then refresh the qcreate events of qcreate 1's course. + $this->assertTrue(qcreate_refresh_events($course->id)); + + // Confirm that the qcreate 1's close date event now has the new close date after refresh. + $eventparams = array('modulename' => 'qcreate', 'instance' => $qcreate->id, 'eventtype' => 'close'); + $eventtime = $DB->get_field('event', 'timestart', $eventparams, MUST_EXIST); + $this->assertEquals($eventtime, $newtimeclose); + + // Create a second course and qcreate. + $course2 = $this->getDataGenerator()->create_course(); + $qcreate2 = $this->getDataGenerator()->create_module('qcreate', array('course' => $course2->id, + 'timeopen' => $timeopen, 'timeclose' => $timeclose)); + + // Manually update qcreate 1 and 2's close dates. + $newtimeclose += DAYSECS; + $DB->update_record('qcreate', (object)['id' => $qcreate->id, 'timeclose' => $newtimeclose]); + $DB->update_record('qcreate', (object)['id' => $qcreate2->id, 'timeclose' => $newtimeclose]); + + // Refresh events of all courses. + $this->assertTrue(qcreate_refresh_events()); + + // Check the due date calendar event for qcreate 1. + $eventtime = $DB->get_field('event', 'timestart', $eventparams, MUST_EXIST); + $this->assertEquals($eventtime, $newtimeclose); + + // Check the due date calendar event for qcreate 2. + $eventparams['instance'] = $qcreate2->id; + $eventtime = $DB->get_field('event', 'timestart', $eventparams, MUST_EXIST); + $this->assertEquals($eventtime, $newtimeclose); + } + + public function test_qcreate_core_calendar_provide_event_action_open() { + $this->resetAfterTest(); + + $this->setAdminUser(); + + // Create a course. + $course = $this->getDataGenerator()->create_course(); + + // Create a qcreate. + $qcreate = $this->getDataGenerator()->create_module('qcreate', array('course' => $course->id, + 'timeopen' => time() - DAYSECS, 'timeclose' => time() + DAYSECS)); + + // Create a calendar event. + $event = $this->create_action_event($course->id, $qcreate->id, QCREATE_EVENT_TYPE_OPEN); + + // Create an action factory. + $factory = new \core_calendar\action_factory(); + + // Decorate action event. + $actionevent = mod_qcreate_core_calendar_provide_event_action($event, $factory); + + // Confirm the event was decorated. + $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); + $this->assertEquals(get_string('attemptqcreatenow', 'qcreate'), $actionevent->get_name()); + $this->assertInstanceOf('moodle_url', $actionevent->get_url()); + $this->assertEquals(1, $actionevent->get_item_count()); + $this->assertTrue($actionevent->is_actionable()); + } + + public function test_qcreate_core_calendar_provide_event_action_closed() { + $this->resetAfterTest(); + + $this->setAdminUser(); + + // Create a course. + $course = $this->getDataGenerator()->create_course(); + + // Create a qcreate. + $qcreate = $this->getDataGenerator()->create_module('qcreate', array('course' => $course->id, + 'timeclose' => time() - DAYSECS)); + + // Create a calendar event. + $event = $this->create_action_event($course->id, $qcreate->id, QCREATE_EVENT_TYPE_CLOSE); + + // Create an action factory. + $factory = new \core_calendar\action_factory(); + + // Decorate action event. + $actionevent = mod_qcreate_core_calendar_provide_event_action($event, $factory); + + // No event on the dashboard if module is closed. + $this->assertNull($actionevent); + } + + public function test_qcreate_core_calendar_provide_event_action_open_in_future() { + $this->resetAfterTest(); + + $this->setAdminUser(); + + // Create a course. + $course = $this->getDataGenerator()->create_course(); + + // Create a qcreate. + $qcreate = $this->getDataGenerator()->create_module('qcreate', array('course' => $course->id, + 'timeopen' => time() + DAYSECS)); + + // Create a calendar event. + $event = $this->create_action_event($course->id, $qcreate->id, QCREATE_EVENT_TYPE_OPEN); + + // Create an action factory. + $factory = new \core_calendar\action_factory(); + + // Decorate action event. + $actionevent = mod_qcreate_core_calendar_provide_event_action($event, $factory); + + // Confirm the event was decorated. + $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); + $this->assertEquals(get_string('attemptqcreatenow', 'qcreate'), $actionevent->get_name()); + $this->assertInstanceOf('moodle_url', $actionevent->get_url()); + $this->assertEquals(1, $actionevent->get_item_count()); + $this->assertFalse($actionevent->is_actionable()); + } + + public function test_qcreate_core_calendar_provide_event_action_no_capability() { + global $DB; + + $this->resetAfterTest(); + $this->setAdminUser(); + + // Create a course. + $course = $this->getDataGenerator()->create_course(); + + // Create a student. + $student = $this->getDataGenerator()->create_user(); + $studentrole = $DB->get_record('role', array('shortname' => 'student')); + + // Enrol student. + $this->assertTrue($this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id)); + + // Create a qcreate. + $qcreate = $this->getDataGenerator()->create_module('qcreate', array('course' => $course->id)); + + // Remove the permission to submit the qcreate for the student role. + $coursecontext = context_course::instance($course->id); + assign_capability('mod/qcreate:view', CAP_PROHIBIT, $studentrole->id, $coursecontext); + assign_capability('mod/qcreate:submit', CAP_PROHIBIT, $studentrole->id, $coursecontext); + + // Create a calendar event. + $event = $this->create_action_event($course->id, $qcreate->id, QCREATE_EVENT_TYPE_OPEN); + + // Create an action factory. + $factory = new \core_calendar\action_factory(); + + // Set current user to the student. + $this->setUser($student); + + // Confirm null is returned. + $this->assertNull(mod_qcreate_core_calendar_provide_event_action($event, $factory)); + } + + /** + * Creates an action event. + * + * @param int $courseid + * @param int $instanceid The qcreate id. + * @param string $eventtype The event type. eg. QCREATE_EVENT_TYPE_OPEN. + * @return bool|calendar_event + */ + private function create_action_event($courseid, $instanceid, $eventtype) { + $event = new stdClass(); + $event->name = 'Calendar event'; + $event->modulename = 'qcreate'; + $event->courseid = $courseid; + $event->instance = $instanceid; + $event->type = CALENDAR_EVENT_TYPE_ACTION; + $event->eventtype = $eventtype; + $event->timestart = time(); + + return calendar_event::create($event); + } + + /** + * Test the callback responsible for returning the completion rule descriptions. + * This function should work given either an instance of the module (cm_info), such as when checking the active rules, + * or if passed a stdClass of similar structure, such as when checking the the default completion settings for a mod type. + */ + public function test_mod_qcreate_completion_get_active_rule_descriptions() { + $this->resetAfterTest(); + $this->setAdminUser(); + + // Two activities, both with automatic completion. One has the 'completionquestions' rule, one doesn't. + $course = $this->getDataGenerator()->create_course(['enablecompletion' => 2]); + $qcreate1 = $this->getDataGenerator()->create_module('qcreate', [ + 'course' => $course->id, + 'completion' => 2, + 'completionquestions' => 3 + ]); + $qcreate2 = $this->getDataGenerator()->create_module('qcreate', [ + 'course' => $course->id, + 'completion' => 2, + 'completionquestions' => 0 + ]); + $cm1 = cm_info::create(get_coursemodule_from_instance('qcreate', $qcreate1->id)); + $cm2 = cm_info::create(get_coursemodule_from_instance('qcreate', $qcreate2->id)); + + // Data for the stdClass input type. + // This type of input would occur when checking the default completion rules for an activity type, where we don't have + // any access to cm_info, rather the input is a stdClass containing completion and customdata attributes, just like cm_info. + $moddefaults = new stdClass(); + $moddefaults->customdata = ['customcompletionrules' => [ + 'completionquestions' => 3, + ]]; + $moddefaults->completion = 2; + + $activeruledescriptions = [ + get_string('completionquestionsdesc', 'qcreate', 3) + ]; + + $this->assertEquals(mod_qcreate_get_completion_active_rule_descriptions($cm1), $activeruledescriptions); + $this->assertEquals(mod_qcreate_get_completion_active_rule_descriptions($cm2), []); + $this->assertEquals(mod_qcreate_get_completion_active_rule_descriptions($moddefaults), $activeruledescriptions); + $this->assertEquals(mod_qcreate_get_completion_active_rule_descriptions(new stdClass()), []); + } + + /** + * An unkown event type should not change the qcreate instance. + */ + public function test_mod_qcreate_core_calendar_event_timestart_updated_unknown_event() { + global $CFG, $DB; + require_once($CFG->dirroot . "/calendar/lib.php"); + + $this->resetAfterTest(true); + $this->setAdminUser(); + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $qcreategenerator = $generator->get_plugin_generator('mod_qcreate'); + $timeopen = time(); + $timeclose = $timeopen + DAYSECS; + $qcreate = $qcreategenerator->create_instance(['course' => $course->id]); + $qcreate->timeopen = $timeopen; + $qcreate->timeclose = $timeclose; + $DB->update_record('qcreate', $qcreate); + + // Create a valid event. + $event = new \calendar_event([ + 'name' => 'Test event', + 'description' => '', + 'format' => 1, + 'courseid' => $course->id, + 'groupid' => 0, + 'userid' => 2, + 'modulename' => 'qcreate', + 'instance' => $qcreate->id, + 'eventtype' => QCREATE_EVENT_TYPE_OPEN . "SOMETHING ELSE", + 'timestart' => 1, + 'timeduration' => 86400, + 'visible' => 1 + ]); + + mod_qcreate_core_calendar_event_timestart_updated($event, $qcreate); + + $qcreate = $DB->get_record('qcreate', ['id' => $qcreate->id]); + $this->assertEquals($timeopen, $qcreate->timeopen); + $this->assertEquals($timeclose, $qcreate->timeclose); + } + + /** + * A QREATE_EVENT_TYPE_OPEN event should update the timeopen property of + * the qcreate activity. + */ + public function test_mod_qcreate_core_calendar_event_timestart_updated_open_event() { + global $CFG, $DB; + require_once($CFG->dirroot . "/calendar/lib.php"); + + $this->resetAfterTest(true); + $this->setAdminUser(); + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $qcreategenerator = $generator->get_plugin_generator('mod_qcreate'); + $timeopen = time(); + $timeclose = $timeopen + DAYSECS; + $timemodified = 1; + $newtimeopen = $timeopen - DAYSECS; + $qcreate = $qcreategenerator->create_instance(['course' => $course->id]); + $qcreate->timeopen = $timeopen; + $qcreate->timeclose = $timeclose; + $qcreate->timemodified = $timemodified; + $DB->update_record('qcreate', $qcreate); + + // Create a valid event. + $event = new \calendar_event([ + 'name' => 'Test event', + 'description' => '', + 'format' => 1, + 'courseid' => $course->id, + 'groupid' => 0, + 'userid' => 2, + 'modulename' => 'qcreate', + 'instance' => $qcreate->id, + 'eventtype' => QCREATE_EVENT_TYPE_OPEN, + 'timestart' => $newtimeopen, + 'timeduration' => 86400, + 'visible' => 1 + ]); + + // Trigger and capture the event. + $sink = $this->redirectEvents(); + + mod_qcreate_core_calendar_event_timestart_updated($event, $qcreate); + + $triggeredevents = $sink->get_events(); + $moduleupdatedevents = array_filter($triggeredevents, function($e) { + return is_a($e, 'core\event\course_module_updated'); + }); + + $qcreate = $DB->get_record('qcreate', ['id' => $qcreate->id]); + // Ensure the timeopen property matches the event timestart. + $this->assertEquals($newtimeopen, $qcreate->timeopen); + // Ensure the timeclose isn't changed. + $this->assertEquals($timeclose, $qcreate->timeclose); + // Ensure the timemodified property has been changed. + $this->assertNotEquals($timemodified, $qcreate->timemodified); + // Confirm that a module updated event is fired when the module + // is changed. + $this->assertNotEmpty($moduleupdatedevents); + } + + /** + * A QCREATE_EVENT_TYPE_CLOSE event should update the timeclose property of + * the qcreate activity. + */ + public function test_mod_qcreate_core_calendar_event_timestart_updated_close_event() { + global $CFG, $DB; + require_once($CFG->dirroot . "/calendar/lib.php"); + + $this->resetAfterTest(true); + $this->setAdminUser(); + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $qcreategenerator = $generator->get_plugin_generator('mod_qcreate'); + $timeopen = time(); + $timeclose = $timeopen + DAYSECS; + $timemodified = 1; + $newtimeclose = $timeclose + DAYSECS; + $qcreate = $qcreategenerator->create_instance(['course' => $course->id]); + $qcreate->timeopen = $timeopen; + $qcreate->timeclose = $timeclose; + $qcreate->timemodified = $timemodified; + $DB->update_record('qcreate', $qcreate); + + // Create a valid event. + $event = new \calendar_event([ + 'name' => 'Test event', + 'description' => '', + 'format' => 1, + 'courseid' => $course->id, + 'groupid' => 0, + 'userid' => 2, + 'modulename' => 'qcreate', + 'instance' => $qcreate->id, + 'eventtype' => QCREATE_EVENT_TYPE_CLOSE, + 'timestart' => $newtimeclose, + 'timeduration' => 86400, + 'visible' => 1 + ]); + + // Trigger and capture the event when adding a contact. + $sink = $this->redirectEvents(); + + mod_qcreate_core_calendar_event_timestart_updated($event, $qcreate); + + $triggeredevents = $sink->get_events(); + $moduleupdatedevents = array_filter($triggeredevents, function($e) { + return is_a($e, 'core\event\course_module_updated'); + }); + + $qcreate = $DB->get_record('qcreate', ['id' => $qcreate->id]); + // Ensure the timeclose property matches the event timestart. + $this->assertEquals($newtimeclose, $qcreate->timeclose); + // Ensure the timeopen isn't changed. + $this->assertEquals($timeopen, $qcreate->timeopen); + // Ensure the timemodified property has been changed. + $this->assertNotEquals($timemodified, $qcreate->timemodified); + // Confirm that a module updated event is fired when the module + // is changed. + $this->assertNotEmpty($moduleupdatedevents); + } + + /** + * An unkown event type should not have any limits + */ + public function test_mod_qcreate_core_calendar_get_valid_event_timestart_range_unknown_event() { + global $CFG, $DB; + require_once($CFG->dirroot . "/calendar/lib.php"); + + $this->resetAfterTest(true); + $this->setAdminUser(); + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $timeopen = time(); + $timeclose = $timeopen + DAYSECS; + $qcreate = new \stdClass(); + $qcreate->timeopen = $timeopen; + $qcreate->timeclose = $timeclose; + + // Create a valid event. + $event = new \calendar_event([ + 'name' => 'Test event', + 'description' => '', + 'format' => 1, + 'courseid' => $course->id, + 'groupid' => 0, + 'userid' => 2, + 'modulename' => 'qcreate', + 'instance' => 1, + 'eventtype' => QCREATE_EVENT_TYPE_OPEN . "SOMETHING ELSE", + 'timestart' => 1, + 'timeduration' => 86400, + 'visible' => 1 + ]); + + list ($min, $max) = mod_qcreate_core_calendar_get_valid_event_timestart_range($event, $qcreate); + $this->assertNull($min); + $this->assertNull($max); + } + + /** + * The open event should be limited by the qcreate's timeclose property, if it's set. + */ + public function test_mod_qcreate_core_calendar_get_valid_event_timestart_range_open_event() { + global $CFG, $DB; + require_once($CFG->dirroot . "/calendar/lib.php"); + + $this->resetAfterTest(true); + $this->setAdminUser(); + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $timeopen = time(); + $timeclose = $timeopen + DAYSECS; + $qcreate = new \stdClass(); + $qcreate->timeopen = $timeopen; + $qcreate->timeclose = $timeclose; + + // Create a valid event. + $event = new \calendar_event([ + 'name' => 'Test event', + 'description' => '', + 'format' => 1, + 'courseid' => $course->id, + 'groupid' => 0, + 'userid' => 2, + 'modulename' => 'qcreate', + 'instance' => 1, + 'eventtype' => QCREATE_EVENT_TYPE_OPEN, + 'timestart' => 1, + 'timeduration' => 86400, + 'visible' => 1 + ]); + + // The max limit should be bounded by the timeclose value. + list ($min, $max) = mod_qcreate_core_calendar_get_valid_event_timestart_range($event, $qcreate); + + $this->assertNull($min); + $this->assertEquals($timeclose, $max[0]); + + // No timeclose value should result in no upper limit. + $qcreate->timeclose = 0; + list ($min, $max) = mod_qcreate_core_calendar_get_valid_event_timestart_range($event, $qcreate); + + $this->assertNull($min); + $this->assertNull($max); + } + + /** + * The close event should be limited by the qcreate's timeopen property, if it's set. + */ + public function test_mod_qcreate_core_calendar_get_valid_event_timestart_range_close_event() { + global $CFG, $DB; + require_once($CFG->dirroot . "/calendar/lib.php"); + + $this->resetAfterTest(true); + $this->setAdminUser(); + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $timeopen = time(); + $timeclose = $timeopen + DAYSECS; + $qcreate = new \stdClass(); + $qcreate->timeopen = $timeopen; + $qcreate->timeclose = $timeclose; + + // Create a valid event. + $event = new \calendar_event([ + 'name' => 'Test event', + 'description' => '', + 'format' => 1, + 'courseid' => $course->id, + 'groupid' => 0, + 'userid' => 2, + 'modulename' => 'qcreate', + 'instance' => 1, + 'eventtype' => CHOICE_EVENT_TYPE_CLOSE, + 'timestart' => 1, + 'timeduration' => 86400, + 'visible' => 1 + ]); + + // The max limit should be bounded by the timeclose value. + list ($min, $max) = mod_qcreate_core_calendar_get_valid_event_timestart_range($event, $qcreate); + + $this->assertEquals($timeopen, $min[0]); + $this->assertNull($max); + + // No timeclose value should result in no upper limit. + $qcreate->timeopen = 0; + list ($min, $max) = mod_qcreate_core_calendar_get_valid_event_timestart_range($event, $qcreate); + + $this->assertNull($min); + $this->assertNull($max); + } + + /** + * Test check_updates_since callback. + */ + public function test_check_updates_since() { + global $DB; + + $this->resetAfterTest(); + $this->setAdminUser(); + $course = $this->getDataGenerator()->create_course(); + + $this->setCurrentTimeStart(); + $record = array( + 'course' => $course->id, + 'custom' => 0, + 'feedback' => 1, + ); + $this->setUser($this->students[0]); + $qcreate = $this->create_instance(); + $cm = $qcreate->get_course_module(); + $cm = cm_info::create($cm); + + // Check that upon creation, the updates are only about the new configuration created. + $onehourago = time() - HOURSECS; + $updates = qcreate_check_updates_since($cm, $onehourago); + foreach ($updates as $el => $val) { + if ($el == 'configuration') { + $this->assertTrue($val->updated); + $this->assertTimeCurrent($val->timeupdated); + } else { + $this->assertFalse($val->updated); + } + } + + // Create a question as student0. + $this->setUser($this->students[0]); + $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); + $q1 = $questiongenerator->create_question('shortanswer', null, + array('category' => $qcreate->get_question_category()->id)); + + // Create another question as student0. + $q2 = $questiongenerator->create_question('shortanswer', null, + array('category' => $qcreate->get_question_category()->id)); + + $submittedgrade = 80; + $submitcomment = 'Good job.'; + // Create a local grade without notification. + $instance = $qcreate->get_instance(); + // The qcreate_process_local_grade needs cmidnumber set. + $instance->cmidnumber = $cm->id; + + // Grade first question. + qcreate_process_local_grade($instance, $q1, false, false, $submittedgrade, $submitcomment); + // Check now for updates. + $updates = qcreate_check_updates_since($cm, $onehourago); + $this->assertTrue($updates->questions->updated); + $this->assertCount(2, $updates->questions->itemids); + $this->assertEquals([$q1->id, $q2->id], $updates->questions->itemids, '', 0, 10, true); + $this->assertTrue($updates->grades->updated); + $this->assertCount(1, $updates->grades->itemids); + + // Other student should see no update. + $this->setUser($this->students[1]); + $updates = qcreate_check_updates_since($cm, $onehourago); + $this->assertFalse($updates->questions->updated); + $this->assertFalse($updates->grades->updated); + + } +} diff --git a/tests/locallib_test.php b/tests/locallib_test.php new file mode 100644 index 0000000..de97805 --- /dev/null +++ b/tests/locallib_test.php @@ -0,0 +1,244 @@ +. + +/** + * Unit tests for (some of) mod/qcreate/locallib.php. + * + * @package mod_qcreate + * @category phpunit + * @copyright 2014 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->dirroot . '/mod/qcreate/locallib.php'); +require_once($CFG->dirroot . '/mod/qcreate/tests/base_test.php'); + +/** + * Unit tests for (some of) mod/qcreate/locallib.php. + * + * @copyright Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_qcreate_locallib_testcase extends mod_qcreate_base_testcase { + + public function test_count_user_questions() { + $this->setUser($this->editingteachers[0]); + $qcreate = $this->create_instance(); + + $instance = $qcreate->get_instance(); + + // Should start empty. + $this->assertEquals(0, $qcreate->count_user_questions($this->students[0]->id)); + + // Create a question as student0. + $this->setUser($this->students[0]); + $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); + $standardq = $questiongenerator->create_question('shortanswer', null, + array('category' => $qcreate->get_question_category()->id)); + + // Now test again. + $this->assertEquals(1, $qcreate->count_user_questions($this->students[0]->id)); + $this->assertEquals(0, $qcreate->count_user_questions($this->students[1]->id)); + } + + public function test_delete_instance() { + $this->setUser($this->editingteachers[0]); + $qcreate = $this->create_instance(); + + // Create a question as student0. + $this->setUser($this->students[0]); + $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); + $standardq = $questiongenerator->create_question('shortanswer', null, + array('category' => $qcreate->get_question_category()->id)); + + // TODO : simulate adding alocal grade and a gradebook grade. + + // Now try and delete. + $this->assertEquals(true, $qcreate->delete_instance()); + } + + public function test_reset_userdata() { + global $DB; + + $now = time(); + $this->setUser($this->editingteachers[0]); + $qcreate = $this->create_instance(array('timeopen' => $now)); + + // Create a question as student0. + $this->setUser($this->students[0]); + $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); + $standardq = $questiongenerator->create_question('shortanswer', null, + array('category' => $qcreate->get_question_category()->id)); + + $this->assertEquals(1, $qcreate->count_user_questions()); + // Now try and reset. + $data = new stdClass(); + $data->reset_qcreate = 1; + $data->reset_gradebook_grades = 1; + $data->courseid = $this->course->id; + $data->timeshift = 24 * 60 * 60; + $this->setUser($this->editingteachers[0]); + $qcreate->reset_userdata($data); + + $this->assertEquals(0, $qcreate->count_user_questions()); + + // Reload the instance data. + $instance = $DB->get_record('qcreate', array('id' => $qcreate->get_instance()->id)); + $this->assertEquals($now + 24 * 60 * 60, $instance->timeopen); + + // Test reset using qcreate_reset_userdata(). + $qcreatetimeopen = $instance->timeopen; // Keep old updated value for comparison. + $data->timeshift = 2 * 24 * 60 * 60; + qcreate_reset_userdata($data); + $instance = $DB->get_record('qcreate', array('id' => $qcreate->get_instance()->id)); + $this->assertEquals($qcreatetimeopen + 2 * 24 * 60 * 60, $instance->timeopen); + + // Create one more qcreate and reset, make sure time shifted for previous qcreate is not changed. + $qcreate2 = $this->create_instance(array('timeopen' => $now)); + $qcreatetimeopen = $instance->timeopen; + $data->timeshift = 3 * 24 * 60 * 60; + $qcreate2->reset_userdata($data); + $instance = $DB->get_record('qcreate', array('id' => $qcreate->get_instance()->id)); + $this->assertEquals($qcreatetimeopen, $instance->timeopen); + $instance2 = $DB->get_record('qcreate', array('id' => $qcreate2->get_instance()->id)); + $this->assertEquals($now + 3 * 24 * 60 * 60, $instance2->timeopen); + + // Reset both qcreates using qcreate_reset_userdata() and make sure both qcreates have same date. + $qcreatetimeopen = $instance->timeopen; + $qcreate2timeopen = $instance2->timeopen; + $data->timeshift = 4 * 24 * 60 * 60; + qcreate_reset_userdata($data); + $instance = $DB->get_record('qcreate', array('id' => $qcreate->get_instance()->id)); + $this->assertEquals($qcreatetimeopen + 4 * 24 * 60 * 60, $instance->timeopen); + $instance2 = $DB->get_record('qcreate', array('id' => $qcreate2->get_instance()->id)); + $this->assertEquals($qcreate2timeopen + 4 * 24 * 60 * 60, $instance2->timeopen); + } + + public function test_update_calendar() { + global $DB; + + $this->setUser($this->editingteachers[0]); + $userctx = context_user::instance($this->editingteachers[0]->id)->id; + + // Create a new qcreate. + $now = time(); + $qcreate = $this->create_instance(array( + 'timeopen' => $now, + 'intro' => 'Some text', + 'introformat' => FORMAT_HTML + )); + + // See if there is an event in the calendar. + $params = array('modulename' => 'qcreate', 'instance' => $qcreate->get_instance()->id); + $event = $DB->get_record('event', $params); + $this->assertNotEmpty($event); + $this->assertContains('Some text', $event->description); + + // Make sure the same works when updating the qcreate. + + $formdata = $qcreate->get_instance(); + $formdata->timeopen = $now + 60; + $formdata->allowed = array('multichoice' => 1, 'numerical' => 1); + $formdata->qtype = array(); + $formdata->instance = $formdata->id; + $formdata->intro = 'New text'; + $qcreate->update_instance($formdata); + + $params = array('modulename' => 'qcreate', 'instance' => $qcreate->get_instance()->id); + $event = $DB->get_record('event', $params); + $this->assertNotEmpty($event); + $this->assertContains('New text', $event->description); + } + + public function test_update_instance() { + global $DB; + + $now = time(); + $this->setUser($this->editingteachers[0]); + $qcreate = $this->create_instance(array('studentqaccess' => 1, 'timeopen' => $now)); + + $formdata = $qcreate->get_instance(); + $formdata->timeopen = $now + 60; + $formdata->allowed = array('multichoice' => 1, 'numerical' => 1); + $formdata->qtype = array(); + $formdata->instance = $formdata->id; + $formdata->studentqaccess = 2; + + $qcreate->update_instance($formdata); + + $instance = $DB->get_record('qcreate', array('id' => $qcreate->get_instance()->id)); + $this->assertEquals($now + 60, $instance->timeopen); + $this->assertEquals(2, $instance->studentqaccess); + $this->assertEquals('multichoice,numerical', $instance->allowed); + } + + public function test_qcreate_refresh_events() { + global $DB; + + $now = time(); + $this->setUser($this->editingteachers[0]); + $qcreate = $this->create_instance(array('timeopen' => $now)); + + // See if there is an event in the calendar. + $params = array('modulename' => 'qcreate', 'instance' => $qcreate->get_instance()->id); + $event = $DB->get_record('event', $params); + $this->assertEquals($now, $event->timestart); + + // Change timeopen. + $formdata = $qcreate->get_instance(); + $formdata->timeopen = $now + 60; + $formdata->allowed = array('ALL' => 1); + $formdata->qtype = array(); + $formdata->instance = $formdata->id; + + $qcreate->update_instance($formdata); + qcreate_refresh_events(); + + // See if the event has been updated. + $params = array('modulename' => 'qcreate', 'instance' => $qcreate->get_instance()->id); + $event = $DB->get_record('event', $params); + $this->assertEquals($now + 60, $event->timestart); + } + + public function test_get_graders() { + $this->create_extra_users(); + $this->setUser($this->editingteachers[0]); + + // Create a qcreate with no groups. + $qcreate = $this->create_instance(); + $this->assertCount(self::DEFAULT_TEACHER_COUNT + self::DEFAULT_EDITING_TEACHER_COUNT + self::EXTRA_TEACHER_COUNT + self::EXTRA_EDITING_TEACHER_COUNT, + $qcreate->testable_get_graders($this->students[0]->id)); + + // Force create a qcreate with SEPARATEGROUPS. + $data = new stdClass(); + $data->courseid = $this->course->id; + $data->name = 'Grouping'; + $groupingid = groups_create_grouping($data); + groups_assign_grouping($groupingid, $this->groups[0]->id); + $qcreate = $this->create_instance(array('groupingid' => $groupingid, 'groupmode' => SEPARATEGROUPS)); + + $this->setUser($this->students[1]); + $this->assertCount(4, $qcreate->testable_get_graders($this->students[0]->id)); + // Note the second student is in a group that is not in the grouping. + // This means that we get all graders that are not in a group in the grouping. + $this->assertCount(10, $qcreate->testable_get_graders($this->students[1]->id)); + } +} + diff --git a/tests/privacy_test.php b/tests/privacy_test.php new file mode 100644 index 0000000..0fb42bf --- /dev/null +++ b/tests/privacy_test.php @@ -0,0 +1,345 @@ +. + +/** + * Privacy provider tests. + * + * @package mod_qcreate + * @copyright 2018 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use core_privacy\local\metadata\collection; +use core_privacy\tests\provider_testcase; +use core_privacy\local\request\approved_contextlist; +use core_privacy\local\request\transform; +use core_privacy\local\request\writer; +use mod_qcreate\privacy\provider; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; + +/** + * Data provider testcase class. + * + * @package mod_qcreate + * @copyright 2018 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_qcreate_privacy_testcase extends provider_testcase { + /** @var stdClass The student object. */ + protected $student; + + /** @var stdClass The teacher object. */ + protected $teacher; + + /** @var stdClass The qcreate object. */ + protected $qcreate; + + /** @var stdClass The course object. */ + protected $course; + + /** @var stdClass The question object. */ + protected $question; + + protected function setUp() { + $this->resetAfterTest(); + // Create a course. + $course = $this->getDataGenerator()->create_course(); + + // Create a student and a teacher. + $user = $this->getDataGenerator()->create_user(); + $teacher = $this->getDataGenerator()->create_user();; + $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student'); + $this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher'); + + // Make a qcreate activity. + $this->setUser($teacher); + $qcreate = $this->create_test_qcreate($course); + + // The qcreate_process_local_grade needs cmidnumber set. + list($course, $cm) = get_course_and_cm_from_cmid($qcreate->cmid, 'qcreate'); + $qcreate->cmidnumber = $cm->id; + + // Create a question. + $q1 = $this->qcreate_add_qcreate_question($user, $qcreate, 'shortanswer'); + + // Grade question. + $this->setUser($teacher); + qcreate_process_local_grade($qcreate, $q1, false, false, 90, 'Good job.'); + + $this->student = $user; + $this->teacher = $teacher; + $this->qcreate = $qcreate; + $this->course = $course; + $this->question = $q1; + } + + /** + * Test for provider::get_metadata(). + */ + public function test_get_metadata() { + $collection = new collection('mod_qcreate'); + $newcollection = provider::get_metadata($collection); + $itemcollection = $newcollection->get_collection(); + + $this->assertCount(2, $itemcollection); + + $table = reset($itemcollection); + $this->assertEquals('qcreate_grades', $table->get_name()); + + $privacyfields = $table->get_privacy_fields(); + $this->assertArrayHasKey('qcreateid', $privacyfields); + $this->assertArrayHasKey('questionid', $privacyfields); + $this->assertArrayHasKey('grade', $privacyfields); + $this->assertArrayHasKey('gradecomment', $privacyfields); + $this->assertArrayHasKey('teacher', $privacyfields); + $this->assertArrayHasKey('timemarked', $privacyfields); + + $this->assertEquals('privacy:metadata:qcreate_grades', $table->get_summary()); + } + /** + * Test that a user who has no data gets no contexts + */ + public function test_get_contexts_for_userid_no_data() { + global $USER; + $this->resetAfterTest(); + $this->setAdminUser(); + + $contextlist = provider::get_contexts_for_userid($USER->id); + $this->assertEmpty($contextlist); + } + + /** + * The export function should handle an empty contextlist properly. + */ + public function test_export_user_data_no_data() { + global $USER; + $this->resetAfterTest(); + $this->setAdminUser(); + + $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist( + \core_user::get_user($USER->id), + 'mod_qcreate', + [] + ); + + provider::export_user_data($approvedcontextlist); + $this->assertDebuggingNotCalled(); + + // No data should have been exported. + $writer = \core_privacy\local\request\writer::with_context(\context_system::instance()); + $this->assertFalse($writer->has_any_data_in_any_context()); + } + + /** + * The delete function should handle an empty contextlist properly. + */ + public function test_delete_data_for_user_no_data() { + global $USER; + $this->resetAfterTest(); + $this->setAdminUser(); + + $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist( + \core_user::get_user($USER->id), + 'mod_qcreate', + [] + ); + + provider::delete_data_for_user($approvedcontextlist); + $this->assertDebuggingNotCalled(); + } + + /** + * Export + Delete qcreate data for a user who has made a single question. + */ + public function test_user_with_data() { + global $DB; + $cm = get_coursemodule_from_instance('qcreate', $this->qcreate->id); + $context = context_module::instance($cm->id); + + // Fetch the contexts - only one context should be returned. + $contextlist = provider::get_contexts_for_userid($this->student->id); + $this->assertCount(1, $contextlist); + $this->assertEquals($context, $contextlist->current()); + // Verify that there is one local grade. + $count = $DB->count_records('qcreate_grades', ['qcreateid' => $this->qcreate->id]); + $this->assertEquals(1, $count); + + // Perform the export and check the data. + $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist( + \core_user::get_user($this->student->id), + 'mod_qcreate', + $contextlist->get_contextids() + ); + provider::export_user_data($approvedcontextlist); + + // Ensure that the qcreate data was exported correctly. + $writer = writer::with_context($context); + $this->assertTrue($writer->has_any_data()); + + $qcreatedata = $writer->get_data([]); + $this->assertEquals($this->qcreate->name, $qcreatedata->name); + + // Every module has an intro. + $this->assertTrue(isset($qcreatedata->intro)); + + // Delete the data and check it is removed. + $this->setUser(); + provider::delete_data_for_user($approvedcontextlist); + // Verify that there is no local grade. + $count = $DB->count_records('qcreate_grades', ['qcreateid' => $this->qcreate->id]); + $this->assertEquals(0, $count); + } + + /** + * Export + Delete qcreate data for a user who has made a single attempt. + */ + public function test_delete_data_for_all_users_in_context() { + global $DB; + $cm = get_coursemodule_from_instance('qcreate', $this->qcreate->id); + $context = context_module::instance($cm->id); + + $otheruser = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($otheruser->id, $this->course->id, 'student'); + + $q1 = $this->question; + // Create another question. + $q2 = $this->qcreate_add_qcreate_question($otheruser, $this->qcreate, 'shortanswer'); + + // Grade question. + $this->setUser($this->teacher); + qcreate_process_local_grade($this->qcreate, $q2, false, false, 70, 'Not too bad.'); + + // Create another qcreate. + $this->setUser(); + $otherqcreate = $this->create_test_qcreate($this->course); + + // The qcreate_process_local_grade needs cmidnumber set. + list($course, $cm) = get_course_and_cm_from_cmid($otherqcreate->cmid, 'qcreate'); + $otherqcreate->cmidnumber = $cm->id; + + // Create questions. + $q3 = $this->qcreate_add_qcreate_question($this->student, $otherqcreate, 'shortanswer'); + $q4 = $this->qcreate_add_qcreate_question($otheruser, $otherqcreate, 'shortanswer'); + + // Grade the questions. + $this->setUser($this->teacher); + qcreate_process_local_grade($otherqcreate, $q3, false, false, 50, 'You can do better.'); + qcreate_process_local_grade($otherqcreate, $q4, false, false, 40, 'Poor job.'); + + $count = $DB->count_records('qcreate_grades', ['qcreateid' => $this->qcreate->id]); + $this->assertEquals(2, $count); + + // Delete all data for all users in the context under test. + $this->setUser(); + provider::delete_data_for_all_users_in_context($context); + // Verify that there is only grades for second qcreate. + $count = $DB->count_records('qcreate_grades', ['qcreateid' => $this->qcreate->id]); + $this->assertEquals(0, $count); + $count = $DB->count_records('qcreate_grades', ['qcreateid' => $otherqcreate->id]); + $this->assertEquals(2, $count); + + // Delete data only for first student. + $contextlist = provider::get_contexts_for_userid($this->student->id); + $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist( + \core_user::get_user($this->student->id), + 'mod_qcreate', + $contextlist->get_contextids() + ); + $this->setUser(); + provider::delete_data_for_user($approvedcontextlist); + // Verify that there is 1 local grade. + $count = $DB->count_records('qcreate_grades', ['qcreateid' => $otherqcreate->id]); + $this->assertEquals(1, $count); + + } + + /** + * Export + Delete qcreate data for a teacher who has both made and graded question. + */ + public function test_teacher_with_data() { + global $DB; + $cm = get_coursemodule_from_instance('qcreate', $this->qcreate->id); + $context = context_module::instance($cm->id); + + // Create another question. + $q2 = $this->qcreate_add_qcreate_question($this->teacher, $this->qcreate, 'shortanswer'); + + $otherteacher = $this->getDataGenerator()->create_user();; + $this->getDataGenerator()->enrol_user($otherteacher->id, $this->course->id, 'editingteacher'); + // Grade question. + $this->setUser($otherteacher); + qcreate_process_local_grade($this->qcreate, $q2, false, false, 95, 'Very good.'); + + // Fetch the contexts - only one context should be returned. + $contextlist = provider::get_contexts_for_userid($this->teacher->id); + $this->assertCount(1, $contextlist); + $this->assertEquals($context, $contextlist->current()); + + // Verify that there is 2 local grades. + $count = $DB->count_records('qcreate_grades', ['qcreateid' => $this->qcreate->id]); + $this->assertEquals(2, $count); + + // Perform the export and check the data. + $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist( + \core_user::get_user($this->teacher->id), + 'mod_qcreate', + $contextlist->get_contextids() + ); + $grade = $DB->get_record('qcreate_grades', array('questionid' => $this->question->id)); + + // Delete the data and check it is removed. + $this->setUser(); + provider::delete_data_for_user($approvedcontextlist); + + // Verify that teacher's grade was deleted. + $count = $DB->count_records('qcreate_grades', ['qcreateid' => $this->qcreate->id]); + $this->assertEquals(1, $count); + + // Verify that question graded has been anonymized. + $grade = $DB->get_record('qcreate_grades', array('questionid' => $this->question->id)); + $this->assertEquals($grade->teacher, 0); + $this->assertEquals($grade->gradecomment, ''); + } + + /** + * Create a test qcreate for the specified course. + * + * @param \stdClass $course + * @return array + */ + protected function create_test_qcreate($course) { + global $DB; + + $qcreategenerator = $this->getDataGenerator()->get_plugin_generator('mod_qcreate'); + + $qcreate = $qcreategenerator->create_instance(['course' => $course->id]); + return $qcreate; + } + + protected function qcreate_add_qcreate_question($user, $qcreate, $qtype) { + $this->setUser($user); + $cm = get_coursemodule_from_instance('qcreate', $qcreate->id); + $context = context_module::instance($cm->id); + $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); + $cat = question_get_default_category($context->id); + $q = $questiongenerator->create_question($qtype, null, + array('category' => $cat->id)); + return $q; + } +} diff --git a/version.php b/version.php index d03a61c..8fe8f16 100644 --- a/version.php +++ b/version.php @@ -1,10 +1,31 @@ . + +/** + * Code fragment to define the version of the qcreate module + * + * @package mod_qcreate + * @copyright 2008 Jamie Pratt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or late + */ defined('MOODLE_INTERNAL') || die(); -$module->version = 2013032101; -$module->maturity = MATURITY_BETA; -$module->release = "1.0"; -$module->requires = 2011033005.06; -$module->cron = 300; -$module->component = 'mod_qcreate'; +$plugin->version = 2018051500; +$plugin->maturity = MATURITY_BETA; +$plugin->release = "2.2 for Moodle 2.9 ... 3.5"; +$plugin->requires = 2015050500; +$plugin->component = 'mod_qcreate'; diff --git a/view.php b/view.php index b88cabe..193e44a 100644 --- a/view.php +++ b/view.php @@ -1,127 +1,133 @@ -. + /** * This page prints a particular instance of qcreate * - * @author - * @version $Id: view.php,v 1.10 2008/12/01 13:18:25 jamiesensei Exp $ - * @package qcreate - **/ - -/// (Replace qcreate with the name of your module) + * @package mod_qcreate + * @copyright 2008 Jamie Pratt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or late + */ -require_once("../../config.php"); -require_once("lib.php"); -require_once("locallib.php"); +require_once(dirname(__FILE__) . '/../../config.php'); +require_once($CFG->dirroot . '/mod/qcreate/lib.php'); +require_once($CFG->dirroot . '/mod/qcreate/locallib.php'); $id = optional_param('id', 0, PARAM_INT); // Course Module ID, or $a = optional_param('a', 0, PARAM_INT); // qcreate ID -$delete = optional_param('delete', 0, PARAM_INT); // question id to delete -$confirm = optional_param('confirm', 0, PARAM_BOOL); +$delete = optional_param('delete', 0, PARAM_INT); // Question id to delete. +$confirm = optional_param('confirm', 0, PARAM_BOOL); +$qaction = optional_param('qaction', '', PARAM_ALPHA); // Return from question bank. +$lastchanged = optional_param('lastchanged', 0, PARAM_INT); // Id of created or edited question. +$thisurl = new moodle_url('/mod/qcreate/view.php'); if ($id) { - if (! $cm = $DB->get_record("course_modules", array("id"=>$id))) { - error("Course Module ID was incorrect"); - } - - if (! $course = $DB->get_record("course", array("id"=>$cm->course))) { - error("Course is misconfigured"); - } - - if (! $qcreate = $DB->get_record("qcreate", array("id"=>$cm->instance))) { - error("Course module is incorrect"); - } - + if (! $cm = $DB->get_record("course_modules", array("id" => $id))) { + print_error('invalidcoursemodule'); + } + + if (! $course = $DB->get_record("course", array("id" => $cm->course))) { + print_error('coursemisconf'); + } + + if (! $qcreate = $DB->get_record("qcreate", array("id" => $cm->instance))) { + print_error("Course module is incorrect"); + } + $thisurl->param('id', $id); } else { - if (! $qcreate = $DB->get_record("qcreate", array("id"=>$a))) { - error("Course module is incorrect"); - } - if (! $course = $DB->get_record("course", array("id"=>$qcreate->course))) { - error("Course is misconfigured"); - } - if (! $cm = get_coursemodule_from_instance("qcreate", $qcreate->id, $course->id)) { - error("Course Module ID was incorrect"); - } + if (! $qcreate = $DB->get_record("qcreate", array("id" => $a))) { + print_error('invalidqcreateid', 'qcreate'); + } + if (! $course = $DB->get_record("course", array("id" => $qcreate->course))) { + print_error('invalidcourseid'); + } + if (! $cm = get_coursemodule_from_instance("qcreate", $qcreate->id, $course->id)) { + print_error('invalidcoursemodule'); + } + $thisurl->param('a', $a); } -$qcreate->cmidnumber = $cm->id; - -$requireds = $DB->get_records('qcreate_required', array('qcreateid'=>$qcreate->id), 'qtype', 'qtype, no, id'); - -$thisurl = new moodle_url('/mod/qcreate/view.php', array('id'=>$cm->id)); -$PAGE->set_url($thisurl); - -$modulecontext = get_context_instance(CONTEXT_MODULE, $cm->id); - - - -//modedit.php forwards to this page after creating coursemodule record. -//this is the first chance we get to set capabilities in the newly created -//context. -qcreate_student_q_access_sync($qcreate, $modulecontext, $course); - +$modulecontext = context_module::instance($cm->id); -require_login($course->id); - -if (has_capability('mod/qcreate:grade', $modulecontext)){ - redirect($CFG->wwwroot.'/mod/qcreate/edit.php?cmid='.$cm->id); +if (!$cats = get_categories_for_contexts($modulecontext->id)) { + debugging('default category not set', DEBUG_DEVELOPER); } +$qcreate->cmidnumber = $cm->id; -/// Print the page header -$strqcreates = get_string("modulenameplural", "qcreate"); -$strqcreate = get_string("modulename", "qcreate"); +$thisurl = new moodle_url('/mod/qcreate/view.php', array('id' => $cm->id)); +$PAGE->set_url($thisurl); -$navlinks = array(); -$navlinks[] = array('name' => $strqcreates, 'link' => "index.php?id=$course->id", 'type' => 'activity'); -$navlinks[] = array('name' => format_string($qcreate->name), 'link' => '', 'type' => 'activityinstance'); +$qcreateobj = new qcreate($modulecontext, $cm, $course); -$navigation = build_navigation($navlinks); +qcreate_student_q_access_sync($modulecontext, $qcreateobj->get_instance()); -$headerargs = array(format_string($qcreate->name), "", $navigation, "", "", true, - update_module_button($cm->id, $course->id, $strqcreate), navmenu($course, $cm)); +require_login($course, true, $cm); -if (!$cats = get_categories_for_contexts($modulecontext->id)){ - //if it has not been made yet then make a default cat - question_make_default_categories(array($modulecontext)); - $cats = get_categories_for_contexts($modulecontext->id); -} -$catsinctx = array(); -foreach ($cats as $catinctx){ - $catsinctx[]=$catinctx->id; -} -$catsinctxlist = join($catsinctx, ','); -$cat = array_shift($cats); - -if ($delete && question_require_capability_on($delete, 'edit')){ - if ($confirm && confirm_sesskey()){ - if (!$DB->delete_records_select('question', "id = $delete AND category IN ($catsinctxlist)")){ - print_error('question_not_found'); - } else { - qcreate_update_grades($qcreate, $USER->id); - redirect($CFG->wwwroot.'/mod/qcreate/view.php?id='.$cm->id); - } - } else { - call_user_func_array('print_header_simple', $headerargs); - echo $OUTPUT->heading(get_string('delete')); - echo $OUTPUT->confirm(get_string('confirmdeletequestion', 'qcreate'), - new moodle_url('view.php', array('id' => $cm->id, 'sesskey'=> sesskey(), 'confirm'=>1, 'delete'=>$delete)), - new moodle_url('view.php', array('id' => $cm->id))); - echo $OUTPUT->footer('none'); - die; - } +if (has_capability('mod/qcreate:grade', $modulecontext)) { + // View for teachers. + // Get the qcreate class to render the overview page. + echo $qcreateobj->view(optional_param('action', 'overview', PARAM_TEXT)); +} else { + // View for students. + require_capability('mod/qcreate:view', $modulecontext); + + // Update completion state. + $completion = new completion_info($course); + if ($completion->is_enabled($cm) && $qcreateobj->get_instance()->completionquestions) { + $completion->update_state($cm, COMPLETION_COMPLETE); + } + $completion->set_module_viewed($cm); + + if ($lastchanged &&($qaction == 'edit' || $qaction == 'add')) { + qcreate_update_grades($qcreate, $USER->id); + $params['cid'] = $qcreateobj->get_question_category()->id; + $params['lastchanged'] = $lastchanged; + if (!$question = $DB->get_record_select('question', "id = :lastchanged AND category = :cid", $params)) { + print_error('question_not_found'); + } else { + $qcreateobj->notify_graders($question); + } + } + + if ($delete && question_has_capability_on($delete, 'edit')) { + if ($confirm && confirm_sesskey()) { + $params['cid'] = $qcreateobj->get_question_category()->id; + $params['deleteid'] = $delete; + if (!$question = $DB->get_record_select('question', "id = :deleteid AND category = :cid", $params)) { + print_error('question_not_found'); + } else { + $DB->delete_records('qcreate_grades', + array('qcreateid' => $qcreateobj->get_instance()->id, 'questionid' => $question->id)); + question_delete_question($question->id); + qcreate_update_grades($qcreate, $USER->id); + // Update completion state. + $completion = new completion_info($course); + if ($completion->is_enabled($cm) && $qcreateobj->get_instance()->completionquestions) { + $completion->update_state($cm, COMPLETION_INCOMPLETE); + } + + redirect($CFG->wwwroot.'/mod/qcreate/view.php?id='.$cm->id); + } + } else { + echo $qcreateobj->view(optional_param('action', 'confirmdelete', PARAM_TEXT)); + die; + } + } + + // Get the qcreate class to render the view page. + echo $qcreateobj->view(optional_param('action', 'view', PARAM_TEXT)); } - -call_user_func_array('print_header_simple', $headerargs); -add_to_log($course->id, "qcreate", "view", "view.php?id=$cm->id", "$qcreate->id"); - -$OUTPUT->box(format_text($qcreate->intro, $qcreate->introformat), 'generalbox', 'intro'); - -echo '
    '; -echo '

    '.qcreate_time_status($qcreate).'

    '; -echo '
    '; - - - -qcreate_required_q_list($requireds, $cat, $thisurl, $qcreate, $cm, $modulecontext); - -echo $OUTPUT->footer();