From a74dba715b0b6582031241c70dcb37af7039becd Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Tue, 15 May 2018 09:53:36 -0400 Subject: [PATCH 001/341] CONTRIB-7300 - Removed columns from installation script. --- db/install.xml | 2 -- db/upgrade.php | 18 ++++++++++++++++++ version.php | 2 +- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/db/install.xml b/db/install.xml index 6fb05863..edbadb7a 100644 --- a/db/install.xml +++ b/db/install.xml @@ -84,8 +84,6 @@ - - diff --git a/db/upgrade.php b/db/upgrade.php index f2087b24..ffc7d963 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -694,6 +694,24 @@ function xmldb_questionnaire_upgrade($oldversion=0) { upgrade_mod_savepoint(true, 2017111101, 'questionnaire'); } + // Converting to new dependency system. + if ($oldversion < 2017111103) { + + // If these fields exist, possibly due to incorrect creation from a new install (see CONTRIB-7300), remove them. + $table = new xmldb_table('questionnaire_question'); + $field1 = new xmldb_field('dependquestion'); + $field2 = new xmldb_field('dependchoice'); + if ($dbman->field_exists($table, $field1)) { + $dbman->drop_field($table, $field1); + } + if ($dbman->field_exists($table, $field2)) { + $dbman->drop_field($table, $field2); + } + + // Questionnaire savepoint reached. + upgrade_mod_savepoint(true, 2017111103, 'questionnaire'); + } + return $result; } diff --git a/version.php b/version.php index 03d55664..0ca78e71 100644 --- a/version.php +++ b/version.php @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2017111102; // The current module version (Date: YYYYMMDDXX) +$plugin->version = 2017111103; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2017042800; // Moodle version. $plugin->component = 'mod_questionnaire'; From 157f5a5552672830214bc7333639a31195d58097 Mon Sep 17 00:00:00 2001 From: Mark Sharp Date: Tue, 5 Jun 2018 16:02:01 +0100 Subject: [PATCH 002/341] CONTRIB-6982-M34_dataformat-api --- classes/output/renderer.php | 47 +++++++++++++++++++- report.php | 44 +++++++++++++------ templates/dataformat_selector.mustache | 59 ++++++++++++++++++++++++++ templates/extrafields.mustache | 2 + 4 files changed, 137 insertions(+), 15 deletions(-) mode change 100644 => 100755 classes/output/renderer.php mode change 100644 => 100755 report.php create mode 100755 templates/dataformat_selector.mustache create mode 100755 templates/extrafields.mustache diff --git a/classes/output/renderer.php b/classes/output/renderer.php old mode 100644 new mode 100755 index 58825ab4..e104e5db --- a/classes/output/renderer.php +++ b/classes/output/renderer.php @@ -444,4 +444,49 @@ public function flexible_table(\flexible_table $table, $buffering = false) { return $o; } -} \ No newline at end of file + + /** + * Returns a dataformat selection and download form + * + * @param string $label A text label + * @param moodle_url|string $base The download page url + * @param string $name The query param which will hold the type of the download + * @param array $params Extra params sent to the download page + * @param string $extrafields HTML for extra form fields + * @return string HTML fragment + */ + public function download_dataformat_selector($label, $base, $name = 'dataformat', $params = array(), $extrafields = '') { + + $formats = \core_plugin_manager::instance()->get_plugins_of_type('dataformat'); + $options = array(); + foreach ($formats as $format) { + if ($format->is_enabled()) { + $options[] = array( + 'value' => $format->name, + 'label' => get_string('dataformat', $format->component), + ); + } + } + $hiddenparams = array(); + foreach ($params as $key => $value) { + $hiddenparams[] = array( + 'name' => $key, + 'value' => $value, + ); + } + $data = array( + 'label' => $label, + 'base' => $base, + 'name' => $name, + 'params' => $hiddenparams, + 'options' => $options, + 'extrafields' => $extrafields, + 'sesskey' => sesskey(), + 'submit' => get_string('download'), + ); + + return $this->render_from_template('mod_questionnaire/dataformat_selector', $data); + } + + +} diff --git a/report.php b/report.php old mode 100644 new mode 100755 index 28fc482d..cd4b3b75 --- a/report.php +++ b/report.php @@ -444,19 +444,16 @@ $output .= ' '.(get_string('downloadtext')).': '.get_string('responses', 'questionnaire').' '.$groupname; $output .= $questionnaire->renderer->heading(get_string('textdownloadoptions', 'questionnaire')); $output .= $questionnaire->renderer->box_start(); - $output .= "
wwwroot}/mod/questionnaire/report.php\" method=\"GET\">\n"; - $output .= "\n"; - $output .= "\n"; - $output .= "\n"; - $output .= "\n"; - $output .= "\n"; - $output .= html_writer::checkbox('choicecodes', 1, true, get_string('includechoicecodes', 'questionnaire')); - $output .= "
\n"; - $output .= html_writer::checkbox('choicetext', 1, true, get_string('includechoicetext', 'questionnaire')); - $output .= "
\n"; - $output .= "
\n"; - $output .= "\n"; - $output .= "
\n"; + $downloadparams = [ + 'instance' => $instance, + 'user' => $user, + 'sid' => $sid, + 'action' => 'dfs', + 'group' => $currentgroupid + ]; + $extrafields = $questionnaire->renderer->render_from_template('mod_questionnaire/extrafields', []); + $output .= $questionnaire->renderer->download_dataformat_selector(get_string('download', 'questionnaire'), + 'report.php', 'downloadformat', $downloadparams, $extrafields); $output .= $questionnaire->renderer->box_end(); $questionnaire->page->add_to_page('respondentinfo', $output); @@ -499,6 +496,25 @@ } exit(); break; + case 'dfs': + require_capability('mod/questionnaire:downloadresponses', $context); + require_once($CFG->dirroot . '/lib/dataformatlib.php'); + // Use the questionnaire name as the file name. Clean it and change any non-filename characters to '_'. + $name = clean_param($questionnaire->name, PARAM_FILE); + $name = preg_replace("/[^A-Z0-9]+/i", "_", trim($name)); + + $choicecodes = optional_param('choicecodes', '0', PARAM_INT); + $choicetext = optional_param('choicetext', '0', PARAM_INT); + $dataformat = optional_param('downloadformat', '', PARAM_ALPHA); + + $output = $questionnaire->generate_csv('', $user, $choicecodes, $choicetext, $currentgroupid); + + $columns = $output[0]; + unset($output[0]); + download_as_dataformat($name, $dataformat, $columns, $output); + + exit(); + break; case 'vall': // View all responses. case 'vallasort': // View all responses sorted in ascending order. @@ -742,4 +758,4 @@ // Finish the page. echo $questionnaire->renderer->footer($course); break; -} \ No newline at end of file +} diff --git a/templates/dataformat_selector.mustache b/templates/dataformat_selector.mustache new file mode 100755 index 00000000..73ea5c44 --- /dev/null +++ b/templates/dataformat_selector.mustache @@ -0,0 +1,59 @@ +{{! + 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 . +}} +{{! + @template core/dataformat_selector + + Template for dataformat selection and download form. + + Context variables required for this template: + * label + * base + * name + * params + * options + * sesskey + * submit + * extrafields + + Example context (json): + { + "base": "http://example.org/", + "name": "test", + "label": "Download table data as", + "params": false, + "extrafields": '', + "options": [{"label": "CSV", "name": "csv"}, {"label": "Excel", "name": "excel"}], + "submit": "Download", + } +}} +
+
+ + {{#extrafields}}{{{extrafields}}}{{/extrafields}} + + + + + {{#params}} + + {{/params}} +
+
diff --git a/templates/extrafields.mustache b/templates/extrafields.mustache new file mode 100755 index 00000000..2bd0c8e1 --- /dev/null +++ b/templates/extrafields.mustache @@ -0,0 +1,2 @@ +
+
From 5196bb1b3ffe02c76b72b0afc97aa16a56eac923 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Tue, 19 Jun 2018 10:19:49 -0400 Subject: [PATCH 003/341] Poet - Implementing the GDPR Privacy API. --- classes/privacy/provider.php | 289 +++++++++++++++++++++++++++++++++++ lang/en/questionnaire.php | 59 ++++++- questionnaire.class.php | 75 ++++++--- version.php | 4 +- 4 files changed, 401 insertions(+), 26 deletions(-) create mode 100644 classes/privacy/provider.php diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php new file mode 100644 index 00000000..47237350 --- /dev/null +++ b/classes/privacy/provider.php @@ -0,0 +1,289 @@ +. + +/** + * Contains class mod_questionnaire\privacy\provider + * + * @package mod_questionnaire + * @copyright 2018 onward Mike Churchward (mike.churchward@poetopensource.org) + * @author Mike Churchward + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_questionnaire\privacy; + +defined('MOODLE_INTERNAL') || die(); + +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 { + + /** + * Returns meta data about this system. + * + * @param collection $items The collection to add metadata to. + * @return collection The array of metadata + */ + public static function get_metadata(\core_privacy\local\metadata\collection $collection): \core_privacy\local\metadata\collection { + + // Add all of the relevant tables and fields to the collection. + $collection->add_database_table('questionnaire_attempts', [ + 'userid' => 'privacy:metadata:questionnaire_attempts:userid', + 'rid' => 'privacy:metadata:questionnaire_attempts:rid', + 'qid' => 'privacy:metadata:questionnaire_attempts:qid', + 'timemodified' => 'privacy:metadata:questionnaire_attempts:timemodified', + ], 'privacy:metadata:questionnaire_attempts'); + + $collection->add_database_table('questionnaire_response', [ + 'userid' => 'privacy:metadata:questionnaire_response:userid', + 'survey_id' => 'privacy:metadata:questionnaire_response:survey_id', + 'complete' => 'privacy:metadata:questionnaire_response:complete', + 'grade' => 'privacy:metadata:questionnaire_response:grade', + 'submitted' => 'privacy:metadata:questionnaire_response:submitted', + ], 'privacy:metadata:questionnaire_response'); + + $collection->add_database_table('questionnaire_response_bool', [ + 'response_id' => 'privacy:metadata:questionnaire_response_bool:response_id', + 'question_id' => 'privacy:metadata:questionnaire_response_bool:question_id', + 'choice_id' => 'privacy:metadata:questionnaire_response_bool:choice_id', + ], 'privacy:metadata:questionnaire_response_bool'); + + $collection->add_database_table('questionnaire_response_date', [ + 'response_id' => 'privacy:metadata:questionnaire_response_date:response_id', + 'question_id' => 'privacy:metadata:questionnaire_response_date:question_id', + 'response' => 'privacy:metadata:questionnaire_response_date:response', + ], 'privacy:metadata:questionnaire_response_date'); + + $collection->add_database_table('questionnaire_response_other', [ + 'response_id' => 'privacy:metadata:questionnaire_response_other:response_id', + 'question_id' => 'privacy:metadata:questionnaire_response_other:question_id', + 'choice_id' => 'privacy:metadata:questionnaire_response_other:choice_id', + 'response' => 'privacy:metadata:questionnaire_response_other:response', + ], 'privacy:metadata:questionnaire_response_other'); + + $collection->add_database_table('questionnaire_response_rank', [ + 'response_id' => 'privacy:metadata:questionnaire_response_rank:response_id', + 'question_id' => 'privacy:metadata:questionnaire_response_rank:question_id', + 'choice_id' => 'privacy:metadata:questionnaire_response_rank:choice_id', + 'rank' => 'privacy:metadata:questionnaire_response_rank:rank', + ], 'privacy:metadata:questionnaire_response_rank'); + + $collection->add_database_table('questionnaire_response_text', [ + 'response_id' => 'privacy:metadata:questionnaire_response_text:response_id', + 'question_id' => 'privacy:metadata:questionnaire_response_text:question_id', + 'response' => 'privacy:metadata:questionnaire_response_text:response', + ], 'privacy:metadata:questionnaire_response_text'); + + $collection->add_database_table('questionnaire_resp_multiple', [ + 'response_id' => 'privacy:metadata:questionnaire_resp_multiple:response_id', + 'question_id' => 'privacy:metadata:questionnaire_resp_multiple:question_id', + 'choice_id' => 'privacy:metadata:questionnaire_resp_multiple:choice_id', + ], 'privacy:metadata:questionnaire_resp_multiple'); + + $collection->add_database_table('questionnaire_resp_single', [ + 'response_id' => 'privacy:metadata:questionnaire_resp_single:response_id', + 'question_id' => 'privacy:metadata:questionnaire_resp_single:question_id', + 'choice_id' => 'privacy:metadata:questionnaire_resp_single:choice_id', + ], 'privacy:metadata:questionnaire_resp_single'); + + return $collection; + } + + /** + * Get the list of contexts that contain user information for the specified user. + * + * @param int $userid The user to search. + * @return contextlist $contextlist The list of contexts used in this plugin. + */ + public static function get_contexts_for_userid(int $userid): \core_privacy\local\request\contextlist { + $contextlist = new \core_privacy\local\request\contextlist(); + + $sql = "SELECT c.id + FROM {context} c + INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel + INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname + INNER JOIN {questionnaire} q ON q.id = cm.instance + INNER JOIN {questionnaire_response} qr ON qr.survey_id = q.sid + WHERE qr.userid = :attemptuserid + "; + + $params = [ + 'modname' => 'questionnaire', + 'contextlevel' => CONTEXT_MODULE, + 'attemptuserid' => $userid, + ]; + + $contextlist->add_from_sql($sql, $params); + + return $contextlist; + } + + /** + * Export all user data for the specified user, in the specified contexts, using the supplied exporter instance. + * + * @param approved_contextlist $contextlist The approved contexts to export information for. + */ + public static function export_user_data(\core_privacy\local\request\approved_contextlist $contextlist) { + global $DB, $CFG; + require_once($CFG->dirroot.'/mod/questionnaire/questionnaire.class.php'); + + if (empty($contextlist->count())) { + return; + } + + $user = $contextlist->get_user(); + + list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED); + + $sql = "SELECT cm.id AS cmid, + q.id AS qid, q.course AS qcourse, + qr.id AS responseid, qr.submitted AS lastsaved, qr.complete AS complete + FROM {context} c + INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel + INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname + INNER JOIN {questionnaire} q ON q.id = cm.instance + INNER JOIN {questionnaire_response} qr ON qr.survey_id = q.sid + LEFT JOIN {questionnaire_attempts} qa ON qa.rid = qr.id + WHERE c.id {$contextsql} + AND qr.userid = :userid + ORDER BY cm.id, qr.id ASC"; + + $params = ['modname' => 'questionnaire', 'contextlevel' => CONTEXT_MODULE, 'userid' => $user->id] + $contextparams; + + // There can be more than one attempt per instance, so we'll gather them by cmid. + $lastcmid = 0; + $responsedata = []; + $responses = $DB->get_recordset_sql($sql, $params); + foreach ($responses as $response) { + // If we've moved to a new choice, then write the last choice data and reinit the choice data array. + if ($lastcmid != $response->cmid) { + if (!empty($responsedata)) { + $context = \context_module::instance($lastcmid); + // Fetch the generic module data for the questionnaire. + $contextdata = \core_privacy\local\request\helper::get_context_data($context, $user); + // Merge with attempt data and write it. + $contextdata = (object)array_merge((array)$contextdata, $responsedata); + \core_privacy\local\request\writer::with_context($context)->export_data([], $contextdata); + } + $responsedata = []; + $lastcmid = $response->cmid; + $course = $DB->get_record("course", ["id" => $response->qcourse]); + $cm = get_coursemodule_from_instance("questionnaire", $response->qid, $course->id); + $questionnaire = new \questionnaire($response->qid, null, $course, $cm); + } + $responsedata['responses'][] = [ + 'complete' => (($response->complete == 'y') ? get_string('yes') : get_string('no')), + 'lastsaved' => \core_privacy\local\request\transform::datetime($response->lastsaved), + 'questions' => $questionnaire->get_structured_response($response->responseid), + ]; + } + $responses->close(); + + // The data for the last activity won't have been written yet, so make sure to write it now! + if (!empty($responsedata)) { + $context = \context_module::instance($lastcmid); + // Fetch the generic module data for the questionnaire. + $contextdata = \core_privacy\local\request\helper::get_context_data($context, $user); + // Merge with attempt data and write it. + $contextdata = (object)array_merge((array)$contextdata, $responsedata); + \core_privacy\local\request\writer::with_context($context)->export_data([], $contextdata); + } + } + + /** + * Delete all personal data for all users in the specified context. + * + * @param context $context Context to delete data from. + */ + public static function delete_data_for_all_users_in_context(\context $context) { + global $DB; + + if (!($context instanceof \context_module)) { + return; + } + + if (!$cm = get_coursemodule_from_id('questionnaire', $context->instanceid)) { + return; + } + + if (!($questionnaire = $DB->get_record('questionnaire', ['id' => $cm->instance]))) { + return; + } + + if ($responses = $DB->get_recordset('questionnaire_response', ['survey_id' => $questionnaire->sid])) { + self::delete_responses($responses); + } + $responses->close(); + $DB->delete_records('questionnaire_response', ['survey_id' => $questionnaire->sid]); + } + + /** + * 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(\core_privacy\local\request\approved_contextlist $contextlist) { + global $DB; + + if (empty($contextlist->count())) { + return; + } + + $userid = $contextlist->get_user()->id; + foreach ($contextlist->get_contexts() as $context) { + if (!($context instanceof \context_module)) { + continue; + } + if (!$cm = get_coursemodule_from_id('questionnaire', $context->instanceid)) { + continue; + } + + if (!($questionnaire = $DB->get_record('questionnaire', ['id' => $cm->instance]))) { + continue; + } + + if ($responses = $DB->get_recordset('questionnaire_response', + ['survey_id' => $questionnaire->sid, 'userid' => $userid])) { + self::delete_responses($responses); + } + $responses->close(); + $DB->delete_records('questionnaire_response', ['survey_id' => $questionnaire->sid, 'userid' => $userid]); + } + } + + /** + * Helper function to delete all the response records for a recordset array of responses. + * + * @param recordset $responses The list of response records to delete for. + */ + private static function delete_responses($responses) { + global $DB; + + foreach ($responses as $response) { + $DB->delete_records('questionnaire_response_bool', ['response_id' => $response->id]); + $DB->delete_records('questionnaire_response_date', ['response_id' => $response->id]); + $DB->delete_records('questionnaire_resp_multiple', ['response_id' => $response->id]); + $DB->delete_records('questionnaire_response_other', ['response_id' => $response->id]); + $DB->delete_records('questionnaire_response_rank', ['response_id' => $response->id]); + $DB->delete_records('questionnaire_resp_single', ['response_id' => $response->id]); + $DB->delete_records('questionnaire_response_text', ['response_id' => $response->id]); + $DB->delete_records('questionnaire_attempts', ['rid' => $response->id]); + } + } +} \ No newline at end of file diff --git a/lang/en/questionnaire.php b/lang/en/questionnaire.php index 88154ab3..69ed2a7d 100644 --- a/lang/en/questionnaire.php +++ b/lang/en/questionnaire.php @@ -15,11 +15,11 @@ // along with Moodle. If not, see . /** - * Strings for component 'questionnaire', language 'en', branch 'MOODLE_24_STABLE' + * Strings for component 'questionnaire', language 'en' * - * @package mod - * @subpackage quiz - * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} + * @package mod_questionnaire + * @copyright 2018 onward Mike Churchward (mike.churchward@poetopensource.org) + * @author Mike Churchward * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -372,6 +372,57 @@ $string['printblank'] = 'Print Blank'; $string['printblanktooltip'] = 'Opens printer-friendly window with blank Questionnaire'; $string['printtooltip'] = 'Opens printer-friendly window with current Response'; + +$string['privacy:metadata:questionnaire_attempts'] = 'Details about each submission of a questionnaire by a user.'; +$string['privacy:metadata:questionnaire_attempts:userid'] = 'The ID of the user for this attempt.'; +$string['privacy:metadata:questionnaire_attempts:rid'] = 'The ID of the user\'s response record for this attempt.'; +$string['privacy:metadata:questionnaire_attempts:qid'] = 'The ID of the questionnaire record for this attempt.'; +$string['privacy:metadata:questionnaire_attempts:timemodified'] = 'The timestamp for the latest submission of this attempt.'; + +$string['privacy:metadata:questionnaire_response'] = 'A response in progress or submitted'; +$string['privacy:metadata:questionnaire_response:userid'] = 'The ID of the user for this response.'; +$string['privacy:metadata:questionnaire_response:survey_id'] = 'The ID of the survey record for this response.'; +$string['privacy:metadata:questionnaire_response:complete'] = 'The response completion status.'; +$string['privacy:metadata:questionnaire_response:grade'] = 'The grade for this response.'; +$string['privacy:metadata:questionnaire_response:submitted'] = 'The timestamp for the most recent save for this response.'; + +$string['privacy:metadata:questionnaire_response_bool'] = 'A boolean (yes/no) question response.'; +$string['privacy:metadata:questionnaire_response_bool:response_id'] = 'The ID of the response record for this response.'; +$string['privacy:metadata:questionnaire_response_bool:question_id'] = 'The ID of the question record for this response.'; +$string['privacy:metadata:questionnaire_response_bool:choice_id'] = 'The specific boolean answer.'; + +$string['privacy:metadata:questionnaire_response_date'] = 'A date question response.'; +$string['privacy:metadata:questionnaire_response_date:response_id'] = 'The ID of the response record for this response.'; +$string['privacy:metadata:questionnaire_response_date:question_id'] = 'The ID of the question record for this response.'; +$string['privacy:metadata:questionnaire_response_date:response'] = 'The specific date answer.'; + +$string['privacy:metadata:questionnaire_response_other'] = 'An \'other\' choice text response.'; +$string['privacy:metadata:questionnaire_response_other:response_id'] = 'The ID of the response record for this response.'; +$string['privacy:metadata:questionnaire_response_other:question_id'] = 'The ID of the question record for this response.'; +$string['privacy:metadata:questionnaire_response_other:choice_id'] = 'The ID of the choice record for this response.'; +$string['privacy:metadata:questionnaire_response_other:response'] = 'The specific text answer.'; + +$string['privacy:metadata:questionnaire_response_rank'] = 'A rank question response.'; +$string['privacy:metadata:questionnaire_response_rank:response_id'] = 'The ID of the response record for this response.'; +$string['privacy:metadata:questionnaire_response_rank:question_id'] = 'The ID of the question record for this response.'; +$string['privacy:metadata:questionnaire_response_rank:choice_id'] = 'The ID of the choice record for this response.'; +$string['privacy:metadata:questionnaire_response_rank:rank'] = 'The specific rank answer.'; + +$string['privacy:metadata:questionnaire_response_text'] = 'A text question response.'; +$string['privacy:metadata:questionnaire_response_text:response_id'] = 'The ID of the response record for this response.'; +$string['privacy:metadata:questionnaire_response_text:question_id'] = 'The ID of the question record for this response.'; +$string['privacy:metadata:questionnaire_response_text:response'] = 'The specific text answer.'; + +$string['privacy:metadata:questionnaire_resp_multiple'] = 'A multiple choice question response.'; +$string['privacy:metadata:questionnaire_resp_multiple:response_id'] = 'The ID of the response record for this response.'; +$string['privacy:metadata:questionnaire_resp_multiple:question_id'] = 'The ID of the question record for this response.'; +$string['privacy:metadata:questionnaire_resp_multiple:choice_id'] = 'The ID of a choice record for this response.'; + +$string['privacy:metadata:questionnaire_resp_single'] = 'A single choice question response.'; +$string['privacy:metadata:questionnaire_resp_single:response_id'] = 'The ID of the response record for this response.'; +$string['privacy:metadata:questionnaire_resp_single:question_id'] = 'The ID of the question record for this response.'; +$string['privacy:metadata:questionnaire_resp_single:choice_id'] = 'The ID of the choice record for this response.'; + $string['private'] = 'Private'; $string['public'] = 'Public'; $string['publiccopy'] = 'Copy:'; diff --git a/questionnaire.class.php b/questionnaire.class.php index 2661756e..ea75ff08 100644 --- a/questionnaire.class.php +++ b/questionnaire.class.php @@ -1749,13 +1749,48 @@ protected function get_notifiable_users($userid) { * @throws coding_exception */ private function get_full_submission_for_notifications($answers) { + $responses = $this->get_full_submission_for_export($answers); $message = ''; + foreach ($responses as $response) { + $message .= html_to_text($response->questionname) . "
\n"; + $message .= get_string('question') . ': ' . html_to_text($response->questiontext) . "
\n"; + $message .= get_string('answers', 'questionnaire') . ":
\n"; + foreach ($response->answers as $answer) { + $message .= html_to_text($answer) . "
\n"; + } + $message .= "
\n"; + } + + return $message; + } + + /** + * Construct the response data for a given response and return a structured export. + * @param $rid + * @return string + * @throws coding_exception + */ + public function get_structured_response($rid) { + $answers = new stdClass(); + $this->response_import_all($rid, $answers); + return $this->get_full_submission_for_export($answers); + } + + /** + * Return a JSON structure containing all the questions and answers for a specific submission. + * @param $answers The array of answers from import_all_responses. + * @return string + * @throws coding_exception + */ + private function get_full_submission_for_export($answers) { + $exportstructure = []; foreach ($this->questions as $question) { $rqid = 'q' . $question->id; - $message .= $question->position . '. ' . html_to_text($question->name) . "
\n"; - $message .= get_string('question') . ': ' . html_to_text($question->content) . "
\n"; + $response = new stdClass(); + $response->questionname = $question->position . '. ' . $question->name; + $response->questiontext = $question->content; + $response->answers = []; if ($question->type_id == 8) { - $message .= get_string('answers', 'questionnaire') . ":
\n"; $choices = []; $cids = []; foreach ($question->choices as $cid => $choice) { @@ -1769,8 +1804,11 @@ private function get_full_submission_for_notifications($answers) { if (isset($answers->$rqid)) { $cid = substr($rqid, (strpos($rqid, '_') + 1)); if (isset($question->choices[$cid]) && isset($choices[$answers->$rqid + 1])) { - $message .= $question->choices[$cid]->content . ' = ' . $choices[$answers->$rqid + 1] . "
\n"; + $rating = $choices[$answers->$rqid + 1]; + } else { + $rating = $answers->$rqid + 1; } + $response->answers[] = $question->choices[$cid]->content . ' = ' . $rating; } } } else if ($question->has_choices()) { @@ -1781,37 +1819,34 @@ private function get_full_submission_for_notifications($answers) { break; } } + $answertext = ''; if (isset($answers->$rqid) && is_array($answers->$rqid)) { - $message .= get_string('answers', 'questionnaire') . ': '; $i = 0; foreach ($answers->$rqid as $answer) { if ($i > 0) { - $message .= '; '; + $answertext .= '; '; } - if ($answer == ('other_' . $other)) { - $message .= $answers->{$rqid . '_' . $other}; + if (isset($other) && ($answer == ('other_' . $other))) { + $answertext .= $answers->{$rqid . '_' . $other}; } else { - $message .= $question->choices[$answer]->content; + $answertext .= $question->choices[$answer]->content; } $i++; } - $message .= "
\n"; - } else if (isset($answers->$rqid) && ($answers->$rqid == ('other_' . $other))) { - $message .= get_string('answer', 'questionnaire') . ': '; - $message .= $answers->{$rqid . '_' . $other} . "
\n"; - } else if (isset($answers->$rqid)) { - $message .= get_string('answer', 'questionnaire') . ': '; - $message .= $question->choices[$answers->$rqid]->content . "
\n"; + } else if (isset($answers->$rqid) && isset($other) && ($answers->$rqid == ('other_' . $other))) { + $answertext .= $answers->{$rqid . '_' . $other}; + } else if (isset($answers->$rqid) && isset($question->choices[$answers->$rqid])) { + $answertext .= $question->choices[$answers->$rqid]->content; } + $response->answers[] = $answertext; } else if (isset($answers->$rqid)) { - $message .= get_string('answer', 'questionnaire') . ': '; - $message .= html_to_text($answers->$rqid) . "
\n"; + $response->answers[] = $answers->$rqid; } - $message .= "
\n"; + $exportstructure[] = $response; } - return $message; + return $exportstructure; } /** diff --git a/version.php b/version.php index 0ca78e71..1305d404 100644 --- a/version.php +++ b/version.php @@ -25,10 +25,10 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2017111103; // The current module version (Date: YYYYMMDDXX) +$plugin->version = 2017111104; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2017042800; // Moodle version. $plugin->component = 'mod_questionnaire'; -$plugin->release = '3.4.1 (Build - 2018013100)'; +$plugin->release = '3.4.2 (Build - 2018061900)'; $plugin->maturity = MATURITY_STABLE; \ No newline at end of file From 161fdf4503b2971b8ef2f92c3f119eaed664e55c Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Tue, 19 Jun 2018 11:26:23 -0400 Subject: [PATCH 004/341] Poet - Setting up for release 3.4.2. --- CHANGES.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 631a2dd4..ad3215dd 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -26,6 +26,13 @@ enhanced notification feature. Note, this release can also be installed in Moodle 3.3, if you want the new features prior to upgrading to Moodle 3.4. This release does make database structure changes, so it cannot be downgraded after installation. +Release 3.4.2 (Build - 2018061900) +New features: +This is an early release providing the GDPR Privacy API implementation. + +Bug fixes: +CONTRIB-7187 - Fixed preview mode with dependencies bug and added tests to verify. +CONTRIB-7300 - Removed database columns from install.xml and readded the upgrade step for them. Release 3.4.1 (Build - 2018013100) New Features: From a53e38fd91bd93df38b4363aa34885edea44ca36 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Tue, 28 Aug 2018 13:59:29 -0400 Subject: [PATCH 005/341] CONTRIB-7420 - Quoting newly reserved word 'rank' for MySQL 8.0.2. --- classes/response/boolean.php | 2 +- classes/response/multiple.php | 2 +- classes/response/single.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/classes/response/boolean.php b/classes/response/boolean.php index 798970c4..48dacc48 100644 --- a/classes/response/boolean.php +++ b/classes/response/boolean.php @@ -215,7 +215,7 @@ protected function bulk_sql() { // while all others are an integer. So put the boolean response in "response" field instead (CONTRIB-6436). // NOTE - the actual use of "boolean" should probably change to not use "choice_id" at all, or use it as // numeric zero and one instead. - $extraselect = '0 AS choice_id, ' . $DB->sql_order_by_text('qrb.choice_id', 1000) . ' AS response, 0 AS rank'; + $extraselect = '0 AS choice_id, ' . $DB->sql_order_by_text('qrb.choice_id', 1000) . ' AS response, 0 AS "rank"'; $alias = 'qrb'; return " diff --git a/classes/response/multiple.php b/classes/response/multiple.php index 1deded24..425c67dc 100644 --- a/classes/response/multiple.php +++ b/classes/response/multiple.php @@ -305,7 +305,7 @@ protected function bulk_sql() { $userfields = $this->user_fields_sql(); $extraselect = ''; - $extraselect .= 'qrm.choice_id, ' . $DB->sql_order_by_text('qro.response', 1000) . ' AS response, 0 AS rank'; + $extraselect .= 'qrm.choice_id, ' . $DB->sql_order_by_text('qro.response', 1000) . ' AS response, 0 AS "rank"'; $alias = 'qrm'; return " diff --git a/classes/response/single.php b/classes/response/single.php index eab0aa21..f579b863 100644 --- a/classes/response/single.php +++ b/classes/response/single.php @@ -279,7 +279,7 @@ protected function bulk_sql() { global $DB; $userfields = $this->user_fields_sql(); - $extraselect = 'qrs.choice_id, ' . $DB->sql_order_by_text('qro.response', 1000) . ' AS response, 0 AS rank'; + $extraselect = 'qrs.choice_id, ' . $DB->sql_order_by_text('qro.response', 1000) . ' AS response, 0 AS "rank"'; $alias = 'qrs'; return " From ecae3b919c05c2f2551cca7e7988c6116a474fab Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Wed, 29 Aug 2018 14:19:07 -0400 Subject: [PATCH 006/341] Poet - Changing quoted field to use safer getEncQuoted function. --- classes/response/boolean.php | 3 ++- classes/response/multiple.php | 5 +++-- classes/response/single.php | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/classes/response/boolean.php b/classes/response/boolean.php index 48dacc48..63604f15 100644 --- a/classes/response/boolean.php +++ b/classes/response/boolean.php @@ -215,8 +215,9 @@ protected function bulk_sql() { // while all others are an integer. So put the boolean response in "response" field instead (CONTRIB-6436). // NOTE - the actual use of "boolean" should probably change to not use "choice_id" at all, or use it as // numeric zero and one instead. - $extraselect = '0 AS choice_id, ' . $DB->sql_order_by_text('qrb.choice_id', 1000) . ' AS response, 0 AS "rank"'; $alias = 'qrb'; + $rank = $DB->get_manager()->generator->getEncQuoted('rank'); + $extraselect = '0 AS choice_id, ' . $DB->sql_order_by_text('qrb.choice_id', 1000) . ' AS response, 0 AS ' . $rank; return " SELECT " . $DB->sql_concat_join("'_'", ['qr.id', "'".$this->question->helpname()."'", $alias.'.id']) . " AS id, diff --git a/classes/response/multiple.php b/classes/response/multiple.php index 425c67dc..408a8844 100644 --- a/classes/response/multiple.php +++ b/classes/response/multiple.php @@ -304,9 +304,10 @@ protected function bulk_sql() { global $DB; $userfields = $this->user_fields_sql(); - $extraselect = ''; - $extraselect .= 'qrm.choice_id, ' . $DB->sql_order_by_text('qro.response', 1000) . ' AS response, 0 AS "rank"'; $alias = 'qrm'; + $rank = $DB->get_manager()->generator->getEncQuoted('rank'); + $extraselect = ''; + $extraselect .= 'qrm.choice_id, ' . $DB->sql_order_by_text('qro.response', 1000) . ' AS response, 0 AS ' . $rank; return " SELECT " . $DB->sql_concat_join("'_'", ['qr.id', "'".$this->question->helpname()."'", $alias.'.id']) . " AS id, diff --git a/classes/response/single.php b/classes/response/single.php index f579b863..4e005f93 100644 --- a/classes/response/single.php +++ b/classes/response/single.php @@ -279,8 +279,9 @@ protected function bulk_sql() { global $DB; $userfields = $this->user_fields_sql(); - $extraselect = 'qrs.choice_id, ' . $DB->sql_order_by_text('qro.response', 1000) . ' AS response, 0 AS "rank"'; $alias = 'qrs'; + $rank = $DB->get_manager()->generator->getEncQuoted('rank'); + $extraselect = 'qrs.choice_id, ' . $DB->sql_order_by_text('qro.response', 1000) . ' AS response, 0 AS ' . $rank; return " SELECT " . $DB->sql_concat_join("'_'", ['qr.id', "'".$this->question->helpname()."'", $alias.'.id']) . " AS id, From 482055c3e5473e17d2100b6834ec2efd79953bc5 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Wed, 5 Sep 2018 11:40:36 -0400 Subject: [PATCH 007/341] CONTRIB-7420 - Changing the 'rank' field name to 'rankvalue'. --- .../moodle2/backup_questionnaire_stepslib.php | 2 +- .../restore_questionnaire_stepslib.php | 6 ++++ classes/db/bulk_sql_config.php | 2 +- classes/generator/question_response_rank.php | 4 +-- classes/privacy/provider.php | 2 +- classes/response/boolean.php | 3 +- classes/response/display_support.php | 6 ++-- classes/response/multiple.php | 3 +- classes/response/rank.php | 32 +++++++++---------- classes/response/single.php | 3 +- db/install.xml | 2 +- db/upgrade.php | 13 ++++++++ lang/en/questionnaire.php | 2 +- questionnaire.class.php | 2 +- tests/behat/behat_mod_questionnaire.php | 4 ++- tests/generator/lib.php | 2 +- tests/responsetypes_test.php | 2 +- version.php | 2 +- 18 files changed, 55 insertions(+), 37 deletions(-) diff --git a/backup/moodle2/backup_questionnaire_stepslib.php b/backup/moodle2/backup_questionnaire_stepslib.php index 1fe37a22..e506d5ce 100644 --- a/backup/moodle2/backup_questionnaire_stepslib.php +++ b/backup/moodle2/backup_questionnaire_stepslib.php @@ -108,7 +108,7 @@ protected function define_structure() { $responseranks = new backup_nested_element('response_ranks'); $responserank = new backup_nested_element('response_rank', array('id'), array( - 'response_id', 'question_id', 'choice_id', 'rank')); + 'response_id', 'question_id', 'choice_id', 'rankvalue')); $responsesingles = new backup_nested_element('response_singles'); diff --git a/backup/moodle2/restore_questionnaire_stepslib.php b/backup/moodle2/restore_questionnaire_stepslib.php index 4b1e95e0..718b577a 100644 --- a/backup/moodle2/restore_questionnaire_stepslib.php +++ b/backup/moodle2/restore_questionnaire_stepslib.php @@ -301,6 +301,12 @@ protected function process_questionnaire_response_rank($data) { global $DB; $data = (object)$data; + + // Older versions of questionnaire used 'rank' instead of 'rankvalue'. If 'rank' exists, change it to 'rankvalue'. + if (isset($data->rank) && !isset($data->rankvalue)) { + $data->rankvalue = $data->rank; + } + $data->response_id = $this->get_new_parentid('questionnaire_response'); $data->question_id = $this->get_mappingid('questionnaire_question', $data->question_id); $data->choice_id = $this->get_mappingid('questionnaire_quest_choice', $data->choice_id); diff --git a/classes/db/bulk_sql_config.php b/classes/db/bulk_sql_config.php index edc6593e..1069c0c9 100644 --- a/classes/db/bulk_sql_config.php +++ b/classes/db/bulk_sql_config.php @@ -75,7 +75,7 @@ public function get_extra_select() { return [ 'choice_id' => $this->usechoiceid, 'response' => $this->useresponse, - 'rank' => $this->userank + 'rankvalue' => $this->userank ]; } } \ No newline at end of file diff --git a/classes/generator/question_response_rank.php b/classes/generator/question_response_rank.php index e9dbe555..65edd13f 100644 --- a/classes/generator/question_response_rank.php +++ b/classes/generator/question_response_rank.php @@ -26,10 +26,10 @@ class question_response_rank { public $choice; - public $rank; + public $rankvalue; public function __construct($choice, $rank) { $this->choice = $choice; - $this->rank = $rank; + $this->rankvalue = $rank; } } \ No newline at end of file diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php index 47237350..196b1c9d 100644 --- a/classes/privacy/provider.php +++ b/classes/privacy/provider.php @@ -81,7 +81,7 @@ public static function get_metadata(\core_privacy\local\metadata\collection $col 'response_id' => 'privacy:metadata:questionnaire_response_rank:response_id', 'question_id' => 'privacy:metadata:questionnaire_response_rank:question_id', 'choice_id' => 'privacy:metadata:questionnaire_response_rank:choice_id', - 'rank' => 'privacy:metadata:questionnaire_response_rank:rank', + 'rank' => 'privacy:metadata:questionnaire_response_rank:rankvalue', ], 'privacy:metadata:questionnaire_response_rank'); $collection->add_database_table('questionnaire_response_text', [ diff --git a/classes/response/boolean.php b/classes/response/boolean.php index 63604f15..fb687563 100644 --- a/classes/response/boolean.php +++ b/classes/response/boolean.php @@ -216,8 +216,7 @@ protected function bulk_sql() { // NOTE - the actual use of "boolean" should probably change to not use "choice_id" at all, or use it as // numeric zero and one instead. $alias = 'qrb'; - $rank = $DB->get_manager()->generator->getEncQuoted('rank'); - $extraselect = '0 AS choice_id, ' . $DB->sql_order_by_text('qrb.choice_id', 1000) . ' AS response, 0 AS ' . $rank; + $extraselect = '0 AS choice_id, ' . $DB->sql_order_by_text('qrb.choice_id', 1000) . ' AS response, 0 AS rankvalue'; return " SELECT " . $DB->sql_concat_join("'_'", ['qr.id', "'".$this->question->helpname()."'", $alias.'.id']) . " AS id, diff --git a/classes/response/display_support.php b/classes/response/display_support.php index c2cd278e..92dcef4f 100644 --- a/classes/response/display_support.php +++ b/classes/response/display_support.php @@ -419,14 +419,14 @@ public static function mkrescount($counts, $rids, $rows, $question, $precision, } array_unshift($params, $question->id); // This is question_id. - $sql = 'SELECT r.id, c.content, r.rank, c.id AS choiceid ' . + $sql = 'SELECT r.id, c.content, r.rankvalue, c.id AS choiceid ' . 'FROM {questionnaire_quest_choice} c , ' . '{questionnaire_response_rank} r ' . 'WHERE c.question_id = ?' . ' AND r.question_id = c.question_id' . ' AND r.choice_id = c.id ' . $rsql . - ' ORDER BY choiceid, rank ASC'; + ' ORDER BY choiceid, rankvalue ASC'; $choices = $DB->get_records_sql($sql, $params); // Sort rows (results) by average value. @@ -458,7 +458,7 @@ public static function mkrescount($counts, $rids, $rows, $question, $precision, if ($choice->choiceid == $choiceid) { $n = 0; for ($i = 0; $i < $nbranks; $i++) { - if ($choice->rank == $i) { + if ($choice->rankvalue == $i) { $n++; if (!isset($ranks[$choice->content][$i])) { $ranks[$choice->content][$i] = 0; diff --git a/classes/response/multiple.php b/classes/response/multiple.php index 408a8844..0be87da2 100644 --- a/classes/response/multiple.php +++ b/classes/response/multiple.php @@ -305,9 +305,8 @@ protected function bulk_sql() { $userfields = $this->user_fields_sql(); $alias = 'qrm'; - $rank = $DB->get_manager()->generator->getEncQuoted('rank'); $extraselect = ''; - $extraselect .= 'qrm.choice_id, ' . $DB->sql_order_by_text('qro.response', 1000) . ' AS response, 0 AS ' . $rank; + $extraselect .= 'qrm.choice_id, ' . $DB->sql_order_by_text('qro.response', 1000) . ' AS response, 0 AS rankvalue'; return " SELECT " . $DB->sql_concat_join("'_'", ['qr.id', "'".$this->question->helpname()."'", $alias.'.id']) . " AS id, diff --git a/classes/response/rank.php b/classes/response/rank.php index d1e538ac..afa3fe3d 100644 --- a/classes/response/rank.php +++ b/classes/response/rank.php @@ -63,7 +63,7 @@ public function insert_response($rid, $val) { $record->response_id = $rid; $record->question_id = $this->question->id; $record->choice_id = $cid; - $record->rank = $rank; + $record->rankvalue = $rank; $resid = $DB->insert_record(self::response_table(), $record); } return $resid; @@ -76,7 +76,7 @@ public function insert_response($rid, $val) { $record = new \stdClass(); $record->response_id = $rid; $record->question_id = $this->question->id; - $record->rank = $rank; + $record->rankvalue = $rank; return $DB->insert_record(self::response_table(), $record); } } @@ -103,7 +103,7 @@ public function get_results($rids=false, $anonymous=false) { foreach ($rows as $row) { $this->counts[$row->content] = new \stdClass(); $nbna = $DB->count_records(self::response_table(), array('question_id' => $this->question->id, - 'choice_id' => $row->id, 'rank' => '-1')); + 'choice_id' => $row->id, 'rankvalue' => '-1')); $this->counts[$row->content]->nbna = $nbna; // The $row->value may be null (i.e. empty) or have a 'NULL' value. if ($row->value !== null && $row->value !== 'NULL') { @@ -116,19 +116,19 @@ public function get_results($rids=false, $anonymous=false) { // Usual case. if (!$isrestricted) { if (!empty ($rankvalue)) { - $sql = "SELECT r.id, c.content, r.rank, c.id AS choiceid + $sql = "SELECT r.id, c.content, r.rankvalue, c.id AS choiceid FROM {questionnaire_quest_choice} c, {".self::response_table()."} r WHERE r.choice_id = c.id AND c.question_id = " . $this->question->id . " - AND r.rank >= 0{$rsql} + AND r.rankvalue >= 0{$rsql} ORDER BY choiceid"; $results = $DB->get_records_sql($sql, $params); $value = array(); foreach ($results as $result) { if (isset ($value[$result->choiceid])) { - $value[$result->choiceid] += $rankvalue[$result->rank]; + $value[$result->choiceid] += $rankvalue[$result->rankvalue]; } else { - $value[$result->choiceid] = $rankvalue[$result->rank]; + $value[$result->choiceid] = $rankvalue[$result->rankvalue]; } } } @@ -136,9 +136,9 @@ public function get_results($rids=false, $anonymous=false) { $sql = "SELECT c.id, c.content, a.average, a.num FROM {questionnaire_quest_choice} c INNER JOIN - (SELECT c2.id, AVG(a2.rank+1) AS average, COUNT(a2.response_id) AS num + (SELECT c2.id, AVG(a2.rankvalue+1) AS average, COUNT(a2.response_id) AS num FROM {questionnaire_quest_choice} c2, {".self::response_table()."} a2 - WHERE c2.question_id = ? AND a2.question_id = ? AND a2.choice_id = c2.id AND a2.rank >= 0{$rsql} + WHERE c2.question_id = ? AND a2.question_id = ? AND a2.choice_id = c2.id AND a2.rankvalue >= 0{$rsql} GROUP BY c2.id) a ON a.id = c.id order by c.id"; $results = $DB->get_records_sql($sql, array_merge(array($this->question->id, $this->question->id), $params)); @@ -158,9 +158,9 @@ public function get_results($rids=false, $anonymous=false) { $sql = "SELECT c.id, c.content, a.sum, a.num FROM {questionnaire_quest_choice} c INNER JOIN - (SELECT c2.id, SUM(a2.rank+1) AS sum, COUNT(a2.response_id) AS num + (SELECT c2.id, SUM(a2.rankvalue+1) AS sum, COUNT(a2.response_id) AS num FROM {questionnaire_quest_choice} c2, {".self::response_table()."} a2 - WHERE c2.question_id = ? AND a2.question_id = ? AND a2.choice_id = c2.id AND a2.rank >= 0{$rsql} + WHERE c2.question_id = ? AND a2.question_id = ? AND a2.choice_id = c2.id AND a2.rankvalue >= 0{$rsql} GROUP BY c2.id) a ON a.id = c.id"; $results = $DB->get_records_sql($sql, array_merge(array($this->question->id, $this->question->id), $params)); // Formula to calculate the best ranking order. @@ -173,10 +173,10 @@ public function get_results($rids=false, $anonymous=false) { return $results; } } else { - $sql = 'SELECT A.rank, COUNT(A.response_id) AS num ' . + $sql = 'SELECT A.rankvalue, COUNT(A.response_id) AS num ' . 'FROM {'.self::response_table().'} A ' . 'WHERE A.question_id= ? ' . $rsql . ' ' . - 'GROUP BY A.rank'; + 'GROUP BY A.rankvalue'; return $DB->get_records_sql($sql, array_merge(array($this->question->id), $params)); } } @@ -198,7 +198,7 @@ public function get_feedback_scores(array $rids) { } $params[] = 'y'; - $sql = 'SELECT r.id, r.response_id as rid, r.question_id AS qid, r.choice_id AS cid, r.rank ' . + $sql = 'SELECT r.id, r.response_id as rid, r.question_id AS qid, r.choice_id AS cid, r.rankvalue ' . 'FROM {'.$this->response_table().'} r ' . 'INNER JOIN {questionnaire_quest_choice} c ON r.choice_id = c.id ' . 'WHERE r.question_id= ? ' . $rsql . ' ' . @@ -226,7 +226,7 @@ public function get_feedback_scores(array $rids) { $feedbackscores[$rid]->rid = $rid; $feedbackscores[$rid]->score = 0; } - $feedbackscores[$rid]->score += isset($scores[$response->rank]) ? $scores[$response->rank] : 0; + $feedbackscores[$rid]->score += isset($scores[$response->rankvalue]) ? $scores[$response->rankvalue] : 0; } return (!empty($feedbackscores) ? $feedbackscores : false); @@ -299,7 +299,7 @@ static public function response_select($rid, $col = null, $csvexport = false, $c $values = []; $sql = 'SELECT a.id as aid, q.id AS qid, q.precise AS precise, c.id AS cid '.$col.', c.content as ccontent, - a.rank as arank '. + a.rankvalue as arank '. 'FROM {'.self::response_table().'} a, {questionnaire_question} q, {questionnaire_quest_choice} c '. 'WHERE a.response_id= ? AND a.question_id=q.id AND a.choice_id=c.id '. 'ORDER BY aid, a.question_id, c.id'; diff --git a/classes/response/single.php b/classes/response/single.php index 4e005f93..40675bbe 100644 --- a/classes/response/single.php +++ b/classes/response/single.php @@ -280,8 +280,7 @@ protected function bulk_sql() { $userfields = $this->user_fields_sql(); $alias = 'qrs'; - $rank = $DB->get_manager()->generator->getEncQuoted('rank'); - $extraselect = 'qrs.choice_id, ' . $DB->sql_order_by_text('qro.response', 1000) . ' AS response, 0 AS ' . $rank; + $extraselect = 'qrs.choice_id, ' . $DB->sql_order_by_text('qro.response', 1000) . ' AS response, 0 AS rankvalue'; return " SELECT " . $DB->sql_concat_join("'_'", ['qr.id', "'".$this->question->helpname()."'", $alias.'.id']) . " AS id, diff --git a/db/install.xml b/db/install.xml index edbadb7a..7a19bbfe 100644 --- a/db/install.xml +++ b/db/install.xml @@ -192,7 +192,7 @@ - + diff --git a/db/upgrade.php b/db/upgrade.php index ffc7d963..716a4718 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -712,6 +712,19 @@ function xmldb_questionnaire_upgrade($oldversion=0) { upgrade_mod_savepoint(true, 2017111103, 'questionnaire'); } + // Rename the mdl_questionnaire_response_rank.rank field as it is reserved in MySQL as of 8.0.2. + if ($oldversion < 2017111105) { + // Change the name from rank to rankvalue. + $table = new xmldb_table('questionnaire_response_rank'); + $field = new xmldb_field('rank', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, null, null, null, '0', 'choice_id'); + if ($dbman->field_exists($table, $field)) { + $dbman->rename_field($table, $field, 'rankvalue'); + } + + // Questionnaire savepoint reached. + upgrade_mod_savepoint(true, 2017111105, 'questionnaire'); + } + return $result; } diff --git a/lang/en/questionnaire.php b/lang/en/questionnaire.php index 69ed2a7d..174efe61 100644 --- a/lang/en/questionnaire.php +++ b/lang/en/questionnaire.php @@ -406,7 +406,7 @@ $string['privacy:metadata:questionnaire_response_rank:response_id'] = 'The ID of the response record for this response.'; $string['privacy:metadata:questionnaire_response_rank:question_id'] = 'The ID of the question record for this response.'; $string['privacy:metadata:questionnaire_response_rank:choice_id'] = 'The ID of the choice record for this response.'; -$string['privacy:metadata:questionnaire_response_rank:rank'] = 'The specific rank answer.'; +$string['privacy:metadata:questionnaire_response_rank:rankvalue'] = 'The specific rank answer.'; $string['privacy:metadata:questionnaire_response_text'] = 'A text question response.'; $string['privacy:metadata:questionnaire_response_text:response_id'] = 'The ID of the response record for this response.'; diff --git a/questionnaire.class.php b/questionnaire.class.php index ea75ff08..c0238588 100644 --- a/questionnaire.class.php +++ b/questionnaire.class.php @@ -2991,7 +2991,7 @@ public function generate_csv($rid='', $userid='', $choicecodes=1, $choicetext=0, $key = $qid.'_'.$responserow->choice_id; $position = $questionpositions[$key]; if ($qtype === QUESRATE) { - $choicetxt = $responserow->rank + 1; + $choicetxt = $responserow->rankvalue + 1; } else { $content = $choicesbyqid[$qid][$responserow->choice_id]->content; if (preg_match('/^!other/', $content)) { diff --git a/tests/behat/behat_mod_questionnaire.php b/tests/behat/behat_mod_questionnaire.php index 3ce7a764..a500c95a 100644 --- a/tests/behat/behat_mod_questionnaire.php +++ b/tests/behat/behat_mod_questionnaire.php @@ -206,6 +206,8 @@ private function add_question_data($sid) { * @return null */ private function add_response_data($qid, $sid) { + global $DB; + $responses = array( array("id", "survey_id", "submitted", "complete", "grade", "userid"), array("1", $sid, "1419011935", "y", "0", "2"), @@ -272,7 +274,7 @@ private function add_response_data($qid, $sid) { array('responsemap' => 'response_id', 'questionmap' => 'question_id', 'choicemap' => 'choice_id')); $responserank = array( - array("id", "response_id", "question_id", "choice_id", "rank"), + array("id", "response_id", "question_id", "choice_id", "rankvalue"), array("", "1", "13", "16", "0"), array("", "1", "13", "17", "1"), array("", "1", "13", "18", "2"), diff --git a/tests/generator/lib.php b/tests/generator/lib.php index a757b4fc..a9b01baa 100644 --- a/tests/generator/lib.php +++ b/tests/generator/lib.php @@ -472,7 +472,7 @@ protected function add_response_choice($questionresponse, $responseid) { 'response_id' => $responseid, 'question_id' => $questionresponse->questionid, 'choice_id' => $choiceid, - 'rank' => $questionresponse->response->rank + 'rankvalue' => $questionresponse->response->rankvalue ] ); } else { diff --git a/tests/responsetypes_test.php b/tests/responsetypes_test.php index e40550d2..34c5f5b8 100644 --- a/tests/responsetypes_test.php +++ b/tests/responsetypes_test.php @@ -280,7 +280,7 @@ public function test_create_response_rank() { $this->assertEquals(3, count($multresponses)); foreach ($multresponses as $multresponse) { $this->assertEquals($question->id, $multresponse->question_id); - $this->assertEquals($vals[$multresponse->choice_id], $multresponse->rank); + $this->assertEquals($vals[$multresponse->choice_id], $multresponse->rankvalue); } } diff --git a/version.php b/version.php index 1305d404..46cd8a0c 100644 --- a/version.php +++ b/version.php @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2017111104; // The current module version (Date: YYYYMMDDXX) +$plugin->version = 2017111105; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2017042800; // Moodle version. $plugin->component = 'mod_questionnaire'; From ba5302f7b30bddc389ef8812efa555b1a72e76ea Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Mon, 10 Sep 2018 16:36:10 -0400 Subject: [PATCH 008/341] CONTRIB-7420 - Adding specialized code for MySQL upgrade. --- db/upgrade.php | 26 +++++++++++++++++++------- version.php | 2 +- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/db/upgrade.php b/db/upgrade.php index 716a4718..134603cd 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -713,16 +713,28 @@ function xmldb_questionnaire_upgrade($oldversion=0) { } // Rename the mdl_questionnaire_response_rank.rank field as it is reserved in MySQL as of 8.0.2. - if ($oldversion < 2017111105) { - // Change the name from rank to rankvalue. - $table = new xmldb_table('questionnaire_response_rank'); - $field = new xmldb_field('rank', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, null, null, null, '0', 'choice_id'); - if ($dbman->field_exists($table, $field)) { - $dbman->rename_field($table, $field, 'rankvalue'); + if ($oldversion < 2017111106) { + // Change the name from username to userid. + // Due to MDL-63310, the 'rename_field' function cannot be used for MySQL. Create special code for this. This can be + // replaced when MDL-63310 is fixed and released. + if ($DB->get_dbfamily() !== 'mysql') { + $table = new xmldb_table('questionnaire_response_rank'); + $field = new xmldb_field('rank', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, null, null, null, '0', 'choice_id'); + if ($dbman->field_exists($table, $field)) { + $dbman->rename_field($table, $field, 'rankvalue'); + } + } else { + if ($dbman->field_exists('questionnaire_response_rank', 'rank')) { + $rankoldfieldname = $DB->get_manager()->generator->getEncQuoted('rank'); + $ranknewfieldname = $DB->get_manager()->generator->getEncQuoted('rankvalue'); + $sql = 'ALTER TABLE {questionnaire_response_rank} ' . + 'CHANGE ' . $rankoldfieldname . ' ' . $ranknewfieldname . ' BIGINT(11) NOT NULL'; + $DB->execute($sql); + } } // Questionnaire savepoint reached. - upgrade_mod_savepoint(true, 2017111105, 'questionnaire'); + upgrade_mod_savepoint(true, 2017111106, 'questionnaire'); } return $result; diff --git a/version.php b/version.php index 46cd8a0c..afe493dc 100644 --- a/version.php +++ b/version.php @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2017111105; // The current module version (Date: YYYYMMDDXX) +$plugin->version = 2017111106; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2017042800; // Moodle version. $plugin->component = 'mod_questionnaire'; From d7bccf177fb3540c83d0256d63bb323f9bf808f1 Mon Sep 17 00:00:00 2001 From: Mark Sharp Date: Tue, 5 Jun 2018 16:02:01 +0100 Subject: [PATCH 009/341] CONTRIB-6982-M34_dataformat-api --- classes/output/renderer.php | 47 +++++++++++++++++++- report.php | 44 +++++++++++++------ templates/dataformat_selector.mustache | 59 ++++++++++++++++++++++++++ templates/extrafields.mustache | 2 + 4 files changed, 137 insertions(+), 15 deletions(-) mode change 100644 => 100755 classes/output/renderer.php mode change 100644 => 100755 report.php create mode 100755 templates/dataformat_selector.mustache create mode 100755 templates/extrafields.mustache diff --git a/classes/output/renderer.php b/classes/output/renderer.php old mode 100644 new mode 100755 index 58825ab4..e104e5db --- a/classes/output/renderer.php +++ b/classes/output/renderer.php @@ -444,4 +444,49 @@ public function flexible_table(\flexible_table $table, $buffering = false) { return $o; } -} \ No newline at end of file + + /** + * Returns a dataformat selection and download form + * + * @param string $label A text label + * @param moodle_url|string $base The download page url + * @param string $name The query param which will hold the type of the download + * @param array $params Extra params sent to the download page + * @param string $extrafields HTML for extra form fields + * @return string HTML fragment + */ + public function download_dataformat_selector($label, $base, $name = 'dataformat', $params = array(), $extrafields = '') { + + $formats = \core_plugin_manager::instance()->get_plugins_of_type('dataformat'); + $options = array(); + foreach ($formats as $format) { + if ($format->is_enabled()) { + $options[] = array( + 'value' => $format->name, + 'label' => get_string('dataformat', $format->component), + ); + } + } + $hiddenparams = array(); + foreach ($params as $key => $value) { + $hiddenparams[] = array( + 'name' => $key, + 'value' => $value, + ); + } + $data = array( + 'label' => $label, + 'base' => $base, + 'name' => $name, + 'params' => $hiddenparams, + 'options' => $options, + 'extrafields' => $extrafields, + 'sesskey' => sesskey(), + 'submit' => get_string('download'), + ); + + return $this->render_from_template('mod_questionnaire/dataformat_selector', $data); + } + + +} diff --git a/report.php b/report.php old mode 100644 new mode 100755 index 28fc482d..cd4b3b75 --- a/report.php +++ b/report.php @@ -444,19 +444,16 @@ $output .= ' '.(get_string('downloadtext')).': '.get_string('responses', 'questionnaire').' '.$groupname; $output .= $questionnaire->renderer->heading(get_string('textdownloadoptions', 'questionnaire')); $output .= $questionnaire->renderer->box_start(); - $output .= "
wwwroot}/mod/questionnaire/report.php\" method=\"GET\">\n"; - $output .= "\n"; - $output .= "\n"; - $output .= "\n"; - $output .= "\n"; - $output .= "\n"; - $output .= html_writer::checkbox('choicecodes', 1, true, get_string('includechoicecodes', 'questionnaire')); - $output .= "
\n"; - $output .= html_writer::checkbox('choicetext', 1, true, get_string('includechoicetext', 'questionnaire')); - $output .= "
\n"; - $output .= "
\n"; - $output .= "\n"; - $output .= "
\n"; + $downloadparams = [ + 'instance' => $instance, + 'user' => $user, + 'sid' => $sid, + 'action' => 'dfs', + 'group' => $currentgroupid + ]; + $extrafields = $questionnaire->renderer->render_from_template('mod_questionnaire/extrafields', []); + $output .= $questionnaire->renderer->download_dataformat_selector(get_string('download', 'questionnaire'), + 'report.php', 'downloadformat', $downloadparams, $extrafields); $output .= $questionnaire->renderer->box_end(); $questionnaire->page->add_to_page('respondentinfo', $output); @@ -499,6 +496,25 @@ } exit(); break; + case 'dfs': + require_capability('mod/questionnaire:downloadresponses', $context); + require_once($CFG->dirroot . '/lib/dataformatlib.php'); + // Use the questionnaire name as the file name. Clean it and change any non-filename characters to '_'. + $name = clean_param($questionnaire->name, PARAM_FILE); + $name = preg_replace("/[^A-Z0-9]+/i", "_", trim($name)); + + $choicecodes = optional_param('choicecodes', '0', PARAM_INT); + $choicetext = optional_param('choicetext', '0', PARAM_INT); + $dataformat = optional_param('downloadformat', '', PARAM_ALPHA); + + $output = $questionnaire->generate_csv('', $user, $choicecodes, $choicetext, $currentgroupid); + + $columns = $output[0]; + unset($output[0]); + download_as_dataformat($name, $dataformat, $columns, $output); + + exit(); + break; case 'vall': // View all responses. case 'vallasort': // View all responses sorted in ascending order. @@ -742,4 +758,4 @@ // Finish the page. echo $questionnaire->renderer->footer($course); break; -} \ No newline at end of file +} diff --git a/templates/dataformat_selector.mustache b/templates/dataformat_selector.mustache new file mode 100755 index 00000000..73ea5c44 --- /dev/null +++ b/templates/dataformat_selector.mustache @@ -0,0 +1,59 @@ +{{! + 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 . +}} +{{! + @template core/dataformat_selector + + Template for dataformat selection and download form. + + Context variables required for this template: + * label + * base + * name + * params + * options + * sesskey + * submit + * extrafields + + Example context (json): + { + "base": "http://example.org/", + "name": "test", + "label": "Download table data as", + "params": false, + "extrafields": '', + "options": [{"label": "CSV", "name": "csv"}, {"label": "Excel", "name": "excel"}], + "submit": "Download", + } +}} +
+
+ + {{#extrafields}}{{{extrafields}}}{{/extrafields}} + + + + + {{#params}} + + {{/params}} +
+
diff --git a/templates/extrafields.mustache b/templates/extrafields.mustache new file mode 100755 index 00000000..2bd0c8e1 --- /dev/null +++ b/templates/extrafields.mustache @@ -0,0 +1,2 @@ +
+
From e54d4efde8d588885b03045539c7165b5c3d20dc Mon Sep 17 00:00:00 2001 From: Mark Sharp Date: Thu, 18 Oct 2018 09:55:19 +0100 Subject: [PATCH 010/341] Fix template json --- templates/dataformat_selector.mustache | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/templates/dataformat_selector.mustache b/templates/dataformat_selector.mustache index 73ea5c44..1846746c 100755 --- a/templates/dataformat_selector.mustache +++ b/templates/dataformat_selector.mustache @@ -34,7 +34,10 @@ "base": "http://example.org/", "name": "test", "label": "Download table data as", - "params": false, + "params": { + "name": "fieldname", + "value": "defaultvalue" + }, "extrafields": '', "options": [{"label": "CSV", "name": "csv"}, {"label": "Excel", "name": "excel"}], "submit": "Download", From ea95c05c1afb9f9405e86112eebb1ffc9d2d34a5 Mon Sep 17 00:00:00 2001 From: Mark Sharp Date: Thu, 18 Oct 2018 10:06:43 +0100 Subject: [PATCH 011/341] add id for input. Failing on Travis --- templates/extrafields.mustache | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/extrafields.mustache b/templates/extrafields.mustache index 2bd0c8e1..e4010565 100755 --- a/templates/extrafields.mustache +++ b/templates/extrafields.mustache @@ -1,2 +1,2 @@ -
-
+
+
From 1a3be8dbf44d72993c5ff3c3f6169a7bd5febe03 Mon Sep 17 00:00:00 2001 From: Mark Sharp Date: Thu, 18 Oct 2018 10:27:40 +0100 Subject: [PATCH 012/341] rejigged json --- templates/dataformat_selector.mustache | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/templates/dataformat_selector.mustache b/templates/dataformat_selector.mustache index 1846746c..b5065553 100755 --- a/templates/dataformat_selector.mustache +++ b/templates/dataformat_selector.mustache @@ -15,7 +15,7 @@ along with Moodle. If not, see . }} {{! - @template core/dataformat_selector + @template mod_questionnaire/dataformat_selector Template for dataformat selection and download form. @@ -34,12 +34,23 @@ "base": "http://example.org/", "name": "test", "label": "Download table data as", - "params": { - "name": "fieldname", - "value": "defaultvalue" - }, - "extrafields": '', - "options": [{"label": "CSV", "name": "csv"}, {"label": "Excel", "name": "excel"}], + "params": [ + { + "name": "fieldname", + "value": "defaultvalue" + } + ], + "extrafields": "Input HTML", + "options": [ + { + "label": "CSV", + "name": "csv" + }, + { + "label": "Excel", + "name": "excel" + } + ], "submit": "Download", } }} From d25fc3a8fefebccb6f807e98b6ea63a256b7ae4c Mon Sep 17 00:00:00 2001 From: Mark Sharp Date: Fri, 19 Oct 2018 12:23:47 +0100 Subject: [PATCH 013/341] think I fixed the mustache error --- templates/dataformat_selector.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/dataformat_selector.mustache b/templates/dataformat_selector.mustache index b5065553..0ecfbaf5 100755 --- a/templates/dataformat_selector.mustache +++ b/templates/dataformat_selector.mustache @@ -51,7 +51,7 @@ "name": "excel" } ], - "submit": "Download", + "submit": "Download" } }}
From 79592521a939da39acefb52f8b37ae59dec9413e Mon Sep 17 00:00:00 2001 From: Mark Sharp Date: Fri, 19 Oct 2018 13:52:39 +0100 Subject: [PATCH 014/341] add template definition --- templates/extrafields.mustache | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/templates/extrafields.mustache b/templates/extrafields.mustache index e4010565..26c410c9 100755 --- a/templates/extrafields.mustache +++ b/templates/extrafields.mustache @@ -1,2 +1,24 @@ +{{! + 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 . +}} +{{! + @template mod_questionnaire/extrafields + + Templates which inserts a couple of extra inputs in the dataformat_selector. + + }}

From bc1a3b32a376871db82a31b11363aaad014e69cd Mon Sep 17 00:00:00 2001 From: Igor Sazonov Date: Wed, 31 Oct 2018 18:12:12 +0300 Subject: [PATCH 015/341] Moodle Mobile 3.5 support First integration to Moodle Mobile v3.5. --- classes/output/mobile.php | 210 +++++++++ db/mobile.php | 51 +++ db/services.php | 47 ++ externallib.php | 133 ++++++ javascript/mobile.js | 34 ++ lib.php | 430 ++++++++++++++++++ questionnaire.class.php | 43 ++ templates/mobile_view_activity_page.mustache | 173 +++++++ ..._view_activity_page_with_required.mustache | 181 ++++++++ version.php | 2 +- 10 files changed, 1303 insertions(+), 1 deletion(-) create mode 100644 classes/output/mobile.php create mode 100644 db/mobile.php create mode 100644 db/services.php create mode 100644 externallib.php create mode 100644 javascript/mobile.js create mode 100644 templates/mobile_view_activity_page.mustache create mode 100644 templates/mobile_view_activity_page_with_required.mustache diff --git a/classes/output/mobile.php b/classes/output/mobile.php new file mode 100644 index 00000000..7d577d2c --- /dev/null +++ b/classes/output/mobile.php @@ -0,0 +1,210 @@ +. + +/** + * Mobile output class for mod_questionnaire. + * + * @copyright 2018 Igor Sazonov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_questionnaire\output; + +defined('MOODLE_INTERNAL') || die(); + +class mobile { + + /** + * Returns the initial page when viewing the activity for the mobile app. + * + * @param array $args Arguments from tool_mobile_get_content WS + * @return array HTML, javascript and other data + */ + public static function mobile_view_activity($args) { + global $OUTPUT, $USER, $CFG, $DB; + + require_once($CFG->dirroot.'/mod/questionnaire/questionnaire.class.php'); + $args = (object) $args; + $cmid = $args->cmid; + $pagenum = (isset($args->pagenum) && !empty($args->pagenum)) ? intval($args->pagenum) : 1; + $prevpage = 0; + // Capabilities check. + $cm = get_coursemodule_from_id('questionnaire', $cmid); + $context = \context_module::instance($cmid); + self::require_capability($cm, $context, 'mod/questionnaire:view'); + // Set some variables we are going to be using. + $questionnaire = get_questionnaire_data($cmid, $USER->id); + if (isset($questionnaire['questions'][$pagenum-1]) && !empty($questionnaire['questions'][$pagenum-1])) { + $prevpage = $pagenum-1; + } + //$jsfilepath = $CFG->wwwroot . '/mod/questionnaire/javascript/mobile.js'; + $data = [ + 'questionnaire' => $questionnaire, + 'cmid' => $cmid, + 'courseid' => intval($cm->course), + 'pagenum' => $pagenum, + 'userid' => $USER->id, + 'nextpage' => 0, + 'prevpage' => 0, + 'emptypage' => false + ]; + // Check for required fields filled + $break = false; + if (($pagenum - 1) > 0 && isset($questionnaire['questions'][$pagenum - 1]) && !empty($questionnaire['questions'][$pagenum - 1])) { + $prepn = $pagenum - 1; + $cnt = 0; + while (($prepn) > 0 && isset($questionnaire['questions'][$prepn]) && !empty($questionnaire['questions'][$prepn])) { + if (($prepn) > 0 && isset($questionnaire['questions'][$prepn]) && !empty($questionnaire['questions'][$prepn])) { + $keys = array_keys($questionnaire['questions'][$prepn]); + foreach ($keys as $questionid) { + if (isset($questionnaire['questionsinfo'][$prepn][$questionid]) && + $questionnaire['questionsinfo'][$prepn][$questionid]['required'] === 'y' && + (!isset($questionnaire['answered'][$questionid]) || empty($questionnaire['answered'][$questionid]))) { + $pagenum = $prepn; + $prepn = 0; + $break = true; + break; + } else { + $cnt++; + if (count($keys) == $cnt) { + $break = true; + } + } + } + if ($break) { + break; + } + } + if ($break) { + break; + } + } + } + if (intval($args->pagenum) == $pagenum) { + if (isset($questionnaire['questions'][$pagenum-1]) && !empty($questionnaire['questions'][$pagenum-1])) { + $prevpage = $pagenum-1; + } + $questionnaireobj = new \questionnaire($questionnaire['questionnaire']['id'], null, + $DB->get_record('course', ['id' => $cm->course]), $cm); + $rid = $DB->get_field('questionnaire_response', 'id', + [ + 'questionnaireid' => $questionnaire['questionnaire']['questionnaireid'], + 'complete' => 'n', + 'userid' => $USER->id + ]); + if (isset($questionnaire['questions'][$pagenum]) && !empty($questionnaire['questions'][$pagenum])) { + // Maybe for future + //$qnumplus = 0; + // Search for the next page to output + while (!$questionnaireobj->eligible_questions_on_page($pagenum, $rid)) { + if (isset($questionnaire['questions'][$pagenum]) && !empty($questionnaire['questions'][$pagenum])) { + /*if ($questionnaire['questionnaire']['autonumquestions']) { + $qnumplus += count($questionnaire['questions'][$pagenum]); + }*/ + $pagenum++; + } else { + $cmid = 0; + break; + } + } + } + if ($prevpage > 0 && isset($questionnaire['questions'][$prevpage]) && !empty($questionnaire['questions'][$prevpage])) { + while (!$questionnaireobj->eligible_questions_on_page($prevpage, $rid)) { + if ($prevpage > 0 && isset($questionnaire['questions'][$prevpage]) && !empty($questionnaire['questions'][$prevpage])) { + $prevpage--; + } else { + break; + } + } + } + } + if ($cmid) { + $data['completed'] = (isset($questionnaire['response']['complete']) && $questionnaire['response']['complete'] == 'y') ? 1 : 0; + $data['complete_userdate'] = (isset($questionnaire['response']['complete']) && $questionnaire['response']['complete'] == 'y') ? + userdate($questionnaire['response']['submitted']) : ''; + if (isset($questionnaire['questions'][$pagenum])) { + $i = 0; + foreach ($questionnaire['questions'][$pagenum] as $questionid => $choices) { + if (isset($questionnaire['questionsinfo'][$pagenum][$questionid]) && !empty($questionnaire['questionsinfo'][$pagenum][$questionid])) { + $data['questions'][$pagenum][$i]['info'] = $questionnaire['questionsinfo'][$pagenum][$questionid]; + if ($data['questions'][$pagenum][$i]['info']['required'] == 'n') { + unset($data['questions'][$pagenum][$i]['info']['required']); + } + $ii = 0; + foreach ($choices as $k => $v) { + $data['questions'][$pagenum][$i]['choices'][$ii] = (array) $v; + $ii++; + } + if (count($choices) == 1) { + $data['questions'][$pagenum][$i]['value'] = $data['questions'][$pagenum][$i]['choices'][0]['value']; + } + $i++; + } + } + if (isset($data['questions'][$pagenum]) && !empty($data['questions'][$pagenum])) { + $i = 0; + foreach ($data['questions'][$pagenum] as $arr) { + $data['pagequestions'][$i] = $arr; + $i++; + } + } + if (isset($questionnaire['questions'][$pagenum+1]) && !empty($questionnaire['questions'][$pagenum+1])) { + $data['nextpage'] = $pagenum+1; + } + if ($prevpage) { + $data['prevpage'] = $prevpage; + } + } + } else { + $data['emptypage'] = true; + $data['emptypage_content'] = get_string('questionnaire:submit', 'questionnaire'); + } + return [ + 'templates' => [ + [ + 'id' => 'main', + 'html' => $OUTPUT->render_from_template('mod_questionnaire/mobile_view_activity_page', $data) + ], + ], + //'javascript' => file_get_contents($jsfilepath), + 'otherdata' => [ + 'fields' => json_encode($questionnaire['fields']), + 'questionsinfo' => json_encode($questionnaire['questionsinfo']), + 'questions' => json_encode($questionnaire['questions']), + 'pagequestions' => json_encode($data['pagequestions']), + 'responses' => json_encode($questionnaire['responses']), + 'pagenum' => $pagenum, + 'nextpage' => $data['nextpage'], + 'prevpage' => $data['prevpage'], + 'completed' => $data['completed'], + 'intro' => $questionnaire['questionnaire']['intro'], + 'string_required' => get_string('required') + ], + 'files' => null + ]; + } + + /** + * Confirms the user is logged in and has the specified capability. + * + * @param \stdClass $cm + * @param \context $context + * @param string $cap + */ + protected static function require_capability(\stdClass $cm, \context $context, string $cap) { + require_login($cm->course, false, $cm, true, true); + require_capability($cap, $context); + } +} \ No newline at end of file diff --git a/db/mobile.php b/db/mobile.php new file mode 100644 index 00000000..9923fd1f --- /dev/null +++ b/db/mobile.php @@ -0,0 +1,51 @@ +. + +/** + * Defines mobile handlers. + * + * @package mod_questionnaire + * @copyright 2018 Igor Sazonov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$addons = [ + 'mod_questionnaire' => [ + 'handlers' => [ + 'questionsview' => [ + 'displaydata' => [ + 'icon' => $CFG->wwwroot . '/mod/questionnaire/pix/icon.gif', + 'class' => '', + ], + 'delegate' => 'CoreCourseModuleDelegate', + 'method' => 'mobile_view_activity' + ] + ], + 'lang' => [ + ['yourresponse', 'questionnaire'], + ['submitted', 'questionnaire'], + ['areyousure', 'moodle'], + ['success', 'moodle'], + ['savechanges', 'moodle'], + ['nextpage', 'questionnaire'], + ['previouspage', 'questionnaire'], + ['fullname', 'moodle'], + ['required', 'moodle'] + ], + ] +]; \ No newline at end of file diff --git a/db/services.php b/db/services.php new file mode 100644 index 00000000..ca027ed1 --- /dev/null +++ b/db/services.php @@ -0,0 +1,47 @@ +. + +/** + * Questionnaire external functions and service definitions. + * + * @package mod_questionnaire + * @category external + * @copyright 2018 Igor Sazonov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 3.0 + */ + +defined('MOODLE_INTERNAL') || die; + +$services = [ + 'mod_questionnaire_ws' => [ + 'functions' => ['mod_questionnaire_submit_questionnaire_response'], + 'requiredcapability' => '', + 'enabled' => 1 + ] +]; + +$functions = [ + 'mod_questionnaire_submit_questionnaire_response' => [ + 'classname' => 'mod_questionnaire_external', + 'methodname' => 'submit_questionnaire_response', + 'classpath' => 'mod/questionnaire/externallib.php', + 'description' => 'Questionnaire submit', + 'type' => 'write', + 'capabilities' => 'mod/questionnaire:submit', + 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE] + ] +]; \ No newline at end of file diff --git a/externallib.php b/externallib.php new file mode 100644 index 00000000..f47d0fdc --- /dev/null +++ b/externallib.php @@ -0,0 +1,133 @@ +. + +/** + * Questionnaire module external API + * + * @package mod_questionnaire + * @category external + * @copyright 2018 Igor Sazonov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 3.0 + */ + +defined('MOODLE_INTERNAL') || die; +require_once($CFG->libdir . '/externallib.php'); +require_once($CFG->dirroot . '/mod/questionnaire/lib.php'); + +/** + * Questionnaire module external functions + * + * @package mod_questionnaire + * @category external + * @copyright 2018 Igor Sazonov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 3.0 + */ +class mod_questionnaire_external extends \external_api { + + /** + * Describes the parameters for submit_questionnaire_response. + * + * @return external_function_parameters + * @since Moodle 3.0 + */ + public static function submit_questionnaire_response_parameters() { + return new \external_function_parameters( + [ + 'questionnaireid' => new \external_value(PARAM_INT, 'Questionnaire instance id'), + 'surveyid' => new \external_value(PARAM_INT, 'Survey id'), + 'userid' => new \external_value(PARAM_INT, 'User id'), + 'cmid' => new \external_value(PARAM_INT, 'Course module id'), + 'sec' => new \external_value(PARAM_INT, 'Section number'), + 'completed' => new \external_value(PARAM_INT, 'Completed survey or not'), + 'submit' => new \external_value(PARAM_INT, 'Submit survey or not'), + 'responses' => new \external_multiple_structure( + new \external_single_structure( + [ + 'name' => new \external_value(PARAM_RAW, 'data key'), + 'value' => new \external_value(PARAM_RAW, 'data value') + ] + ), + 'The data to be saved', VALUE_DEFAULT, [] + ) + ] + ); + } + + /** + * Submit questionnaire responses + * + * @param int $questionnaireid the questionnaire instance id + * @param int $surveyid Survey id + * @param int $userid User id + * @param int $cmid Course module id + * @param int $sec Section number + * @param int $completed Completed survey 1/0 + * @param int $submit Submit survey? + * @param array $responses the response ids + * @return array answers information and warnings + * @since Moodle 3.0 + */ + public static function submit_questionnaire_response($questionnaireid, $surveyid, $userid, $cmid, $sec, $completed, $submit, $responses) { + $params = self::validate_parameters(self::submit_questionnaire_response_parameters(), + [ + 'questionnaireid' => $questionnaireid, + 'surveyid' => $surveyid, + 'userid' => $userid, + 'cmid' => $cmid, + 'sec' => $sec, + 'completed' => $completed, + 'submit' => $submit, + 'responses' => $responses + ] + ); + + if (!$questionnaire = get_questionnaire($params['questionnaireid'])) { + throw new \moodle_exception("invalidcoursemodule", "error"); + } + list($course, $cm) = get_course_and_cm_from_instance($questionnaire, 'questionnaire'); + + $context = \context_module::instance($cm->id); + self::validate_context($context); + + require_capability('mod/questionnaire:submit', $context); + + $result = save_questionnaire_data($questionnaireid, $surveyid, $userid, $cmid, $sec, $completed, $submit, $responses); + $result['submitted'] = true; + if (isset($result['warnings']) && !empty($result['warnings'])) { + unset($result['responses']); + $result['submitted'] = false; + } + $result['warnings'] = []; + return $result; + } + + /** + * Describes the submit_questionnaire_response return value. + * + * @return external_multiple_structure + * @since Moodle 3.0 + */ + public static function submit_questionnaire_response_returns() { + return new \external_single_structure( + [ + 'submitted' => new \external_value(PARAM_BOOL, 'submitted', true, false, false), + 'warnings' => new \external_warnings() + ] + ); + } +} \ No newline at end of file diff --git a/javascript/mobile.js b/javascript/mobile.js new file mode 100644 index 00000000..dc2defed --- /dev/null +++ b/javascript/mobile.js @@ -0,0 +1,34 @@ +// 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 . + +/** + * Support for Moodle Mobile integration + */ + + +var questionsFormFields = {}; +this.questionsFormErrors = {}; +for (const fieldkey in this.CONTENT_OTHERDATA.fields) { + questionsFormFields[fieldkey] = []; + questionsFormFields[fieldkey][0] = ''; + for (const itemid in this.CONTENT_OTHERDATA.questions[this.CONTENT_OTHERDATA.pagenum][this.CONTENT_OTHERDATA.fields[fieldkey].id]) { + questionsFormFields[fieldkey][0] = this.CONTENT_OTHERDATA.questions[this.CONTENT_OTHERDATA.pagenum][this.CONTENT_OTHERDATA.fields[fieldkey].id][itemid].value; + } + if (this.CONTENT_OTHERDATA.fields[fieldkey].required === 'y') { + questionsFormFields[fieldkey][1] = this.Validators.required; + this.questionsFormErrors[fieldkey] = this.CONTENT_OTHERDATA.fields[fieldkey].errormessage; + } +} +this.questionsForm = this.FormBuilder.group(questionsFormFields); \ No newline at end of file diff --git a/lib.php b/lib.php index c8c6cead..e1345c8b 100644 --- a/lib.php +++ b/lib.php @@ -61,6 +61,11 @@ function questionnaire_get_extra_capabilities() { return array('moodle/site:accessallgroups'); } +function get_questionnaire($questionnaireid) { + global $DB; + return $DB->get_record('questionnaire', array('id' => $questionnaireid)); +} + function questionnaire_add_instance($questionnaire) { // Given an object containing all the necessary data, // (defined by the form in mod.html) this function @@ -461,6 +466,431 @@ function questionnaire_scale_used_anywhere($scaleid) { return false; } +/** + * Get questionnaire data + * + * @global object $DB + * @param int $cmid + * @param int|bool $userid + * @return array + * @throws moodle_exception + */ +function get_questionnaire_data($cmid, $userid = false) { + global $DB, $USER; + if ($q = get_coursemodule_from_id('questionnaire', $cmid)) { + if (!$questionnaire = get_questionnaire($q->instance)) { + throw new \moodle_exception("invalidcoursemodule", "error"); + } + } + $ret = [ + 'questionnaire' => [ + 'id' => $questionnaire->id, + 'name' => format_string($questionnaire->name), + 'intro' => $questionnaire->intro, + 'userid' => intval($userid ? $userid : $USER->id), + 'questionnaireid' => intval($questionnaire->sid), + 'autonumpages' => in_array($questionnaire->autonum, [1, 2]), + 'autonumquestions' => in_array($questionnaire->autonum, [1, 3]) + ], + 'response' => [ + 'id' => 0, + 'questionnaireid' => 0, + 'submitted' => 0, + 'complete' => 'n', + 'grade' => 0, + 'userid' => 0, + 'fullname' => '', + 'userdate' => '', + ], + 'answered' => [], + 'fields' => [], + 'responses' => [], + 'questionscount' => 0, + 'pagescount' => 1, + ]; + $sql = 'SELECT qq.*,qqt.response_table FROM ' + . '{questionnaire_question} qq LEFT JOIN {questionnaire_question_type} qqt ' + . 'ON qq.type_id = qqt.typeid WHERE qq.survey_id = ? AND qq.deleted = ? ' + . 'ORDER BY qq.position'; + if ($questions = $DB->get_records_sql($sql, [$questionnaire->sid, 'n'])) { + require_once('classes/question/base.php'); + $pagenum = 1; + $context = \context_module::instance($cmid); + $qnum = 0; + foreach ($questions as $question) { + $ret['questionscount']++; + $qnum++; + $fieldkey = 'response_'.$question->type_id.'_'.$question->id; + $options = ['noclean' => true, 'para' => false, 'filter' => true, + 'context' => $context, 'overflowdiv' => true]; + if ($question->type_id != QUESPAGEBREAK) { + $ret['questionsinfo'][$pagenum][$question->id] = + $ret['fields'][$fieldkey] = [ + 'id' => $question->id, + 'survey_id' => $question->survey_id, + 'name' => $question->name, + 'type_id' => $question->type_id, + 'length' => $question->length, + 'content' => ($ret['questionnaire']['autonumquestions'] ? '' : '') . format_text(file_rewrite_pluginfile_urls( + $question->content, 'pluginfile.php', $context->id, + 'mod_questionnaire', 'question', $question->id), + FORMAT_HTML, $options), + 'content_stripped' => strip_tags($question->content), + 'required' => $question->required, + 'deleted' => $question->deleted, + 'response_table' => $question->response_table, + 'fieldkey' => $fieldkey, + 'precise' => $question->precise, + 'qnum' => $qnum, + 'errormessage' => get_string('required') . ': ' . $question->name + ]; + } + $std = new \stdClass(); + $std->id = $std->choice_id = 0; + $std->question_id = $question->id; + $std->content = ''; + $std->value = null; + switch ($question->type_id) { + case QUESYESNO: // Yes/No bool + $stdyes = new \stdClass(); + $stdyes->id = 1; + $stdyes->choice_id = 'y'; + $stdyes->question_id = $question->id; + $stdyes->value = null; + $stdyes->content = get_string('yes'); + $stdyes->isbool = true; + if ($ret['questionsinfo'][$pagenum][$question->id]['required']) { + $stdyes->value = 'y'; + $stdyes->firstone = true; + } + $ret['questions'][$pagenum][$question->id][1] = $stdyes; + $stdno = new \stdClass(); + $stdno->id = 0; + $stdno->choice_id = 'n'; + $stdno->question_id = $question->id; + $stdno->value = null; + $stdno->content = get_string('no'); + $stdno->isbool = true; + $ret['questions'][$pagenum][$question->id][0] = $stdno; + $ret['questionsinfo'][$pagenum][$question->id]['isbool'] = true; + $ret['responses']['response_'.$question->type_id.'_'.$question->id] = 'n'; + break; + case QUESTEXT: // Text + case QUESESSAY: // Essay + $ret['questions'][$pagenum][$question->id][0] = $std; + $ret['questionsinfo'][$pagenum][$question->id]['istextessay'] = true; + break; + case QUESRADIO: // Radiobutton + case QUESCHECK: // Checkbox + case QUESDROP: // Select + case QUESRATE: // Rate 1-NN + $excludes = []; + if ($items = $DB->get_records('questionnaire_quest_choice', + ['question_id' => $question->id])) { + if ($question->type_id == QUESRADIO) { + $ret['questionsinfo'][$pagenum][$question->id]['isradiobutton'] = true; + } + if ($question->type_id == QUESCHECK) { + $ret['questionsinfo'][$pagenum][$question->id]['ischeckbox'] = true; + } + if ($question->type_id == QUESDROP) { + $ret['questionsinfo'][$pagenum][$question->id]['isselect'] = true; + } + if ($question->type_id == QUESRATE) { + $ret['questionsinfo'][$pagenum][$question->id]['israte'] = true; + $vals = $extracontents = []; + foreach ($items as $item) { + $item->na = false; + if ($question->precise == 0) { + $ret['questions'][$pagenum][$question->id][$item->id] = $item; + if ($ret['questionsinfo'][$pagenum][$question->id]['required'] == 'y') { + $ret['questions'][$pagenum][$question->id][$item->id]->min + = $ret['questions'][$pagenum][$question->id][$item->id]->minstr = 1; + } else { + $ret['questions'][$pagenum][$question->id][$item->id]->min + = $ret['questions'][$pagenum][$question->id][$item->id]->minstr = 0; + } + $ret['questions'][$pagenum][$question->id][$item->id]->max + = $ret['questions'][$pagenum][$question->id][$item->id]->maxstr + = intval($question->length); + } else if ($question->precise == 1) { + $ret['questions'][$pagenum][$question->id][$item->id] = $item; + if ($ret['questionsinfo'][$pagenum][$question->id]['required'] == 'y') { + $ret['questions'][$pagenum][$question->id][$item->id]->min + = $ret['questions'][$pagenum][$question->id][$item->id]->minstr = 1; + } else { + $ret['questions'][$pagenum][$question->id][$item->id]->min + = $ret['questions'][$pagenum][$question->id][$item->id]->minstr = 0; + } + $ret['questions'][$pagenum][$question->id][$item->id]->max = intval($question->length) + 1; + $ret['questions'][$pagenum][$question->id][$item->id]->na = true; + $extracontents[] = $ret['questions'][$pagenum][$question->id][$item->id]->max + . ' = ' . get_string('notapplicable', 'mod_questionnaire'); + } else if ($question->precise > 1) { + $excludes[$item->id] = $item->id; + if ($item->value == null) { + if ($arr = explode('|', $item->content)) { + if (count($arr) == 2) { + $ret['questions'][$pagenum][$question->id][$item->id] = $item; + $ret['questions'][$pagenum][$question->id][$item->id]->content = ''; + $ret['questions'][$pagenum][$question->id][$item->id]->minstr = $arr[0]; + $ret['questions'][$pagenum][$question->id][$item->id]->maxstr = $arr[1]; + } + } + } else { + $val = intval($item->value); + $vals[$val] = $val; + $extracontents[] = $item->content; + } + } + } + if ($vals) { + if ($q = $ret['questions'][$pagenum][$question->id]) { + foreach (array_keys($q) as $itemid) { + $ret['questions'][$pagenum][$question->id][$itemid]->min = min($vals); + $ret['questions'][$pagenum][$question->id][$itemid]->max = max($vals); + } + } + } + if ($extracontents) { + $extracontents = array_unique($extracontents); + $extrahtml = '
    '; + foreach ($extracontents as $extracontent) { + $extrahtml .= '
  • '.$extracontent.'
  • '; + } + $extrahtml .= '
'; + $ret['questionsinfo'][$pagenum][$question->id]['content'] .= format_text($extrahtml, FORMAT_HTML, $options); + } + } + foreach ($items as $item) { + if (!in_array($item->id, $excludes)) { + $item->choice_id = $item->id; + if ($item->value == null) { + $item->value = ''; + } + $ret['questions'][$pagenum][$question->id][$item->id] = $item; + if ($question->type_id != QUESRATE) { + if ($ret['questionsinfo'][$pagenum][$question->id]['required']) { + if (!isset($ret['questionsinfo'][$pagenum][$question->id]['firstone'])) { + $ret['questionsinfo'][$pagenum][$question->id]['firstone'] = true; + $ret['questions'][$pagenum][$question->id][$item->id]->value = intval($item->choice_id); + $ret['questions'][$pagenum][$question->id][$item->id]->firstone = true; + } + } + } + } + } + } + break; + case QUESPAGEBREAK: + $ret['questionscount']--; + $ret['pagescount']++; + $pagenum++; + $qnum--; + continue; + } + $ret['questionsinfo'][$pagenum][$question->id]['qnum'] = $qnum; + if ($ret['questionnaire']['autonumquestions']) { + $ret['questionsinfo'][$pagenum][$question->id]['content'] = + $qnum.'. '.$ret['questionsinfo'][$pagenum][$question->id]['content']; + $ret['questionsinfo'][$pagenum][$question->id]['content_stripped'] = + $qnum.'. '.$ret['questionsinfo'][$pagenum][$question->id]['content_stripped']; + } + } + if ($userid) { + if ($response = $DB->get_record_sql('SELECT qr.* FROM {questionnaire_response} qr ' + . 'LEFT JOIN {user} u ON qr.userid = u.id WHERE qr.questionnaireid = ? ' + . 'AND qr.userid = ?', [$questionnaire->id, $userid])) { + $ret['response'] = (array) $response; + $ret['response']['submitted_userdate'] = ''; + if (isset($ret['response']['submitted']) && !empty($ret['response']['submitted'])) { + $ret['response']['submitted_userdate'] = userdate($ret['response']['submitted']); + } + $ret['response']['fullname'] = fullname($DB->get_record('user', ['id' => $userid])); + $ret['response']['userdate'] = userdate($ret['response']['submitted']); + foreach ($ret['questionsinfo'] as $pagenum => $data1) { + foreach ($data1 as $questionid => $data2) { + $ret['answered'][$questionid] = false; + if (isset($data2['response_table']) && !empty($data2['response_table'])) { + if ($values = $DB->get_records_sql('SELECT * FROM {questionnaire_' + . $data2['response_table'] . '} WHERE response_id = ? AND question_id = ?', + [$response->id, $questionid])) { + foreach ($values as $value) { + switch($data2['type_id']) { + case QUESYESNO: // Yes/No bool + if (isset($ret['questions'][$pagenum][$questionid])) { + if (isset($value->choice_id) && !empty($value->choice_id)) { + $ret['answered'][$questionid] = true; + if ($value->choice_id == 'y') { + $ret['questions'][$pagenum][$questionid][1]->value = 'y'; + $ret['responses']['response_'.$data2['type_id'].'_'.$questionid] = 'y'; + } else { + $ret['questions'][$pagenum][$questionid][0]->value = 'n'; + $ret['responses']['response_'.$data2['type_id'].'_'.$questionid] = 'n'; + } + } + } + break; + case QUESTEXT: // Text + case QUESESSAY: // Essay + if (isset($value->response) && !empty($value->response)) { + $ret['answered'][$questionid] = true; + $ret['questions'][$pagenum][$questionid][0]->value = $value->response; + $ret['responses']['response_'.$data2['type_id'].'_'.$questionid] = $value->response; + } + break; + case QUESRADIO: // Radiobutton + case QUESCHECK: // Checkbox + case QUESDROP: // Select + if ($value = $DB->get_records_sql('SELECT * FROM {questionnaire_' + . $data2['response_table'] . '} WHERE response_id = ? AND question_id = ?', + [$response->id, $questionid])) { + foreach ($value as $row) { + foreach ($ret['questions'][$pagenum][$questionid] as $k => $item) { + if ($item->id == $row->choice_id) { + $ret['answered'][$questionid] = true; + $ret['questions'][$pagenum][$questionid][$k]->value = intval($item->id); + $ret['responses']['response_'.$data2['type_id'].'_'.$questionid] = intval($item->id); + } + } + } + } + break; + case QUESRATE: // Rate 1-NN + if ($value = $DB->get_records_sql('SELECT * FROM {questionnaire_' + . $data2['response_table'] . '} WHERE response_id = ? AND question_id = ?', + [$response->id, $questionid])) { + foreach ($value as $row) { + if ($questionid == $row->question_id) { + $ret['answered'][$questionid] = true; + $v = $row->rankvalue + 1; + if ($ret['questionsinfo'][$pagenum][$questionid]['precise'] == 1) { + if ($row->rankvalue == -1) { + $v = $ret['questions'][$pagenum][$questionid][$row->choice_id]->max; + } + } + $ret['questions'][$pagenum][$questionid][$row->choice_id]->value + = $ret['responses']['response_'.$data2['type_id'].'_'.$questionid.'_'.$row->choice_id] = $v; + $ret['questions'][$pagenum][$questionid][$row->choice_id]->choice_id = $row->choice_id; + } + } + } + break; + } + } + } + } + } + } + } + } + } + return $ret; +} + +function save_questionnaire_data($questionnaireid, $surveyid, $userid, $cmid, $sec, $completed, $submit, array $responses) { + global $DB, $CFG; //do not delete $CFG!!! + $ret = [ + 'responses' => [], + 'warnings' => [] + ]; + if (!$completed) { + require_once('questionnaire.class.php'); + $cm = get_coursemodule_from_id('questionnaire', $cmid); + $questionnaire = new \questionnaire($questionnaireid, null, + $DB->get_record('course', ['id' => $cm->course]), $cm); + $rid = $questionnaire->delete_insert_response( + $DB->get_field('questionnaire_response', 'id', + ['questionnaireid' => $surveyid, 'complete' => 'n', + 'userid' => $userid]), $sec, $userid); + $questionnairedata = get_questionnaire_data($cmid, $userid); + $pagequestions = isset($questionnairedata['questions'][$sec]) ? $questionnairedata['questions'][$sec] : []; + if (!empty($pagequestions)) { + $pagequestionsids = array_keys($pagequestions); + $missingquestions = $warningmessages = []; + foreach ($pagequestionsids as $questionid) { + $missingquestions[$questionid] = $questionid; + } + foreach ($pagequestionsids as $questionid) { + foreach ($responses as $response) { + $args = explode('_', $response['name']); + if (count($args) >= 3) { + $typeid = intval($args[1]); + $rquestionid = intval($args[2]); + if (in_array($rquestionid, $pagequestionsids)) { + unset($missingquestions[$rquestionid]); + if ($rquestionid == $questionid) { + if ($typeid == $questionnairedata['questionsinfo'][$sec][$rquestionid]['type_id']) { + if ($rquestionid > 0 && !in_array($response['value'], array(-9999, 'undefined'))) { + switch ($questionnairedata['questionsinfo'][$sec][$rquestionid]['type_id']) { + case QUESRATE: + if (isset($args[3]) && !empty($args[3])) { + $choiceid = intval($args[3]); + $value = intval($response['value']) - 1; + $rec = new \stdClass(); + $rec->response_id = $rid; + $rec->question_id = intval($rquestionid); + $rec->choice_id = $choiceid; + $rec->rankvalue = $value; + if ($questionnairedata['questionsinfo'][$sec][$rquestionid]['precise'] == 1) { + if ($value == $questionnairedata['questions'][$sec][$rquestionid][$choiceid]->max) { + $rec->rank = -1; + } + } + $DB->insert_record('questionnaire_response_rank', $rec); + } + break; + default: + if ($questionnairedata['questionsinfo'][$sec][$rquestionid]['required'] == 'n' + || ($questionnairedata['questionsinfo'][$sec][$rquestionid]['required'] == 'y' + && !empty($response['value']))) { + $questionobj = \mod_questionnaire\question\base::question_builder( + $questionnairedata['questionsinfo'][$sec][$rquestionid]['type_id'], + $questionnairedata['questionsinfo'][$sec][$rquestionid]); + if ($questionobj->insert_response($rid, $response['value'])) { + $ret['responses'][$rid][$questionid] = $response['value']; + } + } else { + $ret['warnings'][] = [ + 'item' => 'mod_questionnaire_question', + 'itemid' => $questionid, + 'warningcode' => 'required', + 'message' => s(get_string('required') . ': ' . $questionnairedata['questionsinfo'][$sec][$questionid]['name']) + ]; + } + } + } else { + $missingquestions[$rquestionid] = $rquestionid; + } + } + } + } + } + } + } + if ($missingquestions) { + foreach ($missingquestions as $questionid) { + if ($questionnairedata['questionsinfo'][$sec][$questionid]['required'] == 'y') { + $ret['warnings'][] = [ + 'item' => 'mod_questionnaire_question', + 'itemid' => $questionid, + 'warningcode' => 'required', + 'message' => s(get_string('required') . ': ' . $questionnairedata['questionsinfo'][$sec][$questionid]['name']) + ]; + } + } + } + } + } + if ($submit && (!isset($ret['warnings']) || empty($ret['warnings']))) { + $questionnaire->commit_submission_response( + $DB->get_field('questionnaire_response', 'id', + ['questionnaireid' => $surveyid, 'complete' => 'n', + 'userid' => $userid]), $userid); + } + return $ret; +} + /** * Serves the questionnaire attachments. Implements needed access control ;-) * diff --git a/questionnaire.class.php b/questionnaire.class.php index 1816fad3..de80b4d7 100644 --- a/questionnaire.class.php +++ b/questionnaire.class.php @@ -278,6 +278,49 @@ public function view() { } } + public function delete_insert_response($rid, $sec, $quser) { + $this->response_delete($rid, $sec); + $this->rid = $this->response_insert($sec, $rid, $quser); + return $this->rid; + } + + public function commit_submission_response($rid, $quser) { + $this->response_commit($rid); + // If it was a previous save, rid is in the form... + if (!empty($rid) && is_numeric($rid)) { + $rid = $rid; + // Otherwise its in this object. + } else { + $rid = $this->rid; + } + if ($this->grade != 0) { + $questionnaire = new \stdClass(); + $questionnaire->id = $this->id; + $questionnaire->name = $this->name; + $questionnaire->grade = $this->grade; + $questionnaire->cmidnumber = $this->cm->idnumber; + $questionnaire->courseid = $this->course->id; + questionnaire_update_grades($questionnaire, $quser); + } + // Update completion state. + $completion = new \completion_info($this->course); + if ($completion->is_enabled($this->cm) && $this->completionsubmit) { + $completion->update_state($this->cm, COMPLETION_COMPLETE); + } + // Log this submitted response. + $context = \context_module::instance($this->cm->id); + $anonymous = $this->respondenttype == 'anonymous'; + $params = [ + 'context' => $context, + 'courseid' => $this->course->id, + 'relateduserid' => $quser, + 'anonymous' => $anonymous, + 'other' => array('questionnaireid' => $this->id) + ]; + $event = \mod_questionnaire\event\attempt_submitted::create($params); + $event->trigger(); + } + /* * Function to view an entire responses data. * diff --git a/templates/mobile_view_activity_page.mustache b/templates/mobile_view_activity_page.mustache new file mode 100644 index 00000000..0437be63 --- /dev/null +++ b/templates/mobile_view_activity_page.mustache @@ -0,0 +1,173 @@ +{{! + 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 . +}} +{{=<% %>=}} +
+ + <%#completed%> + + + {{ 'plugin.mod_questionnaire.submitted' | translate }} <%complete_userdate%> + + + <%/completed%> +
+ + + <%^emptypage%> + <%#pagequestions%> + + <%#questionnaire.questionnaire.autonumquestions%><%info.qnum%><%/questionnaire.questionnaire.autonumquestions%> + + <%#info.required%>{{ 'plugin.mod_questionnaire.required' | translate }}<%/info.required%> + + <%#info.isselect%> + + <%#choices%> + disabled="true"<%/completed%><%#value%> selected="true"<%/value%> value="<%id%>"><%content%> + <%/choices%> + + <%/info.isselect%> + <%#info.isbool%> + + <%#choices%> + + + disabled="true"<%#value%> checked="true"<%/value%><%/completed%> value="<%choice_id%>"> + + <%/choices%> + + <%/info.isbool%> + <%#info.isradiobutton%> + + <%#choices%> + + + checked="true"<%/value%> + value="<%choice_id%>" + <%#completed%> disabled="true"<%/completed%>> + + <%/choices%> + + <%/info.isradiobutton%> + <%#info.ischeckbox%> + + <%#choices%> + + + checked="true"<%/value%> + value="<%choice_id%>"<%#completed%> disabled="true"<%/completed%> + > + + <%/choices%> + + <%/info.ischeckbox%> + <%#info.istextessay%> + + <%^completed%> + + <%/completed%> + + <%/info.istextessay%> + <%#info.israte%> + <%#choices%> + + + {{ CONTENT_OTHERDATA.responses['response_<%info.type_id%>_<%info.id%>_<%id%>'] }} + + + + <%minstr%> + <%#na%><%/na%><%^na%><%maxstr%><%/na%> + + + <%/choices%> + <%/info.israte%> + <%/pagequestions%> + <%/emptypage%> + <%#emptypage%> + + + + <%/emptypage%> + <%#prevpage%> + + <%/prevpage%> + <%#nextpage%> + + <%/nextpage%> + <%^completed%> + <%^nextpage%> + + <%/nextpage%> + <%/completed%> + + +
+
\ No newline at end of file diff --git a/templates/mobile_view_activity_page_with_required.mustache b/templates/mobile_view_activity_page_with_required.mustache new file mode 100644 index 00000000..427307d5 --- /dev/null +++ b/templates/mobile_view_activity_page_with_required.mustache @@ -0,0 +1,181 @@ +{{! + 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 . +}} +{{=<% %>=}} +
+ + <%#completed%> + + + {{ 'plugin.mod_questionnaire.submitted' | translate }} <%complete_userdate%> + + + <%/completed%> +
+ + <%#completed%><%/completed%> + <%^completed%><%/completed%> + <%#pagequestions%> + + + <%#info.required%>{{ 'plugin.mod_questionnaire.required' | translate }}<%/info.required%> + + <%#info.isselect%> + name="<%info.fieldkey%>" formControlName="<%info.fieldkey%>"<%/completed%>> + <%#choices%> + disabled="true"<%/completed%><%#value%> selected="true"<%/value%> value="<%id%>"><%content%> + <%/choices%> + + <%^completed%><%/completed%> + <%/info.isselect%> + <%#info.isbool%> + name="<%info.fieldkey%>" formControlName="<%info.fieldkey%>"<%/completed%>> + <%#choices%> + + + disabled="true"<%#value%> checked="true"<%/value%><%/completed%> value="<%choice_id%>"> + + <%/choices%> + + <%^completed%><%/completed%> + <%/info.isbool%> + <%#info.isradiobutton%> + name="<%info.fieldkey%>" formControlName="<%info.fieldkey%>"<%/completed%>> + <%#choices%> + + + checked="true"<%/value%> + value="<%choice_id%>" + <%#completed%> disabled="true"<%/completed%>> + + <%/choices%> + + <%^completed%><%/completed%> + <%/info.isradiobutton%> + <%#info.ischeckbox%> + name="<%info.fieldkey%>" formControlName="<%info.fieldkey%>"<%/completed%>> + <%#choices%> + + + checked="true"<%/value%> + value="<%choice_id%>"<%#completed%> disabled="true"<%/completed%> + > + + <%/choices%> + + <%^completed%><%/completed%> + <%/info.ischeckbox%> + <%#info.istextessay%> + + <%^completed%> + + + <%/completed%> + <%#completed%> + + <%/completed%> + + <%/info.istextessay%> + <%#info.israte%> + <%#choices%> + + + {{ CONTENT_OTHERDATA.responses['response_<%info.type_id%>_<%info.id%>_<%id%>'] }} + + + name="<%info.fieldkey%>" formControlName="<%info.fieldkey%>"<%/completed%>> + <%minstr%> + <%#na%><%/na%><%^na%><%maxstr%><%/na%> + + + <%/choices%> + <%/info.israte%> + <%/pagequestions%> + <%#prevpage%> + + <%/prevpage%> + <%#nextpage%> + + <%/nextpage%> + <%^completed%> + <%^nextpage%> + + <%/nextpage%> + <%/completed%> + <%#completed%><%/completed%> + <%^completed%><%/completed%> + +
+
\ No newline at end of file diff --git a/version.php b/version.php index eca16bcc..d209f6eb 100644 --- a/version.php +++ b/version.php @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2018050106; // The current module version (Date: YYYYMMDDXX) +$plugin->version = 2018103101; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2017042800; // Moodle version. $plugin->component = 'mod_questionnaire'; From 0f9b89ecbe43231c16570f95f6b5e6eddb82dca2 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Tue, 6 Nov 2018 14:17:34 -0500 Subject: [PATCH 016/341] Poet - keeping version strategy intact. --- version.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.php b/version.php index 595f2f99..a7dad047 100644 --- a/version.php +++ b/version.php @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2018103101; // The current module version (Date: YYYYMMDDXX) +$plugin->version = 2018050107; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2017042800; // Moodle version. $plugin->component = 'mod_questionnaire'; From e53a12d7c8b4b13cca2bd2a0c05e476806176807 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Tue, 6 Nov 2018 17:23:37 -0500 Subject: [PATCH 017/341] Poet - making Travis happy. --- classes/output/mobile.php | 416 +++++++++++++++++++------------------- db/mobile.php | 100 ++++----- db/services.php | 92 ++++----- externallib.php | 264 ++++++++++++------------ lib.php | 11 +- 5 files changed, 439 insertions(+), 444 deletions(-) diff --git a/classes/output/mobile.php b/classes/output/mobile.php index 7d577d2c..33831a2b 100644 --- a/classes/output/mobile.php +++ b/classes/output/mobile.php @@ -1,210 +1,208 @@ -. - -/** - * Mobile output class for mod_questionnaire. - * - * @copyright 2018 Igor Sazonov - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -namespace mod_questionnaire\output; - -defined('MOODLE_INTERNAL') || die(); - -class mobile { - - /** - * Returns the initial page when viewing the activity for the mobile app. - * - * @param array $args Arguments from tool_mobile_get_content WS - * @return array HTML, javascript and other data - */ - public static function mobile_view_activity($args) { - global $OUTPUT, $USER, $CFG, $DB; - - require_once($CFG->dirroot.'/mod/questionnaire/questionnaire.class.php'); - $args = (object) $args; - $cmid = $args->cmid; - $pagenum = (isset($args->pagenum) && !empty($args->pagenum)) ? intval($args->pagenum) : 1; - $prevpage = 0; - // Capabilities check. - $cm = get_coursemodule_from_id('questionnaire', $cmid); - $context = \context_module::instance($cmid); - self::require_capability($cm, $context, 'mod/questionnaire:view'); - // Set some variables we are going to be using. - $questionnaire = get_questionnaire_data($cmid, $USER->id); - if (isset($questionnaire['questions'][$pagenum-1]) && !empty($questionnaire['questions'][$pagenum-1])) { - $prevpage = $pagenum-1; - } - //$jsfilepath = $CFG->wwwroot . '/mod/questionnaire/javascript/mobile.js'; - $data = [ - 'questionnaire' => $questionnaire, - 'cmid' => $cmid, - 'courseid' => intval($cm->course), - 'pagenum' => $pagenum, - 'userid' => $USER->id, - 'nextpage' => 0, - 'prevpage' => 0, - 'emptypage' => false - ]; - // Check for required fields filled - $break = false; - if (($pagenum - 1) > 0 && isset($questionnaire['questions'][$pagenum - 1]) && !empty($questionnaire['questions'][$pagenum - 1])) { - $prepn = $pagenum - 1; - $cnt = 0; - while (($prepn) > 0 && isset($questionnaire['questions'][$prepn]) && !empty($questionnaire['questions'][$prepn])) { - if (($prepn) > 0 && isset($questionnaire['questions'][$prepn]) && !empty($questionnaire['questions'][$prepn])) { - $keys = array_keys($questionnaire['questions'][$prepn]); - foreach ($keys as $questionid) { - if (isset($questionnaire['questionsinfo'][$prepn][$questionid]) && - $questionnaire['questionsinfo'][$prepn][$questionid]['required'] === 'y' && - (!isset($questionnaire['answered'][$questionid]) || empty($questionnaire['answered'][$questionid]))) { - $pagenum = $prepn; - $prepn = 0; - $break = true; - break; - } else { - $cnt++; - if (count($keys) == $cnt) { - $break = true; - } - } - } - if ($break) { - break; - } - } - if ($break) { - break; - } - } - } - if (intval($args->pagenum) == $pagenum) { - if (isset($questionnaire['questions'][$pagenum-1]) && !empty($questionnaire['questions'][$pagenum-1])) { - $prevpage = $pagenum-1; - } - $questionnaireobj = new \questionnaire($questionnaire['questionnaire']['id'], null, - $DB->get_record('course', ['id' => $cm->course]), $cm); - $rid = $DB->get_field('questionnaire_response', 'id', - [ - 'questionnaireid' => $questionnaire['questionnaire']['questionnaireid'], - 'complete' => 'n', - 'userid' => $USER->id - ]); - if (isset($questionnaire['questions'][$pagenum]) && !empty($questionnaire['questions'][$pagenum])) { - // Maybe for future - //$qnumplus = 0; - // Search for the next page to output - while (!$questionnaireobj->eligible_questions_on_page($pagenum, $rid)) { - if (isset($questionnaire['questions'][$pagenum]) && !empty($questionnaire['questions'][$pagenum])) { - /*if ($questionnaire['questionnaire']['autonumquestions']) { - $qnumplus += count($questionnaire['questions'][$pagenum]); - }*/ - $pagenum++; - } else { - $cmid = 0; - break; - } - } - } - if ($prevpage > 0 && isset($questionnaire['questions'][$prevpage]) && !empty($questionnaire['questions'][$prevpage])) { - while (!$questionnaireobj->eligible_questions_on_page($prevpage, $rid)) { - if ($prevpage > 0 && isset($questionnaire['questions'][$prevpage]) && !empty($questionnaire['questions'][$prevpage])) { - $prevpage--; - } else { - break; - } - } - } - } - if ($cmid) { - $data['completed'] = (isset($questionnaire['response']['complete']) && $questionnaire['response']['complete'] == 'y') ? 1 : 0; - $data['complete_userdate'] = (isset($questionnaire['response']['complete']) && $questionnaire['response']['complete'] == 'y') ? - userdate($questionnaire['response']['submitted']) : ''; - if (isset($questionnaire['questions'][$pagenum])) { - $i = 0; - foreach ($questionnaire['questions'][$pagenum] as $questionid => $choices) { - if (isset($questionnaire['questionsinfo'][$pagenum][$questionid]) && !empty($questionnaire['questionsinfo'][$pagenum][$questionid])) { - $data['questions'][$pagenum][$i]['info'] = $questionnaire['questionsinfo'][$pagenum][$questionid]; - if ($data['questions'][$pagenum][$i]['info']['required'] == 'n') { - unset($data['questions'][$pagenum][$i]['info']['required']); - } - $ii = 0; - foreach ($choices as $k => $v) { - $data['questions'][$pagenum][$i]['choices'][$ii] = (array) $v; - $ii++; - } - if (count($choices) == 1) { - $data['questions'][$pagenum][$i]['value'] = $data['questions'][$pagenum][$i]['choices'][0]['value']; - } - $i++; - } - } - if (isset($data['questions'][$pagenum]) && !empty($data['questions'][$pagenum])) { - $i = 0; - foreach ($data['questions'][$pagenum] as $arr) { - $data['pagequestions'][$i] = $arr; - $i++; - } - } - if (isset($questionnaire['questions'][$pagenum+1]) && !empty($questionnaire['questions'][$pagenum+1])) { - $data['nextpage'] = $pagenum+1; - } - if ($prevpage) { - $data['prevpage'] = $prevpage; - } - } - } else { - $data['emptypage'] = true; - $data['emptypage_content'] = get_string('questionnaire:submit', 'questionnaire'); - } - return [ - 'templates' => [ - [ - 'id' => 'main', - 'html' => $OUTPUT->render_from_template('mod_questionnaire/mobile_view_activity_page', $data) - ], - ], - //'javascript' => file_get_contents($jsfilepath), - 'otherdata' => [ - 'fields' => json_encode($questionnaire['fields']), - 'questionsinfo' => json_encode($questionnaire['questionsinfo']), - 'questions' => json_encode($questionnaire['questions']), - 'pagequestions' => json_encode($data['pagequestions']), - 'responses' => json_encode($questionnaire['responses']), - 'pagenum' => $pagenum, - 'nextpage' => $data['nextpage'], - 'prevpage' => $data['prevpage'], - 'completed' => $data['completed'], - 'intro' => $questionnaire['questionnaire']['intro'], - 'string_required' => get_string('required') - ], - 'files' => null - ]; - } - - /** - * Confirms the user is logged in and has the specified capability. - * - * @param \stdClass $cm - * @param \context $context - * @param string $cap - */ - protected static function require_capability(\stdClass $cm, \context $context, string $cap) { - require_login($cm->course, false, $cm, true, true); - require_capability($cap, $context); - } +. + +/** + * Mobile output class for mod_questionnaire. + * + * @copyright 2018 Igor Sazonov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_questionnaire\output; + +defined('MOODLE_INTERNAL') || die(); + +class mobile { + + /** + * Returns the initial page when viewing the activity for the mobile app. + * + * @param array $args Arguments from tool_mobile_get_content WS + * @return array HTML, javascript and other data + */ + public static function mobile_view_activity($args) { + global $OUTPUT, $USER, $CFG, $DB; + + require_once($CFG->dirroot.'/mod/questionnaire/questionnaire.class.php'); + $args = (object) $args; + $cmid = $args->cmid; + $pagenum = (isset($args->pagenum) && !empty($args->pagenum)) ? intval($args->pagenum) : 1; + $prevpage = 0; + // Capabilities check. + $cm = get_coursemodule_from_id('questionnaire', $cmid); + $context = \context_module::instance($cmid); + self::require_capability($cm, $context, 'mod/questionnaire:view'); + // Set some variables we are going to be using. + $questionnaire = get_questionnaire_data($cmid, $USER->id); + if (isset($questionnaire['questions'][$pagenum - 1]) && !empty($questionnaire['questions'][$pagenum - 1])) { + $prevpage = $pagenum - 1; + } + + $data = [ + 'questionnaire' => $questionnaire, + 'cmid' => $cmid, + 'courseid' => intval($cm->course), + 'pagenum' => $pagenum, + 'userid' => $USER->id, + 'nextpage' => 0, + 'prevpage' => 0, + 'emptypage' => false + ]; + // Check for required fields filled. + $break = false; + if (($pagenum - 1) > 0 && isset($questionnaire['questions'][$pagenum - 1]) && + !empty($questionnaire['questions'][$pagenum - 1])) { + $prepn = $pagenum - 1; + $cnt = 0; + while (($prepn) > 0 && isset($questionnaire['questions'][$prepn]) && !empty($questionnaire['questions'][$prepn])) { + if (($prepn) > 0 && isset($questionnaire['questions'][$prepn]) && !empty($questionnaire['questions'][$prepn])) { + $keys = array_keys($questionnaire['questions'][$prepn]); + foreach ($keys as $questionid) { + if (isset($questionnaire['questionsinfo'][$prepn][$questionid]) && + $questionnaire['questionsinfo'][$prepn][$questionid]['required'] === 'y' && + (!isset($questionnaire['answered'][$questionid]) || empty($questionnaire['answered'][$questionid]))) { + $pagenum = $prepn; + $prepn = 0; + $break = true; + break; + } else { + $cnt++; + if (count($keys) == $cnt) { + $break = true; + } + } + } + if ($break) { + break; + } + } + if ($break) { + break; + } + } + } + if (intval($args->pagenum) == $pagenum) { + if (isset($questionnaire['questions'][$pagenum - 1]) && !empty($questionnaire['questions'][$pagenum - 1])) { + $prevpage = $pagenum - 1; + } + $questionnaireobj = new \questionnaire($questionnaire['questionnaire']['id'], null, + $DB->get_record('course', ['id' => $cm->course]), $cm); + $rid = $DB->get_field('questionnaire_response', 'id', + [ + 'questionnaireid' => $questionnaire['questionnaire']['questionnaireid'], + 'complete' => 'n', + 'userid' => $USER->id + ]); + if (isset($questionnaire['questions'][$pagenum]) && !empty($questionnaire['questions'][$pagenum])) { + // Search for the next page to output. + while (!$questionnaireobj->eligible_questions_on_page($pagenum, $rid)) { + if (isset($questionnaire['questions'][$pagenum]) && !empty($questionnaire['questions'][$pagenum])) { + $pagenum++; + } else { + $cmid = 0; + break; + } + } + } + if ($prevpage > 0 && isset($questionnaire['questions'][$prevpage]) && !empty($questionnaire['questions'][$prevpage])) { + while (!$questionnaireobj->eligible_questions_on_page($prevpage, $rid)) { + if ($prevpage > 0 && isset($questionnaire['questions'][$prevpage]) && + !empty($questionnaire['questions'][$prevpage])) { + $prevpage--; + } else { + break; + } + } + } + } + if ($cmid) { + $data['completed'] = (isset($questionnaire['response']['complete']) && + $questionnaire['response']['complete'] == 'y') ? 1 : 0; + $data['complete_userdate'] = (isset($questionnaire['response']['complete']) && + $questionnaire['response']['complete'] == 'y') ? userdate($questionnaire['response']['submitted']) : ''; + if (isset($questionnaire['questions'][$pagenum])) { + $i = 0; + foreach ($questionnaire['questions'][$pagenum] as $questionid => $choices) { + if (isset($questionnaire['questionsinfo'][$pagenum][$questionid]) && + !empty($questionnaire['questionsinfo'][$pagenum][$questionid])) { + $data['questions'][$pagenum][$i]['info'] = $questionnaire['questionsinfo'][$pagenum][$questionid]; + if ($data['questions'][$pagenum][$i]['info']['required'] == 'n') { + unset($data['questions'][$pagenum][$i]['info']['required']); + } + $ii = 0; + foreach ($choices as $k => $v) { + $data['questions'][$pagenum][$i]['choices'][$ii] = (array) $v; + $ii++; + } + if (count($choices) == 1) { + $data['questions'][$pagenum][$i]['value'] = $data['questions'][$pagenum][$i]['choices'][0]['value']; + } + $i++; + } + } + if (isset($data['questions'][$pagenum]) && !empty($data['questions'][$pagenum])) { + $i = 0; + foreach ($data['questions'][$pagenum] as $arr) { + $data['pagequestions'][$i] = $arr; + $i++; + } + } + if (isset($questionnaire['questions'][$pagenum + 1]) && !empty($questionnaire['questions'][$pagenum + 1])) { + $data['nextpage'] = $pagenum + 1; + } + if ($prevpage) { + $data['prevpage'] = $prevpage; + } + } + } else { + $data['emptypage'] = true; + $data['emptypage_content'] = get_string('questionnaire:submit', 'questionnaire'); + } + return [ + 'templates' => [ + [ + 'id' => 'main', + 'html' => $OUTPUT->render_from_template('mod_questionnaire/mobile_view_activity_page', $data) + ], + ], + 'otherdata' => [ + 'fields' => json_encode($questionnaire['fields']), + 'questionsinfo' => json_encode($questionnaire['questionsinfo']), + 'questions' => json_encode($questionnaire['questions']), + 'pagequestions' => json_encode($data['pagequestions']), + 'responses' => json_encode($questionnaire['responses']), + 'pagenum' => $pagenum, + 'nextpage' => $data['nextpage'], + 'prevpage' => $data['prevpage'], + 'completed' => $data['completed'], + 'intro' => $questionnaire['questionnaire']['intro'], + 'string_required' => get_string('required') + ], + 'files' => null + ]; + } + + /** + * Confirms the user is logged in and has the specified capability. + * + * @param \stdClass $cm + * @param \context $context + * @param string $cap + */ + protected static function require_capability(\stdClass $cm, \context $context, string $cap) { + require_login($cm->course, false, $cm, true, true); + require_capability($cap, $context); + } } \ No newline at end of file diff --git a/db/mobile.php b/db/mobile.php index 9923fd1f..4f6d5f0d 100644 --- a/db/mobile.php +++ b/db/mobile.php @@ -1,51 +1,51 @@ -. - -/** - * Defines mobile handlers. - * - * @package mod_questionnaire - * @copyright 2018 Igor Sazonov - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -$addons = [ - 'mod_questionnaire' => [ - 'handlers' => [ - 'questionsview' => [ - 'displaydata' => [ - 'icon' => $CFG->wwwroot . '/mod/questionnaire/pix/icon.gif', - 'class' => '', - ], - 'delegate' => 'CoreCourseModuleDelegate', - 'method' => 'mobile_view_activity' - ] - ], - 'lang' => [ - ['yourresponse', 'questionnaire'], - ['submitted', 'questionnaire'], - ['areyousure', 'moodle'], - ['success', 'moodle'], - ['savechanges', 'moodle'], - ['nextpage', 'questionnaire'], - ['previouspage', 'questionnaire'], - ['fullname', 'moodle'], - ['required', 'moodle'] - ], - ] +. + +/** + * Defines mobile handlers. + * + * @package mod_questionnaire + * @copyright 2018 Igor Sazonov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$addons = [ + 'mod_questionnaire' => [ + 'handlers' => [ + 'questionsview' => [ + 'displaydata' => [ + 'icon' => $CFG->wwwroot . '/mod/questionnaire/pix/icon.gif', + 'class' => '', + ], + 'delegate' => 'CoreCourseModuleDelegate', + 'method' => 'mobile_view_activity' + ] + ], + 'lang' => [ + ['yourresponse', 'questionnaire'], + ['submitted', 'questionnaire'], + ['areyousure', 'moodle'], + ['success', 'moodle'], + ['savechanges', 'moodle'], + ['nextpage', 'questionnaire'], + ['previouspage', 'questionnaire'], + ['fullname', 'moodle'], + ['required', 'moodle'] + ], + ] ]; \ No newline at end of file diff --git a/db/services.php b/db/services.php index ca027ed1..4453ed7e 100644 --- a/db/services.php +++ b/db/services.php @@ -1,47 +1,47 @@ -. - -/** - * Questionnaire external functions and service definitions. - * - * @package mod_questionnaire - * @category external - * @copyright 2018 Igor Sazonov - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 3.0 - */ - -defined('MOODLE_INTERNAL') || die; - -$services = [ - 'mod_questionnaire_ws' => [ - 'functions' => ['mod_questionnaire_submit_questionnaire_response'], - 'requiredcapability' => '', - 'enabled' => 1 - ] -]; - -$functions = [ - 'mod_questionnaire_submit_questionnaire_response' => [ - 'classname' => 'mod_questionnaire_external', - 'methodname' => 'submit_questionnaire_response', - 'classpath' => 'mod/questionnaire/externallib.php', - 'description' => 'Questionnaire submit', - 'type' => 'write', - 'capabilities' => 'mod/questionnaire:submit', - 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE] - ] +. + +/** + * Questionnaire external functions and service definitions. + * + * @package mod_questionnaire + * @category external + * @copyright 2018 Igor Sazonov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 3.0 + */ + +defined('MOODLE_INTERNAL') || die; + +$services = [ + 'mod_questionnaire_ws' => [ + 'functions' => ['mod_questionnaire_submit_questionnaire_response'], + 'requiredcapability' => '', + 'enabled' => 1 + ] +]; + +$functions = [ + 'mod_questionnaire_submit_questionnaire_response' => [ + 'classname' => 'mod_questionnaire_external', + 'methodname' => 'submit_questionnaire_response', + 'classpath' => 'mod/questionnaire/externallib.php', + 'description' => 'Questionnaire submit', + 'type' => 'write', + 'capabilities' => 'mod/questionnaire:submit', + 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE] + ] ]; \ No newline at end of file diff --git a/externallib.php b/externallib.php index f47d0fdc..12cc2624 100644 --- a/externallib.php +++ b/externallib.php @@ -1,133 +1,133 @@ -. - -/** - * Questionnaire module external API - * - * @package mod_questionnaire - * @category external - * @copyright 2018 Igor Sazonov - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 3.0 - */ - -defined('MOODLE_INTERNAL') || die; -require_once($CFG->libdir . '/externallib.php'); -require_once($CFG->dirroot . '/mod/questionnaire/lib.php'); - -/** - * Questionnaire module external functions - * - * @package mod_questionnaire - * @category external - * @copyright 2018 Igor Sazonov - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 3.0 - */ -class mod_questionnaire_external extends \external_api { - - /** - * Describes the parameters for submit_questionnaire_response. - * - * @return external_function_parameters - * @since Moodle 3.0 - */ - public static function submit_questionnaire_response_parameters() { - return new \external_function_parameters( - [ - 'questionnaireid' => new \external_value(PARAM_INT, 'Questionnaire instance id'), - 'surveyid' => new \external_value(PARAM_INT, 'Survey id'), - 'userid' => new \external_value(PARAM_INT, 'User id'), - 'cmid' => new \external_value(PARAM_INT, 'Course module id'), - 'sec' => new \external_value(PARAM_INT, 'Section number'), - 'completed' => new \external_value(PARAM_INT, 'Completed survey or not'), - 'submit' => new \external_value(PARAM_INT, 'Submit survey or not'), - 'responses' => new \external_multiple_structure( - new \external_single_structure( - [ - 'name' => new \external_value(PARAM_RAW, 'data key'), - 'value' => new \external_value(PARAM_RAW, 'data value') - ] - ), - 'The data to be saved', VALUE_DEFAULT, [] - ) - ] - ); - } - - /** - * Submit questionnaire responses - * - * @param int $questionnaireid the questionnaire instance id - * @param int $surveyid Survey id - * @param int $userid User id - * @param int $cmid Course module id - * @param int $sec Section number - * @param int $completed Completed survey 1/0 - * @param int $submit Submit survey? - * @param array $responses the response ids - * @return array answers information and warnings - * @since Moodle 3.0 - */ - public static function submit_questionnaire_response($questionnaireid, $surveyid, $userid, $cmid, $sec, $completed, $submit, $responses) { - $params = self::validate_parameters(self::submit_questionnaire_response_parameters(), - [ - 'questionnaireid' => $questionnaireid, - 'surveyid' => $surveyid, - 'userid' => $userid, - 'cmid' => $cmid, - 'sec' => $sec, - 'completed' => $completed, - 'submit' => $submit, - 'responses' => $responses - ] - ); - - if (!$questionnaire = get_questionnaire($params['questionnaireid'])) { - throw new \moodle_exception("invalidcoursemodule", "error"); - } - list($course, $cm) = get_course_and_cm_from_instance($questionnaire, 'questionnaire'); - - $context = \context_module::instance($cm->id); - self::validate_context($context); - - require_capability('mod/questionnaire:submit', $context); - - $result = save_questionnaire_data($questionnaireid, $surveyid, $userid, $cmid, $sec, $completed, $submit, $responses); - $result['submitted'] = true; - if (isset($result['warnings']) && !empty($result['warnings'])) { - unset($result['responses']); - $result['submitted'] = false; - } - $result['warnings'] = []; - return $result; - } - - /** - * Describes the submit_questionnaire_response return value. - * - * @return external_multiple_structure - * @since Moodle 3.0 - */ - public static function submit_questionnaire_response_returns() { - return new \external_single_structure( - [ - 'submitted' => new \external_value(PARAM_BOOL, 'submitted', true, false, false), - 'warnings' => new \external_warnings() - ] - ); - } +. + +/** + * Questionnaire module external API + * + * @package mod_questionnaire + * @category external + * @copyright 2018 Igor Sazonov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 3.0 + */ + +defined('MOODLE_INTERNAL') || die; +require_once($CFG->libdir . '/externallib.php'); +require_once($CFG->dirroot . '/mod/questionnaire/lib.php'); + +/** + * Questionnaire module external functions + * + * @package mod_questionnaire + * @category external + * @copyright 2018 Igor Sazonov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 3.0 + */ +class mod_questionnaire_external extends \external_api { + + /** + * Describes the parameters for submit_questionnaire_response. + * + * @return external_function_parameters + * @since Moodle 3.0 + */ + public static function submit_questionnaire_response_parameters() { + return new \external_function_parameters( + [ + 'questionnaireid' => new \external_value(PARAM_INT, 'Questionnaire instance id'), + 'surveyid' => new \external_value(PARAM_INT, 'Survey id'), + 'userid' => new \external_value(PARAM_INT, 'User id'), + 'cmid' => new \external_value(PARAM_INT, 'Course module id'), + 'sec' => new \external_value(PARAM_INT, 'Section number'), + 'completed' => new \external_value(PARAM_INT, 'Completed survey or not'), + 'submit' => new \external_value(PARAM_INT, 'Submit survey or not'), + 'responses' => new \external_multiple_structure( + new \external_single_structure( + [ + 'name' => new \external_value(PARAM_RAW, 'data key'), + 'value' => new \external_value(PARAM_RAW, 'data value') + ] + ), + 'The data to be saved', VALUE_DEFAULT, [] + ) + ] + ); + } + + /** + * Submit questionnaire responses + * + * @param int $questionnaireid the questionnaire instance id + * @param int $surveyid Survey id + * @param int $userid User id + * @param int $cmid Course module id + * @param int $sec Section number + * @param int $completed Completed survey 1/0 + * @param int $submit Submit survey? + * @param array $responses the response ids + * @return array answers information and warnings + * @since Moodle 3.0 + */ + public static function submit_questionnaire_response($questionnaireid, $surveyid, $userid, $cmid, $sec, $completed, $submit, $responses) { + $params = self::validate_parameters(self::submit_questionnaire_response_parameters(), + [ + 'questionnaireid' => $questionnaireid, + 'surveyid' => $surveyid, + 'userid' => $userid, + 'cmid' => $cmid, + 'sec' => $sec, + 'completed' => $completed, + 'submit' => $submit, + 'responses' => $responses + ] + ); + + if (!$questionnaire = get_questionnaire($params['questionnaireid'])) { + throw new \moodle_exception("invalidcoursemodule", "error"); + } + list($course, $cm) = get_course_and_cm_from_instance($questionnaire, 'questionnaire'); + + $context = \context_module::instance($cm->id); + self::validate_context($context); + + require_capability('mod/questionnaire:submit', $context); + + $result = save_questionnaire_data($questionnaireid, $surveyid, $userid, $cmid, $sec, $completed, $submit, $responses); + $result['submitted'] = true; + if (isset($result['warnings']) && !empty($result['warnings'])) { + unset($result['responses']); + $result['submitted'] = false; + } + $result['warnings'] = []; + return $result; + } + + /** + * Describes the submit_questionnaire_response return value. + * + * @return external_multiple_structure + * @since Moodle 3.0 + */ + public static function submit_questionnaire_response_returns() { + return new \external_single_structure( + [ + 'submitted' => new \external_value(PARAM_BOOL, 'submitted', true, false, false), + 'warnings' => new \external_warnings() + ] + ); + } } \ No newline at end of file diff --git a/lib.php b/lib.php index e1345c8b..9e263189 100644 --- a/lib.php +++ b/lib.php @@ -789,7 +789,7 @@ function get_questionnaire_data($cmid, $userid = false) { } function save_questionnaire_data($questionnaireid, $surveyid, $userid, $cmid, $sec, $completed, $submit, array $responses) { - global $DB, $CFG; //do not delete $CFG!!! + global $DB, $CFG; // Do not delete $CFG!!! $ret = [ 'responses' => [], 'warnings' => [] @@ -797,12 +797,9 @@ function save_questionnaire_data($questionnaireid, $surveyid, $userid, $cmid, $s if (!$completed) { require_once('questionnaire.class.php'); $cm = get_coursemodule_from_id('questionnaire', $cmid); - $questionnaire = new \questionnaire($questionnaireid, null, - $DB->get_record('course', ['id' => $cm->course]), $cm); - $rid = $questionnaire->delete_insert_response( - $DB->get_field('questionnaire_response', 'id', - ['questionnaireid' => $surveyid, 'complete' => 'n', - 'userid' => $userid]), $sec, $userid); + $questionnaire = new \questionnaire($questionnaireid, null, $DB->get_record('course', ['id' => $cm->course]), $cm); + $rid = $questionnaire->delete_insert_response($DB->get_field('questionnaire_response', 'id', + ['questionnaireid' => $surveyid, 'complete' => 'n', 'userid' => $userid]), $sec, $userid); $questionnairedata = get_questionnaire_data($cmid, $userid); $pagequestions = isset($questionnairedata['questions'][$sec]) ? $questionnairedata['questions'][$sec] : []; if (!empty($pagequestions)) { From 172930e1e7aa22f8133b93a9b8e0ddea723a01a9 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Wed, 7 Nov 2018 11:00:18 -0500 Subject: [PATCH 018/341] Poet - Renaming lib file functions to use 'questionnaire_' prefix. --- classes/output/mobile.php | 2 +- externallib.php | 4 ++-- lib.php | 10 +++++----- questionnaire.class.php | 39 +++++++++++++++++++++------------------ 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/classes/output/mobile.php b/classes/output/mobile.php index 33831a2b..1eadc49f 100644 --- a/classes/output/mobile.php +++ b/classes/output/mobile.php @@ -45,7 +45,7 @@ public static function mobile_view_activity($args) { $context = \context_module::instance($cmid); self::require_capability($cm, $context, 'mod/questionnaire:view'); // Set some variables we are going to be using. - $questionnaire = get_questionnaire_data($cmid, $USER->id); + $questionnaire = questionnaire_get_mobile_data($cmid, $USER->id); if (isset($questionnaire['questions'][$pagenum - 1]) && !empty($questionnaire['questions'][$pagenum - 1])) { $prevpage = $pagenum - 1; } diff --git a/externallib.php b/externallib.php index 12cc2624..ce459680 100644 --- a/externallib.php +++ b/externallib.php @@ -96,7 +96,7 @@ public static function submit_questionnaire_response($questionnaireid, $surveyid ] ); - if (!$questionnaire = get_questionnaire($params['questionnaireid'])) { + if (!$questionnaire = questionnaire_get_instance($params['questionnaireid'])) { throw new \moodle_exception("invalidcoursemodule", "error"); } list($course, $cm) = get_course_and_cm_from_instance($questionnaire, 'questionnaire'); @@ -106,7 +106,7 @@ public static function submit_questionnaire_response($questionnaireid, $surveyid require_capability('mod/questionnaire:submit', $context); - $result = save_questionnaire_data($questionnaireid, $surveyid, $userid, $cmid, $sec, $completed, $submit, $responses); + $result = questionnaire_save_mobile_data($questionnaireid, $surveyid, $userid, $cmid, $sec, $completed, $submit, $responses); $result['submitted'] = true; if (isset($result['warnings']) && !empty($result['warnings'])) { unset($result['responses']); diff --git a/lib.php b/lib.php index 9e263189..4ec1c8ba 100644 --- a/lib.php +++ b/lib.php @@ -61,7 +61,7 @@ function questionnaire_get_extra_capabilities() { return array('moodle/site:accessallgroups'); } -function get_questionnaire($questionnaireid) { +function questionnaire_get_instance($questionnaireid) { global $DB; return $DB->get_record('questionnaire', array('id' => $questionnaireid)); } @@ -475,10 +475,10 @@ function questionnaire_scale_used_anywhere($scaleid) { * @return array * @throws moodle_exception */ -function get_questionnaire_data($cmid, $userid = false) { +function questionnaire_get_mobile_data($cmid, $userid = false) { global $DB, $USER; if ($q = get_coursemodule_from_id('questionnaire', $cmid)) { - if (!$questionnaire = get_questionnaire($q->instance)) { + if (!$questionnaire = questionnaire_get_instance($q->instance)) { throw new \moodle_exception("invalidcoursemodule", "error"); } } @@ -788,7 +788,7 @@ function get_questionnaire_data($cmid, $userid = false) { return $ret; } -function save_questionnaire_data($questionnaireid, $surveyid, $userid, $cmid, $sec, $completed, $submit, array $responses) { +function questionnaire_save_mobile_data($questionnaireid, $surveyid, $userid, $cmid, $sec, $completed, $submit, array $responses) { global $DB, $CFG; // Do not delete $CFG!!! $ret = [ 'responses' => [], @@ -800,7 +800,7 @@ function save_questionnaire_data($questionnaireid, $surveyid, $userid, $cmid, $s $questionnaire = new \questionnaire($questionnaireid, null, $DB->get_record('course', ['id' => $cm->course]), $cm); $rid = $questionnaire->delete_insert_response($DB->get_field('questionnaire_response', 'id', ['questionnaireid' => $surveyid, 'complete' => 'n', 'userid' => $userid]), $sec, $userid); - $questionnairedata = get_questionnaire_data($cmid, $userid); + $questionnairedata = questionnaire_get_mobile_data($cmid, $userid); $pagequestions = isset($questionnairedata['questions'][$sec]) ? $questionnairedata['questions'][$sec] : []; if (!empty($pagequestions)) { $pagequestionsids = array_keys($pagequestions); diff --git a/questionnaire.class.php b/questionnaire.class.php index de80b4d7..a36224b2 100644 --- a/questionnaire.class.php +++ b/questionnaire.class.php @@ -243,15 +243,7 @@ public function view() { $rid = $this->rid; } - if ($this->grade != 0) { - $questionnaire = new stdClass(); - $questionnaire->id = $this->id; - $questionnaire->name = $this->name; - $questionnaire->grade = $this->grade; - $questionnaire->cmidnumber = $this->cm->idnumber; - $questionnaire->courseid = $this->course->id; - questionnaire_update_grades($questionnaire, $quser); - } + $this->update_grades($quser); // Update completion state. $completion = new completion_info($this->course); @@ -293,15 +285,9 @@ public function commit_submission_response($rid, $quser) { } else { $rid = $this->rid; } - if ($this->grade != 0) { - $questionnaire = new \stdClass(); - $questionnaire->id = $this->id; - $questionnaire->name = $this->name; - $questionnaire->grade = $this->grade; - $questionnaire->cmidnumber = $this->cm->idnumber; - $questionnaire->courseid = $this->course->id; - questionnaire_update_grades($questionnaire, $quser); - } + + $this->update_grades($quser); + // Update completion state. $completion = new \completion_info($this->course); if ($completion->is_enabled($this->cm) && $this->completionsubmit) { @@ -321,6 +307,23 @@ public function commit_submission_response($rid, $quser) { $event->trigger(); } + /** + * Update the grade for this questionnaire and user. + * + * @param $userid + */ + private function update_grades($userid) { + if ($this->grade != 0) { + $questionnaire = new \stdClass(); + $questionnaire->id = $this->id; + $questionnaire->name = $this->name; + $questionnaire->grade = $this->grade; + $questionnaire->cmidnumber = $this->cm->idnumber; + $questionnaire->courseid = $this->course->id; + questionnaire_update_grades($questionnaire, $userid); + } + } + /* * Function to view an entire responses data. * From 292c0bf03ed795cbfcc5b6c9e4f356bebf0a6ad9 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Mon, 12 Nov 2018 11:38:40 -0500 Subject: [PATCH 019/341] Poet - making it compliant. --- javascript/mobile.js | 66 ++-- lib.php | 4 +- templates/mobile_view_activity_page.mustache | 344 ++++++++--------- ..._view_activity_page_with_required.mustache | 360 +++++++++--------- 4 files changed, 387 insertions(+), 387 deletions(-) diff --git a/javascript/mobile.js b/javascript/mobile.js index dc2defed..1291f57a 100644 --- a/javascript/mobile.js +++ b/javascript/mobile.js @@ -1,34 +1,34 @@ -// 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 . - -/** - * Support for Moodle Mobile integration - */ - - -var questionsFormFields = {}; -this.questionsFormErrors = {}; -for (const fieldkey in this.CONTENT_OTHERDATA.fields) { - questionsFormFields[fieldkey] = []; - questionsFormFields[fieldkey][0] = ''; - for (const itemid in this.CONTENT_OTHERDATA.questions[this.CONTENT_OTHERDATA.pagenum][this.CONTENT_OTHERDATA.fields[fieldkey].id]) { - questionsFormFields[fieldkey][0] = this.CONTENT_OTHERDATA.questions[this.CONTENT_OTHERDATA.pagenum][this.CONTENT_OTHERDATA.fields[fieldkey].id][itemid].value; - } - if (this.CONTENT_OTHERDATA.fields[fieldkey].required === 'y') { - questionsFormFields[fieldkey][1] = this.Validators.required; - this.questionsFormErrors[fieldkey] = this.CONTENT_OTHERDATA.fields[fieldkey].errormessage; - } -} +// 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 . + +/** + * Support for Moodle Mobile integration + */ + + +var questionsFormFields = {}; +this.questionsFormErrors = {}; +for (const fieldkey in this.CONTENT_OTHERDATA.fields) { + questionsFormFields[fieldkey] = []; + questionsFormFields[fieldkey][0] = ''; + for (const itemid in this.CONTENT_OTHERDATA.questions[this.CONTENT_OTHERDATA.pagenum][this.CONTENT_OTHERDATA.fields[fieldkey].id]) { + questionsFormFields[fieldkey][0] = this.CONTENT_OTHERDATA.questions[this.CONTENT_OTHERDATA.pagenum][this.CONTENT_OTHERDATA.fields[fieldkey].id][itemid].value; + } + if (this.CONTENT_OTHERDATA.fields[fieldkey].required === 'y') { + questionsFormFields[fieldkey][1] = this.Validators.required; + this.questionsFormErrors[fieldkey] = this.CONTENT_OTHERDATA.fields[fieldkey].errormessage; + } +} this.questionsForm = this.FormBuilder.group(questionsFormFields); \ No newline at end of file diff --git a/lib.php b/lib.php index 4ec1c8ba..722feb34 100644 --- a/lib.php +++ b/lib.php @@ -510,7 +510,7 @@ function questionnaire_get_mobile_data($cmid, $userid = false) { ]; $sql = 'SELECT qq.*,qqt.response_table FROM ' . '{questionnaire_question} qq LEFT JOIN {questionnaire_question_type} qqt ' - . 'ON qq.type_id = qqt.typeid WHERE qq.survey_id = ? AND qq.deleted = ? ' + . 'ON qq.type_id = qqt.typeid WHERE qq.surveyid = ? AND qq.deleted = ? ' . 'ORDER BY qq.position'; if ($questions = $DB->get_records_sql($sql, [$questionnaire->sid, 'n'])) { require_once('classes/question/base.php'); @@ -527,7 +527,7 @@ function questionnaire_get_mobile_data($cmid, $userid = false) { $ret['questionsinfo'][$pagenum][$question->id] = $ret['fields'][$fieldkey] = [ 'id' => $question->id, - 'survey_id' => $question->survey_id, + 'surveyid' => $question->surveyid, 'name' => $question->name, 'type_id' => $question->type_id, 'length' => $question->length, diff --git a/templates/mobile_view_activity_page.mustache b/templates/mobile_view_activity_page.mustache index 0437be63..82028fa4 100644 --- a/templates/mobile_view_activity_page.mustache +++ b/templates/mobile_view_activity_page.mustache @@ -1,173 +1,173 @@ -{{! - 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 . -}} -{{=<% %>=}} -
- - <%#completed%> - - - {{ 'plugin.mod_questionnaire.submitted' | translate }} <%complete_userdate%> - - - <%/completed%> -
- - - <%^emptypage%> - <%#pagequestions%> - - <%#questionnaire.questionnaire.autonumquestions%><%info.qnum%><%/questionnaire.questionnaire.autonumquestions%> - - <%#info.required%>{{ 'plugin.mod_questionnaire.required' | translate }}<%/info.required%> - - <%#info.isselect%> - - <%#choices%> - disabled="true"<%/completed%><%#value%> selected="true"<%/value%> value="<%id%>"><%content%> - <%/choices%> - - <%/info.isselect%> - <%#info.isbool%> - - <%#choices%> - - - disabled="true"<%#value%> checked="true"<%/value%><%/completed%> value="<%choice_id%>"> - - <%/choices%> - - <%/info.isbool%> - <%#info.isradiobutton%> - - <%#choices%> - - - checked="true"<%/value%> - value="<%choice_id%>" - <%#completed%> disabled="true"<%/completed%>> - - <%/choices%> - - <%/info.isradiobutton%> - <%#info.ischeckbox%> - - <%#choices%> - - - checked="true"<%/value%> - value="<%choice_id%>"<%#completed%> disabled="true"<%/completed%> - > - - <%/choices%> - - <%/info.ischeckbox%> - <%#info.istextessay%> - - <%^completed%> - - <%/completed%> - - <%/info.istextessay%> - <%#info.israte%> - <%#choices%> - - - {{ CONTENT_OTHERDATA.responses['response_<%info.type_id%>_<%info.id%>_<%id%>'] }} - - - - <%minstr%> - <%#na%><%/na%><%^na%><%maxstr%><%/na%> - - - <%/choices%> - <%/info.israte%> - <%/pagequestions%> - <%/emptypage%> - <%#emptypage%> - - - - <%/emptypage%> - <%#prevpage%> - - <%/prevpage%> - <%#nextpage%> - - <%/nextpage%> - <%^completed%> - <%^nextpage%> - - <%/nextpage%> - <%/completed%> - - -
+{{! + 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 . +}} +{{=<% %>=}} +
+ + <%#completed%> + + + {{ 'plugin.mod_questionnaire.submitted' | translate }} <%complete_userdate%> + + + <%/completed%> +
+ + + <%^emptypage%> + <%#pagequestions%> + + <%#questionnaire.questionnaire.autonumquestions%><%info.qnum%><%/questionnaire.questionnaire.autonumquestions%> + + <%#info.required%>{{ 'plugin.mod_questionnaire.required' | translate }}<%/info.required%> + + <%#info.isselect%> + + <%#choices%> + disabled="true"<%/completed%><%#value%> selected="true"<%/value%> value="<%id%>"><%content%> + <%/choices%> + + <%/info.isselect%> + <%#info.isbool%> + + <%#choices%> + + + disabled="true"<%#value%> checked="true"<%/value%><%/completed%> value="<%choice_id%>"> + + <%/choices%> + + <%/info.isbool%> + <%#info.isradiobutton%> + + <%#choices%> + + + checked="true"<%/value%> + value="<%choice_id%>" + <%#completed%> disabled="true"<%/completed%>> + + <%/choices%> + + <%/info.isradiobutton%> + <%#info.ischeckbox%> + + <%#choices%> + + + checked="true"<%/value%> + value="<%choice_id%>"<%#completed%> disabled="true"<%/completed%> + > + + <%/choices%> + + <%/info.ischeckbox%> + <%#info.istextessay%> + + <%^completed%> + + <%/completed%> + + <%/info.istextessay%> + <%#info.israte%> + <%#choices%> + + + {{ CONTENT_OTHERDATA.responses['response_<%info.type_id%>_<%info.id%>_<%id%>'] }} + + + + <%minstr%> + <%#na%><%/na%><%^na%><%maxstr%><%/na%> + + + <%/choices%> + <%/info.israte%> + <%/pagequestions%> + <%/emptypage%> + <%#emptypage%> + + + + <%/emptypage%> + <%#prevpage%> + + <%/prevpage%> + <%#nextpage%> + + <%/nextpage%> + <%^completed%> + <%^nextpage%> + + <%/nextpage%> + <%/completed%> + + +
\ No newline at end of file diff --git a/templates/mobile_view_activity_page_with_required.mustache b/templates/mobile_view_activity_page_with_required.mustache index 427307d5..4ce0db31 100644 --- a/templates/mobile_view_activity_page_with_required.mustache +++ b/templates/mobile_view_activity_page_with_required.mustache @@ -1,181 +1,181 @@ -{{! - 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 . -}} -{{=<% %>=}} -
- - <%#completed%> - - - {{ 'plugin.mod_questionnaire.submitted' | translate }} <%complete_userdate%> - - - <%/completed%> -
- - <%#completed%><%/completed%> - <%^completed%>
<%/completed%> - <%#pagequestions%> - - - <%#info.required%>{{ 'plugin.mod_questionnaire.required' | translate }}<%/info.required%> - - <%#info.isselect%> - name="<%info.fieldkey%>" formControlName="<%info.fieldkey%>"<%/completed%>> - <%#choices%> - disabled="true"<%/completed%><%#value%> selected="true"<%/value%> value="<%id%>"><%content%> - <%/choices%> - - <%^completed%><%/completed%> - <%/info.isselect%> - <%#info.isbool%> - name="<%info.fieldkey%>" formControlName="<%info.fieldkey%>"<%/completed%>> - <%#choices%> - - - disabled="true"<%#value%> checked="true"<%/value%><%/completed%> value="<%choice_id%>"> - - <%/choices%> - - <%^completed%><%/completed%> - <%/info.isbool%> - <%#info.isradiobutton%> - name="<%info.fieldkey%>" formControlName="<%info.fieldkey%>"<%/completed%>> - <%#choices%> - - - checked="true"<%/value%> - value="<%choice_id%>" - <%#completed%> disabled="true"<%/completed%>> - - <%/choices%> - - <%^completed%><%/completed%> - <%/info.isradiobutton%> - <%#info.ischeckbox%> - name="<%info.fieldkey%>" formControlName="<%info.fieldkey%>"<%/completed%>> - <%#choices%> - - - checked="true"<%/value%> - value="<%choice_id%>"<%#completed%> disabled="true"<%/completed%> - > - - <%/choices%> - - <%^completed%><%/completed%> - <%/info.ischeckbox%> - <%#info.istextessay%> - - <%^completed%> - - - <%/completed%> - <%#completed%> - - <%/completed%> - - <%/info.istextessay%> - <%#info.israte%> - <%#choices%> - - - {{ CONTENT_OTHERDATA.responses['response_<%info.type_id%>_<%info.id%>_<%id%>'] }} - - - name="<%info.fieldkey%>" formControlName="<%info.fieldkey%>"<%/completed%>> - <%minstr%> - <%#na%><%/na%><%^na%><%maxstr%><%/na%> - - - <%/choices%> - <%/info.israte%> - <%/pagequestions%> - <%#prevpage%> - - <%/prevpage%> - <%#nextpage%> - - <%/nextpage%> - <%^completed%> - <%^nextpage%> - - <%/nextpage%> - <%/completed%> - <%#completed%><%/completed%> - <%^completed%>
<%/completed%> -
-
+{{! + 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 . +}} +{{=<% %>=}} +
+ + <%#completed%> + + + {{ 'plugin.mod_questionnaire.submitted' | translate }} <%complete_userdate%> + + + <%/completed%> +
+ + <%#completed%><%/completed%> + <%^completed%>
<%/completed%> + <%#pagequestions%> + + + <%#info.required%>{{ 'plugin.mod_questionnaire.required' | translate }}<%/info.required%> + + <%#info.isselect%> + name="<%info.fieldkey%>" formControlName="<%info.fieldkey%>"<%/completed%>> + <%#choices%> + disabled="true"<%/completed%><%#value%> selected="true"<%/value%> value="<%id%>"><%content%> + <%/choices%> + + <%^completed%><%/completed%> + <%/info.isselect%> + <%#info.isbool%> + name="<%info.fieldkey%>" formControlName="<%info.fieldkey%>"<%/completed%>> + <%#choices%> + + + disabled="true"<%#value%> checked="true"<%/value%><%/completed%> value="<%choice_id%>"> + + <%/choices%> + + <%^completed%><%/completed%> + <%/info.isbool%> + <%#info.isradiobutton%> + name="<%info.fieldkey%>" formControlName="<%info.fieldkey%>"<%/completed%>> + <%#choices%> + + + checked="true"<%/value%> + value="<%choice_id%>" + <%#completed%> disabled="true"<%/completed%>> + + <%/choices%> + + <%^completed%><%/completed%> + <%/info.isradiobutton%> + <%#info.ischeckbox%> + name="<%info.fieldkey%>" formControlName="<%info.fieldkey%>"<%/completed%>> + <%#choices%> + + + checked="true"<%/value%> + value="<%choice_id%>"<%#completed%> disabled="true"<%/completed%> + > + + <%/choices%> + + <%^completed%><%/completed%> + <%/info.ischeckbox%> + <%#info.istextessay%> + + <%^completed%> + + + <%/completed%> + <%#completed%> + + <%/completed%> + + <%/info.istextessay%> + <%#info.israte%> + <%#choices%> + + + {{ CONTENT_OTHERDATA.responses['response_<%info.type_id%>_<%info.id%>_<%id%>'] }} + + + name="<%info.fieldkey%>" formControlName="<%info.fieldkey%>"<%/completed%>> + <%minstr%> + <%#na%><%/na%><%^na%><%maxstr%><%/na%> + + + <%/choices%> + <%/info.israte%> + <%/pagequestions%> + <%#prevpage%> + + <%/prevpage%> + <%#nextpage%> + + <%/nextpage%> + <%^completed%> + <%^nextpage%> + + <%/nextpage%> + <%/completed%> + <%#completed%><%/completed%> + <%^completed%>
<%/completed%> +
+
\ No newline at end of file From 96b88743d56e118eeda1b65ea198833d9ebbae61 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Tue, 13 Nov 2018 13:27:13 -0500 Subject: [PATCH 020/341] GHI-156 - Set 'completed' status in templates and updated SQL. --- lib.php | 11 ++++++----- templates/mobile_view_activity_page.mustache | 2 +- .../mobile_view_activity_page_with_required.mustache | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib.php b/lib.php index 722feb34..6d69084d 100644 --- a/lib.php +++ b/lib.php @@ -790,16 +790,17 @@ function questionnaire_get_mobile_data($cmid, $userid = false) { function questionnaire_save_mobile_data($questionnaireid, $surveyid, $userid, $cmid, $sec, $completed, $submit, array $responses) { global $DB, $CFG; // Do not delete $CFG!!! + require_once($CFG->dirroot.'/mod/questionnaire/questionnaire.class.php'); $ret = [ 'responses' => [], 'warnings' => [] ]; + $cm = get_coursemodule_from_id('questionnaire', $cmid); + $questionnaire = new questionnaire($questionnaireid, null, $DB->get_record('course', ['id' => $cm->course]), $cm); + if (!$completed) { - require_once('questionnaire.class.php'); - $cm = get_coursemodule_from_id('questionnaire', $cmid); - $questionnaire = new \questionnaire($questionnaireid, null, $DB->get_record('course', ['id' => $cm->course]), $cm); $rid = $questionnaire->delete_insert_response($DB->get_field('questionnaire_response', 'id', - ['questionnaireid' => $surveyid, 'complete' => 'n', 'userid' => $userid]), $sec, $userid); + ['questionnaireid' => $questionnaireid, 'complete' => 'n', 'userid' => $userid]), $sec, $userid); $questionnairedata = questionnaire_get_mobile_data($cmid, $userid); $pagequestions = isset($questionnairedata['questions'][$sec]) ? $questionnairedata['questions'][$sec] : []; if (!empty($pagequestions)) { @@ -882,7 +883,7 @@ function questionnaire_save_mobile_data($questionnaireid, $surveyid, $userid, $c if ($submit && (!isset($ret['warnings']) || empty($ret['warnings']))) { $questionnaire->commit_submission_response( $DB->get_field('questionnaire_response', 'id', - ['questionnaireid' => $surveyid, 'complete' => 'n', + ['questionnaireid' => $questionnaireid, 'complete' => 'n', 'userid' => $userid]), $userid); } return $ret; diff --git a/templates/mobile_view_activity_page.mustache b/templates/mobile_view_activity_page.mustache index 82028fa4..80148cd9 100644 --- a/templates/mobile_view_activity_page.mustache +++ b/templates/mobile_view_activity_page.mustache @@ -157,7 +157,7 @@ userid: <%userid%>, cmid: <%cmid%>, sec: <%pagenum%>, - completed: <%completed%>, + completed: 1, submit: 1, responses: CoreUtilsProvider.objectToArrayOfObjects(CONTENT_OTHERDATA.responses, 'name', 'value') }" diff --git a/templates/mobile_view_activity_page_with_required.mustache b/templates/mobile_view_activity_page_with_required.mustache index 4ce0db31..6e0729e0 100644 --- a/templates/mobile_view_activity_page_with_required.mustache +++ b/templates/mobile_view_activity_page_with_required.mustache @@ -164,7 +164,7 @@ userid: <%userid%>, cmid: <%cmid%>, sec: <%pagenum%>, - completed: <%completed%>, + completed: 1, submit: 1, responses: CoreUtilsProvider.objectToArrayOfObjects(CONTENT_OTHERDATA.responses, 'name', 'value') }" From 068d45cd61c7e8329f32dfedb2b827ade003a81d Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Thu, 15 Nov 2018 15:10:35 -0500 Subject: [PATCH 021/341] Poet - Setting back changes in templates. --- templates/mobile_view_activity_page.mustache | 2 +- templates/mobile_view_activity_page_with_required.mustache | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/mobile_view_activity_page.mustache b/templates/mobile_view_activity_page.mustache index 80148cd9..82028fa4 100644 --- a/templates/mobile_view_activity_page.mustache +++ b/templates/mobile_view_activity_page.mustache @@ -157,7 +157,7 @@ userid: <%userid%>, cmid: <%cmid%>, sec: <%pagenum%>, - completed: 1, + completed: <%completed%>, submit: 1, responses: CoreUtilsProvider.objectToArrayOfObjects(CONTENT_OTHERDATA.responses, 'name', 'value') }" diff --git a/templates/mobile_view_activity_page_with_required.mustache b/templates/mobile_view_activity_page_with_required.mustache index 6e0729e0..4ce0db31 100644 --- a/templates/mobile_view_activity_page_with_required.mustache +++ b/templates/mobile_view_activity_page_with_required.mustache @@ -164,7 +164,7 @@ userid: <%userid%>, cmid: <%cmid%>, sec: <%pagenum%>, - completed: 1, + completed: <%completed%>, submit: 1, responses: CoreUtilsProvider.objectToArrayOfObjects(CONTENT_OTHERDATA.responses, 'name', 'value') }" From d11f091f5dc5fd3668409cdfd6407729d5c78061 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Thu, 29 Nov 2018 15:40:03 -0500 Subject: [PATCH 022/341] Poet - Adding new privacy API functions. --- classes/privacy/provider.php | 55 ++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php index da174f92..5e4fc7ea 100644 --- a/classes/privacy/provider.php +++ b/classes/privacy/provider.php @@ -31,6 +31,9 @@ class provider implements // This plugin has data. \core_privacy\local\metadata\provider, + // This plugin is capable of determining which users have data within it. + \core_privacy\local\request\core_userlist_provider, + // This plugin currently implements the original plugin_provider interface. \core_privacy\local\request\plugin\provider { @@ -129,6 +132,31 @@ public static function _get_contexts_for_userid(int $userid): \core_privacy\loca return $contextlist; } + /** + * Get the list of users who have data within a context. + * + * @param \core_privacy\local\request\userlist $userlist The userlist containing the list of users who have data in this + * context/plugin combination. + */ + public static function get_users_in_context(\core_privacy\local\request\userlist $userlist) { + + $context = $userlist->get_context(); + if (!$context instanceof \context_module) { + return; + } + + $params = ['modulename' => 'questionnaire', 'instanceid' => $context->instanceid]; + + // Questionnaire respondents. + $sql = "SELECT qr.userid + FROM {course_modules} cm + JOIN {modules} m ON m.id = cm.module AND m.name = :modulename + JOIN {questionnaire} q ON q.id = cm.instance + JOIN {questionnaire_response} qr ON qr.questionnaireid = q.id + WHERE cm.id = :instanceid"; + $userlist->add_from_sql('userid', $sql, $params); + } + /** * Export all user data for the specified user, in the specified contexts, using the supplied exporter instance. * @@ -261,6 +289,33 @@ public static function _delete_data_for_user(\core_privacy\local\request\approve } } + /** + * Delete multiple users within a single context. + * + * @param \core_privacy\local\request\approved_userlist $userlist The approved context and user information to delete + * information for. + */ + public static function delete_data_for_users(\core_privacy\local\request\approved_userlist $userlist) { + global $DB; + + $context = $userlist->get_context(); + if (!$cm = get_coursemodule_from_id('questionnaire', $context->instanceid)) { + return; + } + if (!($questionnaire = $DB->get_record('questionnaire', ['id' => $cm->instance]))) { + return; + } + + list($userinsql, $userinparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED); + $params = array_merge(['questionnaireid' => $questionnaire->id], $userinparams); + $select = 'questionnaireid = :questionnaireid AND userid ' . $userinsql; + if ($responses = $DB->get_recordset_select('questionnaire_response', $select, $params)) { + self::delete_responses($responses); + } + $responses->close(); + $DB->delete_records_select('questionnaire_response', $select, $params); + } + /** * Helper function to delete all the response records for a recordset array of responses. * From 275710bfce4d501a0e1058234c8e023702e81b03 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Thu, 29 Nov 2018 16:40:25 -0500 Subject: [PATCH 023/341] Poet - Updating Travis CI java version. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9950369d..061be2a4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,8 +8,8 @@ addons: mysql: "8.0.2" apt: packages: - - oracle-java8-installer - - oracle-java8-set-default + - oracle-java9-installer + - oracle-java9-set-default cache: directories: From a8cdb9e05348a703684886b6897a812c0f679a2e Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Thu, 29 Nov 2018 13:43:05 -0500 Subject: [PATCH 024/341] Poet - Making code work with 3.6. --- locallib.php | 1 - tests/behat/check_responses.feature | 4 ++-- tests/behat/public_questionnaire.feature | 4 ++-- tests/behat/public_questionnaire_teacher.feature | 4 ++-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/locallib.php b/locallib.php index 4732dfee..be92b0ba 100644 --- a/locallib.php +++ b/locallib.php @@ -40,7 +40,6 @@ defined('MOODLE_INTERNAL') || die(); -require_once($CFG->libdir.'/eventslib.php'); require_once($CFG->dirroot.'/calendar/lib.php'); // Constants. diff --git a/tests/behat/check_responses.feature b/tests/behat/check_responses.feature index 240f1dfb..488f2cb8 100644 --- a/tests/behat/check_responses.feature +++ b/tests/behat/check_responses.feature @@ -22,11 +22,11 @@ Feature: Review responses | questionnaire | Test questionnaire | Test questionnaire description | C1 | questionnaire0 | And "Test questionnaire" has questions and responses And I log in as "admin" - And I navigate to "Location settings" node in "Site administration > Location" + And I navigate to "Location > Location settings" in site administration And I set the field "id_s__timezone" to "Europe/London" And I set the field "id_s__forcetimezone" to "Europe/London" And I press "Save changes" - And I navigate to "Language settings" node in "Site administration > Language" + And I navigate to "Language > Language settings" in site administration And I set the field "id_s__autolang" to "0" # And I set the field "id_s__lang" to "en‎" And I log out diff --git a/tests/behat/public_questionnaire.feature b/tests/behat/public_questionnaire.feature index 81eb4a26..0d9f5e6c 100644 --- a/tests/behat/public_questionnaire.feature +++ b/tests/behat/public_questionnaire.feature @@ -43,7 +43,7 @@ Feature: Questionnaires can use an existing public survey to gather responses in And I am on "Course 2" course homepage with editing mode on And I follow "Add an activity or resource" And I click on "Questionnaire" "radio" - And I press "Add" + And I click on "Add" "button" in the "Add an activity or resource" "dialogue" And I set the field "Name" to "Questionnaire instance 1" And I expand all fieldsets Then I should see "Content options" @@ -56,7 +56,7 @@ Feature: Questionnaires can use an existing public survey to gather responses in And I am on "Course 3" course homepage with editing mode on And I follow "Add an activity or resource" And I click on "Questionnaire" "radio" - And I press "Add" + And I click on "Add" "button" in the "Add an activity or resource" "dialogue" And I set the field "Name" to "Questionnaire instance 2" And I expand all fieldsets Then I should see "Content options" diff --git a/tests/behat/public_questionnaire_teacher.feature b/tests/behat/public_questionnaire_teacher.feature index a1254f32..26be404c 100644 --- a/tests/behat/public_questionnaire_teacher.feature +++ b/tests/behat/public_questionnaire_teacher.feature @@ -44,7 +44,7 @@ Feature: Public questionnaires gather all instance responses in one master cours And I am on "Course 2" course homepage with editing mode on And I follow "Add an activity or resource" And I click on "Questionnaire" "radio" - And I press "Add" + And I click on "Add" "button" in the "Add an activity or resource" "dialogue" And I set the field "Name" to "Questionnaire instance 1" And I expand all fieldsets Then I should see "Content options" @@ -57,7 +57,7 @@ Feature: Public questionnaires gather all instance responses in one master cours And I am on "Course 3" course homepage with editing mode on And I follow "Add an activity or resource" And I click on "Questionnaire" "radio" - And I press "Add" + And I click on "Add" "button" in the "Add an activity or resource" "dialogue" And I set the field "Name" to "Questionnaire instance 2" And I expand all fieldsets Then I should see "Content options" From 7f47861f5646b5ad0657eced0fefb9d6107a1eea Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Fri, 30 Nov 2018 14:23:23 -0500 Subject: [PATCH 025/341] Poet - Getting Travis to test multiple Moodle branches. --- .travis.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 061be2a4..6dff5922 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ cache: - $HOME/.npm php: - - 7.0 + - 5.6 - 7.1 env: @@ -25,8 +25,12 @@ env: - MOODLE_BRANCH=MOODLE_35_STABLE matrix: - - DB=pgsql - - DB=mysqli + - DB=pgsql MOODLE_BRANCH=MOODLE_35_STABLE + - DB=mysqli MOODLE_BRANCH=MOODLE_35_STABLE + - DB=pgsql MOODLE_BRANCH=MOODLE_34_STABLE + - DB=mysqli MOODLE_BRANCH=MOODLE_34_STABLE + - DB=pgsql MOODLE_BRANCH=MOODLE_33_STABLE + - DB=mysqli MOODLE_BRANCH=MOODLE_33_STABLE before_install: - phpenv config-rm xdebug.ini From aa46de0a48693fb62195daf29194f2d85dedfff1 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Fri, 30 Nov 2018 15:32:58 -0500 Subject: [PATCH 026/341] Poet - Fixing 3.3 provider defs for PHP 5.6. --- classes/privacy/provider.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php index 5e4fc7ea..1408ebed 100644 --- a/classes/privacy/provider.php +++ b/classes/privacy/provider.php @@ -45,7 +45,7 @@ class provider implements * @param collection $items The collection to add metadata to. * @return collection The array of metadata */ - public static function _get_metadata(\core_privacy\local\metadata\collection $collection) : \core_privacy\local\metadata\collection { + public static function _get_metadata(\core_privacy\local\metadata\collection $collection) { // Add all of the relevant tables and fields to the collection. $collection->add_database_table('questionnaire_response', [ @@ -109,7 +109,7 @@ public static function _get_metadata(\core_privacy\local\metadata\collection $co * @param int $userid The user to search. * @return contextlist $contextlist The list of contexts used in this plugin. */ - public static function _get_contexts_for_userid(int $userid): \core_privacy\local\request\contextlist { + public static function _get_contexts_for_userid($userid) { $contextlist = new \core_privacy\local\request\contextlist(); $sql = "SELECT c.id From 5f2f81608329e6d4e354f21e94c91a59ada39352 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Sat, 1 Dec 2018 11:48:55 -0500 Subject: [PATCH 027/341] Poet - Setting up for 3.5.2 release with 3.6 compat. --- .travis.yml | 56 ++++++++++++++++++++++++++++++++++++++++++----------- CHANGES.txt | 17 ++++++++++++++++ version.php | 2 +- 3 files changed, 63 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6dff5922..caa2a3a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ sudo: required addons: firefox: "47.0.1" - postgresql: "9.3" + postgresql: "9.4" mysql: "8.0.2" apt: packages: @@ -19,18 +19,52 @@ cache: php: - 5.6 - 7.1 + - 7.2 env: - global: - - MOODLE_BRANCH=MOODLE_35_STABLE - - matrix: - - DB=pgsql MOODLE_BRANCH=MOODLE_35_STABLE - - DB=mysqli MOODLE_BRANCH=MOODLE_35_STABLE - - DB=pgsql MOODLE_BRANCH=MOODLE_34_STABLE - - DB=mysqli MOODLE_BRANCH=MOODLE_34_STABLE - - DB=pgsql MOODLE_BRANCH=MOODLE_33_STABLE - - DB=mysqli MOODLE_BRANCH=MOODLE_33_STABLE + - MOODLE_BRANCH=master DB=pgsql + - MOODLE_BRANCH=master DB=mysqli + - MOODLE_BRANCH=MOODLE_35_STABLE DB=pgsql + - MOODLE_BRANCH=MOODLE_35_STABLE DB=mysqli + - MOODLE_BRANCH=MOODLE_34_STABLE DB=pgsql + - MOODLE_BRANCH=MOODLE_34_STABLE DB=mysqli + - MOODLE_BRANCH=MOODLE_33_STABLE DB=pgsql + - MOODLE_BRANCH=MOODLE_33_STABLE DB=mysqli + +matrix: + exclude: + - php: 7.1 + env: MOODLE_BRANCH=MOODLE_33_STABLE DB=mysqli + - php: 7.1 + env: MOODLE_BRANCH=MOODLE_33_STABLE DB=pgsql + - php: 7.2 + env: MOODLE_BRANCH=MOODLE_33_STABLE DB=mysqli + - php: 7.2 + env: MOODLE_BRANCH=MOODLE_33_STABLE DB=pgsql + - php: 5.6 + env: MOODLE_BRANCH=MOODLE_34_STABLE DB=mysqli + - php: 5.6 + env: MOODLE_BRANCH=MOODLE_34_STABLE DB=pgsql + - php: 7.2 + env: MOODLE_BRANCH=MOODLE_34_STABLE DB=mysqli + - php: 7.2 + env: MOODLE_BRANCH=MOODLE_34_STABLE DB=pgsql + - php: 5.6 + env: MOODLE_BRANCH=MOODLE_35_STABLE DB=mysqli + - php: 5.6 + env: MOODLE_BRANCH=MOODLE_35_STABLE DB=pgsql + - php: 7.2 + env: MOODLE_BRANCH=MOODLE_35_STABLE DB=mysqli + - php: 7.2 + env: MOODLE_BRANCH=MOODLE_35_STABLE DB=pgsql + - php: 5.6 + env: MOODLE_BRANCH=master DB=mysqli + - php: 5.6 + env: MOODLE_BRANCH=master DB=pgsql + - php: 7.1 + env: MOODLE_BRANCH=master DB=mysqli + - php: 7.1 + env: MOODLE_BRANCH=master DB=pgsql before_install: - phpenv config-rm xdebug.ini diff --git a/CHANGES.txt b/CHANGES.txt index 3bd37463..a1293711 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,22 @@ Release Notes +Version 3.5.2 (Build - 2018120100) +NOTE - This release will work on Moodle 3.6 and can be backported to Moodle 3.4 and 3.3. If doing so, please ensure that your +questionnaire installation is at the latest version for those releases. For Moodle 3.4, this is questionnaire release 3.4.2. +For Moodle 3.3, this is questionnaire release 3.3.3. + +New features: +Code has been updated to work on Moodle 3.6 + +The two new privacy api functions have been added (get_users_in_context, delete_data_for_users). + +Bug Fixes: +The privacy api polyfill functions have been fixed so that they work correctly with PHP 5.6 under Moodle 3.3. +(Thanks to Paul Holden - https://github.com/paulholden - https://github.com/PoetOS/moodle-mod_questionnaire/pull/166) + +GHI167 - Fixed the feedback scoring for boolean questions. + + Version 3.5.1 (Build - 2018110100) NOTE - This release can be backported to Moodle 3.4 and 3.3. If doing so, please ensure that your questionnaire installation is at the latest version for those releases. For Moodle 3.4, this is questionnaire release 3.4.2. For Moodle 3.3, this is questionnaire diff --git a/version.php b/version.php index a7dad047..67f4dfe6 100644 --- a/version.php +++ b/version.php @@ -29,5 +29,5 @@ $plugin->component = 'mod_questionnaire'; -$plugin->release = '3.5.1 (Build - 2018110100)'; +$plugin->release = '3.5.2 (Build - 2018120100)'; $plugin->maturity = MATURITY_STABLE; \ No newline at end of file From 6c0f70451e0901109bc2cf46b12654fda60622b6 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Sat, 1 Dec 2018 14:46:14 -0500 Subject: [PATCH 028/341] GHI167 - Fixing feedback score calculations for boolean questions. --- classes/response/boolean.php | 16 ++++++++++++---- classes/response/single.php | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/classes/response/boolean.php b/classes/response/boolean.php index 2643dfb0..dddcfcba 100644 --- a/classes/response/boolean.php +++ b/classes/response/boolean.php @@ -103,12 +103,20 @@ public function get_feedback_scores(array $rids) { } $params[] = 'y'; - $sql = 'SELECT response_id as rid, COUNT(response_id) AS score ' . + $feedbackscores = false; + $sql = 'SELECT response_id, choice_id ' . 'FROM {'.$this->response_table().'} ' . - 'WHERE question_id= ? ' . $rsql . ' AND choice_id = ? ' . - 'GROUP BY response_id ' . + 'WHERE question_id= ? ' . $rsql . ' ' . 'ORDER BY response_id ASC'; - return $DB->get_recordset_sql($sql, $params); + if ($responses = $DB->get_recordset_sql($sql, $params)) { + $feedbackscores = []; + foreach ($responses as $rid => $response) { + $feedbackscores[$rid] = new \stdClass(); + $feedbackscores[$rid]->rid = $rid; + $feedbackscores[$rid]->score = ($response->choice_id == 'y') ? 1 : 0; + } + } + return $feedbackscores; } /** diff --git a/classes/response/single.php b/classes/response/single.php index 6c26653d..cdc4b647 100644 --- a/classes/response/single.php +++ b/classes/response/single.php @@ -152,7 +152,7 @@ public function get_feedback_scores(array $rids) { 'INNER JOIN {questionnaire_quest_choice} c ON r.choice_id = c.id ' . 'WHERE r.question_id= ? ' . $rsql . ' ' . 'ORDER BY response_id ASC'; - return $DB->get_recordset_sql($sql, $params); + return $DB->get_records_sql($sql, $params); } /** From 724422cac0938e4ed2feb6356950314d5bca89de Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Mon, 3 Dec 2018 19:52:37 -0500 Subject: [PATCH 029/341] Poet - Update master to MOODLE_36_STABLE. --- .travis.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index caa2a3a5..371390b7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,8 +22,8 @@ php: - 7.2 env: - - MOODLE_BRANCH=master DB=pgsql - - MOODLE_BRANCH=master DB=mysqli + - MOODLE_BRANCH=MOODLE_36_STABLE DB=pgsql + - MOODLE_BRANCH=MOODLE_36_STABLE DB=mysqli - MOODLE_BRANCH=MOODLE_35_STABLE DB=pgsql - MOODLE_BRANCH=MOODLE_35_STABLE DB=mysqli - MOODLE_BRANCH=MOODLE_34_STABLE DB=pgsql @@ -58,13 +58,13 @@ matrix: - php: 7.2 env: MOODLE_BRANCH=MOODLE_35_STABLE DB=pgsql - php: 5.6 - env: MOODLE_BRANCH=master DB=mysqli + env: MOODLE_BRANCH=MOODLE_36_STABLE DB=mysqli - php: 5.6 - env: MOODLE_BRANCH=master DB=pgsql + env: MOODLE_BRANCH=MOODLE_36_STABLE DB=pgsql - php: 7.1 - env: MOODLE_BRANCH=master DB=mysqli + env: MOODLE_BRANCH=MOODLE_36_STABLE DB=mysqli - php: 7.1 - env: MOODLE_BRANCH=master DB=pgsql + env: MOODLE_BRANCH=MOODLE_36_STABLE DB=pgsql before_install: - phpenv config-rm xdebug.ini From 81125da8b1dda0a9c8477f363a822f7429c0bd32 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Tue, 6 Nov 2018 13:41:55 -0500 Subject: [PATCH 030/341] Poet - Updating master branch with 3.5 latest. --- version.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.php b/version.php index 67f4dfe6..0d3e231b 100644 --- a/version.php +++ b/version.php @@ -30,4 +30,4 @@ $plugin->component = 'mod_questionnaire'; $plugin->release = '3.5.2 (Build - 2018120100)'; -$plugin->maturity = MATURITY_STABLE; \ No newline at end of file +$plugin->maturity = MATURITY_STABLE; From d523d953eef9dda258c66f60de150f4ecf18de72 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Tue, 6 Nov 2018 14:19:30 -0500 Subject: [PATCH 031/341] Poet - Updating travis file to use required Postgres version. From 26400b114ee6899267dfead2081d2e15f5ec9542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Catal=C3=A0?= Date: Wed, 21 Nov 2018 13:13:47 +0100 Subject: [PATCH 032/341] Fix SQL query to work with groups, use the correct groupid field. --- questionnaire.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/questionnaire.class.php b/questionnaire.class.php index a36224b2..b24486d2 100644 --- a/questionnaire.class.php +++ b/questionnaire.class.php @@ -621,7 +621,7 @@ public function count_submissions($userid=false, $groupid=0) { $groupcnd = ''; if ($groupid != 0) { $groupsql = 'INNER JOIN {groups_members} gm ON r.userid = gm.userid '; - $groupcnd = ' AND gm.id = :groupid '; + $groupcnd = ' AND gm.groupid = :groupid '; $params['groupid'] = $groupid; } @@ -667,7 +667,7 @@ public function get_responses($userid=false, $groupid=0) { $groupcnd = ''; if ($groupid != 0) { $groupsql = 'INNER JOIN {groups_members} gm ON r.userid = gm.userid '; - $groupcnd = ' AND gm.id = :groupid '; + $groupcnd = ' AND gm.groupid = :groupid '; $params['groupid'] = $groupid; } From 29326b440f8b0e45b9fc548df2ee6a5a51fadd64 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Fri, 7 Dec 2018 13:49:32 -0500 Subject: [PATCH 033/341] CONTRIB-7555 - Added proper 'require_once'. --- lib.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib.php b/lib.php index 6d69084d..2ab115c1 100644 --- a/lib.php +++ b/lib.php @@ -1186,6 +1186,7 @@ function questionnaire_get_recent_mod_activity(&$activities, &$index, $timestart global $CFG, $COURSE, $USER, $DB; require_once($CFG->dirroot . '/mod/questionnaire/locallib.php'); + require_once($CFG->dirroot.'/mod/questionnaire/questionnaire.class.php'); if ($COURSE->id == $courseid) { $course = $COURSE; From c6a95df43719d91573347bf86ab0656915df82f7 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Fri, 7 Dec 2018 16:14:14 -0500 Subject: [PATCH 034/341] CONTRIB-7557 - Added abstract classes dependent on core_userlist_provider interface's presence. --- classes/privacy/provider.php | 81 +++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php index 1408ebed..f1dfe97b 100644 --- a/classes/privacy/provider.php +++ b/classes/privacy/provider.php @@ -27,15 +27,30 @@ defined('MOODLE_INTERNAL') || die(); -class provider implements - // This plugin has data. - \core_privacy\local\metadata\provider, +// The core_userlist_provider was introduced in 3.6, 3.5.3 and 3.4.6. It will not work in any release supporting the privacy API +// below those. This code will not use it if it does not exist and continue to work on under 3.5.3, under 3.4.6 and 3.3.*. +if (interface_exists('\core_privacy\local\request\core_userlist_provider')) { + abstract class provider_helper implements + // This plugin has data. + \core_privacy\local\metadata\provider, + + // This plugin is capable of determining which users have data within it. + \core_privacy\local\request\core_userlist_provider, + + // This plugin currently implements the original plugin_provider interface. + \core_privacy\local\request\plugin\provider { + } +} else { + abstract class provider_helper implements + // This plugin has data. + \core_privacy\local\metadata\provider, - // This plugin is capable of determining which users have data within it. - \core_privacy\local\request\core_userlist_provider, + // This plugin currently implements the original plugin_provider interface. + \core_privacy\local\request\plugin\provider { + } +} - // This plugin currently implements the original plugin_provider interface. - \core_privacy\local\request\plugin\provider { +class provider extends provider_helper { use \core_privacy\local\legacy_polyfill; @@ -113,13 +128,13 @@ public static function _get_contexts_for_userid($userid) { $contextlist = new \core_privacy\local\request\contextlist(); $sql = "SELECT c.id - FROM {context} c - INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel - INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname - INNER JOIN {questionnaire} q ON q.id = cm.instance - INNER JOIN {questionnaire_response} qr ON qr.questionnaireid = q.id - WHERE qr.userid = :attemptuserid - "; + FROM {context} c + INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel + INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname + INNER JOIN {questionnaire} q ON q.id = cm.instance + INNER JOIN {questionnaire_response} qr ON qr.questionnaireid = q.id + WHERE qr.userid = :attemptuserid + "; $params = [ 'modname' => 'questionnaire', @@ -149,22 +164,22 @@ public static function get_users_in_context(\core_privacy\local\request\userlist // Questionnaire respondents. $sql = "SELECT qr.userid - FROM {course_modules} cm - JOIN {modules} m ON m.id = cm.module AND m.name = :modulename - JOIN {questionnaire} q ON q.id = cm.instance - JOIN {questionnaire_response} qr ON qr.questionnaireid = q.id - WHERE cm.id = :instanceid"; + FROM {course_modules} cm + JOIN {modules} m ON m.id = cm.module AND m.name = :modulename + JOIN {questionnaire} q ON q.id = cm.instance + JOIN {questionnaire_response} qr ON qr.questionnaireid = q.id + WHERE cm.id = :instanceid"; $userlist->add_from_sql('userid', $sql, $params); } /** * Export all user data for the specified user, in the specified contexts, using the supplied exporter instance. * - * @param approved_contextlist $contextlist The approved contexts to export information for. + * @param approved_contextlist $contextlist The approved contexts to export information for. */ public static function _export_user_data(\core_privacy\local\request\approved_contextlist $contextlist) { global $DB, $CFG; - require_once($CFG->dirroot.'/mod/questionnaire/questionnaire.class.php'); + require_once($CFG->dirroot . '/mod/questionnaire/questionnaire.class.php'); if (empty($contextlist->count())) { return; @@ -175,16 +190,16 @@ public static function _export_user_data(\core_privacy\local\request\approved_co list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED); $sql = "SELECT cm.id AS cmid, - q.id AS qid, q.course AS qcourse, - qr.id AS responseid, qr.submitted AS lastsaved, qr.complete AS complete - FROM {context} c - INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel - INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname - INNER JOIN {questionnaire} q ON q.id = cm.instance - INNER JOIN {questionnaire_response} qr ON qr.questionnaireid = q.id - WHERE c.id {$contextsql} - AND qr.userid = :userid - ORDER BY cm.id, qr.id ASC"; + q.id AS qid, q.course AS qcourse, + qr.id AS responseid, qr.submitted AS lastsaved, qr.complete AS complete + FROM {context} c + INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel + INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname + INNER JOIN {questionnaire} q ON q.id = cm.instance + INNER JOIN {questionnaire_response} qr ON qr.questionnaireid = q.id + WHERE c.id {$contextsql} + AND qr.userid = :userid + ORDER BY cm.id, qr.id ASC"; $params = ['modname' => 'questionnaire', 'contextlevel' => CONTEXT_MODULE, 'userid' => $user->id] + $contextparams; @@ -258,7 +273,7 @@ public static function _delete_data_for_all_users_in_context(\context $context) /** * 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. + * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. */ public static function _delete_data_for_user(\core_privacy\local\request\approved_contextlist $contextlist) { global $DB; @@ -319,7 +334,7 @@ public static function delete_data_for_users(\core_privacy\local\request\approve /** * Helper function to delete all the response records for a recordset array of responses. * - * @param recordset $responses The list of response records to delete for. + * @param recordset $responses The list of response records to delete for. */ private static function delete_responses($responses) { global $DB; From 17c84347f3ee1d92e138e23e34c1f520607da66b Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Mon, 10 Dec 2018 10:28:11 -0500 Subject: [PATCH 035/341] Poet - Setting up for 3.5.3 release. --- CHANGES.txt | 15 ++++++++++++--- version.php | 6 +++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index a1293711..41b4b81f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,10 +1,19 @@ Release Notes -Version 3.5.2 (Build - 2018120100) -NOTE - This release will work on Moodle 3.6 and can be backported to Moodle 3.4 and 3.3. If doing so, please ensure that your +NOTE - This release will work on Moodle 3.6 and can be backported to Moodle 3.4 and 3.3. If doing so, please ensure that your questionnaire installation is at the latest version for those releases. For Moodle 3.4, this is questionnaire release 3.4.2. -For Moodle 3.3, this is questionnaire release 3.3.3. +For Moodle 3.3, this is questionnaire release 3.3.3. + +Version 3.5.3 (Build - 2018121000) +Bug Fixes: +GHI162 - Fixed response by group. + +CONTRIB-7555 - Fixed error on recent activity block. +CONTRIB-7557 - Allowed compatibility with older releases of Moodle privacy API. + + +Version 3.5.2 (Build - 2018120100) New features: Code has been updated to work on Moodle 3.6 diff --git a/version.php b/version.php index 0d3e231b..dd39b839 100644 --- a/version.php +++ b/version.php @@ -24,10 +24,10 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2018050107; // The current module version (Date: YYYYMMDDXX) +$plugin->version = 2018050108; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2017042800; // Moodle version. $plugin->component = 'mod_questionnaire'; -$plugin->release = '3.5.2 (Build - 2018120100)'; -$plugin->maturity = MATURITY_STABLE; +$plugin->release = '3.5.3 (Build - 2018121000)'; +$plugin->maturity = MATURITY_STABLE; \ No newline at end of file From f3789fa86bbaf78436d2c25f2fc4318198178fd4 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Fri, 7 Dec 2018 11:14:04 -0500 Subject: [PATCH 036/341] Poet - Moving lib functions to class. --- classes/output/mobile.php | 84 ++++---- externallib.php | 13 +- lib.php | 423 -------------------------------------- questionnaire.class.php | 7 + 4 files changed, 58 insertions(+), 469 deletions(-) diff --git a/classes/output/mobile.php b/classes/output/mobile.php index 1eadc49f..b243004c 100644 --- a/classes/output/mobile.php +++ b/classes/output/mobile.php @@ -34,24 +34,28 @@ class mobile { */ public static function mobile_view_activity($args) { global $OUTPUT, $USER, $CFG, $DB; - require_once($CFG->dirroot.'/mod/questionnaire/questionnaire.class.php'); + $args = (object) $args; $cmid = $args->cmid; $pagenum = (isset($args->pagenum) && !empty($args->pagenum)) ? intval($args->pagenum) : 1; $prevpage = 0; + + list($cm, $course, $questionnaire) = questionnaire_get_standard_page_items($cmid); + $questionnaire = new \questionnaire(0, $questionnaire, $course, $cm); + // Capabilities check. - $cm = get_coursemodule_from_id('questionnaire', $cmid); $context = \context_module::instance($cmid); self::require_capability($cm, $context, 'mod/questionnaire:view'); + // Set some variables we are going to be using. - $questionnaire = questionnaire_get_mobile_data($cmid, $USER->id); - if (isset($questionnaire['questions'][$pagenum - 1]) && !empty($questionnaire['questions'][$pagenum - 1])) { + $questionnairedata = $questionnaire->get_mobile_data($USER->id); + if (isset($questionnairedata['questions'][$pagenum - 1]) && !empty($questionnairedata['questions'][$pagenum - 1])) { $prevpage = $pagenum - 1; } $data = [ - 'questionnaire' => $questionnaire, + 'questionnaire' => $questionnairedata, 'cmid' => $cmid, 'courseid' => intval($cm->course), 'pagenum' => $pagenum, @@ -62,17 +66,20 @@ public static function mobile_view_activity($args) { ]; // Check for required fields filled. $break = false; - if (($pagenum - 1) > 0 && isset($questionnaire['questions'][$pagenum - 1]) && - !empty($questionnaire['questions'][$pagenum - 1])) { + if (($pagenum - 1) > 0 && isset($questionnairedata['questions'][$pagenum - 1]) && + !empty($questionnairedata['questions'][$pagenum - 1])) { $prepn = $pagenum - 1; $cnt = 0; - while (($prepn) > 0 && isset($questionnaire['questions'][$prepn]) && !empty($questionnaire['questions'][$prepn])) { - if (($prepn) > 0 && isset($questionnaire['questions'][$prepn]) && !empty($questionnaire['questions'][$prepn])) { - $keys = array_keys($questionnaire['questions'][$prepn]); + while (($prepn) > 0 && isset($questionnairedata['questions'][$prepn]) && + !empty($questionnairedata['questions'][$prepn])) { + if (($prepn) > 0 && isset($questionnairedata['questions'][$prepn]) && + !empty($questionnairedata['questions'][$prepn])) { + $keys = array_keys($questionnairedata['questions'][$prepn]); foreach ($keys as $questionid) { - if (isset($questionnaire['questionsinfo'][$prepn][$questionid]) && - $questionnaire['questionsinfo'][$prepn][$questionid]['required'] === 'y' && - (!isset($questionnaire['answered'][$questionid]) || empty($questionnaire['answered'][$questionid]))) { + if (isset($questionnairedata['questionsinfo'][$prepn][$questionid]) && + $questionnairedata['questionsinfo'][$prepn][$questionid]['required'] === 'y' && + (!isset($questionnairedata['answered'][$questionid]) || + empty($questionnairedata['answered'][$questionid]))) { $pagenum = $prepn; $prepn = 0; $break = true; @@ -94,21 +101,19 @@ public static function mobile_view_activity($args) { } } if (intval($args->pagenum) == $pagenum) { - if (isset($questionnaire['questions'][$pagenum - 1]) && !empty($questionnaire['questions'][$pagenum - 1])) { + if (isset($questionnairedata['questions'][$pagenum - 1]) && !empty($questionnairedata['questions'][$pagenum - 1])) { $prevpage = $pagenum - 1; } - $questionnaireobj = new \questionnaire($questionnaire['questionnaire']['id'], null, - $DB->get_record('course', ['id' => $cm->course]), $cm); $rid = $DB->get_field('questionnaire_response', 'id', [ - 'questionnaireid' => $questionnaire['questionnaire']['questionnaireid'], + 'questionnaireid' => $questionnairedata['questionnaire']['questionnaireid'], 'complete' => 'n', 'userid' => $USER->id ]); - if (isset($questionnaire['questions'][$pagenum]) && !empty($questionnaire['questions'][$pagenum])) { + if (isset($questionnairedata['questions'][$pagenum]) && !empty($questionnairedata['questions'][$pagenum])) { // Search for the next page to output. - while (!$questionnaireobj->eligible_questions_on_page($pagenum, $rid)) { - if (isset($questionnaire['questions'][$pagenum]) && !empty($questionnaire['questions'][$pagenum])) { + while (!$questionnaire->eligible_questions_on_page($pagenum, $rid)) { + if (isset($questionnairedata['questions'][$pagenum]) && !empty($questionnairedata['questions'][$pagenum])) { $pagenum++; } else { $cmid = 0; @@ -116,10 +121,11 @@ public static function mobile_view_activity($args) { } } } - if ($prevpage > 0 && isset($questionnaire['questions'][$prevpage]) && !empty($questionnaire['questions'][$prevpage])) { - while (!$questionnaireobj->eligible_questions_on_page($prevpage, $rid)) { - if ($prevpage > 0 && isset($questionnaire['questions'][$prevpage]) && - !empty($questionnaire['questions'][$prevpage])) { + if ($prevpage > 0 && isset($questionnairedata['questions'][$prevpage]) && + !empty($questionnairedata['questions'][$prevpage])) { + while (!$questionnaire->eligible_questions_on_page($prevpage, $rid)) { + if ($prevpage > 0 && isset($questionnairedata['questions'][$prevpage]) && + !empty($questionnairedata['questions'][$prevpage])) { $prevpage--; } else { break; @@ -128,16 +134,16 @@ public static function mobile_view_activity($args) { } } if ($cmid) { - $data['completed'] = (isset($questionnaire['response']['complete']) && - $questionnaire['response']['complete'] == 'y') ? 1 : 0; - $data['complete_userdate'] = (isset($questionnaire['response']['complete']) && - $questionnaire['response']['complete'] == 'y') ? userdate($questionnaire['response']['submitted']) : ''; - if (isset($questionnaire['questions'][$pagenum])) { + $data['completed'] = (isset($questionnairedata['response']['complete']) && + $questionnairedata['response']['complete'] == 'y') ? 1 : 0; + $data['complete_userdate'] = (isset($questionnairedata['response']['complete']) && + $questionnairedata['response']['complete'] == 'y') ? userdate($questionnairedata['response']['submitted']) : ''; + if (isset($questionnairedata['questions'][$pagenum])) { $i = 0; - foreach ($questionnaire['questions'][$pagenum] as $questionid => $choices) { - if (isset($questionnaire['questionsinfo'][$pagenum][$questionid]) && - !empty($questionnaire['questionsinfo'][$pagenum][$questionid])) { - $data['questions'][$pagenum][$i]['info'] = $questionnaire['questionsinfo'][$pagenum][$questionid]; + foreach ($questionnairedata['questions'][$pagenum] as $questionid => $choices) { + if (isset($questionnairedata['questionsinfo'][$pagenum][$questionid]) && + !empty($questionnairedata['questionsinfo'][$pagenum][$questionid])) { + $data['questions'][$pagenum][$i]['info'] = $questionnairedata['questionsinfo'][$pagenum][$questionid]; if ($data['questions'][$pagenum][$i]['info']['required'] == 'n') { unset($data['questions'][$pagenum][$i]['info']['required']); } @@ -159,7 +165,7 @@ public static function mobile_view_activity($args) { $i++; } } - if (isset($questionnaire['questions'][$pagenum + 1]) && !empty($questionnaire['questions'][$pagenum + 1])) { + if (isset($questionnairedata['questions'][$pagenum + 1]) && !empty($questionnairedata['questions'][$pagenum + 1])) { $data['nextpage'] = $pagenum + 1; } if ($prevpage) { @@ -178,16 +184,16 @@ public static function mobile_view_activity($args) { ], ], 'otherdata' => [ - 'fields' => json_encode($questionnaire['fields']), - 'questionsinfo' => json_encode($questionnaire['questionsinfo']), - 'questions' => json_encode($questionnaire['questions']), + 'fields' => json_encode($questionnairedata['fields']), + 'questionsinfo' => json_encode($questionnairedata['questionsinfo']), + 'questions' => json_encode($questionnairedata['questions']), 'pagequestions' => json_encode($data['pagequestions']), - 'responses' => json_encode($questionnaire['responses']), + 'responses' => json_encode($questionnairedata['responses']), 'pagenum' => $pagenum, 'nextpage' => $data['nextpage'], 'prevpage' => $data['prevpage'], 'completed' => $data['completed'], - 'intro' => $questionnaire['questionnaire']['intro'], + 'intro' => $questionnairedata['questionnaire']['intro'], 'string_required' => get_string('required') ], 'files' => null diff --git a/externallib.php b/externallib.php index ce459680..c510c0d2 100644 --- a/externallib.php +++ b/externallib.php @@ -27,6 +27,7 @@ defined('MOODLE_INTERNAL') || die; require_once($CFG->libdir . '/externallib.php'); require_once($CFG->dirroot . '/mod/questionnaire/lib.php'); +require_once($CFG->dirroot . '/mod/questionnaire/questionnaire.class.php'); /** * Questionnaire module external functions @@ -83,7 +84,7 @@ public static function submit_questionnaire_response_parameters() { * @since Moodle 3.0 */ public static function submit_questionnaire_response($questionnaireid, $surveyid, $userid, $cmid, $sec, $completed, $submit, $responses) { - $params = self::validate_parameters(self::submit_questionnaire_response_parameters(), + self::validate_parameters(self::submit_questionnaire_response_parameters(), [ 'questionnaireid' => $questionnaireid, 'surveyid' => $surveyid, @@ -96,17 +97,15 @@ public static function submit_questionnaire_response($questionnaireid, $surveyid ] ); - if (!$questionnaire = questionnaire_get_instance($params['questionnaireid'])) { - throw new \moodle_exception("invalidcoursemodule", "error"); - } - list($course, $cm) = get_course_and_cm_from_instance($questionnaire, 'questionnaire'); + list($cm, $course, $questionnaire) = questionnaire_get_standard_page_items($cmid); + $questionnaire = new \questionnaire(0, $questionnaire, $course, $cm); $context = \context_module::instance($cm->id); self::validate_context($context); require_capability('mod/questionnaire:submit', $context); - $result = questionnaire_save_mobile_data($questionnaireid, $surveyid, $userid, $cmid, $sec, $completed, $submit, $responses); + $result = $questionnaire->save_mobile_data($userid, $sec, $completed, $submit, $responses); $result['submitted'] = true; if (isset($result['warnings']) && !empty($result['warnings'])) { unset($result['responses']); @@ -119,7 +118,7 @@ public static function submit_questionnaire_response($questionnaireid, $surveyid /** * Describes the submit_questionnaire_response return value. * - * @return external_multiple_structure + * @return external_single_structure * @since Moodle 3.0 */ public static function submit_questionnaire_response_returns() { diff --git a/lib.php b/lib.php index 2ab115c1..d2f75ff5 100644 --- a/lib.php +++ b/lib.php @@ -466,429 +466,6 @@ function questionnaire_scale_used_anywhere($scaleid) { return false; } -/** - * Get questionnaire data - * - * @global object $DB - * @param int $cmid - * @param int|bool $userid - * @return array - * @throws moodle_exception - */ -function questionnaire_get_mobile_data($cmid, $userid = false) { - global $DB, $USER; - if ($q = get_coursemodule_from_id('questionnaire', $cmid)) { - if (!$questionnaire = questionnaire_get_instance($q->instance)) { - throw new \moodle_exception("invalidcoursemodule", "error"); - } - } - $ret = [ - 'questionnaire' => [ - 'id' => $questionnaire->id, - 'name' => format_string($questionnaire->name), - 'intro' => $questionnaire->intro, - 'userid' => intval($userid ? $userid : $USER->id), - 'questionnaireid' => intval($questionnaire->sid), - 'autonumpages' => in_array($questionnaire->autonum, [1, 2]), - 'autonumquestions' => in_array($questionnaire->autonum, [1, 3]) - ], - 'response' => [ - 'id' => 0, - 'questionnaireid' => 0, - 'submitted' => 0, - 'complete' => 'n', - 'grade' => 0, - 'userid' => 0, - 'fullname' => '', - 'userdate' => '', - ], - 'answered' => [], - 'fields' => [], - 'responses' => [], - 'questionscount' => 0, - 'pagescount' => 1, - ]; - $sql = 'SELECT qq.*,qqt.response_table FROM ' - . '{questionnaire_question} qq LEFT JOIN {questionnaire_question_type} qqt ' - . 'ON qq.type_id = qqt.typeid WHERE qq.surveyid = ? AND qq.deleted = ? ' - . 'ORDER BY qq.position'; - if ($questions = $DB->get_records_sql($sql, [$questionnaire->sid, 'n'])) { - require_once('classes/question/base.php'); - $pagenum = 1; - $context = \context_module::instance($cmid); - $qnum = 0; - foreach ($questions as $question) { - $ret['questionscount']++; - $qnum++; - $fieldkey = 'response_'.$question->type_id.'_'.$question->id; - $options = ['noclean' => true, 'para' => false, 'filter' => true, - 'context' => $context, 'overflowdiv' => true]; - if ($question->type_id != QUESPAGEBREAK) { - $ret['questionsinfo'][$pagenum][$question->id] = - $ret['fields'][$fieldkey] = [ - 'id' => $question->id, - 'surveyid' => $question->surveyid, - 'name' => $question->name, - 'type_id' => $question->type_id, - 'length' => $question->length, - 'content' => ($ret['questionnaire']['autonumquestions'] ? '' : '') . format_text(file_rewrite_pluginfile_urls( - $question->content, 'pluginfile.php', $context->id, - 'mod_questionnaire', 'question', $question->id), - FORMAT_HTML, $options), - 'content_stripped' => strip_tags($question->content), - 'required' => $question->required, - 'deleted' => $question->deleted, - 'response_table' => $question->response_table, - 'fieldkey' => $fieldkey, - 'precise' => $question->precise, - 'qnum' => $qnum, - 'errormessage' => get_string('required') . ': ' . $question->name - ]; - } - $std = new \stdClass(); - $std->id = $std->choice_id = 0; - $std->question_id = $question->id; - $std->content = ''; - $std->value = null; - switch ($question->type_id) { - case QUESYESNO: // Yes/No bool - $stdyes = new \stdClass(); - $stdyes->id = 1; - $stdyes->choice_id = 'y'; - $stdyes->question_id = $question->id; - $stdyes->value = null; - $stdyes->content = get_string('yes'); - $stdyes->isbool = true; - if ($ret['questionsinfo'][$pagenum][$question->id]['required']) { - $stdyes->value = 'y'; - $stdyes->firstone = true; - } - $ret['questions'][$pagenum][$question->id][1] = $stdyes; - $stdno = new \stdClass(); - $stdno->id = 0; - $stdno->choice_id = 'n'; - $stdno->question_id = $question->id; - $stdno->value = null; - $stdno->content = get_string('no'); - $stdno->isbool = true; - $ret['questions'][$pagenum][$question->id][0] = $stdno; - $ret['questionsinfo'][$pagenum][$question->id]['isbool'] = true; - $ret['responses']['response_'.$question->type_id.'_'.$question->id] = 'n'; - break; - case QUESTEXT: // Text - case QUESESSAY: // Essay - $ret['questions'][$pagenum][$question->id][0] = $std; - $ret['questionsinfo'][$pagenum][$question->id]['istextessay'] = true; - break; - case QUESRADIO: // Radiobutton - case QUESCHECK: // Checkbox - case QUESDROP: // Select - case QUESRATE: // Rate 1-NN - $excludes = []; - if ($items = $DB->get_records('questionnaire_quest_choice', - ['question_id' => $question->id])) { - if ($question->type_id == QUESRADIO) { - $ret['questionsinfo'][$pagenum][$question->id]['isradiobutton'] = true; - } - if ($question->type_id == QUESCHECK) { - $ret['questionsinfo'][$pagenum][$question->id]['ischeckbox'] = true; - } - if ($question->type_id == QUESDROP) { - $ret['questionsinfo'][$pagenum][$question->id]['isselect'] = true; - } - if ($question->type_id == QUESRATE) { - $ret['questionsinfo'][$pagenum][$question->id]['israte'] = true; - $vals = $extracontents = []; - foreach ($items as $item) { - $item->na = false; - if ($question->precise == 0) { - $ret['questions'][$pagenum][$question->id][$item->id] = $item; - if ($ret['questionsinfo'][$pagenum][$question->id]['required'] == 'y') { - $ret['questions'][$pagenum][$question->id][$item->id]->min - = $ret['questions'][$pagenum][$question->id][$item->id]->minstr = 1; - } else { - $ret['questions'][$pagenum][$question->id][$item->id]->min - = $ret['questions'][$pagenum][$question->id][$item->id]->minstr = 0; - } - $ret['questions'][$pagenum][$question->id][$item->id]->max - = $ret['questions'][$pagenum][$question->id][$item->id]->maxstr - = intval($question->length); - } else if ($question->precise == 1) { - $ret['questions'][$pagenum][$question->id][$item->id] = $item; - if ($ret['questionsinfo'][$pagenum][$question->id]['required'] == 'y') { - $ret['questions'][$pagenum][$question->id][$item->id]->min - = $ret['questions'][$pagenum][$question->id][$item->id]->minstr = 1; - } else { - $ret['questions'][$pagenum][$question->id][$item->id]->min - = $ret['questions'][$pagenum][$question->id][$item->id]->minstr = 0; - } - $ret['questions'][$pagenum][$question->id][$item->id]->max = intval($question->length) + 1; - $ret['questions'][$pagenum][$question->id][$item->id]->na = true; - $extracontents[] = $ret['questions'][$pagenum][$question->id][$item->id]->max - . ' = ' . get_string('notapplicable', 'mod_questionnaire'); - } else if ($question->precise > 1) { - $excludes[$item->id] = $item->id; - if ($item->value == null) { - if ($arr = explode('|', $item->content)) { - if (count($arr) == 2) { - $ret['questions'][$pagenum][$question->id][$item->id] = $item; - $ret['questions'][$pagenum][$question->id][$item->id]->content = ''; - $ret['questions'][$pagenum][$question->id][$item->id]->minstr = $arr[0]; - $ret['questions'][$pagenum][$question->id][$item->id]->maxstr = $arr[1]; - } - } - } else { - $val = intval($item->value); - $vals[$val] = $val; - $extracontents[] = $item->content; - } - } - } - if ($vals) { - if ($q = $ret['questions'][$pagenum][$question->id]) { - foreach (array_keys($q) as $itemid) { - $ret['questions'][$pagenum][$question->id][$itemid]->min = min($vals); - $ret['questions'][$pagenum][$question->id][$itemid]->max = max($vals); - } - } - } - if ($extracontents) { - $extracontents = array_unique($extracontents); - $extrahtml = '
    '; - foreach ($extracontents as $extracontent) { - $extrahtml .= '
  • '.$extracontent.'
  • '; - } - $extrahtml .= '
'; - $ret['questionsinfo'][$pagenum][$question->id]['content'] .= format_text($extrahtml, FORMAT_HTML, $options); - } - } - foreach ($items as $item) { - if (!in_array($item->id, $excludes)) { - $item->choice_id = $item->id; - if ($item->value == null) { - $item->value = ''; - } - $ret['questions'][$pagenum][$question->id][$item->id] = $item; - if ($question->type_id != QUESRATE) { - if ($ret['questionsinfo'][$pagenum][$question->id]['required']) { - if (!isset($ret['questionsinfo'][$pagenum][$question->id]['firstone'])) { - $ret['questionsinfo'][$pagenum][$question->id]['firstone'] = true; - $ret['questions'][$pagenum][$question->id][$item->id]->value = intval($item->choice_id); - $ret['questions'][$pagenum][$question->id][$item->id]->firstone = true; - } - } - } - } - } - } - break; - case QUESPAGEBREAK: - $ret['questionscount']--; - $ret['pagescount']++; - $pagenum++; - $qnum--; - continue; - } - $ret['questionsinfo'][$pagenum][$question->id]['qnum'] = $qnum; - if ($ret['questionnaire']['autonumquestions']) { - $ret['questionsinfo'][$pagenum][$question->id]['content'] = - $qnum.'. '.$ret['questionsinfo'][$pagenum][$question->id]['content']; - $ret['questionsinfo'][$pagenum][$question->id]['content_stripped'] = - $qnum.'. '.$ret['questionsinfo'][$pagenum][$question->id]['content_stripped']; - } - } - if ($userid) { - if ($response = $DB->get_record_sql('SELECT qr.* FROM {questionnaire_response} qr ' - . 'LEFT JOIN {user} u ON qr.userid = u.id WHERE qr.questionnaireid = ? ' - . 'AND qr.userid = ?', [$questionnaire->id, $userid])) { - $ret['response'] = (array) $response; - $ret['response']['submitted_userdate'] = ''; - if (isset($ret['response']['submitted']) && !empty($ret['response']['submitted'])) { - $ret['response']['submitted_userdate'] = userdate($ret['response']['submitted']); - } - $ret['response']['fullname'] = fullname($DB->get_record('user', ['id' => $userid])); - $ret['response']['userdate'] = userdate($ret['response']['submitted']); - foreach ($ret['questionsinfo'] as $pagenum => $data1) { - foreach ($data1 as $questionid => $data2) { - $ret['answered'][$questionid] = false; - if (isset($data2['response_table']) && !empty($data2['response_table'])) { - if ($values = $DB->get_records_sql('SELECT * FROM {questionnaire_' - . $data2['response_table'] . '} WHERE response_id = ? AND question_id = ?', - [$response->id, $questionid])) { - foreach ($values as $value) { - switch($data2['type_id']) { - case QUESYESNO: // Yes/No bool - if (isset($ret['questions'][$pagenum][$questionid])) { - if (isset($value->choice_id) && !empty($value->choice_id)) { - $ret['answered'][$questionid] = true; - if ($value->choice_id == 'y') { - $ret['questions'][$pagenum][$questionid][1]->value = 'y'; - $ret['responses']['response_'.$data2['type_id'].'_'.$questionid] = 'y'; - } else { - $ret['questions'][$pagenum][$questionid][0]->value = 'n'; - $ret['responses']['response_'.$data2['type_id'].'_'.$questionid] = 'n'; - } - } - } - break; - case QUESTEXT: // Text - case QUESESSAY: // Essay - if (isset($value->response) && !empty($value->response)) { - $ret['answered'][$questionid] = true; - $ret['questions'][$pagenum][$questionid][0]->value = $value->response; - $ret['responses']['response_'.$data2['type_id'].'_'.$questionid] = $value->response; - } - break; - case QUESRADIO: // Radiobutton - case QUESCHECK: // Checkbox - case QUESDROP: // Select - if ($value = $DB->get_records_sql('SELECT * FROM {questionnaire_' - . $data2['response_table'] . '} WHERE response_id = ? AND question_id = ?', - [$response->id, $questionid])) { - foreach ($value as $row) { - foreach ($ret['questions'][$pagenum][$questionid] as $k => $item) { - if ($item->id == $row->choice_id) { - $ret['answered'][$questionid] = true; - $ret['questions'][$pagenum][$questionid][$k]->value = intval($item->id); - $ret['responses']['response_'.$data2['type_id'].'_'.$questionid] = intval($item->id); - } - } - } - } - break; - case QUESRATE: // Rate 1-NN - if ($value = $DB->get_records_sql('SELECT * FROM {questionnaire_' - . $data2['response_table'] . '} WHERE response_id = ? AND question_id = ?', - [$response->id, $questionid])) { - foreach ($value as $row) { - if ($questionid == $row->question_id) { - $ret['answered'][$questionid] = true; - $v = $row->rankvalue + 1; - if ($ret['questionsinfo'][$pagenum][$questionid]['precise'] == 1) { - if ($row->rankvalue == -1) { - $v = $ret['questions'][$pagenum][$questionid][$row->choice_id]->max; - } - } - $ret['questions'][$pagenum][$questionid][$row->choice_id]->value - = $ret['responses']['response_'.$data2['type_id'].'_'.$questionid.'_'.$row->choice_id] = $v; - $ret['questions'][$pagenum][$questionid][$row->choice_id]->choice_id = $row->choice_id; - } - } - } - break; - } - } - } - } - } - } - } - } - } - return $ret; -} - -function questionnaire_save_mobile_data($questionnaireid, $surveyid, $userid, $cmid, $sec, $completed, $submit, array $responses) { - global $DB, $CFG; // Do not delete $CFG!!! - require_once($CFG->dirroot.'/mod/questionnaire/questionnaire.class.php'); - $ret = [ - 'responses' => [], - 'warnings' => [] - ]; - $cm = get_coursemodule_from_id('questionnaire', $cmid); - $questionnaire = new questionnaire($questionnaireid, null, $DB->get_record('course', ['id' => $cm->course]), $cm); - - if (!$completed) { - $rid = $questionnaire->delete_insert_response($DB->get_field('questionnaire_response', 'id', - ['questionnaireid' => $questionnaireid, 'complete' => 'n', 'userid' => $userid]), $sec, $userid); - $questionnairedata = questionnaire_get_mobile_data($cmid, $userid); - $pagequestions = isset($questionnairedata['questions'][$sec]) ? $questionnairedata['questions'][$sec] : []; - if (!empty($pagequestions)) { - $pagequestionsids = array_keys($pagequestions); - $missingquestions = $warningmessages = []; - foreach ($pagequestionsids as $questionid) { - $missingquestions[$questionid] = $questionid; - } - foreach ($pagequestionsids as $questionid) { - foreach ($responses as $response) { - $args = explode('_', $response['name']); - if (count($args) >= 3) { - $typeid = intval($args[1]); - $rquestionid = intval($args[2]); - if (in_array($rquestionid, $pagequestionsids)) { - unset($missingquestions[$rquestionid]); - if ($rquestionid == $questionid) { - if ($typeid == $questionnairedata['questionsinfo'][$sec][$rquestionid]['type_id']) { - if ($rquestionid > 0 && !in_array($response['value'], array(-9999, 'undefined'))) { - switch ($questionnairedata['questionsinfo'][$sec][$rquestionid]['type_id']) { - case QUESRATE: - if (isset($args[3]) && !empty($args[3])) { - $choiceid = intval($args[3]); - $value = intval($response['value']) - 1; - $rec = new \stdClass(); - $rec->response_id = $rid; - $rec->question_id = intval($rquestionid); - $rec->choice_id = $choiceid; - $rec->rankvalue = $value; - if ($questionnairedata['questionsinfo'][$sec][$rquestionid]['precise'] == 1) { - if ($value == $questionnairedata['questions'][$sec][$rquestionid][$choiceid]->max) { - $rec->rank = -1; - } - } - $DB->insert_record('questionnaire_response_rank', $rec); - } - break; - default: - if ($questionnairedata['questionsinfo'][$sec][$rquestionid]['required'] == 'n' - || ($questionnairedata['questionsinfo'][$sec][$rquestionid]['required'] == 'y' - && !empty($response['value']))) { - $questionobj = \mod_questionnaire\question\base::question_builder( - $questionnairedata['questionsinfo'][$sec][$rquestionid]['type_id'], - $questionnairedata['questionsinfo'][$sec][$rquestionid]); - if ($questionobj->insert_response($rid, $response['value'])) { - $ret['responses'][$rid][$questionid] = $response['value']; - } - } else { - $ret['warnings'][] = [ - 'item' => 'mod_questionnaire_question', - 'itemid' => $questionid, - 'warningcode' => 'required', - 'message' => s(get_string('required') . ': ' . $questionnairedata['questionsinfo'][$sec][$questionid]['name']) - ]; - } - } - } else { - $missingquestions[$rquestionid] = $rquestionid; - } - } - } - } - } - } - } - if ($missingquestions) { - foreach ($missingquestions as $questionid) { - if ($questionnairedata['questionsinfo'][$sec][$questionid]['required'] == 'y') { - $ret['warnings'][] = [ - 'item' => 'mod_questionnaire_question', - 'itemid' => $questionid, - 'warningcode' => 'required', - 'message' => s(get_string('required') . ': ' . $questionnairedata['questionsinfo'][$sec][$questionid]['name']) - ]; - } - } - } - } - } - if ($submit && (!isset($ret['warnings']) || empty($ret['warnings']))) { - $questionnaire->commit_submission_response( - $DB->get_field('questionnaire_response', 'id', - ['questionnaireid' => $questionnaireid, 'complete' => 'n', - 'userid' => $userid]), $userid); - } - return $ret; -} - /** * Serves the questionnaire attachments. Implements needed access control ;-) * diff --git a/questionnaire.class.php b/questionnaire.class.php index b24486d2..c468fd52 100644 --- a/questionnaire.class.php +++ b/questionnaire.class.php @@ -1634,6 +1634,13 @@ private function response_commit($rid) { return $DB->update_record('questionnaire_response', $record); } + /** + * Get the latest response id for the user, or verify that the given response id is valid. + * @param $userid + * @param int $rid + * @return int|string + * @throws dml_exception + */ private function get_response($userid, $rid = 0) { global $DB; From ead76ace21f7d351bdf6652e8cd57d9b1467f458 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Mon, 10 Dec 2018 17:17:49 -0500 Subject: [PATCH 037/341] Poet - Starting the move of mobile output to the question classes. --- classes/question/base.php | 71 ++++++++++ classes/question/check.php | 13 ++ classes/question/drop.php | 13 ++ classes/question/essay.php | 28 ++++ classes/question/pagebreak.php | 34 +++++ classes/question/radio.php | 13 ++ classes/question/rate.php | 97 +++++++++++++ classes/question/text.php | 28 ++++ classes/question/yesno.php | 42 ++++++ questionnaire.class.php | 252 +++++++++++++++++++++++++++++++++ 10 files changed, 591 insertions(+) diff --git a/classes/question/base.php b/classes/question/base.php index beaa48a7..afe1d93b 100644 --- a/classes/question/base.php +++ b/classes/question/base.php @@ -1356,4 +1356,75 @@ protected function form_preprocess_choicedata($formdata) { } return false; } + + /** + * Override and return false if not supporting mobile app. + * + * @param $qnum + * @param $fieldkey + * @param bool $autonum + * @return \stdClass + * @throws \coding_exception + */ + public function get_mobile_data($qnum, $fieldkey, $autonum = false) { + $mobiledata = new \stdClass(); + if ($this->type_id != QUESPAGEBREAK) { + $options = ['noclean' => true, 'para' => false, 'filter' => true, + 'context' => $this->context, 'overflowdiv' => true]; + $mobiledata->questionsinfo = [ + 'id' => $this->id, + 'surveyid' => $this->surveyid, + 'name' => $this->name, + 'type_id' => $this->type_id, + 'length' => $this->length, + 'content' => ($autonum ? '' : '') . format_text(file_rewrite_pluginfile_urls( + $this->content, 'pluginfile.php', $this->context->id, + 'mod_questionnaire', 'question', $this->id), + FORMAT_HTML, $options), + 'content_stripped' => strip_tags($this->content), + 'required' => $this->required, + 'deleted' => $this->deleted, + 'response_table' => $this->responsetable, + 'fieldkey' => $fieldkey, + 'precise' => $this->precise, + 'qnum' => $qnum, + 'errormessage' => get_string('required') . ': ' . $this->name + ]; + $mobiledata->fields = $mobiledata->questionsinfo; + $this->add_mobile_choice_data($mobiledata); + + if ($autonum) { + $mobiledata->questionsinfo['content'] = $qnum . '. ' . $mobiledata->questionsinfo['content']; + $mobiledata->questionsinfo['content_stripped'] = $qnum . '. ' . $mobiledata->questionsinfo['content_stripped']; + } + } + $mobiledata->responses = ''; + return $mobiledata; + } + + /** + * @param $mobiledata + * @return mixed + */ + public function add_mobile_choice_data($mobiledata) { + if ($this->has_choices()) { + $mobiledata->questions = []; + foreach ($this->choices as $choiceid => $choice) { + $choice->choice_id = $choiceid; + if ($choice->value == null) { + $choice->value = ''; + } + $mobiledata->questions[$choiceid] = $choice; + if ($this->required()) { + if (!isset($mobiledata->questionsinfo['firstone'])) { + $mobiledata->questionsinfo['firstone'] = true; + $mobiledata->questions[$choiceid]->value = intval($choice->choice_id); + $mobiledata->questions[$choiceid]->firstone = true; + } + } + } + } + + return $mobiledata; + } } \ No newline at end of file diff --git a/classes/question/check.php b/classes/question/check.php index 387524d0..a60c9ab5 100644 --- a/classes/question/check.php +++ b/classes/question/check.php @@ -300,4 +300,17 @@ protected function form_preprocess_choicedata($formdata) { } return true; } + + /** + * @param $qnum + * @param $fieldkey + * @param bool $autonum + * @return \stdClass + * @throws \coding_exception + */ + public function get_mobile_data($qnum, $fieldkey, $autonum = false) { + $mobiledata = parent::get_mobile_data($qnum, $fieldkey, $autonum = false); + $mobiledata->questionsinfo['ischeckbox'] = true; + return $mobiledata; + } } \ No newline at end of file diff --git a/classes/question/drop.php b/classes/question/drop.php index 635e83cb..868cf708 100644 --- a/classes/question/drop.php +++ b/classes/question/drop.php @@ -148,4 +148,17 @@ protected function form_length(\MoodleQuickForm $mform, $helpname = '') { protected function form_precise(\MoodleQuickForm $mform, $helpname = '') { return base::form_precise_hidden($mform); } + + /** + * @param $qnum + * @param $fieldkey + * @param bool $autonum + * @return \stdClass + * @throws \coding_exception + */ + public function get_mobile_data($qnum, $fieldkey, $autonum = false) { + $mobiledata = parent::get_mobile_data($qnum, $fieldkey, $autonum = false); + $mobiledata->questionsinfo['isselect'] = true; + return $mobiledata; + } } \ No newline at end of file diff --git a/classes/question/essay.php b/classes/question/essay.php index e5279f74..e599ce6b 100644 --- a/classes/question/essay.php +++ b/classes/question/essay.php @@ -101,4 +101,32 @@ protected function form_precise(\MoodleQuickForm $mform, $helptext = '') { $mform->setType('length', PARAM_INT); return $mform; } + + /** + * @param $qnum + * @param $fieldkey + * @param bool $autonum + * @return \stdClass + * @throws \coding_exception + */ + public function get_mobile_data($qnum, $fieldkey, $autonum = false) { + $mobiledata = parent::get_mobile_data($qnum, $fieldkey, $autonum = false); + $mobiledata->questionsinfo['istextessay'] = true; + return $mobiledata; + } + + /** + * @param $mobiledata + * @return mixed + */ + public function add_mobile_choice_data($mobiledata) { + $mobiledata->questions = []; + $mobiledata->questions[0] = new \stdClass(); + $mobiledata->questions[0]->id = 0; + $mobiledata->questions[0]->choice_id = 0; + $mobiledata->questions[0]->question_id = $this->id; + $mobiledata->questions[0]->content = ''; + $mobiledata->questions[0]->value = null; + return $mobiledata; + } } \ No newline at end of file diff --git a/classes/question/pagebreak.php b/classes/question/pagebreak.php index fbb659de..b236c611 100644 --- a/classes/question/pagebreak.php +++ b/classes/question/pagebreak.php @@ -29,23 +29,57 @@ class pagebreak extends base { + /** + * @return object|string + */ protected function responseclass() { return ''; } + /** + * @return string + */ public function helpname() { return ''; } + /** + * @param object $data + * @param $descendantsdata + * @param bool $blankquestionnaire + * @return string + */ protected function question_survey_display($data, $descendantsdata, $blankquestionnaire=false) { return ''; } + /** + * @param object $data + * @return string + */ protected function response_survey_display($data) { return ''; } + /** + * @param edit_question_form $form + * @param questionnaire $questionnaire + * @return bool + */ public function edit_form(edit_question_form $form, questionnaire $questionnaire) { return false; } + + /** + * Override and return false if not supporting mobile app. + * + * @param $qnum + * @param $fieldkey + * @param bool $autonum + * @return \stdClass + * @throws \coding_exception + */ + public function get_mobile_data($qnum, $fieldkey, $autonum = false) { + return false; + } } \ No newline at end of file diff --git a/classes/question/radio.php b/classes/question/radio.php index 99888fed..921c80ae 100644 --- a/classes/question/radio.php +++ b/classes/question/radio.php @@ -268,4 +268,17 @@ protected function form_length(\MoodleQuickForm $mform, $helptext = '') { protected function form_precise(\MoodleQuickForm $mform, $helptext = '') { return base::form_precise_hidden($mform); } + + /** + * @param $qnum + * @param $fieldkey + * @param bool $autonum + * @return \stdClass + * @throws \coding_exception + */ + public function get_mobile_data($qnum, $fieldkey, $autonum = false) { + $mobiledata = parent::get_mobile_data($qnum, $fieldkey, $autonum = false); + $mobiledata->questionsinfo['isradiobutton'] = true; + return $mobiledata; + } } \ No newline at end of file diff --git a/classes/question/rate.php b/classes/question/rate.php index 95f54a3d..cd6e1fff 100644 --- a/classes/question/rate.php +++ b/classes/question/rate.php @@ -598,4 +598,101 @@ protected function form_preprocess_choicedata($formdata) { } return true; } + + /** + * @param $qnum + * @param $fieldkey + * @param bool $autonum + * @return \stdClass + * @throws \coding_exception + */ + public function get_mobile_data($qnum, $fieldkey, $autonum = false) { + $mobiledata = parent::get_mobile_data($qnum, $fieldkey, $autonum = false); + $mobiledata->questionsinfo['israte'] = true; + return $mobiledata; + } + + /** + * @param $mobiledata + * @return mixed + */ + public function add_mobile_choice_data($mobiledata) { + $mobiledata->questions = []; + $excludes = []; + $vals = $extracontents = []; + $mobiledata->questions = []; + foreach ($this->choices as $choiceid => $choice) { + $choice->na = false; + if ($this->precise == 0) { + $mobiledata->questions[$choiceid] = $choice; + if ($this->required()) { + $mobiledata->questions[$choiceid]->min = 1; + $mobiledata->questions[$choiceid]->minstr = 1; + } else { + $mobiledata->questions[$choiceid]->min = 0; + $mobiledata->questions[$choiceid]->minstr = 0; + } + $mobiledata->questions[$choiceid]->max = intval($this->length); + $mobiledata->questions[$choiceid]->maxstr = intval($this->length); + } else if ($this->precise == 1) { + $mobiledata->questions[$choiceid] = $choice; + if ($this->required()) { + $mobiledata->questions[$choiceid]->min = 1; + $mobiledata->questions[$choiceid]->minstr = 1; + } else { + $mobiledata->questions[$choiceid]->min = 0; + $mobiledata->questions[$choiceid]->minstr = 0; + } + $mobiledata->questions[$choiceid]->max = intval($this->length) + 1; + $mobiledata->questions[$choiceid]->na = true; + $extracontents[] = $mobiledata->questions[$choiceid]->max . ' = ' . + get_string('notapplicable', 'mod_questionnaire'); + } else if ($this->precise > 1) { + $excludes[$choiceid] = $choiceid; + if ($choice->value == null) { + if ($arr = explode('|', $choice->content)) { + if (count($arr) == 2) { + $mobiledata->questions[$choiceid] = $choice; + $mobiledata->questions[$choiceid]->content = ''; + $mobiledata->questions[$choiceid]->minstr = $arr[0]; + $mobiledata->questions[$choiceid]->maxstr = $arr[1]; + } + } + } else { + $val = intval($choice->value); + $vals[$val] = $val; + $extracontents[] = $choice->content; + } + } + if ($vals) { + if ($q = $mobiledata->questions) { + foreach (array_keys($q) as $itemid) { + $mobiledata->questions[$itemid]->min = min($vals); + $mobiledata->questions[$itemid]->max = max($vals); + } + } + } + if ($extracontents) { + $extracontents = array_unique($extracontents); + $extrahtml = '
    '; + foreach ($extracontents as $extracontent) { + $extrahtml .= '
  • '.$extracontent.'
  • '; + } + $extrahtml .= '
'; + $options = ['noclean' => true, 'para' => false, 'filter' => true, + 'context' => $this->context, 'overflowdiv' => true]; + $mobiledata->questions['content'] .= format_text($extrahtml, FORMAT_HTML, $options); + } + + if (!in_array($choiceid, $excludes)) { + $choice->choice_id = $choiceid; + if ($choice->value == null) { + $choice->value = ''; + } + $mobiledata->questions[$choiceid] = $choice; + } + } + + return $mobiledata; + } } \ No newline at end of file diff --git a/classes/question/text.php b/classes/question/text.php index f10de144..21468e33 100644 --- a/classes/question/text.php +++ b/classes/question/text.php @@ -107,4 +107,32 @@ protected function form_length(\MoodleQuickForm $mform, $helptext = '') { protected function form_precise(\MoodleQuickForm $mform, $helptext = '') { return parent::form_precise($mform, 'maxtextlength'); } + + /** + * @param $qnum + * @param $fieldkey + * @param bool $autonum + * @return \stdClass + * @throws \coding_exception + */ + public function get_mobile_data($qnum, $fieldkey, $autonum = false) { + $mobiledata = parent::get_mobile_data($qnum, $fieldkey, $autonum = false); + $mobiledata->questionsinfo['istextessay'] = true; + return $mobiledata; + } + + /** + * @param $mobiledata + * @return mixed + */ + public function add_mobile_choice_data($mobiledata) { + $mobiledata->questions = []; + $mobiledata->questions[0] = new \stdClass(); + $mobiledata->questions[0]->id = 0; + $mobiledata->questions[0]->choice_id = 0; + $mobiledata->questions[0]->question_id = $this->id; + $mobiledata->questions[0]->content = ''; + $mobiledata->questions[0]->value = null; + return $mobiledata; + } } \ No newline at end of file diff --git a/classes/question/yesno.php b/classes/question/yesno.php index 4a53d2af..13bc925e 100644 --- a/classes/question/yesno.php +++ b/classes/question/yesno.php @@ -199,4 +199,46 @@ protected function form_length(\MoodleQuickForm $mform, $helpname = '') { protected function form_precise(\MoodleQuickForm $mform, $helpname = '') { return base::form_precise_hidden($mform); } + + /** + * @param $qnum + * @param $fieldkey + * @param bool $autonum + * @return \stdClass + * @throws \coding_exception + */ + public function get_mobile_data($qnum, $fieldkey, $autonum = false) { + $mobiledata = parent::get_mobile_data($qnum, $fieldkey, $autonum = false); + $mobiledata->questionsinfo['isbool'] = true; + return $mobiledata; + } + + /** + * @param $mobiledata + * @return mixed + */ + public function add_mobile_choice_data($mobiledata) { + $mobiledata->questions = []; + $mobiledata->questions[0] = new \stdClass(); + $mobiledata->questions[0]->id = 0; + $mobiledata->questions[0]->choice_id = 'n'; + $mobiledata->questions[0]->question_id = $this->id; + $mobiledata->questions[0]->value = null; + $mobiledata->questions[0]->content = get_string('no'); + $mobiledata->questions[0]->isbool = true; + $mobiledata->questions[1] = new \stdClass(); + $mobiledata->questions[1]->id = 1; + $mobiledata->questions[1]->choice_id = 'y'; + $mobiledata->questions[1]->question_id = $this->id; + $mobiledata->questions[1]->value = null; + $mobiledata->questions[1]->content = get_string('yes'); + $mobiledata->questions[1]->isbool = true; + if ($this->required()) { + $mobiledata->questions[1]->value = 'y'; + $mobiledata->questions[1]->firstone = true; + } + $mobiledata->responses = 'n'; + + return $mobiledata; + } } \ No newline at end of file diff --git a/questionnaire.class.php b/questionnaire.class.php index c468fd52..2a82b20f 100644 --- a/questionnaire.class.php +++ b/questionnaire.class.php @@ -3586,4 +3586,256 @@ public function response_analysis($rid, $resps, $compare, $isgroupmember, $allre return $feedbackmessages; } + // Mobile support area. + /** + * Get questionnaire data + * + * @global object $DB + * @param int|bool $userid + * @return array + * @throws moodle_exception + */ + public function get_mobile_data($userid = false) { + global $DB, $USER; + + $ret = [ + 'questionnaire' => [ + 'id' => $this->id, + 'name' => format_string($this->name), + 'intro' => $this->intro, + 'userid' => intval($userid ? $userid : $USER->id), + 'questionnaireid' => intval($this->sid), + 'autonumpages' => in_array($this->autonum, [1, 2]), + 'autonumquestions' => in_array($this->autonum, [1, 3]) + ], + 'response' => [ + 'id' => 0, + 'questionnaireid' => 0, + 'submitted' => 0, + 'complete' => 'n', + 'grade' => 0, + 'userid' => 0, + 'fullname' => '', + 'userdate' => '', + ], + 'answered' => [], + 'fields' => [], + 'responses' => [], + 'questionscount' => 0, + 'pagescount' => 1, + ]; + + if (!empty($this->questions)) { + $pagenum = 1; + $qnum = 0; + foreach ($this->questions as $question) { + $ret['questionscount']++; + $qnum++; + $fieldkey = 'response_' . $question->type_id . '_' . $question->id; + if ($mobiledata = $question->get_mobile_data($qnum, $fieldkey, $ret['questionnaire']['autonumquestions'])) { + $ret['questionsinfo'][$pagenum][$question->id] = $mobiledata->questionsinfo; + $ret['fields'][$fieldkey] = $mobiledata->fields; + $ret['questions'][$pagenum][$question->id] = $mobiledata->questions; + $ret['responses']['response_' . $question->type_id . '_' . $question->id] = $mobiledata->responses; + } else if ($question->type_id == QUESPAGEBREAK) { + $ret['questionscount']--; + $ret['pagescount']++; + $pagenum++; + $qnum--; + } + } + if ($userid) { + if ($responses = $this->get_responses($userid)) { + $response = end($responses); + $ret['response'] = (array) $response; + $ret['response']['submitted_userdate'] = ''; + if (isset($ret['response']['submitted']) && !empty($ret['response']['submitted'])) { + $ret['response']['submitted_userdate'] = userdate($ret['response']['submitted']); + } + $ret['response']['fullname'] = fullname($DB->get_record('user', ['id' => $userid])); + $ret['response']['userdate'] = userdate($ret['response']['submitted']); + foreach ($ret['questionsinfo'] as $pagenum => $data1) { + foreach ($data1 as $questionid => $data2) { + $ret['answered'][$questionid] = false; + if (isset($data2['response_table']) && !empty($data2['response_table'])) { + if ($values = $DB->get_records_sql('SELECT * FROM {questionnaire_' + . $data2['response_table'] . '} WHERE response_id = ? AND question_id = ?', + [$response->id, $questionid])) { + foreach ($values as $value) { + switch($data2['type_id']) { + case QUESYESNO: // Yes/No bool + if (isset($ret['questions'][$pagenum][$questionid])) { + if (isset($value->choice_id) && !empty($value->choice_id)) { + $ret['answered'][$questionid] = true; + if ($value->choice_id == 'y') { + $ret['questions'][$pagenum][$questionid][1]->value = 'y'; + $ret['responses']['response_'.$data2['type_id'].'_'.$questionid] = 'y'; + } else { + $ret['questions'][$pagenum][$questionid][0]->value = 'n'; + $ret['responses']['response_'.$data2['type_id'].'_'.$questionid] = 'n'; + } + } + } + break; + case QUESTEXT: // Text + case QUESESSAY: // Essay + if (isset($value->response) && !empty($value->response)) { + $ret['answered'][$questionid] = true; + $ret['questions'][$pagenum][$questionid][0]->value = $value->response; + $ret['responses']['response_'.$data2['type_id'].'_'.$questionid] = $value->response; + } + break; + case QUESRADIO: // Radiobutton + case QUESCHECK: // Checkbox + case QUESDROP: // Select + if ($value = $DB->get_records_sql('SELECT * FROM {questionnaire_' + . $data2['response_table'] . '} WHERE response_id = ? AND question_id = ?', + [$response->id, $questionid])) { + foreach ($value as $row) { + foreach ($ret['questions'][$pagenum][$questionid] as $k => $item) { + if ($item->id == $row->choice_id) { + $ret['answered'][$questionid] = true; + $ret['questions'][$pagenum][$questionid][$k]->value = intval($item->id); + $ret['responses']['response_'.$data2['type_id'].'_'.$questionid] = intval($item->id); + } + } + } + } + break; + case QUESRATE: // Rate 1-NN + if ($value = $DB->get_records_sql('SELECT * FROM {questionnaire_' + . $data2['response_table'] . '} WHERE response_id = ? AND question_id = ?', + [$response->id, $questionid])) { + foreach ($value as $row) { + if ($questionid == $row->question_id) { + $ret['answered'][$questionid] = true; + $v = $row->rankvalue + 1; + if ($ret['questionsinfo'][$pagenum][$questionid]['precise'] == 1) { + if ($row->rankvalue == -1) { + $v = $ret['questions'][$pagenum][$questionid][$row->choice_id]->max; + } + } + $ret['questions'][$pagenum][$questionid][$row->choice_id]->value + = $ret['responses']['response_'.$data2['type_id'].'_'.$questionid.'_'.$row->choice_id] = $v; + $ret['questions'][$pagenum][$questionid][$row->choice_id]->choice_id = $row->choice_id; + } + } + } + break; + } + } + } + } + } + } + } + } + } + return $ret; + } + + /** + * @param $userid + * @param $sec + * @param $completed + * @param $submit + * @param array $responses + * @return array + * @throws coding_exception + * @throws dml_exception + * @throws moodle_exception + */ + function save_mobile_data($userid, $sec, $completed, $submit, array $responses) { + global $DB, $CFG; // Do not delete $CFG!!! + + if (!$completed) { + $rid = $this->delete_insert_response($DB->get_field('questionnaire_response', 'id', + ['questionnaireid' => $this->id, 'complete' => 'n', 'userid' => $userid]), $sec, $userid); + $questionnairedata = $this->get_mobile_data($userid); + $pagequestions = isset($questionnairedata['questions'][$sec]) ? $questionnairedata['questions'][$sec] : []; + if (!empty($pagequestions)) { + $pagequestionsids = array_keys($pagequestions); + $missingquestions = $warningmessages = []; + foreach ($pagequestionsids as $questionid) { + $missingquestions[$questionid] = $questionid; + } + foreach ($pagequestionsids as $questionid) { + foreach ($responses as $response) { + $args = explode('_', $response['name']); + if (count($args) >= 3) { + $typeid = intval($args[1]); + $rquestionid = intval($args[2]); + if (in_array($rquestionid, $pagequestionsids)) { + unset($missingquestions[$rquestionid]); + if ($rquestionid == $questionid) { + if ($typeid == $questionnairedata['questionsinfo'][$sec][$rquestionid]['type_id']) { + if ($rquestionid > 0 && !in_array($response['value'], array(-9999, 'undefined'))) { + switch ($questionnairedata['questionsinfo'][$sec][$rquestionid]['type_id']) { + case QUESRATE: + if (isset($args[3]) && !empty($args[3])) { + $choiceid = intval($args[3]); + $value = intval($response['value']) - 1; + $rec = new \stdClass(); + $rec->response_id = $rid; + $rec->question_id = intval($rquestionid); + $rec->choice_id = $choiceid; + $rec->rankvalue = $value; + if ($questionnairedata['questionsinfo'][$sec][$rquestionid]['precise'] == 1) { + if ($value == $questionnairedata['questions'][$sec][$rquestionid][$choiceid]->max) { + $rec->rank = -1; + } + } + $DB->insert_record('questionnaire_response_rank', $rec); + } + break; + default: + if ($questionnairedata['questionsinfo'][$sec][$rquestionid]['required'] == 'n' + || ($questionnairedata['questionsinfo'][$sec][$rquestionid]['required'] == 'y' + && !empty($response['value']))) { + $questionobj = \mod_questionnaire\question\base::question_builder( + $questionnairedata['questionsinfo'][$sec][$rquestionid]['type_id'], + $questionnairedata['questionsinfo'][$sec][$rquestionid]); + if ($questionobj->insert_response($rid, $response['value'])) { + $ret['responses'][$rid][$questionid] = $response['value']; + } + } else { + $ret['warnings'][] = [ + 'item' => 'mod_questionnaire_question', + 'itemid' => $questionid, + 'warningcode' => 'required', + 'message' => s(get_string('required') . ': ' . $questionnairedata['questionsinfo'][$sec][$questionid]['name']) + ]; + } + } + } else { + $missingquestions[$rquestionid] = $rquestionid; + } + } + } + } + } + } + } + if ($missingquestions) { + foreach ($missingquestions as $questionid) { + if ($questionnairedata['questionsinfo'][$sec][$questionid]['required'] == 'y') { + $ret['warnings'][] = [ + 'item' => 'mod_questionnaire_question', + 'itemid' => $questionid, + 'warningcode' => 'required', + 'message' => s(get_string('required') . ': ' . $questionnairedata['questionsinfo'][$sec][$questionid]['name']) + ]; + } + } + } + } + } + if ($submit && (!isset($ret['warnings']) || empty($ret['warnings']))) { + $this->commit_submission_response( + $DB->get_field('questionnaire_response', 'id', + ['questionnaireid' => $this->id, 'complete' => 'n', + 'userid' => $userid]), $userid); + } + return $ret; + } } \ No newline at end of file From c1571cf18ddbddd84cd6eeeabc852857eec30359 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Tue, 11 Dec 2018 15:04:29 -0500 Subject: [PATCH 038/341] Poet - Moving mobile results to question classes. --- classes/question/base.php | 135 ++++++++++++++++++++++++++++++++- classes/question/check.php | 13 +++- classes/question/drop.php | 13 +++- classes/question/essay.php | 84 ++++++++++++-------- classes/question/pagebreak.php | 11 ++- classes/question/radio.php | 13 +++- classes/question/rate.php | 49 +++++++++++- classes/question/text.php | 50 +++++++++++- classes/question/yesno.php | 43 ++++++++++- questionnaire.class.php | 84 +++----------------- 10 files changed, 371 insertions(+), 124 deletions(-) diff --git a/classes/question/base.php b/classes/question/base.php index afe1d93b..28ead279 100644 --- a/classes/question/base.php +++ b/classes/question/base.php @@ -211,6 +211,9 @@ public function has_choices() { return false; } + /** + * @throws \dml_exception + */ private function get_choices() { global $DB; @@ -249,6 +252,9 @@ public function allows_dependents() { return false; } + /** + * @throws \dml_exception + */ private function get_dependencies() { global $DB; @@ -349,6 +355,9 @@ public function dependency_fulfilled($rid, $questions) { return $fulfilled; } + /** + * @return mixed + */ public function response_table() { return $this->response->response_table(); } @@ -598,6 +607,9 @@ public function add($questionrecord, array $choicerecords = null, $calcposition } } + /** + * @return bool + */ public function update_choices() { $retvalue = true; if ($this->has_choices() && isset($this->choices)) { @@ -619,11 +631,21 @@ public function update_choices() { return $retvalue; } + /** + * @param $choicerecord + * @return bool + * @throws \dml_exception + */ public function update_choice($choicerecord) { global $DB; return $DB->update_record('questionnaire_quest_choice', $choicerecord); } + /** + * @param $choicerecord + * @return bool + * @throws \dml_exception + */ public function add_choice($choicerecord) { global $DB; $retvalue = true; @@ -659,11 +681,21 @@ public function delete_choice($choice) { return $retvalue; } + /** + * @param $dependencyrecord + * @return bool + * @throws \dml_exception + */ public function update_dependency($dependencyrecord) { global $DB; return $DB->update_record('questionnaire_dependency', $dependencyrecord); } + /** + * @param $dependencyrecord + * @return bool + * @throws \dml_exception + */ public function add_dependency($dependencyrecord) { global $DB; @@ -928,6 +960,11 @@ public function edit_form(edit_question_form $form, questionnaire $questionnaire return true; } + /** + * @param \MoodleQuickForm $mform + * @param string $helpname + * @throws \coding_exception + */ protected function form_header(\MoodleQuickForm $mform, $helpname = '') { // Display different messages for new question creation and existing question modification. if (isset($this->qid) && !empty($this->qid)) { @@ -943,6 +980,11 @@ protected function form_header(\MoodleQuickForm $mform, $helpname = '') { $mform->addHelpButton('questionhdredit', $helpname, 'questionnaire'); } + /** + * @param \MoodleQuickForm $mform + * @return \MoodleQuickForm + * @throws \coding_exception + */ protected function form_name(\MoodleQuickForm $mform) { $mform->addElement('text', 'name', get_string('optionalname', 'questionnaire'), ['size' => '30', 'maxlength' => '30']); @@ -951,6 +993,11 @@ protected function form_name(\MoodleQuickForm $mform) { return $mform; } + /** + * @param \MoodleQuickForm $mform + * @return \MoodleQuickForm + * @throws \coding_exception + */ protected function form_required(\MoodleQuickForm $mform) { $reqgroup = []; $reqgroup[] =& $mform->createElement('radio', 'required', '', get_string('yes'), 'y'); @@ -960,10 +1007,18 @@ protected function form_required(\MoodleQuickForm $mform) { return $mform; } + /** + * @param \MoodleQuickForm $mform + * @param string $helpname + */ protected function form_length(\MoodleQuickForm $mform, $helpname = '') { self::form_length_text($mform, $helpname); } + /** + * @param \MoodleQuickForm $mform + * @param string $helpname + */ protected function form_precise(\MoodleQuickForm $mform, $helpname = '') { self::form_precise_text($mform, $helpname); } @@ -1057,6 +1112,12 @@ protected function form_dependencies($form, $questions) { return true; } + /** + * @param \MoodleQuickForm $mform + * @param $context + * @return \MoodleQuickForm + * @throws \coding_exception + */ protected function form_question_text(\MoodleQuickForm $mform, $context) { $editoroptions = ['maxfiles' => EDITOR_UNLIMITED_FILES, 'trusttext' => true, 'context' => $context]; $mform->addElement('editor', 'content', get_string('text', 'questionnaire'), null, $editoroptions); @@ -1065,6 +1126,13 @@ protected function form_question_text(\MoodleQuickForm $mform, $context) { return $mform; } + /** + * @param \MoodleQuickForm $mform + * @param array $choices + * @param string $helpname + * @return string + * @throws \coding_exception + */ protected function form_choices(\MoodleQuickForm $mform, array $choices, $helpname = '') { $numchoices = count($choices); $allchoices = ''; @@ -1092,12 +1160,24 @@ protected function form_choices(\MoodleQuickForm $mform, array $choices, $helpna // Helper functions for commonly used editing functions. + /** + * @param \MoodleQuickForm $mform + * @param int $value + * @return \MoodleQuickForm + */ static public function form_length_hidden(\MoodleQuickForm $mform, $value = 0) { $mform->addElement('hidden', 'length', $value); $mform->setType('length', PARAM_INT); return $mform; } + /** + * @param \MoodleQuickForm $mform + * @param string $helpname + * @param int $value + * @return \MoodleQuickForm + * @throws \coding_exception + */ static public function form_length_text(\MoodleQuickForm $mform, $helpname = '', $value = 0) { $mform->addElement('text', 'length', get_string($helpname, 'questionnaire'), ['size' => '1'], $value); $mform->setType('length', PARAM_INT); @@ -1107,12 +1187,24 @@ static public function form_length_text(\MoodleQuickForm $mform, $helpname = '', return $mform; } + /** + * @param \MoodleQuickForm $mform + * @param int $value + * @return \MoodleQuickForm + */ static public function form_precise_hidden(\MoodleQuickForm $mform, $value = 0) { $mform->addElement('hidden', 'precise', $value); $mform->setType('precise', PARAM_INT); return $mform; } + /** + * @param \MoodleQuickForm $mform + * @param string $helpname + * @param int $value + * @return \MoodleQuickForm + * @throws \coding_exception + */ static public function form_precise_text(\MoodleQuickForm $mform, $helpname = '', $value = 0) { $mform->addElement('text', 'precise', get_string($helpname, 'questionnaire'), ['size' => '1']); $mform->setType('precise', PARAM_INT); @@ -1357,6 +1449,15 @@ protected function form_preprocess_choicedata($formdata) { return false; } + /** + * True if question provides mobile support. + * + * @return bool + */ + public function supports_mobile() { + return false; + } + /** * Override and return false if not supporting mobile app. * @@ -1366,7 +1467,7 @@ protected function form_preprocess_choicedata($formdata) { * @return \stdClass * @throws \coding_exception */ - public function get_mobile_data($qnum, $fieldkey, $autonum = false) { + public function get_mobile_question_data($qnum, $fieldkey, $autonum = false) { $mobiledata = new \stdClass(); if ($this->type_id != QUESPAGEBREAK) { $options = ['noclean' => true, 'para' => false, 'filter' => true, @@ -1391,7 +1492,7 @@ public function get_mobile_data($qnum, $fieldkey, $autonum = false) { 'errormessage' => get_string('required') . ': ' . $this->name ]; $mobiledata->fields = $mobiledata->questionsinfo; - $this->add_mobile_choice_data($mobiledata); + $this->add_mobile_question_choice_data($mobiledata); if ($autonum) { $mobiledata->questionsinfo['content'] = $qnum . '. ' . $mobiledata->questionsinfo['content']; @@ -1406,10 +1507,12 @@ public function get_mobile_data($qnum, $fieldkey, $autonum = false) { * @param $mobiledata * @return mixed */ - public function add_mobile_choice_data($mobiledata) { + public function add_mobile_question_choice_data($mobiledata) { if ($this->has_choices()) { $mobiledata->questions = []; foreach ($this->choices as $choiceid => $choice) { + $choice->id = $choiceid; + $choice->question_id = $this->id; $choice->choice_id = $choiceid; if ($choice->value == null) { $choice->value = ''; @@ -1427,4 +1530,30 @@ public function add_mobile_choice_data($mobiledata) { return $mobiledata; } + + /** + * @param $rid + * @return \stdClass + */ + public function get_mobile_response_data($rid) { + $results = $this->get_results($rid); + $resultdata = new \stdClass(); + $resultdata->answered = false; + $resultdata->questions = []; + $resultdata->responses = ''; + if (!empty($results) && $this->has_choices()) { + foreach ($results as $result) { + foreach ($this->choices as $choiceid => $choice) { + if ($choiceid == $result->choice_id) { + $resultdata->answered = true; + $resultdata->questions[$choiceid] = new \stdClass(); + $resultdata->questions[$choiceid]->value = $choiceid; + $resultdata->responses = $choiceid; + } + } + } + } + + return $resultdata; + } } \ No newline at end of file diff --git a/classes/question/check.php b/classes/question/check.php index a60c9ab5..4ee10003 100644 --- a/classes/question/check.php +++ b/classes/question/check.php @@ -278,6 +278,15 @@ protected function form_precise(\MoodleQuickForm $mform, $helptext = '') { return parent::form_precise($mform, 'maxforcedresponses'); } + /** + * True if question provides mobile support. + * + * @return bool + */ + public function supports_mobile() { + return true; + } + /** * Preprocess choice data. */ @@ -308,8 +317,8 @@ protected function form_preprocess_choicedata($formdata) { * @return \stdClass * @throws \coding_exception */ - public function get_mobile_data($qnum, $fieldkey, $autonum = false) { - $mobiledata = parent::get_mobile_data($qnum, $fieldkey, $autonum = false); + public function get_mobile_question_data($qnum, $fieldkey, $autonum = false) { + $mobiledata = parent::get_mobile_question_data($qnum, $fieldkey, $autonum = false); $mobiledata->questionsinfo['ischeckbox'] = true; return $mobiledata; } diff --git a/classes/question/drop.php b/classes/question/drop.php index 868cf708..81375fd9 100644 --- a/classes/question/drop.php +++ b/classes/question/drop.php @@ -149,6 +149,15 @@ protected function form_precise(\MoodleQuickForm $mform, $helpname = '') { return base::form_precise_hidden($mform); } + /** + * True if question provides mobile support. + * + * @return bool + */ + public function supports_mobile() { + return true; + } + /** * @param $qnum * @param $fieldkey @@ -156,8 +165,8 @@ protected function form_precise(\MoodleQuickForm $mform, $helpname = '') { * @return \stdClass * @throws \coding_exception */ - public function get_mobile_data($qnum, $fieldkey, $autonum = false) { - $mobiledata = parent::get_mobile_data($qnum, $fieldkey, $autonum = false); + public function get_mobile_question_data($qnum, $fieldkey, $autonum = false) { + $mobiledata = parent::get_mobile_question_data($qnum, $fieldkey, $autonum = false); $mobiledata->questionsinfo['isselect'] = true; return $mobiledata; } diff --git a/classes/question/essay.php b/classes/question/essay.php index e599ce6b..17f44a5b 100644 --- a/classes/question/essay.php +++ b/classes/question/essay.php @@ -26,16 +26,44 @@ defined('MOODLE_INTERNAL') || die(); use \html_writer; -class essay extends base { +class essay extends text { + /** + * @return object|string + */ protected function responseclass() { return '\\mod_questionnaire\\response\\text'; } + /** + * @return string + */ public function helpname() { return 'essaybox'; } + /** + * Override and return a form template if provided. Output of question_survey_display is iterpreted based on this. + * @return boolean | string + */ + public function question_template() { + return false; + } + + /** + * Override and return a response template if provided. Output of response_survey_display is iterpreted based on this. + * @return boolean | string + */ + public function response_template() { + return false; + } + + /** + * @param object $data + * @param $descendantsdata + * @param bool $blankquestionnaire + * @return object|string + */ protected function question_survey_display($data, $descendantsdata, $blankquestionnaire=false) { $output = ''; @@ -73,6 +101,10 @@ protected function question_survey_display($data, $descendantsdata, $blankquesti return $output; } + /** + * @param object $data + * @return object|string + */ protected function response_survey_display($data) { $output = ''; $output .= '
'; @@ -82,7 +114,12 @@ protected function response_survey_display($data) { } // Note - intentianally returning 'precise' for length and 'length' for precise. - + /** + * @param \MoodleQuickForm $mform + * @param string $helptext + * @return \MoodleQuickForm|void + * @throws \coding_exception + */ protected function form_length(\MoodleQuickForm $mform, $helptext = '') { $responseformats = array( "0" => get_string('formateditor', 'questionnaire'), @@ -92,6 +129,21 @@ protected function form_length(\MoodleQuickForm $mform, $helptext = '') { return $mform; } + /** + * True if question provides mobile support. + * + * @return bool + */ + public function supports_mobile() { + return true; + } + + /** + * @param \MoodleQuickForm $mform + * @param string $helptext + * @return \MoodleQuickForm|void + * @throws \coding_exception + */ protected function form_precise(\MoodleQuickForm $mform, $helptext = '') { $choices = array(); for ($lines = 5; $lines <= 40; $lines += 5) { @@ -101,32 +153,4 @@ protected function form_precise(\MoodleQuickForm $mform, $helptext = '') { $mform->setType('length', PARAM_INT); return $mform; } - - /** - * @param $qnum - * @param $fieldkey - * @param bool $autonum - * @return \stdClass - * @throws \coding_exception - */ - public function get_mobile_data($qnum, $fieldkey, $autonum = false) { - $mobiledata = parent::get_mobile_data($qnum, $fieldkey, $autonum = false); - $mobiledata->questionsinfo['istextessay'] = true; - return $mobiledata; - } - - /** - * @param $mobiledata - * @return mixed - */ - public function add_mobile_choice_data($mobiledata) { - $mobiledata->questions = []; - $mobiledata->questions[0] = new \stdClass(); - $mobiledata->questions[0]->id = 0; - $mobiledata->questions[0]->choice_id = 0; - $mobiledata->questions[0]->question_id = $this->id; - $mobiledata->questions[0]->content = ''; - $mobiledata->questions[0]->value = null; - return $mobiledata; - } } \ No newline at end of file diff --git a/classes/question/pagebreak.php b/classes/question/pagebreak.php index b236c611..543b5e4f 100644 --- a/classes/question/pagebreak.php +++ b/classes/question/pagebreak.php @@ -70,6 +70,15 @@ public function edit_form(edit_question_form $form, questionnaire $questionnaire return false; } + /** + * True if question provides mobile support. + * + * @return bool + */ + public function supports_mobile() { + return true; + } + /** * Override and return false if not supporting mobile app. * @@ -79,7 +88,7 @@ public function edit_form(edit_question_form $form, questionnaire $questionnaire * @return \stdClass * @throws \coding_exception */ - public function get_mobile_data($qnum, $fieldkey, $autonum = false) { + public function get_mobile_question_data($qnum, $fieldkey, $autonum = false) { return false; } } \ No newline at end of file diff --git a/classes/question/radio.php b/classes/question/radio.php index 921c80ae..3bae0352 100644 --- a/classes/question/radio.php +++ b/classes/question/radio.php @@ -269,6 +269,15 @@ protected function form_precise(\MoodleQuickForm $mform, $helptext = '') { return base::form_precise_hidden($mform); } + /** + * True if question provides mobile support. + * + * @return bool + */ + public function supports_mobile() { + return true; + } + /** * @param $qnum * @param $fieldkey @@ -276,8 +285,8 @@ protected function form_precise(\MoodleQuickForm $mform, $helptext = '') { * @return \stdClass * @throws \coding_exception */ - public function get_mobile_data($qnum, $fieldkey, $autonum = false) { - $mobiledata = parent::get_mobile_data($qnum, $fieldkey, $autonum = false); + public function get_mobile_question_data($qnum, $fieldkey, $autonum = false) { + $mobiledata = parent::get_mobile_question_data($qnum, $fieldkey, $autonum = false); $mobiledata->questionsinfo['isradiobutton'] = true; return $mobiledata; } diff --git a/classes/question/rate.php b/classes/question/rate.php index cd6e1fff..5441882c 100644 --- a/classes/question/rate.php +++ b/classes/question/rate.php @@ -599,6 +599,15 @@ protected function form_preprocess_choicedata($formdata) { return true; } + /** + * True if question provides mobile support. + * + * @return bool + */ + public function supports_mobile() { + return true; + } + /** * @param $qnum * @param $fieldkey @@ -606,8 +615,8 @@ protected function form_preprocess_choicedata($formdata) { * @return \stdClass * @throws \coding_exception */ - public function get_mobile_data($qnum, $fieldkey, $autonum = false) { - $mobiledata = parent::get_mobile_data($qnum, $fieldkey, $autonum = false); + public function get_mobile_question_data($qnum, $fieldkey, $autonum = false) { + $mobiledata = parent::get_mobile_question_data($qnum, $fieldkey, $autonum = false); $mobiledata->questionsinfo['israte'] = true; return $mobiledata; } @@ -616,13 +625,16 @@ public function get_mobile_data($qnum, $fieldkey, $autonum = false) { * @param $mobiledata * @return mixed */ - public function add_mobile_choice_data($mobiledata) { + public function add_mobile_question_choice_data($mobiledata) { $mobiledata->questions = []; $excludes = []; $vals = $extracontents = []; $mobiledata->questions = []; foreach ($this->choices as $choiceid => $choice) { $choice->na = false; + $choice->choice_id = $choiceid; + $choice->id = $choiceid; + $choice->question_id = $this->id; if ($this->precise == 0) { $mobiledata->questions[$choiceid] = $choice; if ($this->required()) { @@ -695,4 +707,35 @@ public function add_mobile_choice_data($mobiledata) { return $mobiledata; } + + /** + * @param $rid + * @return \stdClass + */ + public function get_mobile_response_data($rid) { + $results = $this->get_results($rid); + $resultdata = new \stdClass(); + $resultdata->answered = false; + $resultdata->questions = []; + $resultdata->responses = ''; + if (!empty($results) && $this->has_choices()) { + foreach ($results as $result) { + if ($this->id == $result->question_id) { + $resultdata->answered = true; + $v = $result->rankvalue + 1; + if (($this->precise == 1) && ($result->rankvalue == -1)) { + $v = $this->choices[$result->choice_id]->max; // This might be maxscore(). + } + if (!isset($resultdata->questions[$result->choice_id])) { + $resultdata->questions[$result->choice_id] = new \stdClass(); + } + $resultdata->questions[$result->choice_id]->value = $v; + $resultdata->questions[$result->choice_id]->choice_id = $result->choice_id; + $resultdata->responses = $v; + } + } + } + + return $resultdata; + } } \ No newline at end of file diff --git a/classes/question/text.php b/classes/question/text.php index 21468e33..1fc4e4a4 100644 --- a/classes/question/text.php +++ b/classes/question/text.php @@ -37,10 +37,16 @@ public function __construct($id = 0, $question = null, $context = null, $params return parent::__construct($id, $question, $context, $params); } + /** + * @return object|string + */ protected function responseclass() { return '\\mod_questionnaire\\response\\text'; } + /** + * @return string + */ public function helpname() { return 'textbox'; } @@ -100,14 +106,31 @@ protected function response_survey_display($data) { return $resptags; } + /** + * @param \MoodleQuickForm $mform + * @param string $helptext + */ protected function form_length(\MoodleQuickForm $mform, $helptext = '') { return parent::form_length($mform, 'fieldlength'); } + /** + * @param \MoodleQuickForm $mform + * @param string $helptext + */ protected function form_precise(\MoodleQuickForm $mform, $helptext = '') { return parent::form_precise($mform, 'maxtextlength'); } + /** + * True if question provides mobile support. + * + * @return bool + */ + public function supports_mobile() { + return true; + } + /** * @param $qnum * @param $fieldkey @@ -115,8 +138,8 @@ protected function form_precise(\MoodleQuickForm $mform, $helptext = '') { * @return \stdClass * @throws \coding_exception */ - public function get_mobile_data($qnum, $fieldkey, $autonum = false) { - $mobiledata = parent::get_mobile_data($qnum, $fieldkey, $autonum = false); + public function get_mobile_question_data($qnum, $fieldkey, $autonum = false) { + $mobiledata = parent::get_mobile_question_data($qnum, $fieldkey, $autonum = false); $mobiledata->questionsinfo['istextessay'] = true; return $mobiledata; } @@ -125,7 +148,7 @@ public function get_mobile_data($qnum, $fieldkey, $autonum = false) { * @param $mobiledata * @return mixed */ - public function add_mobile_choice_data($mobiledata) { + public function add_mobile_question_choice_data($mobiledata) { $mobiledata->questions = []; $mobiledata->questions[0] = new \stdClass(); $mobiledata->questions[0]->id = 0; @@ -135,4 +158,25 @@ public function add_mobile_choice_data($mobiledata) { $mobiledata->questions[0]->value = null; return $mobiledata; } + + /** + * @param $rid + * @return \stdClass + */ + public function get_mobile_response_data($rid) { + $results = $this->get_results($rid); + $resultdata = new \stdClass(); + $resultdata->answered = false; + $resultdata->questions = []; + $resultdata->responses = ''; + if (!empty($results) && $this->has_choices()) { + $resultdata->answered = true; + foreach ($results as $result) { + $resultdata->questions[0]->value = $result->response; + $resultdata->responses = $result->response; + } + } + + return $resultdata; + } } \ No newline at end of file diff --git a/classes/question/yesno.php b/classes/question/yesno.php index 13bc925e..05cd8c8a 100644 --- a/classes/question/yesno.php +++ b/classes/question/yesno.php @@ -200,6 +200,15 @@ protected function form_precise(\MoodleQuickForm $mform, $helpname = '') { return base::form_precise_hidden($mform); } + /** + * True if question provides mobile support. + * + * @return bool + */ + public function supports_mobile() { + return true; + } + /** * @param $qnum * @param $fieldkey @@ -207,8 +216,8 @@ protected function form_precise(\MoodleQuickForm $mform, $helpname = '') { * @return \stdClass * @throws \coding_exception */ - public function get_mobile_data($qnum, $fieldkey, $autonum = false) { - $mobiledata = parent::get_mobile_data($qnum, $fieldkey, $autonum = false); + public function get_mobile_question_data($qnum, $fieldkey, $autonum = false) { + $mobiledata = parent::get_mobile_question_data($qnum, $fieldkey, $autonum = false); $mobiledata->questionsinfo['isbool'] = true; return $mobiledata; } @@ -217,7 +226,7 @@ public function get_mobile_data($qnum, $fieldkey, $autonum = false) { * @param $mobiledata * @return mixed */ - public function add_mobile_choice_data($mobiledata) { + public function add_mobile_question_choice_data($mobiledata) { $mobiledata->questions = []; $mobiledata->questions[0] = new \stdClass(); $mobiledata->questions[0]->id = 0; @@ -241,4 +250,32 @@ public function add_mobile_choice_data($mobiledata) { return $mobiledata; } + + /** + * @param $rid + * @return \stdClass + */ + public function get_mobile_response_data($rid) { + $results = $this->get_results($rid); + $resultdata = new \stdClass(); + $resultdata->answered = false; + $resultdata->questions = []; + $resultdata->responses = ''; + if (!empty($results)) { + $resultdata->answered = true; + foreach ($results as $result) { + if ('y' == $result->choice_id) { + $resultdata->questions[1] = new \stdClass(); + $resultdata->questions[1]->value = 'y'; + $resultdata->responses = 'y'; + } else { + $resultdata->questions[0] = new \stdClass(); + $resultdata->questions[0]->value = 'n'; + $resultdata->responses = 'n'; + } + } + } + + return $resultdata; + } } \ No newline at end of file diff --git a/questionnaire.class.php b/questionnaire.class.php index 2a82b20f..25ad2bf0 100644 --- a/questionnaire.class.php +++ b/questionnaire.class.php @@ -3632,7 +3632,8 @@ public function get_mobile_data($userid = false) { $ret['questionscount']++; $qnum++; $fieldkey = 'response_' . $question->type_id . '_' . $question->id; - if ($mobiledata = $question->get_mobile_data($qnum, $fieldkey, $ret['questionnaire']['autonumquestions'])) { + if ($question->supports_mobile() && ($mobiledata = + $question->get_mobile_question_data($qnum, $fieldkey, $ret['questionnaire']['autonumquestions']))) { $ret['questionsinfo'][$pagenum][$question->id] = $mobiledata->questionsinfo; $ret['fields'][$fieldkey] = $mobiledata->fields; $ret['questions'][$pagenum][$question->id] = $mobiledata->questions; @@ -3654,79 +3655,12 @@ public function get_mobile_data($userid = false) { } $ret['response']['fullname'] = fullname($DB->get_record('user', ['id' => $userid])); $ret['response']['userdate'] = userdate($ret['response']['submitted']); - foreach ($ret['questionsinfo'] as $pagenum => $data1) { - foreach ($data1 as $questionid => $data2) { - $ret['answered'][$questionid] = false; - if (isset($data2['response_table']) && !empty($data2['response_table'])) { - if ($values = $DB->get_records_sql('SELECT * FROM {questionnaire_' - . $data2['response_table'] . '} WHERE response_id = ? AND question_id = ?', - [$response->id, $questionid])) { - foreach ($values as $value) { - switch($data2['type_id']) { - case QUESYESNO: // Yes/No bool - if (isset($ret['questions'][$pagenum][$questionid])) { - if (isset($value->choice_id) && !empty($value->choice_id)) { - $ret['answered'][$questionid] = true; - if ($value->choice_id == 'y') { - $ret['questions'][$pagenum][$questionid][1]->value = 'y'; - $ret['responses']['response_'.$data2['type_id'].'_'.$questionid] = 'y'; - } else { - $ret['questions'][$pagenum][$questionid][0]->value = 'n'; - $ret['responses']['response_'.$data2['type_id'].'_'.$questionid] = 'n'; - } - } - } - break; - case QUESTEXT: // Text - case QUESESSAY: // Essay - if (isset($value->response) && !empty($value->response)) { - $ret['answered'][$questionid] = true; - $ret['questions'][$pagenum][$questionid][0]->value = $value->response; - $ret['responses']['response_'.$data2['type_id'].'_'.$questionid] = $value->response; - } - break; - case QUESRADIO: // Radiobutton - case QUESCHECK: // Checkbox - case QUESDROP: // Select - if ($value = $DB->get_records_sql('SELECT * FROM {questionnaire_' - . $data2['response_table'] . '} WHERE response_id = ? AND question_id = ?', - [$response->id, $questionid])) { - foreach ($value as $row) { - foreach ($ret['questions'][$pagenum][$questionid] as $k => $item) { - if ($item->id == $row->choice_id) { - $ret['answered'][$questionid] = true; - $ret['questions'][$pagenum][$questionid][$k]->value = intval($item->id); - $ret['responses']['response_'.$data2['type_id'].'_'.$questionid] = intval($item->id); - } - } - } - } - break; - case QUESRATE: // Rate 1-NN - if ($value = $DB->get_records_sql('SELECT * FROM {questionnaire_' - . $data2['response_table'] . '} WHERE response_id = ? AND question_id = ?', - [$response->id, $questionid])) { - foreach ($value as $row) { - if ($questionid == $row->question_id) { - $ret['answered'][$questionid] = true; - $v = $row->rankvalue + 1; - if ($ret['questionsinfo'][$pagenum][$questionid]['precise'] == 1) { - if ($row->rankvalue == -1) { - $v = $ret['questions'][$pagenum][$questionid][$row->choice_id]->max; - } - } - $ret['questions'][$pagenum][$questionid][$row->choice_id]->value - = $ret['responses']['response_'.$data2['type_id'].'_'.$questionid.'_'.$row->choice_id] = $v; - $ret['questions'][$pagenum][$questionid][$row->choice_id]->choice_id = $row->choice_id; - } - } - } - break; - } - } - } - } - } + foreach ($this->questions as $question) { + $responsedata = $question->get_mobile_response_data($response->id); + $ret['answered'][$question->id] = $responsedata->answered; + $ret['questions'][$pagenum][$question->id] = $responsedata->questions + + $ret['questions'][$pagenum][$question->id]; + $ret['responses']['response_' . $question->type_id . '_' . $question->id] = $responsedata->responses; } } } @@ -3745,7 +3679,7 @@ public function get_mobile_data($userid = false) { * @throws dml_exception * @throws moodle_exception */ - function save_mobile_data($userid, $sec, $completed, $submit, array $responses) { + public function save_mobile_data($userid, $sec, $completed, $submit, array $responses) { global $DB, $CFG; // Do not delete $CFG!!! if (!$completed) { From b79cc3d1d2e6a87c1de5d7314d8eadabad706c0a Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Thu, 13 Dec 2018 12:01:15 -0500 Subject: [PATCH 039/341] GHI#171 - Refactoring mobile response handling to solve checkbox problem. --- classes/question/base.php | 20 ++++++++-- classes/question/check.php | 17 ++++++++- classes/question/drop.php | 4 +- classes/question/pagebreak.php | 2 +- classes/question/radio.php | 4 +- classes/question/rate.php | 4 +- classes/question/text.php | 4 +- classes/question/yesno.php | 4 +- classes/response/base.php | 9 +++++ classes/response/multiple.php | 12 +++++- classes/response/rank.php | 26 +++++++++++++ questionnaire.class.php | 38 ++++++++++++++++--- templates/mobile_view_activity_page.mustache | 8 ++-- ..._view_activity_page_with_required.mustache | 4 +- 14 files changed, 127 insertions(+), 29 deletions(-) diff --git a/classes/question/base.php b/classes/question/base.php index 28ead279..5008cd98 100644 --- a/classes/question/base.php +++ b/classes/question/base.php @@ -379,7 +379,7 @@ public function response_has_choice($rid, $choiceid) { * Insert response data method. */ public function insert_response($rid, $val) { - if (isset ($this->response) && is_object($this->response) && + if (isset($this->response) && is_object($this->response) && is_subclass_of($this->response, '\\mod_questionnaire\\response\\base')) { return $this->response->insert_response($rid, $val); } else { @@ -1467,7 +1467,7 @@ public function supports_mobile() { * @return \stdClass * @throws \coding_exception */ - public function get_mobile_question_data($qnum, $fieldkey, $autonum = false) { + public function get_mobile_question_data($qnum, $autonum = false) { $mobiledata = new \stdClass(); if ($this->type_id != QUESPAGEBREAK) { $options = ['noclean' => true, 'para' => false, 'filter' => true, @@ -1486,7 +1486,7 @@ public function get_mobile_question_data($qnum, $fieldkey, $autonum = false) { 'required' => $this->required, 'deleted' => $this->deleted, 'response_table' => $this->responsetable, - 'fieldkey' => $fieldkey, + 'fieldkey' => 'response_' . $this->type_id . '_' . $this->id, 'precise' => $this->precise, 'qnum' => $qnum, 'errormessage' => get_string('required') . ': ' . $this->name @@ -1556,4 +1556,18 @@ public function get_mobile_response_data($rid) { return $resultdata; } + + /** + * @param $rid + * @param $respdata + * @return bool + */ + public function save_mobile_response($rid, $respdata) { + if (isset($this->response) && is_object($this->response) && + is_subclass_of($this->response, '\\mod_questionnaire\\response\\base')) { + return $this->response->save_mobile_response($rid, $respdata); + } else { + return false; + } + } } \ No newline at end of file diff --git a/classes/question/check.php b/classes/question/check.php index 4ee10003..d76ead10 100644 --- a/classes/question/check.php +++ b/classes/question/check.php @@ -317,9 +317,22 @@ protected function form_preprocess_choicedata($formdata) { * @return \stdClass * @throws \coding_exception */ - public function get_mobile_question_data($qnum, $fieldkey, $autonum = false) { - $mobiledata = parent::get_mobile_question_data($qnum, $fieldkey, $autonum = false); + public function get_mobile_question_data($qnum, $autonum = false) { + $mobiledata = parent::get_mobile_question_data($qnum, $autonum = false); $mobiledata->questionsinfo['ischeckbox'] = true; return $mobiledata; } + + /** + * @param $mobiledata + * @return mixed + */ + public function add_mobile_question_choice_data($mobiledata) { + $mobiledata = parent::add_mobile_question_choice_data($mobiledata); + foreach ($this->choices as $choiceid => $choice) { + // Add a fieldkey for each choice. + $mobiledata->questions[$choiceid]->fieldkey = 'response_' . $this->type_id . '_' . $this->id . '_' . $choiceid; + } + return $mobiledata; + } } \ No newline at end of file diff --git a/classes/question/drop.php b/classes/question/drop.php index 81375fd9..848beb6b 100644 --- a/classes/question/drop.php +++ b/classes/question/drop.php @@ -165,8 +165,8 @@ public function supports_mobile() { * @return \stdClass * @throws \coding_exception */ - public function get_mobile_question_data($qnum, $fieldkey, $autonum = false) { - $mobiledata = parent::get_mobile_question_data($qnum, $fieldkey, $autonum = false); + public function get_mobile_question_data($qnum, $autonum = false) { + $mobiledata = parent::get_mobile_question_data($qnum, $autonum = false); $mobiledata->questionsinfo['isselect'] = true; return $mobiledata; } diff --git a/classes/question/pagebreak.php b/classes/question/pagebreak.php index 543b5e4f..40783b13 100644 --- a/classes/question/pagebreak.php +++ b/classes/question/pagebreak.php @@ -88,7 +88,7 @@ public function supports_mobile() { * @return \stdClass * @throws \coding_exception */ - public function get_mobile_question_data($qnum, $fieldkey, $autonum = false) { + public function get_mobile_question_data($qnum, $autonum = false) { return false; } } \ No newline at end of file diff --git a/classes/question/radio.php b/classes/question/radio.php index 3bae0352..9f69bedc 100644 --- a/classes/question/radio.php +++ b/classes/question/radio.php @@ -285,8 +285,8 @@ public function supports_mobile() { * @return \stdClass * @throws \coding_exception */ - public function get_mobile_question_data($qnum, $fieldkey, $autonum = false) { - $mobiledata = parent::get_mobile_question_data($qnum, $fieldkey, $autonum = false); + public function get_mobile_question_data($qnum, $autonum = false) { + $mobiledata = parent::get_mobile_question_data($qnum, $autonum = false); $mobiledata->questionsinfo['isradiobutton'] = true; return $mobiledata; } diff --git a/classes/question/rate.php b/classes/question/rate.php index 5441882c..68854ca6 100644 --- a/classes/question/rate.php +++ b/classes/question/rate.php @@ -615,8 +615,8 @@ public function supports_mobile() { * @return \stdClass * @throws \coding_exception */ - public function get_mobile_question_data($qnum, $fieldkey, $autonum = false) { - $mobiledata = parent::get_mobile_question_data($qnum, $fieldkey, $autonum = false); + public function get_mobile_question_data($qnum, $autonum = false) { + $mobiledata = parent::get_mobile_question_data($qnum, $autonum = false); $mobiledata->questionsinfo['israte'] = true; return $mobiledata; } diff --git a/classes/question/text.php b/classes/question/text.php index 1fc4e4a4..9539628e 100644 --- a/classes/question/text.php +++ b/classes/question/text.php @@ -138,8 +138,8 @@ public function supports_mobile() { * @return \stdClass * @throws \coding_exception */ - public function get_mobile_question_data($qnum, $fieldkey, $autonum = false) { - $mobiledata = parent::get_mobile_question_data($qnum, $fieldkey, $autonum = false); + public function get_mobile_question_data($qnum, $autonum = false) { + $mobiledata = parent::get_mobile_question_data($qnum, $autonum = false); $mobiledata->questionsinfo['istextessay'] = true; return $mobiledata; } diff --git a/classes/question/yesno.php b/classes/question/yesno.php index 05cd8c8a..15afa83e 100644 --- a/classes/question/yesno.php +++ b/classes/question/yesno.php @@ -216,8 +216,8 @@ public function supports_mobile() { * @return \stdClass * @throws \coding_exception */ - public function get_mobile_question_data($qnum, $fieldkey, $autonum = false) { - $mobiledata = parent::get_mobile_question_data($qnum, $fieldkey, $autonum = false); + public function get_mobile_question_data($qnum, $autonum = false) { + $mobiledata = parent::get_mobile_question_data($qnum, $autonum = false); $mobiledata->questionsinfo['isbool'] = true; return $mobiledata; } diff --git a/classes/response/base.php b/classes/response/base.php index ce857ad2..8cdf9c62 100644 --- a/classes/response/base.php +++ b/classes/response/base.php @@ -216,6 +216,15 @@ static public function response_select($rid, $col = null, $csvexport = false, $c return []; } + /** + * @param $rid + * @param $respdata + * @return bool + */ + public function save_mobile_response($rid, $respdata) { + return $this->insert_response($rid, $respdata); + } + /** * Return all the fields to be used for users in bulk questionnaire sql. * diff --git a/classes/response/multiple.php b/classes/response/multiple.php index 0d338acd..923bc70f 100644 --- a/classes/response/multiple.php +++ b/classes/response/multiple.php @@ -329,4 +329,14 @@ protected function bulk_sql() { JOIN {".self::response_table()."} $alias ON $alias.response_id = qr.id "; } -} + + /** + * @param $rid + * @param $respdata + * @return bool + */ + public function save_mobile_response($rid, $respdata) { + $resps = array_keys($respdata); + return $this->insert_response($rid, $resps); + } +} \ No newline at end of file diff --git a/classes/response/rank.php b/classes/response/rank.php index afa3fe3d..c432e980 100644 --- a/classes/response/rank.php +++ b/classes/response/rank.php @@ -81,6 +81,32 @@ public function insert_response($rid, $val) { } } + /** + * @param $rid + * @param $respdata + * @return bool + */ + public function save_mobile_response($rid, $respdata) { + global $DB; + + $resid = false; + foreach ($respdata as $choiceid => $choiceval) { + if ($choiceval == get_string('notapplicable', 'questionnaire')) { + $choiceval = -1; + } else { + $choiceval = intval($choiceval); + } + $record = new \stdClass(); + $record->response_id = $rid; + $record->question_id = $this->question->id; + $record->choice_id = $choiceid; + $record->rankvalue = $choiceval; + $resid = $DB->insert_record(self::response_table(), $record); + } + + return $resid; + } + /** * @param bool $rids * @param bool $anonymous diff --git a/questionnaire.class.php b/questionnaire.class.php index 25ad2bf0..68428f16 100644 --- a/questionnaire.class.php +++ b/questionnaire.class.php @@ -3631,11 +3631,10 @@ public function get_mobile_data($userid = false) { foreach ($this->questions as $question) { $ret['questionscount']++; $qnum++; - $fieldkey = 'response_' . $question->type_id . '_' . $question->id; if ($question->supports_mobile() && ($mobiledata = - $question->get_mobile_question_data($qnum, $fieldkey, $ret['questionnaire']['autonumquestions']))) { + $question->get_mobile_question_data($qnum, $ret['questionnaire']['autonumquestions']))) { $ret['questionsinfo'][$pagenum][$question->id] = $mobiledata->questionsinfo; - $ret['fields'][$fieldkey] = $mobiledata->fields; + $ret['fields'][$mobiledata->questionsinfo['fieldkey']] = $mobiledata->fields; $ret['questions'][$pagenum][$question->id] = $mobiledata->questions; $ret['responses']['response_' . $question->type_id . '_' . $question->id] = $mobiledata->responses; } else if ($question->type_id == QUESPAGEBREAK) { @@ -3656,10 +3655,11 @@ public function get_mobile_data($userid = false) { $ret['response']['fullname'] = fullname($DB->get_record('user', ['id' => $userid])); $ret['response']['userdate'] = userdate($ret['response']['submitted']); foreach ($this->questions as $question) { +// TODO - Need to do something with pagenum. $responsedata = $question->get_mobile_response_data($response->id); $ret['answered'][$question->id] = $responsedata->answered; $ret['questions'][$pagenum][$question->id] = $responsedata->questions + - $ret['questions'][$pagenum][$question->id]; + (isset($ret['questions'][$pagenum][$question->id]) ? $ret['questions'][$pagenum][$question->id] : []); $ret['responses']['response_' . $question->type_id . '_' . $question->id] = $responsedata->responses; } } @@ -3682,6 +3682,25 @@ public function get_mobile_data($userid = false) { public function save_mobile_data($userid, $sec, $completed, $submit, array $responses) { global $DB, $CFG; // Do not delete $CFG!!! +// This should create an array of well formed responses then execute question->insert_response one by one. + $processedresponses = []; + foreach ($responses as $response) { + // Array of label 'response', question type id, question id, and possible choice id. + $resparr = explode('_', $response['name']); + if (count($resparr) == 3) { + $questionid = $resparr[2]; + // Single response type. + $processedresponses[$questionid] = $response['value']; + } else if (count($resparr) == 4) { + list(, , $questionid, $choiceid) = $resparr; + // Multiple response type. + if (!isset($processedresponses[$questionid])) { + $processedresponses[$questionid] = []; + } + $processedresponses[$questionid][$choiceid] = $response['value']; + } + } + if (!$completed) { $rid = $this->delete_insert_response($DB->get_field('questionnaire_response', 'id', ['questionnaireid' => $this->id, 'complete' => 'n', 'userid' => $userid]), $sec, $userid); @@ -3694,6 +3713,13 @@ public function save_mobile_data($userid, $sec, $completed, $submit, array $resp $missingquestions[$questionid] = $questionid; } foreach ($pagequestionsids as $questionid) { + if (isset($this->questions[$questionid]) && isset($processedresponses[$questionid])) { + if ($this->questions[$questionid]->save_mobile_response($rid, $processedresponses[$questionid])) { + $ret['responses'][$rid][$questionid] = $response['value']; + } + } + } +/* foreach ($responses as $response) { $args = explode('_', $response['name']); if (count($args) >= 3) { @@ -3748,8 +3774,8 @@ public function save_mobile_data($userid, $sec, $completed, $submit, array $resp } } } - } - } + } */ + if ($missingquestions) { foreach ($missingquestions as $questionid) { if ($questionnairedata['questionsinfo'][$sec][$questionid]['required'] == 'y') { diff --git a/templates/mobile_view_activity_page.mustache b/templates/mobile_view_activity_page.mustache index 82028fa4..9349b4cf 100644 --- a/templates/mobile_view_activity_page.mustache +++ b/templates/mobile_view_activity_page.mustache @@ -66,13 +66,13 @@ <%/info.isradiobutton%> <%#info.ischeckbox%> - + <%#choices%> - checked="true"<%/value%> - value="<%choice_id%>"<%#completed%> disabled="true"<%/completed%> - > + checked="true"<%/value%> value="<%choice_id%>" + <%#completed%> disabled="true"<%/completed%> + [(ngModel)]="CONTENT_OTHERDATA.responses['<%fieldkey%>']"> <%/choices%> diff --git a/templates/mobile_view_activity_page_with_required.mustache b/templates/mobile_view_activity_page_with_required.mustache index 4ce0db31..98b70616 100644 --- a/templates/mobile_view_activity_page_with_required.mustache +++ b/templates/mobile_view_activity_page_with_required.mustache @@ -68,13 +68,13 @@ <%^completed%><%/completed%> <%/info.isradiobutton%> <%#info.ischeckbox%> - name="<%info.fieldkey%>" formControlName="<%info.fieldkey%>"<%/completed%>> + name="<%info.fieldkey%>" formControlName="<%info.fieldkey%>"<%/completed%>> <%#choices%> checked="true"<%/value%> value="<%choice_id%>"<%#completed%> disabled="true"<%/completed%> - > + [(ngModel)]="CONTENT_OTHERDATA.responses['<%fieldkey%>']"> <%/choices%> From 516220fa9921d9af2f59bff91f3f803bd10d9200 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Mon, 17 Dec 2018 15:06:07 -0500 Subject: [PATCH 040/341] GHI#171 - Adding back missing question handling. --- questionnaire.class.php | 69 +++++++---------------------------------- 1 file changed, 12 insertions(+), 57 deletions(-) diff --git a/questionnaire.class.php b/questionnaire.class.php index 68428f16..a987320b 100644 --- a/questionnaire.class.php +++ b/questionnaire.class.php @@ -3682,7 +3682,7 @@ public function get_mobile_data($userid = false) { public function save_mobile_data($userid, $sec, $completed, $submit, array $responses) { global $DB, $CFG; // Do not delete $CFG!!! -// This should create an array of well formed responses then execute question->insert_response one by one. + // This should create an array of well formed responses then execute question->insert_response one by one. $processedresponses = []; foreach ($responses as $response) { // Array of label 'response', question type id, question id, and possible choice id. @@ -3714,67 +3714,22 @@ public function save_mobile_data($userid, $sec, $completed, $submit, array $resp } foreach ($pagequestionsids as $questionid) { if (isset($this->questions[$questionid]) && isset($processedresponses[$questionid])) { + unset($missingquestions[$questionid]); if ($this->questions[$questionid]->save_mobile_response($rid, $processedresponses[$questionid])) { $ret['responses'][$rid][$questionid] = $response['value']; } + } else if (isset($this->questions[$questionid]) && $this->questions[$questionid]->required()) { + $ret['warnings'][] = [ + 'item' => 'mod_questionnaire_question', + 'itemid' => $questionid, + 'warningcode' => 'required', + 'message' => s(get_string('required') . ': ' . + $questionnairedata['questionsinfo'][$sec][$questionid]['name']) + ]; + } else if (isset($this->questions[$questionid])) { + unset($missingquestions[$questionid]); } } -/* - foreach ($responses as $response) { - $args = explode('_', $response['name']); - if (count($args) >= 3) { - $typeid = intval($args[1]); - $rquestionid = intval($args[2]); - if (in_array($rquestionid, $pagequestionsids)) { - unset($missingquestions[$rquestionid]); - if ($rquestionid == $questionid) { - if ($typeid == $questionnairedata['questionsinfo'][$sec][$rquestionid]['type_id']) { - if ($rquestionid > 0 && !in_array($response['value'], array(-9999, 'undefined'))) { - switch ($questionnairedata['questionsinfo'][$sec][$rquestionid]['type_id']) { - case QUESRATE: - if (isset($args[3]) && !empty($args[3])) { - $choiceid = intval($args[3]); - $value = intval($response['value']) - 1; - $rec = new \stdClass(); - $rec->response_id = $rid; - $rec->question_id = intval($rquestionid); - $rec->choice_id = $choiceid; - $rec->rankvalue = $value; - if ($questionnairedata['questionsinfo'][$sec][$rquestionid]['precise'] == 1) { - if ($value == $questionnairedata['questions'][$sec][$rquestionid][$choiceid]->max) { - $rec->rank = -1; - } - } - $DB->insert_record('questionnaire_response_rank', $rec); - } - break; - default: - if ($questionnairedata['questionsinfo'][$sec][$rquestionid]['required'] == 'n' - || ($questionnairedata['questionsinfo'][$sec][$rquestionid]['required'] == 'y' - && !empty($response['value']))) { - $questionobj = \mod_questionnaire\question\base::question_builder( - $questionnairedata['questionsinfo'][$sec][$rquestionid]['type_id'], - $questionnairedata['questionsinfo'][$sec][$rquestionid]); - if ($questionobj->insert_response($rid, $response['value'])) { - $ret['responses'][$rid][$questionid] = $response['value']; - } - } else { - $ret['warnings'][] = [ - 'item' => 'mod_questionnaire_question', - 'itemid' => $questionid, - 'warningcode' => 'required', - 'message' => s(get_string('required') . ': ' . $questionnairedata['questionsinfo'][$sec][$questionid]['name']) - ]; - } - } - } else { - $missingquestions[$rquestionid] = $rquestionid; - } - } - } - } - } - } */ if ($missingquestions) { foreach ($missingquestions as $questionid) { From 868614a70808caa1541e2f78a3ce1bc033c2f3ef Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Mon, 17 Dec 2018 15:54:20 -0500 Subject: [PATCH 041/341] CONTRIB-7575 - Fixing incorrect answer for dropdown when no answer seleted. Disabling others. --- classes/question/drop.php | 1 + templates/response_check.mustache | 4 ++-- templates/response_drop.mustache | 2 +- templates/response_radio.mustache | 4 ++-- templates/response_rate.mustache | 2 +- templates/response_yesno.mustache | 8 ++++---- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/classes/question/drop.php b/classes/question/drop.php index 848beb6b..e1df82a0 100644 --- a/classes/question/drop.php +++ b/classes/question/drop.php @@ -126,6 +126,7 @@ protected function response_survey_display($data) { $resptags->id = 'menu' . $resptags->name; $resptags->class = 'select custom-select ' . $resptags->id; $resptags->options = []; + $resptags->options[] = (object)['value' => '', 'label' => get_string('choosedots')]; foreach ($this->choices as $id => $choice) { $contents = questionnaire_choice_values($choice->content); $chobj = new \stdClass(); diff --git a/templates/response_check.mustache b/templates/response_check.mustache index 464d63f6..7f03cc4c 100644 --- a/templates/response_check.mustache +++ b/templates/response_check.mustache @@ -48,12 +48,12 @@
{{#choices}} {{#selected}} - + {{{content}}} {{#othercontent}}{{{.}}}{{/othercontent}} {{/selected}} {{^selected}} - + {{{content}}} {{/selected}}
diff --git a/templates/response_drop.mustache b/templates/response_drop.mustache index b14eeb89..f3807cec 100644 --- a/templates/response_drop.mustache +++ b/templates/response_drop.mustache @@ -49,7 +49,7 @@ }}
- {{#options}} {{/options}} diff --git a/templates/response_radio.mustache b/templates/response_radio.mustache index ed5aa237..30849344 100644 --- a/templates/response_radio.mustache +++ b/templates/response_radio.mustache @@ -52,12 +52,12 @@ {{#horizontal}}{{/horizontal}} {{#selected}} - + {{{content}}}{{#othercontent}} {{.}}{{/othercontent}} {{/selected}} {{^selected}} - + {{{content}}}  {{/selected}} {{#horizontal}}{{/horizontal}} diff --git a/templates/response_rate.mustache b/templates/response_rate.mustache index b4a707dc..f11b2206 100644 --- a/templates/response_rate.mustache +++ b/templates/response_rate.mustache @@ -74,7 +74,7 @@ {{#cols}} {{#checked}} - + {{/checked}} {{^checked}} diff --git a/templates/response_yesno.mustache b/templates/response_yesno.mustache index 54b8ac36..650e2585 100644 --- a/templates/response_yesno.mustache +++ b/templates/response_yesno.mustache @@ -40,16 +40,16 @@
{{#yesselected}} - {{{stryes}}} + {{{stryes}}} {{/yesselected}} {{^yesselected}} - {{{stryes}}} + {{{stryes}}} {{/yesselected}} {{#noselected}} - {{{strno}}} + {{{strno}}} {{/noselected}} {{^noselected}} - {{{strno}}} + {{{strno}}} {{/noselected}}
\ No newline at end of file From aabb3d7b2930ee7727c911997a9451d94345c3dc Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Mon, 17 Dec 2018 15:54:53 -0500 Subject: [PATCH 042/341] Poet - fixing an indent. --- questionnaire.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/questionnaire.class.php b/questionnaire.class.php index a987320b..147cc7ea 100644 --- a/questionnaire.class.php +++ b/questionnaire.class.php @@ -3655,7 +3655,7 @@ public function get_mobile_data($userid = false) { $ret['response']['fullname'] = fullname($DB->get_record('user', ['id' => $userid])); $ret['response']['userdate'] = userdate($ret['response']['submitted']); foreach ($this->questions as $question) { -// TODO - Need to do something with pagenum. + // TODO - Need to do something with pagenum. $responsedata = $question->get_mobile_response_data($response->id); $ret['answered'][$question->id] = $responsedata->answered; $ret['questions'][$pagenum][$question->id] = $responsedata->questions + From d964b3c88087e902d6acc8b72dab17bc83012d6c Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Mon, 17 Dec 2018 15:54:20 -0500 Subject: [PATCH 043/341] CONTRIB-7575 - Fixing incorrect answer for dropdown when no answer seleted. Disabling others. --- classes/question/drop.php | 1 + templates/response_check.mustache | 4 ++-- templates/response_drop.mustache | 2 +- templates/response_radio.mustache | 4 ++-- templates/response_rate.mustache | 2 +- templates/response_yesno.mustache | 8 ++++---- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/classes/question/drop.php b/classes/question/drop.php index 635e83cb..02a85ded 100644 --- a/classes/question/drop.php +++ b/classes/question/drop.php @@ -126,6 +126,7 @@ protected function response_survey_display($data) { $resptags->id = 'menu' . $resptags->name; $resptags->class = 'select custom-select ' . $resptags->id; $resptags->options = []; + $resptags->options[] = (object)['value' => '', 'label' => get_string('choosedots')]; foreach ($this->choices as $id => $choice) { $contents = questionnaire_choice_values($choice->content); $chobj = new \stdClass(); diff --git a/templates/response_check.mustache b/templates/response_check.mustache index 464d63f6..7f03cc4c 100644 --- a/templates/response_check.mustache +++ b/templates/response_check.mustache @@ -48,12 +48,12 @@
{{#choices}} {{#selected}} - + {{{content}}} {{#othercontent}}{{{.}}}{{/othercontent}} {{/selected}} {{^selected}} - + {{{content}}} {{/selected}}
diff --git a/templates/response_drop.mustache b/templates/response_drop.mustache index b14eeb89..f3807cec 100644 --- a/templates/response_drop.mustache +++ b/templates/response_drop.mustache @@ -49,7 +49,7 @@ }}
- {{#options}} {{/options}} diff --git a/templates/response_radio.mustache b/templates/response_radio.mustache index ed5aa237..30849344 100644 --- a/templates/response_radio.mustache +++ b/templates/response_radio.mustache @@ -52,12 +52,12 @@ {{#horizontal}}{{/horizontal}} {{#selected}} - + {{{content}}}{{#othercontent}} {{.}}{{/othercontent}} {{/selected}} {{^selected}} - + {{{content}}}  {{/selected}} {{#horizontal}}{{/horizontal}} diff --git a/templates/response_rate.mustache b/templates/response_rate.mustache index b4a707dc..f11b2206 100644 --- a/templates/response_rate.mustache +++ b/templates/response_rate.mustache @@ -74,7 +74,7 @@ {{#cols}} {{#checked}} - + {{/checked}} {{^checked}} diff --git a/templates/response_yesno.mustache b/templates/response_yesno.mustache index 54b8ac36..650e2585 100644 --- a/templates/response_yesno.mustache +++ b/templates/response_yesno.mustache @@ -40,16 +40,16 @@
{{#yesselected}} - {{{stryes}}} + {{{stryes}}} {{/yesselected}} {{^yesselected}} - {{{stryes}}} + {{{stryes}}} {{/yesselected}} {{#noselected}} - {{{strno}}} + {{{strno}}} {{/noselected}} {{^noselected}} - {{{strno}}} + {{{strno}}} {{/noselected}}
\ No newline at end of file From 2cc4f2b4154e9858571182c514683de487058233 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Wed, 19 Dec 2018 10:21:11 -0500 Subject: [PATCH 044/341] Poet - Ongoing cleanup. --- classes/output/mobile.php | 4 +++- classes/question/rate.php | 2 +- classes/question/text.php | 2 +- templates/mobile_view_activity_page.mustache | 6 +++++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/classes/output/mobile.php b/classes/output/mobile.php index b243004c..34fd733d 100644 --- a/classes/output/mobile.php +++ b/classes/output/mobile.php @@ -176,7 +176,8 @@ public static function mobile_view_activity($args) { $data['emptypage'] = true; $data['emptypage_content'] = get_string('questionnaire:submit', 'questionnaire'); } - return [ + + $return = [ 'templates' => [ [ 'id' => 'main', @@ -198,6 +199,7 @@ public static function mobile_view_activity($args) { ], 'files' => null ]; + return $return; } /** diff --git a/classes/question/rate.php b/classes/question/rate.php index 68854ca6..db8ae2ba 100644 --- a/classes/question/rate.php +++ b/classes/question/rate.php @@ -718,7 +718,7 @@ public function get_mobile_response_data($rid) { $resultdata->answered = false; $resultdata->questions = []; $resultdata->responses = ''; - if (!empty($results) && $this->has_choices()) { + if (!empty($results)) { foreach ($results as $result) { if ($this->id == $result->question_id) { $resultdata->answered = true; diff --git a/classes/question/text.php b/classes/question/text.php index 9539628e..eef27c63 100644 --- a/classes/question/text.php +++ b/classes/question/text.php @@ -169,7 +169,7 @@ public function get_mobile_response_data($rid) { $resultdata->answered = false; $resultdata->questions = []; $resultdata->responses = ''; - if (!empty($results) && $this->has_choices()) { + if (!empty($results)) { $resultdata->answered = true; foreach ($results as $result) { $resultdata->questions[0]->value = $result->response; diff --git a/templates/mobile_view_activity_page.mustache b/templates/mobile_view_activity_page.mustache index 9349b4cf..046f9df6 100644 --- a/templates/mobile_view_activity_page.mustache +++ b/templates/mobile_view_activity_page.mustache @@ -80,7 +80,11 @@ <%#info.istextessay%> <%^completed%> - + + <%/completed%> + <%#completed%> + <%/completed%> <%/info.istextessay%> From 8bf564747a80c4970856d8568c653ada98a0b185 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Wed, 19 Dec 2018 14:49:02 -0500 Subject: [PATCH 045/341] Poet - More simplification. --- classes/output/mobile.php | 59 +++++++-- classes/question/base.php | 2 +- templates/mobile_view_activity_page.mustache | 121 ++++++++++++++----- 3 files changed, 142 insertions(+), 40 deletions(-) diff --git a/classes/output/mobile.php b/classes/output/mobile.php index 34fd733d..5d28e00e 100644 --- a/classes/output/mobile.php +++ b/classes/output/mobile.php @@ -54,16 +54,55 @@ public static function mobile_view_activity($args) { $prevpage = $pagenum - 1; } - $data = [ - 'questionnaire' => $questionnairedata, - 'cmid' => $cmid, - 'courseid' => intval($cm->course), - 'pagenum' => $pagenum, - 'userid' => $USER->id, - 'nextpage' => 0, - 'prevpage' => 0, - 'emptypage' => false + $data = []; + $data['cmid'] = $cmid; + $data['userid'] = $USER->id; + $data['intro'] = $questionnaire->intro; + $data['autonumquestions'] = $questionnaire->autonum; + $data['id'] = $questionnaire->id; + $data['surveyid'] = $questionnaire->survey->id; + $data['pagenum'] = $pagenum; + $data['prevpage'] = $prevpage; + $data['nextpage'] = 0; + $data['completed'] = (isset($questionnairedata['response']['complete']) && + $questionnairedata['response']['complete'] == 'y') ? 1 : 0; + $data['complete_userdate'] = (isset($questionnairedata['response']['complete']) && + $questionnairedata['response']['complete'] == 'y') ? userdate($questionnairedata['response']['submitted']) : ''; +// $data['emptypage'] = false; +// $data['emptypage_content'] = ''; + $pagequestions = []; + foreach ($questionnairedata['questions'][$pagenum] as $questionid => $choices) { + $pagequestion = $questionnairedata['questionsinfo'][$pagenum][$questionid]; + // Do an array_merge to reindex choices with standard numerical indexing. + $pagequestion['choices'] = array_merge([], $choices); + $pagequestions[] = $pagequestion; + } + $data['pagequestions'] = $pagequestions; + + $return = [ + 'templates' => [ + [ + 'id' => 'main', + 'html' => $OUTPUT->render_from_template('mod_questionnaire/mobile_view_activity_page', $data) + ], + ], + 'otherdata' => [ + 'fields' => json_encode($questionnairedata['fields']), + 'questionsinfo' => json_encode($questionnairedata['questionsinfo']), + 'questions' => json_encode($questionnairedata['questions']), + 'pagequestions' => json_encode($data['pagequestions']), + 'responses' => json_encode($questionnairedata['responses']), + 'pagenum' => $pagenum, + 'nextpage' => $data['nextpage'], + 'prevpage' => $data['prevpage'], + 'completed' => $data['completed'], + 'intro' => $questionnairedata['questionnaire']['intro'], + 'string_required' => get_string('required'), + ], + 'files' => null ]; + return $return; + // Check for required fields filled. $break = false; if (($pagenum - 1) > 0 && isset($questionnairedata['questions'][$pagenum - 1]) && @@ -195,7 +234,7 @@ public static function mobile_view_activity($args) { 'prevpage' => $data['prevpage'], 'completed' => $data['completed'], 'intro' => $questionnairedata['questionnaire']['intro'], - 'string_required' => get_string('required') + 'string_required' => get_string('required'), ], 'files' => null ]; diff --git a/classes/question/base.php b/classes/question/base.php index 5008cd98..bacd5c82 100644 --- a/classes/question/base.php +++ b/classes/question/base.php @@ -1544,7 +1544,7 @@ public function get_mobile_response_data($rid) { if (!empty($results) && $this->has_choices()) { foreach ($results as $result) { foreach ($this->choices as $choiceid => $choice) { - if ($choiceid == $result->choice_id) { + if ($choiceid == $result->cid) { $resultdata->answered = true; $resultdata->questions[$choiceid] = new \stdClass(); $resultdata->questions[$choiceid]->value = $choiceid; diff --git a/templates/mobile_view_activity_page.mustache b/templates/mobile_view_activity_page.mustache index 046f9df6..8306a72f 100644 --- a/templates/mobile_view_activity_page.mustache +++ b/templates/mobile_view_activity_page.mustache @@ -14,9 +14,69 @@ You should have received a copy of the GNU General Public License along with Moodle. If not, see . }} +{{! + @template mod_questionnaire/mobile_view_activity + + Template which defines a questionnaire display in the mobile app. + + Context variables required for this template: + * questionnaire - object: "intro" and "autonumquestions" strings for first respondent link. + * previous - object: "url" and "title" strings for previous link. + * respnumber - object: Current positio ("currpos") and "total" number of responses. + * next - object: "url" and "title" strings for next link. + * lastrespondent - object: "url" and "title" strings for last respondent link. + * listlink - string: Url of the link back to the response list. + * printaction - string: HTML to launch the print function. + + Example context (json): + { + "cmid": 985, + "userid": 267, + "intro": "Welcome to the questionnaire", + "autonumquestions": "1", + "id": "342", + "surveyid": "23", + "pagenum": 0, + "nextpage": 1, + "prevpage": 0, + "completed": "1", + "complete_userdate": "Monday, 17 December 2018, 3:34pm", + "emptypage": "0", + "emptypage_content": "This is an empty page.", + "pagequestions": [ + { + "id": 5432, + "type_id": 4, + "qnum": "Q1", + "content": "Answer this question", + "required": "1", + "fieldkey": "response_1_23", + "isselect": "0", + "isbool": "0", + "isradiobutton": "1", + "ischeckbox": "0", + "istextessay": "0", + "israte": "0", + "choices": [ + { + "id": 745, + "content": "Red", + "value": " ", + "choice_id": 745, + "min": 0, + "max": 5, + "minstr": "Low", + "maxstr": "High", + "na": 0, + } + ], + } + ], + } + }} {{=<% %>=}}
- <%#completed%> @@ -32,19 +92,19 @@ <%^emptypage%> <%#pagequestions%> - <%#questionnaire.questionnaire.autonumquestions%><%info.qnum%><%/questionnaire.questionnaire.autonumquestions%> - - <%#info.required%>{{ 'plugin.mod_questionnaire.required' | translate }}<%/info.required%> + <%#autonumquestions%><%qnum%><%/autonumquestions%> + + <%#required%>{{ 'plugin.mod_questionnaire.required' | translate }}<%/required%> - <%#info.isselect%> - + <%#isselect%> + <%#choices%> disabled="true"<%/completed%><%#value%> selected="true"<%/value%> value="<%id%>"><%content%> <%/choices%> - <%/info.isselect%> - <%#info.isbool%> - + <%/isselect%> + <%#isbool%> + <%#choices%> @@ -52,9 +112,9 @@ <%/choices%> - <%/info.isbool%> - <%#info.isradiobutton%> - + <%/isbool%> + <%#isradiobutton%> + <%#choices%> @@ -64,8 +124,8 @@ <%/choices%> - <%/info.isradiobutton%> - <%#info.ischeckbox%> + <%/isradiobutton%> + <%#ischeckbox%> <%#choices%> @@ -76,34 +136,37 @@ <%/choices%> - <%/info.ischeckbox%> - <%#info.istextessay%> + <%/ischeckbox%> + <%#istextessay%> <%^completed%> - <%/completed%> <%#completed%> - + <%/completed%> - <%/info.istextessay%> - <%#info.israte%> + <%/istextessay%> + <%#israte%> <%#choices%> - {{ CONTENT_OTHERDATA.responses['response_<%info.type_id%>_<%info.id%>_<%id%>'] }} + {{ CONTENT_OTHERDATA.responses['response_<%type_id%>_<%id%>_<%choice_id%>'] }} + [(ngModel)]="CONTENT_OTHERDATA.responses['response_<%type_id%>_<%id%>_<%choice_id%>']"> <%minstr%> <%#na%><%/na%><%^na%><%maxstr%><%/na%> <%/choices%> - <%/info.israte%> + <%/israte%> + <%/pagequestions%> + <%^pagequestions%> +

No questions found.

<%/pagequestions%> <%/emptypage%> <%#emptypage%> @@ -115,8 +178,8 @@ - <%/prevpage%> - <%#nextpage%> - - <%/nextpage%> - <%^completed%> - <%^nextpage%> - - <%/nextpage%> - <%/completed%> - <%#completed%>
<%/completed%> - <%^completed%><%/completed%> - -
-
\ No newline at end of file From 75ae74ce3a996697eca3681cf899dde1e698203c Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Wed, 1 May 2019 11:32:30 -0400 Subject: [PATCH 066/341] Poet - Adding context examples to templates. --- templates/mobile_checkbox_question.mustache | 7 +-- templates/mobile_date_question.mustache | 31 +++++++++++++ templates/mobile_numeric_question.mustache | 31 +++++++++++++ templates/mobile_radio_question.mustache | 44 ++++++++++++++++++ templates/mobile_rate_question.mustache | 49 +++++++++++++++++++++ templates/mobile_select_question.mustache | 42 ++++++++++++++++++ templates/mobile_text_question.mustache | 31 +++++++++++++ 7 files changed, 232 insertions(+), 3 deletions(-) diff --git a/templates/mobile_checkbox_question.mustache b/templates/mobile_checkbox_question.mustache index cee07437..e2420556 100644 --- a/templates/mobile_checkbox_question.mustache +++ b/templates/mobile_checkbox_question.mustache @@ -15,7 +15,7 @@ along with Moodle. If not, see . }} {{! - @template mod_questionnaire/mobile_boolean_question + @template mod_questionnaire/mobile_checkbox_question Template which defines a checkbox question display in the mobile app. @@ -25,15 +25,16 @@ Example context (json): { - "fieldkey": 985, "choices": [ { + "fieldkey": 985, "choice_id": 5432, "content": "Red", "completed": 0, "value": 1 }, { + "fieldkey": 986 , "choice_id": 5433, "content": "Blue", "completed": 0, @@ -49,7 +50,7 @@ checked="true"<%/value%> value="<%choice_id%>" <%#completed%> disabled="true"<%/completed%> - [(ngModel)]="CONTENT_OTHERDATA.responses['<%fieldkey%>']"> + [(ngModel)]="CONTENT_OTHERDATA.responses[<%fieldkey%>]"> <%/choices%> \ No newline at end of file diff --git a/templates/mobile_date_question.mustache b/templates/mobile_date_question.mustache index 23155482..9e50ee89 100644 --- a/templates/mobile_date_question.mustache +++ b/templates/mobile_date_question.mustache @@ -1,3 +1,34 @@ +{{! + 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 . +}} +{{! + @template mod_questionnaire/mobile_date_question + + Template which defines a date question display in the mobile app. + + Context variables required for this template: + * fieldkey - integer: ID of the question. + * completed - boolean: True if question already completed. + + Example context (json): + { + "fieldkey": 985, + "completed": 0 + } +}} {{=<% %>=}} <%^completed%> diff --git a/templates/mobile_numeric_question.mustache b/templates/mobile_numeric_question.mustache index c9620158..b57808cf 100644 --- a/templates/mobile_numeric_question.mustache +++ b/templates/mobile_numeric_question.mustache @@ -1,3 +1,34 @@ +{{! + 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 . +}} +{{! + @template mod_questionnaire/mobile_numeric_question + + Template which defines a numeric question display in the mobile app. + + Context variables required for this template: + * fieldkey - integer: ID of the question. + * completed - boolean: True if question already completed. + + Example context (json): + { + "fieldkey": 985, + "completed": 0 + } +}} {{=<% %>=}} <%^completed%> diff --git a/templates/mobile_radio_question.mustache b/templates/mobile_radio_question.mustache index 04cc6a3d..35ccfd35 100644 --- a/templates/mobile_radio_question.mustache +++ b/templates/mobile_radio_question.mustache @@ -1,3 +1,47 @@ +{{! + 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 . +}} +{{! + @template mod_questionnaire/mobile_radio_question + + Template which defines a radio question display in the mobile app. + + Context variables required for this template: + * fieldkey - integer: ID of the question. + * choices - array of objects: choice_id, content, completed and value of each question choice. + + Example context (json): + { + "fieldkey": 985, + "choices": [ + { + "choice_id": 5432, + "content": "Red", + "completed": 0, + "value": 1 + }, + { + "choice_id": 5433, + "content": "Blue", + "completed": 0, + "value": 0 + } + ] + } +}} {{=<% %>=}} <%#choices%> diff --git a/templates/mobile_rate_question.mustache b/templates/mobile_rate_question.mustache index 65d1ba7e..19eae7af 100644 --- a/templates/mobile_rate_question.mustache +++ b/templates/mobile_rate_question.mustache @@ -1,3 +1,52 @@ +{{! + 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 . +}} +{{! + @template mod_questionnaire/mobile_rate_question + + Template which defines a rate question display in the mobile app. + + Context variables required for this template: + * fieldkey - integer: ID of the question. + * choices - array of objects: choice_id, content, completed and value of each question choice. + + Example context (json): + { + "choices": [ + { + "fieldkey": 985, + "content": "Red", + "min": 0, + "max": 5, + "minstr": "zero", + "maxstr": "five", + "na": 1 + }, + { + "fieldkey": 986, + "content": "Blue", + "min": 0, + "max": 5, + "minstr": "zero", + "maxstr": "five", + "na": 1 + } + ] + } +}} {{=<% %>=}} <%#choices%> diff --git a/templates/mobile_select_question.mustache b/templates/mobile_select_question.mustache index ef84ff52..872594dd 100644 --- a/templates/mobile_select_question.mustache +++ b/templates/mobile_select_question.mustache @@ -1,3 +1,45 @@ +{{! + 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 . +}} +{{! + @template mod_questionnaire/mobile_select_question + + Template which defines a select question display in the mobile app. + + Context variables required for this template: + * fieldkey - integer: ID of the question. + * choices - array of objects: choice_id, content, completed and value of each question choice. + + Example context (json): + { + "fieldkey": 985, + "choices": [ + { + "content": "Red", + "completed": 0, + "value": 1 + }, + { + "content": "Blue", + "completed": 0, + "value": 0 + } + ] + } +}} {{=<% %>=}} <%#choices%> diff --git a/templates/mobile_text_question.mustache b/templates/mobile_text_question.mustache index 64993ba0..f5970cf5 100644 --- a/templates/mobile_text_question.mustache +++ b/templates/mobile_text_question.mustache @@ -1,3 +1,34 @@ +{{! + 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 . +}} +{{! + @template mod_questionnaire/mobile_text_question + + Template which defines a text question display in the mobile app. + + Context variables required for this template: + * fieldkey - integer: ID of the question. + * completed - boolean: True if question already completed. + + Example context (json): + { + "fieldkey": 985, + "completed": 0 + } +}} {{=<% %>=}} <%^completed%> From 7431d73f02b5ae87f9ce234fca0d1f4f519c8894 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Thu, 2 May 2019 11:23:00 -0400 Subject: [PATCH 067/341] Poet - Setting up response_insert to work with any data structure. --- classes/question/base.php | 6 ++-- classes/response/base.php | 5 ++- classes/response/boolean.php | 21 +++++++++-- classes/response/date.php | 34 ++++++++++++------ classes/response/multiple.php | 11 +++--- classes/response/rank.php | 14 ++++---- classes/response/single.php | 24 +++++++++---- classes/response/text.php | 21 +++++++++-- questionnaire.class.php | 65 ++++++++++++++++------------------- tests/generator/lib.php | 7 ++-- tests/responsetypes_test.php | 13 ++++--- 11 files changed, 141 insertions(+), 80 deletions(-) diff --git a/classes/question/base.php b/classes/question/base.php index bfd374c6..bf1051ef 100644 --- a/classes/question/base.php +++ b/classes/question/base.php @@ -377,11 +377,13 @@ public function response_has_choice($rid, $choiceid) { /** * Insert response data method. + * @param object $responsedata All of the responsedata. + * @return bool */ - public function insert_response($rid, $val) { + public function insert_response($responsedata) { if (isset($this->response) && is_object($this->response) && is_subclass_of($this->response, '\\mod_questionnaire\\response\\base')) { - return $this->response->insert_response($rid, $val); + return $this->response->insert_response($responsedata); } else { return false; } diff --git a/classes/response/base.php b/classes/response/base.php index 8cdf9c62..97e7a6c4 100644 --- a/classes/response/base.php +++ b/classes/response/base.php @@ -54,11 +54,10 @@ static public function response_table() { /** * Insert a provided response to the question. * - * @param integer $rid - The data id of the response table id. - * @param mixed $val - The response data provided. + * @param object $responsedata All of the responsedata as an object. * @return int|bool - on error the subtype should call set_error and return false. */ - abstract public function insert_response($rid, $val); + abstract public function insert_response($responsedata); /** * Provide the result information for the specified result records. diff --git a/classes/response/boolean.php b/classes/response/boolean.php index dddcfcba..ef0a87d4 100644 --- a/classes/response/boolean.php +++ b/classes/response/boolean.php @@ -36,15 +36,25 @@ class boolean extends base { + /** + * @return string + */ static public function response_table() { return 'questionnaire_response_bool'; } - public function insert_response($rid, $val) { + /** + * @param int|object $responsedata + * @return bool|int + * @throws \dml_exception + */ + public function insert_response($responsedata) { global $DB; + + $val = isset($responsedata->{'q'.$this->question->id}) ? $responsedata->{'q'.$this->question->id} : ''; if (!empty($val)) { // If "no answer" then choice is empty (CONTRIB-846). $record = new \stdClass(); - $record->response_id = $rid; + $record->response_id = $responsedata->rid; $record->question_id = $this->question->id; $record->choice_id = $val; return $DB->insert_record(self::response_table(), $record); @@ -53,6 +63,13 @@ public function insert_response($rid, $val) { } } + /** + * @param bool $rids + * @param bool $anonymous + * @return array + * @throws \coding_exception + * @throws \dml_exception + */ public function get_results($rids=false, $anonymous=false) { global $DB; diff --git a/classes/response/date.php b/classes/response/date.php index 48262fa4..7811a413 100644 --- a/classes/response/date.php +++ b/classes/response/date.php @@ -35,30 +35,44 @@ */ class date extends base { + /** + * @return string + */ static public function response_table() { return 'questionnaire_response_date'; } - public function insert_response($rid, $val, $checkdateresult = false) { + /** + * @param int|object $responsedata + * @return bool|int + * @throws \dml_exception + */ + public function insert_response($responsedata) { global $DB; - if ($checkdateresult === false) { - $checkdateresult = questionnaire_check_date($val); - $thisdate = $val; - if (substr($checkdateresult, 0, 5) == 'wrong') { - return false; - } - // Now use ISO date formatting. - $checkdateresult = questionnaire_check_date($thisdate, true); + $val = isset($responsedata->{'q'.$this->question->id}) ? $responsedata->{'q'.$this->question->id} : ''; + $checkdateresult = questionnaire_check_date($val); + $thisdate = $val; + if (substr($checkdateresult, 0, 5) == 'wrong') { + return false; } + // Now use ISO date formatting. + $checkdateresult = questionnaire_check_date($thisdate, true); $record = new \stdClass(); - $record->response_id = $rid; + $record->response_id = $responsedata->rid; $record->question_id = $this->question->id; $record->response = $checkdateresult; return $DB->insert_record(self::response_table(), $record); } + /** + * @param bool $rids + * @param bool $anonymous + * @return array + * @throws \coding_exception + * @throws \dml_exception + */ public function get_results($rids=false, $anonymous=false) { global $DB; diff --git a/classes/response/multiple.php b/classes/response/multiple.php index 923bc70f..4ec1a4ee 100644 --- a/classes/response/multiple.php +++ b/classes/response/multiple.php @@ -43,12 +43,15 @@ static public function response_table() { return 'questionnaire_resp_multiple'; } - public function insert_response($rid, $val) { + public function insert_response($responsedata) { global $DB; + + $val = isset($responsedata->{'q'.$this->question->id}) ? $responsedata->{'q'.$this->question->id} : ''; $resid = ''; foreach ($this->question->choices as $cid => $choice) { if (strpos($choice->content, '!other') === 0) { - $other = optional_param('q'.$this->question->id.'_'.$cid, '', PARAM_CLEAN); + $other = isset($responsedata->{'q'.$this->question->id.'_'.$cid}) ? + $responsedata->{'q'.$this->question->id.'_'.$cid} : ''; if (empty($other)) { continue; } @@ -59,7 +62,7 @@ public function insert_response($rid, $val) { } if (preg_match("/[^ \t\n]/", $other)) { $record = new \stdClass(); - $record->response_id = $rid; + $record->response_id = $responsedata->rid; $record->question_id = $this->question->id; $record->choice_id = $cid; $record->response = $other; @@ -79,7 +82,7 @@ public function insert_response($rid, $val) { continue; } $record = new \stdClass(); - $record->response_id = $rid; + $record->response_id = $responsedata->rid; $record->question_id = $this->question->id; $record->choice_id = $cid; $resid = $DB->insert_record(self::response_table(), $record); diff --git a/classes/response/rank.php b/classes/response/rank.php index 0a418325..4798f82e 100644 --- a/classes/response/rank.php +++ b/classes/response/rank.php @@ -40,16 +40,18 @@ static public function response_table() { } /** - * @param int $rid - * @param mixed $val + * @param object $responsedata * @return bool|int */ - public function insert_response($rid, $val) { + public function insert_response($responsedata) { global $DB; + + $val = isset($responsedata->{'q'.$this->question->id}) ? $responsedata->{'q'.$this->question->id} : ''; if ($this->question->type_id == QUESRATE) { $resid = false; foreach ($this->question->choices as $cid => $choice) { - $other = optional_param('q'.$this->question->id.'_'.$cid, null, PARAM_CLEAN); + $other = isset($responsedata->{'q'.$this->question->id.'_'.$cid}) ? + $responsedata->{'q'.$this->question->id.'_'.$cid} : null; // Choice not set or not answered. if (!isset($other) || $other == '') { continue; @@ -60,7 +62,7 @@ public function insert_response($rid, $val) { $rank = intval($other); } $record = new \stdClass(); - $record->response_id = $rid; + $record->response_id = $responsedata->rid; $record->question_id = $this->question->id; $record->choice_id = $cid; $record->rankvalue = $rank; @@ -74,7 +76,7 @@ public function insert_response($rid, $val) { $rank = intval($val); } $record = new \stdClass(); - $record->response_id = $rid; + $record->response_id = $responsedata->rid; $record->question_id = $this->question->id; $record->rankvalue = $rank; return $DB->insert_record(self::response_table(), $record); diff --git a/classes/response/single.php b/classes/response/single.php index cdc4b647..7d602125 100644 --- a/classes/response/single.php +++ b/classes/response/single.php @@ -33,22 +33,33 @@ */ class single extends base { + /** + * @return string + */ static public function response_table() { return 'questionnaire_resp_single'; } - public function insert_response($rid, $val) { + /** + * @param int|object $responsedata + * @return bool|int + * @throws \dml_exception + */ + public function insert_response($responsedata) { global $DB; + + $val = isset($responsedata->{'q'.$this->question->id}) ? $responsedata->{'q'.$this->question->id} : ''; if (!empty($val)) { foreach ($this->question->choices as $cid => $choice) { if (strpos($choice->content, '!other') === 0) { - $other = optional_param('q'.$this->question->id.'_'.$cid, null, PARAM_TEXT); + $other = isset($responsedata->{'q'.$this->question->id.'_'.$cid}) ? + $responsedata->{'q'.$this->question->id.'_'.$cid} : null; if (!isset($other)) { continue; } if (preg_match("/[^ \t\n]/", $other)) { $record = new \stdClass(); - $record->response_id = $rid; + $record->response_id = $responsedata->rid; $record->question_id = $this->question->id; $record->choice_id = $cid; $record->response = $other; @@ -62,11 +73,12 @@ public function insert_response($rid, $val) { if (preg_match("/other_q([0-9]+)/", (isset($val) ? $val : ''), $regs)) { $cid = $regs[1]; if (!isset($other)) { - $other = optional_param('q'.$this->question->id.'_'.$cid, null, PARAM_TEXT); + $other = isset($responsedata->{'q'.$this->question->id.'_'.$cid}) ? + $responsedata->{'q'.$this->question->id.'_'.$cid} : null; } if (preg_match("/[^ \t\n]/", $other)) { $record = new \stdClass(); - $record->response_id = $rid; + $record->response_id = $responsedata->rid; $record->question_id = $this->question->id; $record->choice_id = $cid; $record->response = $other; @@ -75,7 +87,7 @@ public function insert_response($rid, $val) { } } $record = new \stdClass(); - $record->response_id = $rid; + $record->response_id = $responsedata->rid; $record->question_id = $this->question->id; $record->choice_id = isset($val) ? $val : 0; if ($record->choice_id) {// If "no answer" then choice_id is empty (CONTRIB-846). diff --git a/classes/response/text.php b/classes/response/text.php index 8a04ec13..920c64f3 100644 --- a/classes/response/text.php +++ b/classes/response/text.php @@ -35,12 +35,22 @@ */ class text extends base { + /** + * @return string + */ static public function response_table() { return 'questionnaire_response_text'; } - public function insert_response($rid, $val) { + /** + * @param int|object $responsedata + * @return bool|int + * @throws \dml_exception + */ + public function insert_response($responsedata) { global $DB; + + $val = isset($responsedata->{'q'.$this->question->id}) ? $responsedata->{'q'.$this->question->id} : ''; // Only insert if non-empty content. if ($this->question->type_id == QUESNUMERIC) { $val = str_replace(",", ".", $val); // Allow commas as well as points in decimal numbers. @@ -49,7 +59,7 @@ public function insert_response($rid, $val) { if (preg_match("/[^ \t\n]/", $val)) { $record = new \stdClass(); - $record->response_id = $rid; + $record->response_id = $responsedata->rid; $record->question_id = $this->question->id; $record->response = $val; return $DB->insert_record(self::response_table(), $record); @@ -58,6 +68,13 @@ public function insert_response($rid, $val) { } } + /** + * @param bool $rids + * @param bool $anonymous + * @return array + * @throws \coding_exception + * @throws \dml_exception + */ public function get_results($rids=false, $anonymous=false) { global $DB; diff --git a/questionnaire.class.php b/questionnaire.class.php index 2b5d09ed..f703231b 100644 --- a/questionnaire.class.php +++ b/questionnaire.class.php @@ -222,27 +222,18 @@ public function view() { // If Questionnaire was submitted with all required fields completed ($msg is empty), // then record the submittal. $viewform = data_submitted($CFG->wwwroot."/mod/questionnaire/complete.php"); - if (!empty($viewform->rid)) { - $viewform->rid = (int)$viewform->rid; - } - if (!empty($viewform->sec)) { - $viewform->sec = (int)$viewform->sec; - } - if (data_submitted() && confirm_sesskey() && isset($viewform->submit) && isset($viewform->submittype) && + if ($viewform && confirm_sesskey() && isset($viewform->submit) && isset($viewform->submittype) && ($viewform->submittype == "Submit Survey") && empty($msg)) { + if (!empty($viewform->rid)) { + $viewform->rid = (int)$viewform->rid; + } + if (!empty($viewform->sec)) { + $viewform->sec = (int)$viewform->sec; + } $this->response_delete($viewform->rid, $viewform->sec); - $this->rid = $this->response_insert($viewform->sec, $viewform->rid, $quser); + $this->rid = $this->response_insert($viewform, $quser); $this->response_commit($this->rid); - // If it was a previous save, rid is in the form... - if (!empty($viewform->rid) && is_numeric($viewform->rid)) { - $rid = $viewform->rid; - - // Otherwise its in this object. - } else { - $rid = $this->rid; - } - $this->update_grades($quser); // Update completion state. @@ -272,7 +263,7 @@ public function view() { public function delete_insert_response($rid, $sec, $quser) { $this->response_delete($rid, $sec); - $this->rid = $this->response_insert($sec, $rid, $quser); + $this->rid = $this->response_insert((object)['sec' => $sec, 'rid' => $rid], $quser); return $this->rid; } @@ -957,7 +948,7 @@ public function print_survey($userid=false, $quser) { if (!empty($formdata->resume) && ($this->resume)) { $this->response_delete($formdata->rid, $formdata->sec); - $formdata->rid = $this->response_insert($formdata->sec, $formdata->rid, $quser, true); + $formdata->rid = $this->response_insert($formdata, $quser, true); $this->response_goto_saved($action); return; } @@ -969,7 +960,7 @@ public function print_survey($userid=false, $quser) { $formdata->next = ''; } else { $this->response_delete($formdata->rid, $formdata->sec); - $formdata->rid = $this->response_insert($formdata->sec, $formdata->rid, $quser); + $formdata->rid = $this->response_insert($formdata, $quser); // Skip logic. $formdata->sec++; if ($this->has_dependencies()) { @@ -1000,7 +991,7 @@ public function print_survey($userid=false, $quser) { $formdata->prev = ''; } else { $this->response_delete($formdata->rid, $formdata->sec); - $formdata->rid = $this->response_insert($formdata->sec, $formdata->rid, $quser); + $formdata->rid = $this->response_insert($formdata, $quser); $formdata->sec--; // Skip logic. if ($this->has_dependencies()) { @@ -2095,19 +2086,27 @@ private function response_send_email($rid, $email) { return $return; } - public function response_insert($section, $rid, $userid, $resume=false) { + /** + * @param object $responsedata An object containing all data for the response. + * @param int $userid + * @param bool $resume + * @return bool|int + * @throws coding_exception + * @throws dml_exception + */ + public function response_insert($responsedata, $userid, $resume=false) { global $DB; $record = new stdClass(); $record->submitted = time(); - if (empty($rid)) { + if (empty($responsedata->rid)) { // Create a uniqe id for this response. $record->questionnaireid = $this->id; $record->userid = $userid; - $rid = $DB->insert_record('questionnaire_response', $record); + $responsedata->rid = $DB->insert_record('questionnaire_response', $record); } else { - $record->id = $rid; + $record->id = $responsedata->rid; $DB->update_record('questionnaire_response', $record); } if ($resume) { @@ -2126,19 +2125,13 @@ public function response_insert($section, $rid, $userid, $resume=false) { $event->trigger(); } - if (!empty($this->questionsbysec[$section])) { - foreach ($this->questionsbysec[$section] as $question) { - // NOTE *** $val really should be a value obtained from the caller or somewhere else. - // Note that "optional_param" accepting arrays is deprecated for optional_param_array. - if ($question->responsetable == 'resp_multiple') { - $val = optional_param_array('q'.$question->id, '', PARAM_RAW); - } else { - $val = optional_param('q'.$question->id, '', PARAM_RAW); - } - $question->insert_response($rid, $val); + if (!empty($this->questionsbysec[$responsedata->sec])) { + foreach ($this->questionsbysec[$responsedata->sec] as $question) { + $val = isset($responsedata->{'q'.$question->id}) ? $responsedata->{'q'.$question->id} : ''; + $question->insert_response($responsedata); } } - return($rid); + return($responsedata->rid); } private function response_select($rid, $col = null, $csvexport = false, $choicecodes=0, $choicetext=1) { diff --git a/tests/generator/lib.php b/tests/generator/lib.php index 374f5af3..a7a81cbd 100644 --- a/tests/generator/lib.php +++ b/tests/generator/lib.php @@ -228,8 +228,11 @@ public function create_test_questionnaire($course, $qtype = null, $questiondata public function create_question_response($questionnaire, $question, $respval, $userid = 1, $section = 1) { global $DB; $currentrid = 0; - $_POST['q'.$question->id] = $respval; - $responseid = $questionnaire->response_insert($section, $currentrid, $userid); + if (!is_array($respval)) { + $respval = ['q'.$question->id => $respval]; + } + $respdata = (object)(array_merge(['sec' => $section, 'rid' => $currentrid], $respval)); + $responseid = $questionnaire->response_insert($respdata, $userid); $this->response_commit($questionnaire, $responseid); return $DB->get_record('questionnaire_response', array('id' => $responseid)); } diff --git a/tests/responsetypes_test.php b/tests/responsetypes_test.php index 07c74d8e..077e30b8 100644 --- a/tests/responsetypes_test.php +++ b/tests/responsetypes_test.php @@ -165,10 +165,9 @@ public function test_create_response_single() { $val = $cid; } } - // Need an extra $_POST variable for an "other" response. - $_POST['q'.$question->id.'_'.$val] = 'Forty-four'; + $vals = ['q'.$question->id => $val, 'q'.$question->id.'_'.$val => 'Forty-four']; $userid = 2; - $response = $generator->create_question_response($questionnaire, $question, $val, $userid); + $response = $generator->create_question_response($questionnaire, $question, $vals, $userid); // Test the responses for this questionnaire. $this->response_tests($questionnaire->id, $response->id, $userid, 1, 2); @@ -216,8 +215,8 @@ public function test_create_response_multiple() { $val2 = $cid; } } - $_POST['q'.$question->id.'_'.$val2] = 'Forty-four'; - $response = $generator->create_question_response($questionnaire, $question, $val, $userid); + $vals = ['q'.$question->id => $val, 'q'.$question->id.'_'.$val2 => 'Forty-four']; + $response = $generator->create_question_response($questionnaire, $question, $vals, $userid); // Test the responses for this questionnaire. $this->response_tests($questionnaire->id, $response->id, $userid); @@ -268,9 +267,9 @@ public function test_create_response_rank() { $i = 1; foreach ($question->choices as $cid => $choice) { $vals[$cid] = $i; - $_POST['q'.$question->id.'_'.$cid] = $i++; + $vals['q'.$question->id.'_'.$cid] = $i++; } - $response = $generator->create_question_response($questionnaire, $question, null, $userid); + $response = $generator->create_question_response($questionnaire, $question, $vals, $userid); // Test the responses for this questionnaire. $this->response_tests($questionnaire->id, $response->id, $userid); From 4e7a667ec7cbfc74eca93a8bce7426f03b457827 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Thu, 2 May 2019 11:23:00 -0400 Subject: [PATCH 068/341] GHI157 - Refactoring data and code for choices. --- classes/question/base.php | 62 ++++++- classes/question/check.php | 78 +++----- classes/question/radio.php | 80 ++++---- classes/response/base.php | 11 +- classes/response/boolean.php | 33 ++-- classes/response/date.php | 33 +++- classes/response/multiple.php | 226 +++++------------------ classes/response/rank.php | 22 +-- classes/response/single.php | 156 ++++++---------- classes/response/text.php | 29 ++- questionnaire.class.php | 192 ++++++++++--------- tests/behat/radio_question_other.feature | 1 + tests/generator/lib.php | 7 +- tests/responsetypes_test.php | 21 ++- 14 files changed, 434 insertions(+), 517 deletions(-) diff --git a/classes/question/base.php b/classes/question/base.php index beaa48a7..258ead6b 100644 --- a/classes/question/base.php +++ b/classes/question/base.php @@ -204,6 +204,52 @@ static public function qtypename($qtype) { } } + /** + * Return true if the choice object or choice content string is an "other" choice. + * + * @param string | object $choice + * @return bool + */ + static public function other_choice($choice) { + if (is_object($choice)) { + $content = $choice->content; + } else { + $content = $choice; + } + return (strpos($content, '!other') === 0); + } + + /** + * Return the string to display for an "other" option. If the option is not an "other", return false. + * + * @param string | object $choice + * @return string | bool + */ + static public function other_choice_display($choice) { + if (!self::other_choice($choice)) { + return false; + } + + if (is_object($choice)) { + $content = $choice->content; + } else { + $content = $choice; + } + + // If there is a defined string display after the "=", return it. Otherwise the "other" language string. + return preg_replace(["/^!other=/", "/^!other/"], ['', get_string('other', 'questionnaire')], $content); + } + + /** + * Return the string to use as an input name for an other choice. + * + * @param int $choiceid + * @return string + */ + static public function other_choice_name($choiceid) { + return 'o' . $choiceid; + } + /** * Override and return true if the question has choices. */ @@ -368,11 +414,13 @@ public function response_has_choice($rid, $choiceid) { /** * Insert response data method. + * @param object $responsedata All of the responsedata. + * @return bool */ - public function insert_response($rid, $val) { - if (isset ($this->response) && is_object($this->response) && + public function insert_response($responsedata) { + if (isset($this->response) && is_object($this->response) && is_subclass_of($this->response, '\\mod_questionnaire\\response\\base')) { - return $this->response->insert_response($rid, $val); + return $this->response->insert_response($responsedata); } else { return false; } @@ -774,6 +822,7 @@ public function results_template() { * @param array $dependants Array of all questions/choices depending on this question. * @param integer $qnum * @param boolean $blankquestionnaire + * @return \stdClass */ public function question_output($formdata, $dependants=[], $qnum='', $blankquestionnaire) { $pagetags = $this->questionstart_survey_display($qnum, $formdata); @@ -783,10 +832,9 @@ public function question_output($formdata, $dependants=[], $qnum='', $blankquest /** * Get the output for question renderers / templates. - * @param object $formdata - * @param string $descendantdata - * @param integer $qnum - * @param boolean $blankquestionnaire + * @param $data + * @param string $qnum + * @return \stdClass */ public function response_output($data, $qnum='') { $pagetags = $this->questionstart_survey_display($qnum, $data); diff --git a/classes/question/check.php b/classes/question/check.php index 387524d0..9e166436 100644 --- a/classes/question/check.php +++ b/classes/question/check.php @@ -80,10 +80,10 @@ protected function question_survey_display($data, $dependants, $blankquestionnai $otherempty = false; if (!empty($data) ) { if (!isset($data->{'q'.$this->id}) || !is_array($data->{'q'.$this->id})) { - $data->{'q'.$this->id} = array(); + $data->{'q'.$this->id} = []; } // Verify that number of checked boxes (nbboxes) is within set limits (length = min; precision = max). - if ( $data->{'q'.$this->id} ) { + if ($data->{'q'.$this->id} ) { $otherempty = false; $boxes = $data->{'q'.$this->id}; $nbboxes = count($boxes); @@ -130,53 +130,31 @@ protected function question_survey_display($data, $dependants, $blankquestionnai $choicetags = new \stdClass(); $choicetags->qelements = []; foreach ($this->choices as $id => $choice) { - - $other = strpos($choice->content, '!other'); $checkbox = new \stdClass(); - if ($other !== 0) { // This is a normal check box. - $contents = questionnaire_choice_values($choice->content); - $checked = false; - if (!empty($data) ) { - $checked = in_array($id, $data->{'q'.$this->id}); - } - $checkbox->name = 'q'.$this->id.'[]'; - $checkbox->value = $id; - $checkbox->id = 'checkbox_'.$id; - $checkbox->label = format_text($contents->text, FORMAT_HTML, ['noclean' => true]).$contents->image; - if ($checked) { - $checkbox->checked = $checked; - } - } else { // Check box with associated !other text field. - // In case length field has been used to enter max number of choices, set it to 20. - $othertext = preg_replace( - array("/^!other=/", "/^!other/"), - array('', get_string('other', 'questionnaire')), - $choice->content); - $cid = 'q'.$this->id.'_'.$id; - if (!empty($data) && isset($data->$cid) && (trim($data->$cid) != false)) { - $checked = true; - } else { - $checked = false; - } - $name = 'q'.$this->id.'[]'; - $value = 'other_'.$id; - - $checkbox->name = $name; - $checkbox->oname = $cid; - $checkbox->value = $value; - $checkbox->ovalue = (isset($data->$cid) && !empty($data->$cid) ? stripslashes($data->$cid) : ''); - $checkbox->id = 'checkbox_'.$id; - $checkbox->label = format_text($othertext.'', FORMAT_HTML, ['noclean' => true]); - if ($checked) { - $checkbox->checked = $checked; - } + $contents = questionnaire_choice_values($choice->content); + $checked = false; + if (!empty($data) ) { + $checked = isset($data->{'q'.$this->id}[$id]); + } + $checkbox->name = 'q'.$this->id.'['.$id.']'; + $checkbox->value = $id; + $checkbox->id = 'checkbox_'.$id; + $checkbox->label = format_text($contents->text, FORMAT_HTML, ['noclean' => true]).$contents->image; + if ($checked) { + $checkbox->checked = $checked; + } + if (self::other_choice($choice)) { + $checkbox->oname = 'q'.$this->id.'['.self::other_choice_name($id).']'; + $checkbox->ovalue = (isset($data->{'q'.$this->id}[self::other_choice_name($id)]) && + !empty($data->{'q'.$this->id}[self::other_choice_name($id)]) ? + stripslashes($data->{'q'.$this->id}[self::other_choice_name($id)]) : ''); + $checkbox->label = format_text(self::other_choice_display($choice).'', FORMAT_HTML, ['noclean' => true]); } $choicetags->qelements[] = (object)['choice' => $checkbox]; } if ($otherempty) { $this->add_notification(get_string('otherempty', 'questionnaire')); } - return $choicetags; } @@ -198,25 +176,21 @@ protected function response_survey_display($data) { foreach ($this->choices as $id => $choice) { $chobj = new \stdClass(); - if (strpos($choice->content, '!other') !== 0) { + if (!self::other_choice($choice)) { $contents = questionnaire_choice_values($choice->content); $choice->content = $contents->text.$contents->image; - if (in_array($id, $data->{'q'.$this->id})) { + if (isset($data->{'q'.$this->id}[$id])) { $chobj->selected = 1; } $chobj->name = $id.$uniquetag++; $chobj->content = (($choice->content === '') ? $id : format_text($choice->content, FORMAT_HTML, ['noclean' => true])); } else { - $othertext = preg_replace( - array("/^!other=/", "/^!other/"), - array('', get_string('other', 'questionnaire')), - $choice->content); - $cid = 'q'.$this->id.'_'.$id; - - if (isset($data->$cid)) { + $othertext = self::other_choice_display($choice); + if (isset($data->{'q'.$this->id})) { + $oresp = $data->{'q'.$this->id}[self::other_choice_name($id)]; $chobj->selected = 1; - $chobj->othercontent = (!empty($data->$cid) ? htmlspecialchars($data->$cid) : ' '); + $chobj->othercontent = (!empty($oresp) ? htmlspecialchars($oresp) : ' '); } $chobj->name = $id.$uniquetag++; $chobj->content = (($othertext === '') ? $id : $othertext); diff --git a/classes/question/radio.php b/classes/question/radio.php index 99888fed..23cd6847 100644 --- a/classes/question/radio.php +++ b/classes/question/radio.php @@ -86,31 +86,37 @@ protected function question_survey_display($data, $dependants=[], $blankquestion global $idcounter; // To make sure all radio buttons have unique ids. // JR 20 NOV 2007. $otherempty = false; - // Find out which radio button is checked (if any); yields choice ID. - if (isset($data->{'q'.$this->id})) { - $checked = $data->{'q'.$this->id}; - } else { - $checked = ''; - } $horizontal = $this->length; $ischecked = false; $choicetags = new \stdClass(); $choicetags->qelements = []; + + $qdata = new \stdClass(); + if (isset($data->{'q'.$this->id}) && is_array($data->{'q'.$this->id})) { + foreach($data->{'q'.$this->id} as $cid => $cval) { + $qdata->{'q' . $this->id} = $cid; + if (isset($data->{'q'.$this->id}[self::other_choice_name($cid)])) { + $qdata->{'q'.$this->id.self::other_choice_name($cid)} = $data->{'q'.$this->id}[self::other_choice_name($cid)]; + } + } + } else if (isset($data->{'q'.$this->id})) { + $qdata->{'q'.$this->id} = $data->{'q'.$this->id}; + } + foreach ($this->choices as $id => $choice) { $radio = new \stdClass(); - $other = strpos($choice->content, '!other'); if ($horizontal) { $radio->horizontal = $horizontal; } - if ($other !== 0) { // This is a normal radio button. + if (!self::other_choice($choice)) { // This is a normal radio button. $htmlid = 'auto-rb'.sprintf('%04d', ++$idcounter); $radio->name = 'q'.$this->id; $radio->id = $htmlid; $radio->value = $id; - if ($id == $checked) { + if (isset($qdata->{$radio->name}) && ($qdata->{$radio->name} == $id)) { $radio->checked = true; $ischecked = true; } @@ -122,30 +128,24 @@ protected function question_survey_display($data, $dependants=[], $blankquestion $contents = questionnaire_choice_values($choice->content); $radio->label = $value.format_text($contents->text, FORMAT_HTML, ['noclean' => true]).$contents->image; } else { // Radio button with associated !other text field. - $othertext = preg_replace(["/^!other=/", "/^!other/"], ['', get_string('other', 'questionnaire')], - $choice->content); - $cid = 'q'.$this->id.'_'.$id; - $otherempty = false; - if (substr($checked, 0, 6) == 'other_') { // Fix bug CONTRIB-222. - $checked = substr($checked, 6); - } + $othertext = self::other_choice_display($choice); + $cname = self::other_choice_name($id); + $odata = isset($qdata->{'q'.$this->id.$cname}) ? $qdata->{'q'.$this->id.$cname} : ''; + $otherempty = empty($odata); $htmlid = 'auto-rb'.sprintf('%04d', ++$idcounter); $radio->name = 'q'.$this->id; $radio->id = $htmlid; - $radio->value = 'other_'.$id; - if (($id == $checked) || !empty($data->$cid)) { + $radio->value = $id; + if (isset($qdata->{$radio->name}) || !empty($odata)) { $radio->checked = true; $ischecked = true; - if (isset($data->$cid) && (trim($data->$cid) == false)) { - $otherempty = true; - } } $radio->label = format_text($othertext, FORMAT_HTML, ['noclean' => true]); - $radio->oname = $cid; + $radio->oname = 'q'.$this->id.self::other_choice_name($id); $radio->oid = $htmlid.'-other'; - if (isset($data->$cid)) { - $radio->ovalue = stripslashes($data->$cid); + if (isset($odata)) { + $radio->ovalue = stripslashes($odata); } $radio->olabel = 'Text for '.format_text($othertext, FORMAT_HTML, ['noclean' => true]); } @@ -155,7 +155,6 @@ protected function question_survey_display($data, $dependants=[], $blankquestion // CONTRIB-846. if (!$this->required()) { $radio = new \stdClass(); - $id = ''; $htmlid = 'auto-rb'.sprintf('%04d', ++$idcounter); if ($horizontal) { $radio->horizontal = $horizontal; @@ -163,7 +162,7 @@ protected function question_survey_display($data, $dependants=[], $blankquestion $radio->name = 'q'.$this->id; $radio->id = $htmlid; - $radio->value = $id; + $radio->value = 0; if (!$ischecked && !$blankquestionnaire) { $radio->checked = true; @@ -193,15 +192,27 @@ protected function response_survey_display($data) { $resptags = new \stdClass(); $resptags->choices = []; + $qdata = new \stdClass(); + if (isset($data->{'q'.$this->id}) && is_array($data->{'q'.$this->id})) { + foreach($data->{'q'.$this->id} as $cid => $cval) { + $qdata->{'q' . $this->id} = $cid; + if (isset($data->{'q'.$this->id}[self::other_choice_name($cid)])) { + $qdata->{'q'.$this->id.self::other_choice_name($cid)} = $data->{'q'.$this->id}[self::other_choice_name($cid)]; + } + } + } else if (isset($data->{'q'.$this->id})) { + $qdata->{'q'.$this->id} = $data->{'q'.$this->id}; + } + $horizontal = $this->length; - $checked = (isset($data->{'q'.$this->id}) ? $data->{'q'.$this->id} : ''); + $checked = (isset($qdata->{'q'.$this->id}) ? $qdata->{'q'.$this->id} : ''); foreach ($this->choices as $id => $choice) { $chobj = new \stdClass(); if ($horizontal) { $chobj->horizontal = 1; } $chobj->name = $id.$uniquetag++; - if (strpos($choice->content, '!other') !== 0) { + if (!self::other_choice($choice)) { $contents = questionnaire_choice_values($choice->content); $choice->content = $contents->text.$contents->image; if ($id == $checked) { @@ -209,12 +220,11 @@ protected function response_survey_display($data) { } $chobj->content = ($choice->content === '' ? $id : format_text($choice->content, FORMAT_HTML, ['noclean' => true])); } else { - $othertext = preg_replace(["/^!other=/", "/^!other/"], ['', get_string('other', 'questionnaire')], - $choice->content); - $cid = 'q'.$this->id.'_'.$id; - if (isset($data->{'q'.$this->id.'_'.$id})) { + $othertext = self::other_choice_display($choice); + $cid = 'q'.$this->id.self::other_choice_name($id); + if (isset($qdata->{$cid})) { $chobj->selected = 1; - $chobj->othercontent = (!empty($data->$cid) ? htmlspecialchars($data->$cid) : ' '); + $chobj->othercontent = (!empty($qdata->{$cid}) ? htmlspecialchars($qdata->{$cid}) : ' '); } $chobj->content = $othertext; } @@ -246,9 +256,9 @@ public function response_complete($responsedata) { * @return boolean */ public function response_valid($responsedata) { - if (isset($responsedata->{'q'.$this->id}) && (strpos($responsedata->{'q'.$this->id}, 'other_') !== false)) { + if (isset($responsedata->{'q'.$this->id}) && self::other_choice($this->choices[$responsedata->{'q'.$this->id}])) { // False if "other" choice is checked but text box is empty. - return (trim($responsedata->{'q'.$this->id.''.substr($responsedata->{'q'.$this->id}, 5)}) != false); + return !empty($responsedata->{'q'.$this->id.self::other_choice_name($responsedata->{'q'.$this->id})}); } else { return parent::response_valid($responsedata); } diff --git a/classes/response/base.php b/classes/response/base.php index ce857ad2..1680aa29 100644 --- a/classes/response/base.php +++ b/classes/response/base.php @@ -54,11 +54,10 @@ static public function response_table() { /** * Insert a provided response to the question. * - * @param integer $rid - The data id of the response table id. - * @param mixed $val - The response data provided. + * @param object $responsedata All of the responsedata as an object. * @return int|bool - on error the subtype should call set_error and return false. */ - abstract public function insert_response($rid, $val); + abstract public function insert_response($responsedata); /** * Provide the result information for the specified result records. @@ -206,13 +205,9 @@ public function get_feedback_scores(array $rids) { * Return an array of answers by question/choice for the given response. Must be implemented by the subclass. * * @param int $rid The response id. - * @param null $col Other data columns to return. - * @param bool $csvexport Using for CSV export. - * @param int $choicecodes CSV choicecodes are required. - * @param int $choicetext CSV choicetext is required. * @return array */ - static public function response_select($rid, $col = null, $csvexport = false, $choicecodes = 0, $choicetext = 1) { + static public function response_select($rid) { return []; } diff --git a/classes/response/boolean.php b/classes/response/boolean.php index dddcfcba..c6144b44 100644 --- a/classes/response/boolean.php +++ b/classes/response/boolean.php @@ -36,15 +36,25 @@ class boolean extends base { + /** + * @return string + */ static public function response_table() { return 'questionnaire_response_bool'; } - public function insert_response($rid, $val) { + /** + * @param int|object $responsedata + * @return bool|int + * @throws \dml_exception + */ + public function insert_response($responsedata) { global $DB; + + $val = isset($responsedata->{'q'.$this->question->id}) ? $responsedata->{'q'.$this->question->id} : ''; if (!empty($val)) { // If "no answer" then choice is empty (CONTRIB-846). $record = new \stdClass(); - $record->response_id = $rid; + $record->response_id = $responsedata->rid; $record->question_id = $this->question->id; $record->choice_id = $val; return $DB->insert_record(self::response_table(), $record); @@ -53,6 +63,13 @@ public function insert_response($rid, $val) { } } + /** + * @param bool $rids + * @param bool $anonymous + * @return array + * @throws \coding_exception + * @throws \dml_exception + */ public function get_results($rids=false, $anonymous=false) { global $DB; @@ -173,17 +190,13 @@ public function display_results($rids=false, $sort='', $anonymous=false) { * Return an array of answers by question/choice for the given response. Must be implemented by the subclass. * * @param int $rid The response id. - * @param null $col Other data columns to return. - * @param bool $csvexport Using for CSV export. - * @param int $choicecodes CSV choicecodes are required. - * @param int $choicetext CSV choicetext is required. * @return array */ - static public function response_select($rid, $col = null, $csvexport = false, $choicecodes = 0, $choicetext = 1) { + static public function response_select($rid) { global $DB; $values = []; - $sql = 'SELECT q.id '.$col.', a.choice_id '. + $sql = 'SELECT q.id, q.content, a.choice_id '. 'FROM {'.self::response_table().'} a, {questionnaire_question} q '. 'WHERE a.response_id= ? AND a.question_id=q.id '; $records = $DB->get_records_sql($sql, [$rid]); @@ -200,9 +213,7 @@ static public function response_select($rid, $col = null, $csvexport = false, $c } $values[$qid] = $newrow; array_push($values[$qid], ($choice == 'y') ? '1' : '0'); - if (!$csvexport) { - array_push($values[$qid], $choice); // DEV still needed for responses display. - } + array_push($values[$qid], $choice); // DEV still needed for responses display. } return $values; diff --git a/classes/response/date.php b/classes/response/date.php index 45a8712a..1678d827 100644 --- a/classes/response/date.php +++ b/classes/response/date.php @@ -35,12 +35,22 @@ */ class date extends base { + /** + * @return string + */ static public function response_table() { return 'questionnaire_response_date'; } - public function insert_response($rid, $val) { + /** + * @param int|object $responsedata + * @return bool|int + * @throws \dml_exception + */ + public function insert_response($responsedata) { global $DB; + + $val = isset($responsedata->{'q'.$this->question->id}) ? $responsedata->{'q'.$this->question->id} : ''; $checkdateresult = questionnaire_check_date($val); $thisdate = $val; if (substr($checkdateresult, 0, 5) == 'wrong') { @@ -48,13 +58,21 @@ public function insert_response($rid, $val) { } // Now use ISO date formatting. $checkdateresult = questionnaire_check_date($thisdate, true); + $record = new \stdClass(); - $record->response_id = $rid; + $record->response_id = $responsedata->rid; $record->question_id = $this->question->id; $record->response = $checkdateresult; return $DB->insert_record(self::response_table(), $record); } + /** + * @param bool $rids + * @param bool $anonymous + * @return array + * @throws \coding_exception + * @throws \dml_exception + */ public function get_results($rids=false, $anonymous=false) { global $DB; @@ -153,17 +171,13 @@ public function get_results_tags($weights, $participants, $respondents, $showtot * Return an array of answers by question/choice for the given response. Must be implemented by the subclass. * * @param int $rid The response id. - * @param null $col Other data columns to return. - * @param bool $csvexport Using for CSV export. - * @param int $choicecodes CSV choicecodes are required. - * @param int $choicetext CSV choicetext is required. * @return array */ - static public function response_select($rid, $col = null, $csvexport = false, $choicecodes = 0, $choicetext = 1) { + static public function response_select($rid) { global $DB; $values = []; - $sql = 'SELECT q.id '.$col.', a.response as aresponse '. + $sql = 'SELECT q.id, q.content, a.response as aresponse '. 'FROM {'.self::response_table().'} a, {questionnaire_question} q '. 'WHERE a.response_id=? AND a.question_id=q.id '; $records = $DB->get_records_sql($sql, [$rid]); @@ -200,5 +214,4 @@ static public function response_select($rid, $col = null, $csvexport = false, $c protected function bulk_sql_config() { return new bulk_sql_config(self::response_table(), 'qrd', false, true, false); } -} - +} \ No newline at end of file diff --git a/classes/response/multiple.php b/classes/response/multiple.php index 0d338acd..ab24bb8d 100644 --- a/classes/response/multiple.php +++ b/classes/response/multiple.php @@ -43,43 +43,39 @@ static public function response_table() { return 'questionnaire_resp_multiple'; } - public function insert_response($rid, $val) { + public function insert_response($responsedata) { global $DB; + + $val = isset($responsedata->{'q'.$this->question->id}) ? $responsedata->{'q'.$this->question->id} : ''; $resid = ''; - foreach ($this->question->choices as $cid => $choice) { - if (strpos($choice->content, '!other') === 0) { - $other = optional_param('q'.$this->question->id.'_'.$cid, '', PARAM_CLEAN); - if (empty($other)) { - continue; - } - if (!isset($val) || !is_array($val)) { - $val = array($cid); - } else { - array_push($val, $cid); - } - if (preg_match("/[^ \t\n]/", $other)) { - $record = new \stdClass(); - $record->response_id = $rid; - $record->question_id = $this->question->id; - $record->choice_id = $cid; - $record->response = $other; - $resid = $DB->insert_record('questionnaire_response_other', $record); - } - } - } if (!isset($val) || !is_array($val)) { return false; } - foreach ($val as $cid) { + foreach ($val as $cid => $cvalue) { $cid = clean_param($cid, PARAM_CLEAN); - if ($cid != 0) { // Do not save response if choice is empty. - if (preg_match("/other_q[0-9]+/", $cid)) { - continue; + if (isset($this->question->choices[$cid])) { + // If this choice is an "other" choice, look for the added input. + if (\mod_questionnaire\question\base::other_choice($this->question->choices[$cid])) { + $cname = \mod_questionnaire\question\base::other_choice_name($cid); + $other = isset($val[$cname]) ? $val[$cname] : ''; + + // If no input specified, ignore this choice. + if (empty($other) || preg_match("/[ \t\n]/", $other)) { + continue; + } + $record = new \stdClass(); + $record->response_id = $responsedata->rid; + $record->question_id = $this->question->id; + $record->choice_id = $cid; + $record->response = $other; + $DB->insert_record('questionnaire_response_other', $record); } + + // Record the choice selection. $record = new \stdClass(); - $record->response_id = $rid; + $record->response_id = $responsedata->rid; $record->question_id = $this->question->id; $record->choice_id = $cid; $resid = $DB->insert_record(self::response_table(), $record); @@ -90,167 +86,45 @@ public function insert_response($rid, $val) { /** * Return an array of answers by question/choice for the given response. Must be implemented by the subclass. + * Array is indexed by question, and contains an array by choice code of selected choices. * * @param int $rid The response id. - * @param null $col Other data columns to return. - * @param bool $csvexport Using for CSV export. - * @param int $choicecodes CSV choicecodes are required. - * @param int $choicetext CSV choicetext is required. * @return array */ - static public function response_select($rid, $col = null, $csvexport = false, $choicecodes = 0, $choicetext = 1) { + static public function response_select($rid) { global $DB; - $stringother = get_string('other', 'questionnaire'); $values = []; - $sql = 'SELECT a.id as aid, q.id as qid '.$col.',c.content as ccontent,c.id as cid '. - 'FROM {'.self::response_table().'} a, {questionnaire_question} q, {questionnaire_quest_choice} c '. - 'WHERE a.response_id = ? AND a.question_id=q.id AND a.choice_id=c.id '. - 'ORDER BY a.id,a.question_id,c.id'; + $sql = 'SELECT a.id, q.id as qid, q.content, c.content as ccontent, c.id as cid, o.response ' . + 'FROM {'.static::response_table().'} a ' . + 'INNER JOIN {questionnaire_question} q ON a.question_id = q.id ' . + 'INNER JOIN {questionnaire_quest_choice} c ON a.choice_id = c.id ' . + 'LEFT JOIN {questionnaire_response_other} o ON a.response_id = o.response_id AND c.id = o.choice_id ' . + 'WHERE a.response_id = ? '; $records = $DB->get_records_sql($sql, [$rid]); - if ($csvexport) { - $tmp = null; - if (!empty($records)) { - $qids2 = array(); - $oldqid = ''; - foreach ($records as $qid => $row) { - if ($row->qid != $oldqid) { - $qids2[] = $row->qid; - $oldqid = $row->qid; - } - } - list($qsql, $params) = $DB->get_in_or_equal($qids2); - $sql = 'SELECT * FROM {questionnaire_quest_choice} WHERE question_id ' . $qsql . ' ORDER BY id'; - $records2 = $DB->get_records_sql($sql, $params); - foreach ($records2 as $qid => $row2) { - $selected = '0'; - $qid2 = $row2->question_id; - $cid2 = $row2->id; - $c2 = $row2->content; - $otherend = false; - if ($c2 == '!other') { - $c2 = '!other='.get_string('other', 'questionnaire'); - } - if (preg_match('/^!other/', $c2)) { - $otherend = true; - } else { - $contents = questionnaire_choice_values($c2); - if ($contents->modname) { - $c2 = $contents->modname; - } else if ($contents->title) { - $c2 = $contents->title; - } - } - $sql = 'SELECT a.name as name, a.type_id as q_type, a.position as pos ' . - 'FROM {questionnaire_question} a WHERE id = ?'; - $currentquestion = $DB->get_records_sql($sql, [$qid2]); - foreach ($currentquestion as $question) { - $name1 = $question->name; - $type1 = $question->q_type; - } - $newrow = []; - foreach ($records as $qid => $row1) { - $qid1 = $row1->qid; - $cid1 = $row1->cid; - // If available choice has been selected by student. - if ($qid1 == $qid2 && $cid1 == $cid2) { - $selected = '1'; - } - } - if ($otherend) { - $newrow2 = array(); - $newrow2[] = $question->pos; - $newrow2[] = $type1; - $newrow2[] = $name1; - $newrow2[] = '['.get_string('other', 'questionnaire').']'; - $newrow2[] = $selected; - $tmp2 = $qid2.'_other'; - $values["$tmp2"] = $newrow2; - } - $newrow[] = $question->pos; - $newrow[] = $type1; - $newrow[] = $name1; - $newrow[] = $c2; - $newrow[] = $selected; - $tmp = $qid2.'_'.$cid2; - $values["$tmp"] = $newrow; - } - } - unset($tmp); - unset($row); - - } else { - $arr = []; - $tmp = null; + if (!empty($records)) { + $qid = 0; + $newrow = []; foreach ($records as $row) { - $qid = $row->qid; - $cid = $row->cid; - unset($row->aid); - unset($row->qid); - unset($row->cid); - $arow = get_object_vars($row); - $newrow = []; - foreach ($arow as $key => $val) { - if (!is_numeric($key)) { - $newrow[] = $val; - } - } - if (preg_match('/^!other/', $row->ccontent)) { - $newrow[] = 'other_' . $cid; - } else { - $newrow[] = (int)$cid; - } - if ($tmp == $qid) { - $arr[] = $newrow; - continue; + if ($qid == 0) { + $qid = $row->qid; + $newrow['content'] = $row->content; + $newrow['ccontent'] = $row->ccontent; + $newrow['responses'] = []; + } else if ($qid != $row->qid) { + $values[$qid] = $newrow; + $qid = $row->qid; + $newrow = []; + $newrow['content'] = $row->content; + $newrow['ccontent'] = $row->ccontent; + $newrow['responses'] = []; } - if ($tmp != null) { - $values["$tmp"] = $arr; + $newrow['responses'][$row->cid] = $row->cid; + if (\mod_questionnaire\question\base::other_choice($row->ccontent)) { + $newrow['responses'][\mod_questionnaire\question\base::other_choice_name($row->cid)] = $row->response; } - $tmp = $qid; - $arr = array($newrow); - } - if ($tmp != null) { - $values["$tmp"] = $arr; - } - unset($arr); - unset($tmp); - unset($row); - } - - // Response_other. - // This will work even for multiple !other fields within one question - // AND for identical !other responses in different questions JR. - $sql = 'SELECT c.id as cid, c.content as content, a.response as aresponse, q.id as qid, q.position as position, - q.type_id as type_id, q.name as name '. - 'FROM {questionnaire_response_other} a, {questionnaire_question} q, {questionnaire_quest_choice} c '. - 'WHERE a.response_id= ? AND a.question_id=q.id AND a.choice_id=c.id '. - 'ORDER BY a.question_id,c.id '; - $records = $DB->get_records_sql($sql, [$rid]); - foreach ($records as $record) { - $newrow = []; - $position = $record->position; - $typeid = $record->type_id; - $name = $record->name; - $cid = $record->cid; - $qid = $record->qid; - $content = $record->content; - - // The !other modality with no label. - if ($content == '!other') { - $content = '!other='.$stringother; } - $content = substr($content, 7); - $aresponse = $record->aresponse; - // The first two empty values are needed for compatibility with "normal" (non !other) responses. - // They are only needed for the CSV export, in fact. - $newrow[] = $position; - $newrow[] = $typeid; - $newrow[] = $name; - $content = $stringother; - $newrow[] = $content; - $newrow[] = $aresponse; - $values["${qid}_${cid}"] = $newrow; + $values[$qid] = $newrow; } return $values; diff --git a/classes/response/rank.php b/classes/response/rank.php index 9f0e055a..db36cb36 100644 --- a/classes/response/rank.php +++ b/classes/response/rank.php @@ -40,16 +40,18 @@ static public function response_table() { } /** - * @param int $rid - * @param mixed $val + * @param object $responsedata * @return bool|int */ - public function insert_response($rid, $val) { + public function insert_response($responsedata) { global $DB; + + $val = isset($responsedata->{'q'.$this->question->id}) ? $responsedata->{'q'.$this->question->id} : ''; if ($this->question->type_id == QUESRATE) { $resid = false; foreach ($this->question->choices as $cid => $choice) { - $other = optional_param('q'.$this->question->id.'_'.$cid, null, PARAM_CLEAN); + $other = isset($responsedata->{'q'.$this->question->id.'_'.$cid}) ? + $responsedata->{'q'.$this->question->id.'_'.$cid} : null; // Choice not set or not answered. if (!isset($other) || $other == '') { continue; @@ -60,7 +62,7 @@ public function insert_response($rid, $val) { $rank = intval($other); } $record = new \stdClass(); - $record->response_id = $rid; + $record->response_id = $responsedata->rid; $record->question_id = $this->question->id; $record->choice_id = $cid; $record->rankvalue = $rank; @@ -74,7 +76,7 @@ public function insert_response($rid, $val) { $rank = intval($val); } $record = new \stdClass(); - $record->response_id = $rid; + $record->response_id = $responsedata->rid; $record->question_id = $this->question->id; $record->rankvalue = $rank; return $DB->insert_record(self::response_table(), $record); @@ -288,17 +290,13 @@ public function display_results($rids=false, $sort='', $anonymous=false) { * Return an array of answers by question/choice for the given response. Must be implemented by the subclass. * * @param int $rid The response id. - * @param null $col Other data columns to return. - * @param bool $csvexport Using for CSV export. - * @param int $choicecodes CSV choicecodes are required. - * @param int $choicetext CSV choicetext is required. * @return array */ - static public function response_select($rid, $col = null, $csvexport = false, $choicecodes = 0, $choicetext = 1) { + static public function response_select($rid) { global $DB; $values = []; - $sql = 'SELECT a.id as aid, q.id AS qid, q.precise AS precise, c.id AS cid '.$col.', c.content as ccontent, + $sql = 'SELECT a.id as aid, q.id AS qid, q.precise AS precise, c.id AS cid, q.content, c.content as ccontent, a.rankvalue as arank '. 'FROM {'.self::response_table().'} a, {questionnaire_question} q, {questionnaire_quest_choice} c '. 'WHERE a.response_id= ? AND a.question_id=q.id AND a.choice_id=c.id '. diff --git a/classes/response/single.php b/classes/response/single.php index cdc4b647..8a69a076 100644 --- a/classes/response/single.php +++ b/classes/response/single.php @@ -33,60 +33,54 @@ */ class single extends base { + /** + * @return string + */ static public function response_table() { return 'questionnaire_resp_single'; } - public function insert_response($rid, $val) { + /** + * @param int|object $responsedata + * @return bool|int + * @throws \dml_exception + */ + public function insert_response($responsedata) { global $DB; - if (!empty($val)) { - foreach ($this->question->choices as $cid => $choice) { - if (strpos($choice->content, '!other') === 0) { - $other = optional_param('q'.$this->question->id.'_'.$cid, null, PARAM_TEXT); - if (!isset($other)) { - continue; - } - if (preg_match("/[^ \t\n]/", $other)) { - $record = new \stdClass(); - $record->response_id = $rid; - $record->question_id = $this->question->id; - $record->choice_id = $cid; - $record->response = $other; - $resid = $DB->insert_record('questionnaire_response_other', $record); - $val = $cid; - break; - } - } - } - } - if (preg_match("/other_q([0-9]+)/", (isset($val) ? $val : ''), $regs)) { - $cid = $regs[1]; - if (!isset($other)) { - $other = optional_param('q'.$this->question->id.'_'.$cid, null, PARAM_TEXT); - } - if (preg_match("/[^ \t\n]/", $other)) { - $record = new \stdClass(); - $record->response_id = $rid; - $record->question_id = $this->question->id; - $record->choice_id = $cid; - $record->response = $other; - $resid = $DB->insert_record('questionnaire_response_other', $record); - $val = $cid; - } + + $cid = isset($responsedata->{'q'.$this->question->id}) ? $responsedata->{'q'.$this->question->id} : null; + $resid = ''; + + if ($cid === null) { + return false; } - $record = new \stdClass(); - $record->response_id = $rid; - $record->question_id = $this->question->id; - $record->choice_id = isset($val) ? $val : 0; - if ($record->choice_id) {// If "no answer" then choice_id is empty (CONTRIB-846). - try { - return $DB->insert_record(static::response_table(), $record); - } catch (\dml_write_exception $ex) { - return false; + + $cid = clean_param($cid, PARAM_CLEAN); + if (isset($this->question->choices[$cid])) { + // If this choice is an "other" choice, look for the added input. + if (\mod_questionnaire\question\base::other_choice($this->question->choices[$cid])) { + $cname = 'q' . $this->question->id . \mod_questionnaire\question\base::other_choice_name($cid); + $other = isset($responsedata->{$cname}) ? $responsedata->{$cname} : ''; + + // If no input specified, ignore this choice. + if (!empty($other) && !preg_match("/[ \t\n]/", $other)) { + $record = new \stdClass(); + $record->response_id = $responsedata->rid; + $record->question_id = $this->question->id; + $record->choice_id = $cid; + $record->response = $other; + $DB->insert_record('questionnaire_response_other', $record); + } } - } else { - return false; + + // Record the choice selection. + $record = new \stdClass(); + $record->response_id = $responsedata->rid; + $record->question_id = $this->question->id; + $record->choice_id = $cid; + $resid = $DB->insert_record(self::response_table(), $record); } + return $resid; } public function get_results($rids=false, $anonymous=false) { @@ -192,8 +186,7 @@ public function display_results($rids=false, $sort='', $anonymous=false) { if (strpos($idx, 'other') === 0) { $answer = $row->response; $ccontent = $row->content; - $content = preg_replace(array('/^!other=/', '/^!other/'), - array('', get_string('other', 'questionnaire')), $ccontent); + $content = \mod_questionnaire\question\base::other_choice_display($ccontent); $content .= ' ' . clean_text($answer); $textidx = $content; $this->counts[$textidx] = !empty($this->counts[$textidx]) ? ($this->counts[$textidx] + 1) : 1; @@ -213,66 +206,31 @@ public function display_results($rids=false, $sort='', $anonymous=false) { /** * Return an array of answers by question/choice for the given response. Must be implemented by the subclass. + * Array is indexed by question, and contains an array by choice code of selected choices. * * @param int $rid The response id. - * @param null $col Other data columns to return. - * @param bool $csvexport Using for CSV export. - * @param int $choicecodes CSV choicecodes are required. - * @param int $choicetext CSV choicetext is required. * @return array */ - static public function response_select($rid, $col = null, $csvexport = false, $choicecodes = 0, $choicetext = 1) { + static public function response_select($rid) { global $DB; $values = []; - $sql = 'SELECT q.id '.$col.', q.type_id as q_type, c.content as ccontent,c.id as cid '. - 'FROM {'.static::response_table().'} a, {questionnaire_question} q, {questionnaire_quest_choice} c '. - 'WHERE a.response_id = ? AND a.question_id=q.id AND a.choice_id=c.id '; + $sql = 'SELECT a.id, q.id as qid, q.content, c.content as ccontent, c.id as cid, o.response ' . + 'FROM {'.static::response_table().'} a ' . + 'INNER JOIN {questionnaire_question} q ON a.question_id = q.id ' . + 'INNER JOIN {questionnaire_quest_choice} c ON a.choice_id = c.id ' . + 'LEFT JOIN {questionnaire_response_other} o ON a.response_id = o.response_id AND c.id = o.choice_id ' . + 'WHERE a.response_id = ? '; $records = $DB->get_records_sql($sql, [$rid]); - foreach ($records as $qid => $row) { - $cid = $row->cid; - if ($csvexport) { - static $i = 1; - $qrecords = $DB->get_records('questionnaire_quest_choice', ['question_id' => $qid]); - foreach ($qrecords as $value) { - if ($value->id == $cid) { - $contents = questionnaire_choice_values($value->content); - if ($contents->modname) { - $row->ccontent = $contents->modname; - } else { - $content = $contents->text; - if (preg_match('/^!other/', $content)) { - $row->ccontent = get_string('other', 'questionnaire'); - } else if (($choicecodes == 1) && ($choicetext == 1)) { - $row->ccontent = "$i : $content"; - } else if ($choicecodes == 1) { - $row->ccontent = "$i"; - } else { - $row->ccontent = $content; - } - } - $i = 1; - break; - } - $i++; - } - } - unset($row->id); - unset($row->cid); - unset($row->q_type); - $arow = get_object_vars($row); - $newrow = []; - foreach ($arow as $key => $val) { - if (!is_numeric($key)) { - $newrow[] = $val; - } - } - if (preg_match('/^!other/', $row->ccontent)) { - $newrow[] = 'other_' . $cid; - } else { - $newrow[] = (int)$cid; + foreach ($records as $row) { + $newrow['content'] = $row->content; + $newrow['ccontent'] = $row->ccontent; + $newrow['responses'] = []; + $newrow['responses'][$row->cid] = $row->cid; + if (\mod_questionnaire\question\base::other_choice($row->ccontent)) { + $newrow['responses'][\mod_questionnaire\question\base::other_choice_name($row->cid)] = $row->response; } - $values[$qid] = $newrow; + $values[$row->qid] = $newrow; } return $values; diff --git a/classes/response/text.php b/classes/response/text.php index 8a04ec13..c13094df 100644 --- a/classes/response/text.php +++ b/classes/response/text.php @@ -35,12 +35,22 @@ */ class text extends base { + /** + * @return string + */ static public function response_table() { return 'questionnaire_response_text'; } - public function insert_response($rid, $val) { + /** + * @param int|object $responsedata + * @return bool|int + * @throws \dml_exception + */ + public function insert_response($responsedata) { global $DB; + + $val = isset($responsedata->{'q'.$this->question->id}) ? $responsedata->{'q'.$this->question->id} : ''; // Only insert if non-empty content. if ($this->question->type_id == QUESNUMERIC) { $val = str_replace(",", ".", $val); // Allow commas as well as points in decimal numbers. @@ -49,7 +59,7 @@ public function insert_response($rid, $val) { if (preg_match("/[^ \t\n]/", $val)) { $record = new \stdClass(); - $record->response_id = $rid; + $record->response_id = $responsedata->rid; $record->question_id = $this->question->id; $record->response = $val; return $DB->insert_record(self::response_table(), $record); @@ -58,6 +68,13 @@ public function insert_response($rid, $val) { } } + /** + * @param bool $rids + * @param bool $anonymous + * @return array + * @throws \coding_exception + * @throws \dml_exception + */ public function get_results($rids=false, $anonymous=false) { global $DB; @@ -228,17 +245,13 @@ public function get_results_tags($weights, $participants, $respondents, $showtot * Return an array of answers by question/choice for the given response. Must be implemented by the subclass. * * @param int $rid The response id. - * @param null $col Other data columns to return. - * @param bool $csvexport Using for CSV export. - * @param int $choicecodes CSV choicecodes are required. - * @param int $choicetext CSV choicetext is required. * @return array */ - static public function response_select($rid, $col = null, $csvexport = false, $choicecodes = 0, $choicetext = 1) { + static public function response_select($rid) { global $DB; $values = []; - $sql = 'SELECT q.id '.$col.', a.response as aresponse '. + $sql = 'SELECT q.id, q.content, a.response as aresponse '. 'FROM {'.self::response_table().'} a, {questionnaire_question} q '. 'WHERE a.response_id=? AND a.question_id=q.id '; $records = $DB->get_records_sql($sql, [$rid]); diff --git a/questionnaire.class.php b/questionnaire.class.php index 0493a545..66bb0c50 100644 --- a/questionnaire.class.php +++ b/questionnaire.class.php @@ -222,36 +222,19 @@ public function view() { // If Questionnaire was submitted with all required fields completed ($msg is empty), // then record the submittal. $viewform = data_submitted($CFG->wwwroot."/mod/questionnaire/complete.php"); - if (!empty($viewform->rid)) { - $viewform->rid = (int)$viewform->rid; - } - if (!empty($viewform->sec)) { - $viewform->sec = (int)$viewform->sec; - } - if (data_submitted() && confirm_sesskey() && isset($viewform->submit) && isset($viewform->submittype) && + if ($viewform && confirm_sesskey() && isset($viewform->submit) && isset($viewform->submittype) && ($viewform->submittype == "Submit Survey") && empty($msg)) { + if (!empty($viewform->rid)) { + $viewform->rid = (int)$viewform->rid; + } + if (!empty($viewform->sec)) { + $viewform->sec = (int)$viewform->sec; + } $this->response_delete($viewform->rid, $viewform->sec); - $this->rid = $this->response_insert($viewform->sec, $viewform->rid, $quser); + $this->rid = $this->response_insert($viewform, $quser); $this->response_commit($this->rid); - // If it was a previous save, rid is in the form... - if (!empty($viewform->rid) && is_numeric($viewform->rid)) { - $rid = $viewform->rid; - - // Otherwise its in this object. - } else { - $rid = $this->rid; - } - - if ($this->grade != 0) { - $questionnaire = new stdClass(); - $questionnaire->id = $this->id; - $questionnaire->name = $this->name; - $questionnaire->grade = $this->grade; - $questionnaire->cmidnumber = $this->cm->idnumber; - $questionnaire->courseid = $this->course->id; - questionnaire_update_grades($questionnaire, $quser); - } + $this->update_grades($quser); // Update completion state. $completion = new completion_info($this->course); @@ -278,6 +261,60 @@ public function view() { } } + public function delete_insert_response($rid, $sec, $quser) { + $this->response_delete($rid, $sec); + $this->rid = $this->response_insert((object)['sec' => $sec, 'rid' => $rid], $quser); + return $this->rid; + } + + public function commit_submission_response($rid, $quser) { + $this->response_commit($rid); + // If it was a previous save, rid is in the form... + if (!empty($rid) && is_numeric($rid)) { + $rid = $rid; + // Otherwise its in this object. + } else { + $rid = $this->rid; + } + + $this->update_grades($quser); + + // Update completion state. + $completion = new \completion_info($this->course); + if ($completion->is_enabled($this->cm) && $this->completionsubmit) { + $completion->update_state($this->cm, COMPLETION_COMPLETE); + } + // Log this submitted response. + $context = \context_module::instance($this->cm->id); + $anonymous = $this->respondenttype == 'anonymous'; + $params = [ + 'context' => $context, + 'courseid' => $this->course->id, + 'relateduserid' => $quser, + 'anonymous' => $anonymous, + 'other' => array('questionnaireid' => $this->id) + ]; + $event = \mod_questionnaire\event\attempt_submitted::create($params); + $event->trigger(); + } + + /** + * Update the grade for this questionnaire and user. + * + * @param $userid + */ + private function update_grades($userid) { + if ($this->grade != 0) { + $questionnaire = new \stdClass(); + $questionnaire->id = $this->id; + $questionnaire->name = $this->name; + $questionnaire->grade = $this->grade; + $questionnaire->cmidnumber = $this->cm->idnumber; + $questionnaire->courseid = $this->course->id; + questionnaire_update_grades($questionnaire, $userid); + } + } + /* * Function to view an entire responses data. * @@ -911,7 +948,7 @@ public function print_survey($userid=false, $quser) { if (!empty($formdata->resume) && ($this->resume)) { $this->response_delete($formdata->rid, $formdata->sec); - $formdata->rid = $this->response_insert($formdata->sec, $formdata->rid, $quser, true); + $formdata->rid = $this->response_insert($formdata, $quser, true); $this->response_goto_saved($action); return; } @@ -923,7 +960,7 @@ public function print_survey($userid=false, $quser) { $formdata->next = ''; } else { $this->response_delete($formdata->rid, $formdata->sec); - $formdata->rid = $this->response_insert($formdata->sec, $formdata->rid, $quser); + $formdata->rid = $this->response_insert($formdata, $quser); // Skip logic. $formdata->sec++; if ($this->has_dependencies()) { @@ -954,7 +991,7 @@ public function print_survey($userid=false, $quser) { $formdata->prev = ''; } else { $this->response_delete($formdata->rid, $formdata->sec); - $formdata->rid = $this->response_insert($formdata->sec, $formdata->rid, $quser); + $formdata->rid = $this->response_insert($formdata, $quser); $formdata->sec--; // Skip logic. if ($this->has_dependencies()) { @@ -1567,31 +1604,23 @@ private function response_import_sec($rid, $sec, &$varr) { if ($sec < 1 || !isset($this->questionsbysec[$sec])) { return; } - $vals = $this->response_select($rid, 'content'); - reset($vals); - foreach ($vals as $id => $arr) { - if (isset($arr[0]) && is_array($arr[0])) { - // Multiple. - $varr->{'q'.$id} = array_map('array_pop', $arr); - } else { - $varr->{'q'.$id} = array_pop($arr); - } - } + return $this->response_import_all($rid, $varr); } private function response_import_all($rid, &$varr) { - $vals = $this->response_select($rid, 'content'); + $vals = $this->response_select($rid); reset($vals); foreach ($vals as $id => $arr) { - if (strstr($id, '_') && isset($arr[4])) { // Single OR multiple with !other choice selected. - $varr->{'q'.$id} = $arr[4]; - } else { - if (isset($arr[0]) && is_array($arr[0])) { // Multiple. - $varr->{'q'.$id} = array_map('array_pop', $arr); - } else { // Boolean, rate and other. - $varr->{'q'.$id} = array_pop($arr); + if (isset($arr[0]) && is_array($arr[0])) { + // Multiple. + foreach ($arr as $response) { + $val = end($response); + $idx = key($response); + $varr->{'q'.$id}[$idx] = $val; } + } else { + $varr->{'q'.$id} = array_pop($arr); } } } @@ -2042,19 +2071,27 @@ private function response_send_email($rid, $email) { return $return; } - public function response_insert($section, $rid, $userid, $resume=false) { + /** + * @param object $responsedata An object containing all data for the response. + * @param int $userid + * @param bool $resume + * @return bool|int + * @throws coding_exception + * @throws dml_exception + */ + public function response_insert($responsedata, $userid, $resume=false) { global $DB; $record = new stdClass(); $record->submitted = time(); - if (empty($rid)) { + if (empty($responsedata->rid)) { // Create a uniqe id for this response. $record->questionnaireid = $this->id; $record->userid = $userid; - $rid = $DB->insert_record('questionnaire_response', $record); + $responsedata->rid = $DB->insert_record('questionnaire_response', $record); } else { - $record->id = $rid; + $record->id = $responsedata->rid; $DB->update_record('questionnaire_response', $record); } if ($resume) { @@ -2073,52 +2110,33 @@ public function response_insert($section, $rid, $userid, $resume=false) { $event->trigger(); } - if (!empty($this->questionsbysec[$section])) { - foreach ($this->questionsbysec[$section] as $question) { - // NOTE *** $val really should be a value obtained from the caller or somewhere else. - // Note that "optional_param" accepting arrays is deprecated for optional_param_array. - if ($question->responsetable == 'resp_multiple') { - $val = optional_param_array('q'.$question->id, '', PARAM_RAW); - } else { - $val = optional_param('q'.$question->id, '', PARAM_RAW); - } - $question->insert_response($rid, $val); + if (!empty($this->questionsbysec[$responsedata->sec])) { + foreach ($this->questionsbysec[$responsedata->sec] as $question) { + $val = isset($responsedata->{'q'.$question->id}) ? $responsedata->{'q'.$question->id} : ''; + $question->insert_response($responsedata); } } - return($rid); + return($responsedata->rid); } - private function response_select($rid, $col = null, $csvexport = false, $choicecodes=0, $choicetext=1) { - if ($col == null) { - $col = ''; - } - if (!is_array($col) && !empty($col)) { - $col = explode(',', preg_replace("/\s/", '', $col)); - } - if (is_array($col) && count($col) > 0) { - $callback = function($a) { - return 'q.'.$a; - }; - $col = ',' . implode(',', array_map($callback, $col)); - } - + private function response_select($rid) { // Response_bool (yes/no). - $values = \mod_questionnaire\response\boolean::response_select($rid, $col, $csvexport, $choicecodes, $choicetext); + $values = \mod_questionnaire\response\boolean::response_select($rid); // Response_single (radio button or dropdown). - $values += \mod_questionnaire\response\single::response_select($rid, $col, $csvexport, $choicecodes, $choicetext); + $values += \mod_questionnaire\response\single::response_select($rid); // Response_multiple. - $values += \mod_questionnaire\response\multiple::response_select($rid, $col, $csvexport, $choicecodes, $choicetext); + $values += \mod_questionnaire\response\multiple::response_select($rid); // Response_rank. - $values += \mod_questionnaire\response\rank::response_select($rid, $col, $csvexport, $choicecodes, $choicetext); + $values += \mod_questionnaire\response\rank::response_select($rid); // Response_text. - $values += \mod_questionnaire\response\text::response_select($rid, $col, $csvexport, $choicecodes, $choicetext); + $values += \mod_questionnaire\response\text::response_select($rid); // Response_date. - $values += \mod_questionnaire\response\date::response_select($rid, $col, $csvexport, $choicecodes, $choicetext); + $values += \mod_questionnaire\response\date::response_select($rid); return($values); } @@ -2928,7 +2946,7 @@ public function generate_csv($rid='', $userid='', $choicecodes=1, $choicetext=0, foreach ($choices as $choice) { $content = $choice->content; // If "Other" add a column for the actual "other" text entered. - if (preg_match('/^!other/', $content)) { + if (\mod_questionnaire\question\base::other_choice($content)) { $col = $choice->name.'_'.$stringother; $columns[][$qpos] = $col; $questionidcols[][$qpos] = null; @@ -2956,7 +2974,7 @@ public function generate_csv($rid='', $userid='', $choicecodes=1, $choicetext=0, array_push($types, '0'); // If "Other" add a column for the "other" checkbox. // Then add a column for the actual "other" text entered. - if (preg_match('/^!other/', $content)) { + if (\mod_questionnaire\question\base::other_choice($content)) { $content = $stringother; $col = $choice->name.'->['.$content.']'; $columns[][$qpos] = $col; @@ -3086,7 +3104,7 @@ public function generate_csv($rid='', $userid='', $choicecodes=1, $choicetext=0, $choicetxt = $responserow->rankvalue + 1; } else { $content = $choicesbyqid[$qid][$responserow->choice_id]->content; - if (preg_match('/^!other/', $content)) { + if (\mod_questionnaire\question\base::other_choice($content)) { // If this is an "other" column, put the text entered in the next position. $row[$position + 1] = $responserow->response; $choicetxt = empty($responserow->choice_id) ? '0' : '1'; @@ -3115,9 +3133,9 @@ public function generate_csv($rid='', $userid='', $choicecodes=1, $choicetext=0, } $content = $choicesbyqid[$qid][$responserow->choice_id]->content; - if (preg_match('/^!other/', $content)) { + if (\mod_questionnaire\question\base::other_choice($content)) { // If this has an "other" text, use it. - $responsetxt = preg_replace(["/^!other=/", "/^!other/"], ['', get_string('other', 'questionnaire')], $content); + $responsetxt = \mod_questionnaire\question\base::other_choice_display($content); $responsetxt1 = $responserow->response; } else if (($choicecodes == 1) && ($choicetext == 1)) { $responsetxt = $c.' : '.$content; diff --git a/tests/behat/radio_question_other.feature b/tests/behat/radio_question_other.feature index bd20b829..cc3594bb 100644 --- a/tests/behat/radio_question_other.feature +++ b/tests/behat/radio_question_other.feature @@ -57,5 +57,6 @@ Feature: Radio questions allow optional "other" responses with optional labels And I follow "Continue" Then I should see "Your response" And I should see "Test questionnaire" +And I pause And I should see "Other: Yellow" And I should see "Another colour: Indigo" \ No newline at end of file diff --git a/tests/generator/lib.php b/tests/generator/lib.php index 374f5af3..a7a81cbd 100644 --- a/tests/generator/lib.php +++ b/tests/generator/lib.php @@ -228,8 +228,11 @@ public function create_test_questionnaire($course, $qtype = null, $questiondata public function create_question_response($questionnaire, $question, $respval, $userid = 1, $section = 1) { global $DB; $currentrid = 0; - $_POST['q'.$question->id] = $respval; - $responseid = $questionnaire->response_insert($section, $currentrid, $userid); + if (!is_array($respval)) { + $respval = ['q'.$question->id => $respval]; + } + $respdata = (object)(array_merge(['sec' => $section, 'rid' => $currentrid], $respval)); + $responseid = $questionnaire->response_insert($respdata, $userid); $this->response_commit($questionnaire, $responseid); return $DB->get_record('questionnaire_response', array('id' => $responseid)); } diff --git a/tests/responsetypes_test.php b/tests/responsetypes_test.php index 07c74d8e..b3796562 100644 --- a/tests/responsetypes_test.php +++ b/tests/responsetypes_test.php @@ -165,10 +165,9 @@ public function test_create_response_single() { $val = $cid; } } - // Need an extra $_POST variable for an "other" response. - $_POST['q'.$question->id.'_'.$val] = 'Forty-four'; + $vals = ['q'.$question->id => $val, 'q'.$question->id.'_'.$val => 'Forty-four']; $userid = 2; - $response = $generator->create_question_response($questionnaire, $question, $val, $userid); + $response = $generator->create_question_response($questionnaire, $question, $vals, $userid); // Test the responses for this questionnaire. $this->response_tests($questionnaire->id, $response->id, $userid, 1, 2); @@ -211,13 +210,15 @@ public function test_create_response_multiple() { $val = array(); foreach ($question->choices as $cid => $choice) { if (($choice->content == 'Two') || ($choice->content == 'Three')) { - $val[] = $cid; + $val[$cid] = $cid; } else if ($choice->content == '!other=Another number') { - $val2 = $cid; + $val[$cid] = $cid; + $val[\mod_questionnaire\question\base::other_choice_name($cid)] = 'Forty-four'; + $ocid = $cid; } } - $_POST['q'.$question->id.'_'.$val2] = 'Forty-four'; - $response = $generator->create_question_response($questionnaire, $question, $val, $userid); + $vals = ['q'.$question->id => $val]; + $response = $generator->create_question_response($questionnaire, $question, $vals, $userid); // Test the responses for this questionnaire. $this->response_tests($questionnaire->id, $response->id, $userid); @@ -237,7 +238,7 @@ public function test_create_response_multiple() { array('response_id' => $response->id, 'question_id' => $question->id)); $this->assertEquals(1, count($otherresponses)); $otherresponse = reset($otherresponses); - $this->assertEquals($val2, $otherresponse->choice_id); + $this->assertEquals($ocid, $otherresponse->choice_id); $this->assertEquals('Forty-four', $otherresponse->response); } @@ -268,9 +269,9 @@ public function test_create_response_rank() { $i = 1; foreach ($question->choices as $cid => $choice) { $vals[$cid] = $i; - $_POST['q'.$question->id.'_'.$cid] = $i++; + $vals['q'.$question->id.'_'.$cid] = $i++; } - $response = $generator->create_question_response($questionnaire, $question, null, $userid); + $response = $generator->create_question_response($questionnaire, $question, $vals, $userid); // Test the responses for this questionnaire. $this->response_tests($questionnaire->id, $response->id, $userid); From bc95a3c84cac765e60347d99ba5b6fcdf9cd9246 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Wed, 8 May 2019 14:20:30 -0400 Subject: [PATCH 069/341] GHI157 - Starting refactor of response and choice code and data. --- classes/question/check.php | 2 +- classes/question/drop.php | 14 ++++- classes/question/radio.php | 4 +- module.js | 7 ++- tests/behat/radio_question_other.feature | 1 - tests/responsetypes_test.php | 78 ++++++++++++------------ 6 files changed, 60 insertions(+), 46 deletions(-) diff --git a/classes/question/check.php b/classes/question/check.php index fdb81879..285e7307 100644 --- a/classes/question/check.php +++ b/classes/question/check.php @@ -187,7 +187,7 @@ protected function response_survey_display($data) { ['noclean' => true])); } else { $othertext = self::other_choice_display($choice); - if (isset($data->{'q'.$this->id})) { + if (isset($data->{'q'.$this->id}[self::other_choice_name($id)])) { $oresp = $data->{'q'.$this->id}[self::other_choice_name($id)]; $chobj->selected = 1; $chobj->othercontent = (!empty($oresp) ? htmlspecialchars($oresp) : ' '); diff --git a/classes/question/drop.php b/classes/question/drop.php index e1df82a0..f1156e3a 100644 --- a/classes/question/drop.php +++ b/classes/question/drop.php @@ -86,9 +86,21 @@ protected function question_survey_display($data, $dependants, $blankquestionnai // Drop. $options = []; + $qdata = new \stdClass(); + if (isset($data->{'q'.$this->id}) && is_array($data->{'q'.$this->id})) { + foreach($data->{'q'.$this->id} as $cid => $cval) { + $qdata->{'q' . $this->id} = $cid; + if (isset($data->{'q'.$this->id}[self::other_choice_name($cid)])) { + $qdata->{'q'.$this->id.self::other_choice_name($cid)} = $data->{'q'.$this->id}[self::other_choice_name($cid)]; + } + } + } else if (isset($data->{'q'.$this->id})) { + $qdata->{'q'.$this->id} = $data->{'q'.$this->id}; + } + $choicetags = new \stdClass(); $choicetags->qelements = new \stdClass(); - $selected = isset($data->{'q'.$this->id}) ? $data->{'q'.$this->id} : false; + $selected = isset($qdata->{'q'.$this->id}) ? $qdata->{'q'.$this->id} : false; $options[] = (object)['value' => '', 'label' => get_string('choosedots')]; foreach ($this->choices as $key => $choice) { if ($pos = strpos($choice->content, '=')) { diff --git a/classes/question/radio.php b/classes/question/radio.php index e1b937b6..8fc73029 100644 --- a/classes/question/radio.php +++ b/classes/question/radio.php @@ -131,16 +131,16 @@ protected function question_survey_display($data, $dependants=[], $blankquestion $othertext = self::other_choice_display($choice); $cname = self::other_choice_name($id); $odata = isset($qdata->{'q'.$this->id.$cname}) ? $qdata->{'q'.$this->id.$cname} : ''; - $otherempty = empty($odata); $htmlid = 'auto-rb'.sprintf('%04d', ++$idcounter); $radio->name = 'q'.$this->id; $radio->id = $htmlid; $radio->value = $id; - if (isset($qdata->{$radio->name}) || !empty($odata)) { + if ((isset($qdata->{$radio->name}) && ($qdata->{$radio->name} == $id)) || !empty($odata)) { $radio->checked = true; $ischecked = true; } + $otherempty = !empty($radio->checked) && empty($odata); $radio->label = format_text($othertext, FORMAT_HTML, ['noclean' => true]); $radio->oname = 'q'.$this->id.self::other_choice_name($id); $radio->oid = $htmlid.'-other'; diff --git a/module.js b/module.js index cd1647db..c12adcd1 100644 --- a/module.js +++ b/module.js @@ -126,9 +126,14 @@ function dependdrop(qId, children) { /* exported other_check */ function other_check(name) { var other = name.split("_"); + var other = name.slice(name.indexOf("o")+1); + if (other.indexOf("]") != -1) { + other = other.slice(0, other.indexOf("]")); + } + alert(other); var f = document.getElementById("phpesp_response"); for (var i = 0; i <= f.elements.length; i++) { - if (f.elements[i].value == "other_" + other[1]) { + if (f.elements[i].value == other) { f.elements[i].checked = true; break; } diff --git a/tests/behat/radio_question_other.feature b/tests/behat/radio_question_other.feature index cc3594bb..bd20b829 100644 --- a/tests/behat/radio_question_other.feature +++ b/tests/behat/radio_question_other.feature @@ -57,6 +57,5 @@ Feature: Radio questions allow optional "other" responses with optional labels And I follow "Continue" Then I should see "Your response" And I should see "Test questionnaire" -And I pause And I should see "Other: Yellow" And I should see "Another colour: Indigo" \ No newline at end of file diff --git a/tests/responsetypes_test.php b/tests/responsetypes_test.php index b3796562..8a9691e6 100644 --- a/tests/responsetypes_test.php +++ b/tests/responsetypes_test.php @@ -48,7 +48,7 @@ public function test_create_response_boolean() { // Set up a questinnaire with one boolean response question. $course = $this->getDataGenerator()->create_course(); $generator = $this->getDataGenerator()->get_plugin_generator('mod_questionnaire'); - $questionnaire = $generator->create_test_questionnaire($course, QUESYESNO, array('content' => 'Enter yes or no')); + $questionnaire = $generator->create_test_questionnaire($course, QUESYESNO, ['content' => 'Enter yes or no']); $question = reset($questionnaire->questions); $response = $generator->create_question_response($questionnaire, $question, 'y', $userid); @@ -56,7 +56,7 @@ public function test_create_response_boolean() { $this->response_tests($questionnaire->id, $response->id, $userid); // Retrieve the specific boolean response. - $booleanresponses = $DB->get_records('questionnaire_response_bool', array('response_id' => $response->id)); + $booleanresponses = $DB->get_records('questionnaire_response_bool', ['response_id' => $response->id]); $this->assertEquals(1, count($booleanresponses)); $booleanresponse = reset($booleanresponses); $this->assertEquals($question->id, $booleanresponse->question_id); @@ -74,10 +74,7 @@ public function test_create_response_text() { // Set up a questionnaire with one text response question. $course = $this->getDataGenerator()->create_course(); $generator = $this->getDataGenerator()->get_plugin_generator('mod_questionnaire'); - $questiondata = array( - 'content' => 'Enter some text', - 'length' => 0, - 'precise' => 5); + $questiondata = ['content' => 'Enter some text', 'length' => 0, 'precise' => 5]; $questionnaire = $generator->create_test_questionnaire($course, QUESESSAY, $questiondata); $question = reset($questionnaire->questions); $response = $generator->create_question_response($questionnaire, $question, 'This is my essay.', $userid); @@ -86,7 +83,7 @@ public function test_create_response_text() { $this->response_tests($questionnaire->id, $response->id, $userid); // Retrieve the specific text response. - $textresponses = $DB->get_records('questionnaire_response_text', array('response_id' => $response->id)); + $textresponses = $DB->get_records('questionnaire_response_text', ['response_id' => $response->id]); $this->assertEquals(1, count($textresponses)); $textresponse = reset($textresponses); $this->assertEquals($question->id, $textresponse->question_id); @@ -104,7 +101,7 @@ public function test_create_response_date() { // Set up a questionnaire with one text response question. $course = $this->getDataGenerator()->create_course(); $generator = $this->getDataGenerator()->get_plugin_generator('mod_questionnaire'); - $questionnaire = $generator->create_test_questionnaire($course, QUESDATE, array('content' => 'Enter a date')); + $questionnaire = $generator->create_test_questionnaire($course, QUESDATE, ['content' => 'Enter a date']); $question = reset($questionnaire->questions); // Date format is configured per site. This won't work unless it matches the configured format. $response = $generator->create_question_response($questionnaire, $question, '27/1/2015', $userid); @@ -113,7 +110,7 @@ public function test_create_response_date() { $this->response_tests($questionnaire->id, $response->id, $userid); // Retrieve the specific date response. - $dateresponses = $DB->get_records('questionnaire_response_date', array('response_id' => $response->id)); + $dateresponses = $DB->get_records('questionnaire_response_date', ['response_id' => $response->id]); $this->assertEquals(1, count($dateresponses)); $dateresponse = reset($dateresponses); $this->assertEquals($question->id, $dateresponse->question_id); @@ -132,12 +129,13 @@ public function test_create_response_single() { // Set up a questinnaire with one question with choices including an "other" option. $course = $this->getDataGenerator()->create_course(); $generator = $this->getDataGenerator()->get_plugin_generator('mod_questionnaire'); - $choicedata = array( - (object)array('content' => 'One', 'value' => 1), - (object)array('content' => 'Two', 'value' => 2), - (object)array('content' => 'Three', 'value' => 3), - (object)array('content' => '!other=Something else', 'value' => 4)); - $questionnaire = $generator->create_test_questionnaire($course, QUESRADIO, array('content' => 'Select one'), $choicedata); + $choicedata = [ + (object)['content' => 'One', 'value' => 1], + (object)['content' => 'Two', 'value' => 2], + (object)['content' => 'Three', 'value' => 3], + (object)['content' => '!other=Something else', 'value' => 4] + ]; + $questionnaire = $generator->create_test_questionnaire($course, QUESRADIO, ['content' => 'Select one'], $choicedata); // Create a response using one of the choices. $question = reset($questionnaire->questions); @@ -153,7 +151,7 @@ public function test_create_response_single() { $this->response_tests($questionnaire->id, $response->id, $userid); // Retrieve the specific single response. - $singresponses = $DB->get_records('questionnaire_resp_single', array('response_id' => $response->id)); + $singresponses = $DB->get_records('questionnaire_resp_single', ['response_id' => $response->id]); $this->assertEquals(1, count($singresponses)); $singresponse = reset($singresponses); $this->assertEquals($question->id, $singresponse->question_id); @@ -165,7 +163,8 @@ public function test_create_response_single() { $val = $cid; } } - $vals = ['q'.$question->id => $val, 'q'.$question->id.'_'.$val => 'Forty-four']; + $vals = ['q'.$question->id => $val, + 'q'.$question->id.\mod_questionnaire\question\base::other_choice_name($val) => 'Forty-four']; $userid = 2; $response = $generator->create_question_response($questionnaire, $question, $vals, $userid); @@ -173,7 +172,7 @@ public function test_create_response_single() { $this->response_tests($questionnaire->id, $response->id, $userid, 1, 2); // Retrieve the specific single response. - $singresponses = $DB->get_records('questionnaire_resp_single', array('response_id' => $response->id)); + $singresponses = $DB->get_records('questionnaire_resp_single', ['response_id' => $response->id]); $this->assertEquals(1, count($singresponses)); $singresponse = reset($singresponses); $this->assertEquals($question->id, $singresponse->question_id); @@ -181,7 +180,7 @@ public function test_create_response_single() { // Retrieve the 'other' response data. $otherresponses = $DB->get_records('questionnaire_response_other', - array('response_id' => $response->id, 'question_id' => $question->id)); + ['response_id' => $response->id, 'question_id' => $question->id]); $this->assertEquals(1, count($otherresponses)); $otherresponse = reset($otherresponses); $this->assertEquals($val, $otherresponse->choice_id); @@ -199,15 +198,16 @@ public function test_create_response_multiple() { // Set up a questionnaire with one question with choices including an "other" option. $course = $this->getDataGenerator()->create_course(); $generator = $this->getDataGenerator()->get_plugin_generator('mod_questionnaire'); - $choicedata = array( - (object)array('content' => 'One', 'value' => 1), - (object)array('content' => 'Two', 'value' => 2), - (object)array('content' => 'Three', 'value' => 3), - (object)array('content' => '!other=Another number', 'value' => 4)); - $questionnaire = $generator->create_test_questionnaire($course, QUESCHECK, array('content' => 'Select any'), $choicedata); + $choicedata = [ + (object)['content' => 'One', 'value' => 1], + (object)['content' => 'Two', 'value' => 2], + (object)['content' => 'Three', 'value' => 3], + (object)['content' => '!other=Another number', 'value' => 4] + ]; + $questionnaire = $generator->create_test_questionnaire($course, QUESCHECK, ['content' => 'Select any'], $choicedata); $question = reset($questionnaire->questions); - $val = array(); + $val = []; foreach ($question->choices as $cid => $choice) { if (($choice->content == 'Two') || ($choice->content == 'Three')) { $val[$cid] = $cid; @@ -224,7 +224,7 @@ public function test_create_response_multiple() { $this->response_tests($questionnaire->id, $response->id, $userid); // Retrieve the specific multiples responses. - $multresponses = $DB->get_records('questionnaire_resp_multiple', array('response_id' => $response->id)); + $multresponses = $DB->get_records('questionnaire_resp_multiple', ['response_id' => $response->id]); $this->assertEquals(3, count($multresponses)); $multresponse = reset($multresponses); $this->assertEquals($question->id, $multresponse->question_id); @@ -235,7 +235,7 @@ public function test_create_response_multiple() { // Retrieve the specific other response. $otherresponses = $DB->get_records('questionnaire_response_other', - array('response_id' => $response->id, 'question_id' => $question->id)); + ['response_id' => $response->id, 'question_id' => $question->id]); $this->assertEquals(1, count($otherresponses)); $otherresponse = reset($otherresponses); $this->assertEquals($ocid, $otherresponse->choice_id); @@ -253,19 +253,17 @@ public function test_create_response_rank() { // Set up a questionnaire with one ranking question. $course = $this->getDataGenerator()->create_course(); $generator = $this->getDataGenerator()->get_plugin_generator('mod_questionnaire'); - $choicedata = array( - (object)array('content' => 'One', 'value' => 1), - (object)array('content' => 'Two', 'value' => 2), - (object)array('content' => 'Three', 'value' => 3)); - $questiondata = array( - 'content' => 'Rank these', - 'length' => 5, - 'precise' => 0); + $choicedata = [ + (object)['content' => 'One', 'value' => 1], + (object)['content' => 'Two', 'value' => 2], + (object)['content' => 'Three', 'value' => 3] + ]; + $questiondata = ['content' => 'Rank these', 'length' => 5, 'precise' => 0]; $questionnaire = $generator->create_test_questionnaire($course, QUESRATE, $questiondata, $choicedata); // Create a response for each choice. $question = reset($questionnaire->questions); - $vals = array(); + $vals = []; $i = 1; foreach ($question->choices as $cid => $choice) { $vals[$cid] = $i; @@ -277,7 +275,7 @@ public function test_create_response_rank() { $this->response_tests($questionnaire->id, $response->id, $userid); // Retrieve the specific rank response. - $multresponses = $DB->get_records('questionnaire_response_rank', array('response_id' => $response->id)); + $multresponses = $DB->get_records('questionnaire_response_rank', ['response_id' => $response->id]); $this->assertEquals(3, count($multresponses)); foreach ($multresponses as $multresponse) { $this->assertEquals($question->id, $multresponse->question_id); @@ -287,12 +285,12 @@ public function test_create_response_rank() { // General tests to call from specific tests above. - public function create_test_questionnaire($qtype, $questiondata = array(), $choicedata = null) { + public function create_test_questionnaire($qtype, $questiondata = [], $choicedata = null) { $this->resetAfterTest(); $course = $this->getDataGenerator()->create_course(); $generator = $this->getDataGenerator()->get_plugin_generator('mod_questionnaire'); - $questionnaire = $generator->create_instance(array('course' => $course->id)); + $questionnaire = $generator->create_instance(['course' => $course->id]); $cm = get_coursemodule_from_instance('questionnaire', $questionnaire->id); $questiondata['type_id'] = $qtype; From 60eef732e5dd3484e455ce28b97398b9b314112d Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Thu, 9 May 2019 14:44:33 -0400 Subject: [PATCH 070/341] Possibly useless work... --- classes/question/drop.php | 2 +- classes/question/radio.php | 4 +-- classes/response/base.php | 28 ++++++++++++++++-- classes/response/boolean.php | 42 ++++++++++++++++++-------- classes/response/choice/choice.php | 47 ++++++++++++++++++++++++++++++ classes/response/date.php | 14 ++++----- classes/response/single.php | 33 +++++++++++++++++---- classes/response/text.php | 9 +++--- 8 files changed, 144 insertions(+), 35 deletions(-) create mode 100644 classes/response/choice/choice.php diff --git a/classes/question/drop.php b/classes/question/drop.php index f1156e3a..8a96e9c7 100644 --- a/classes/question/drop.php +++ b/classes/question/drop.php @@ -88,7 +88,7 @@ protected function question_survey_display($data, $dependants, $blankquestionnai $qdata = new \stdClass(); if (isset($data->{'q'.$this->id}) && is_array($data->{'q'.$this->id})) { - foreach($data->{'q'.$this->id} as $cid => $cval) { + foreach ($data->{'q'.$this->id} as $cid => $cval) { $qdata->{'q' . $this->id} = $cid; if (isset($data->{'q'.$this->id}[self::other_choice_name($cid)])) { $qdata->{'q'.$this->id.self::other_choice_name($cid)} = $data->{'q'.$this->id}[self::other_choice_name($cid)]; diff --git a/classes/question/radio.php b/classes/question/radio.php index 8fc73029..b0cb514b 100644 --- a/classes/question/radio.php +++ b/classes/question/radio.php @@ -94,7 +94,7 @@ protected function question_survey_display($data, $dependants=[], $blankquestion $qdata = new \stdClass(); if (isset($data->{'q'.$this->id}) && is_array($data->{'q'.$this->id})) { - foreach($data->{'q'.$this->id} as $cid => $cval) { + foreach ($data->{'q'.$this->id} as $cid => $cval) { $qdata->{'q' . $this->id} = $cid; if (isset($data->{'q'.$this->id}[self::other_choice_name($cid)])) { $qdata->{'q'.$this->id.self::other_choice_name($cid)} = $data->{'q'.$this->id}[self::other_choice_name($cid)]; @@ -194,7 +194,7 @@ protected function response_survey_display($data) { $qdata = new \stdClass(); if (isset($data->{'q'.$this->id}) && is_array($data->{'q'.$this->id})) { - foreach($data->{'q'.$this->id} as $cid => $cval) { + foreach ($data->{'q'.$this->id} as $cid => $cval) { $qdata->{'q' . $this->id} = $cid; if (isset($data->{'q'.$this->id}[self::other_choice_name($cid)])) { $qdata->{'q'.$this->id.self::other_choice_name($cid)} = $data->{'q'.$this->id}[self::other_choice_name($cid)]; diff --git a/classes/response/base.php b/classes/response/base.php index 0ae5c526..e14a60ae 100644 --- a/classes/response/base.php +++ b/classes/response/base.php @@ -15,11 +15,11 @@ // along with Moodle. If not, see . /** - * This file contains the parent class for questionnaire question types. + * This file contains the parent class for questionnaire response types. * * @author Mike Churchward * @license http://www.gnu.org/copyleft/gpl.html GNU Public License - * @package questiontypes + * @package response */ namespace mod_questionnaire\response; @@ -38,8 +38,20 @@ abstract class base { - public function __construct($question) { + // Class properties: + /** @var \mod_questionnaire\question\base $question The question for this response. */ + public $question; + + /** @var int $responseid The id of the response this is for. */ + public $responseid; + + /** @var array $choices An array of \mod_questionnaire\response\choice objects. */ + public $choices; + + public function __construct(\mod_questionnaire\question\base $question, int $responseid = null, array $choices = []) { $this->question = $question; + $this->responseid = $responseid; + $this->choices = $choices; } /** @@ -201,6 +213,16 @@ public function get_feedback_scores(array $rids) { return false; } + /** + * Load the requested response into the object. Must be implemented by the subclass. + * + * @param int $rid The response id. + * @return bool + */ + public function load_response($rid) { + return false; + } + /** * Return an array of answers by question/choice for the given response. Must be implemented by the subclass. * diff --git a/classes/response/boolean.php b/classes/response/boolean.php index c6144b44..94067eb8 100644 --- a/classes/response/boolean.php +++ b/classes/response/boolean.php @@ -153,10 +153,8 @@ public function results_template() { * @return string */ public function display_results($rids=false, $sort='', $anonymous=false) { - if (empty($this->stryes)) { - $this->stryes = get_string('yes'); - $this->strno = get_string('no'); - } + $stryes = get_string('yes'); + $strno = get_string('no'); if (is_array($rids)) { $prtotal = 1; @@ -165,27 +163,47 @@ public function display_results($rids=false, $sort='', $anonymous=false) { } $numresps = count($rids); - $this->counts = [$this->stryes => 0, $this->strno => 0]; + $counts = [$stryes => 0, $strno => 0]; $numrespondents = 0; if ($rows = $this->get_results($rids, $anonymous)) { foreach ($rows as $row) { - $this->choice = $row->choice_id; + $choice = $row->choice_id; $count = $row->num; - if ($this->choice == 'y') { - $this->choice = $this->stryes; + if ($choice == 'y') { + $choice = $stryes; } else { - $this->choice = $this->strno; + $choice = $strno; } - $this->counts[$this->choice] = intval($count); - $numrespondents += $this->counts[$this->choice]; + $counts[$choice] = intval($count); + $numrespondents += $counts[$choice]; } - $pagetags = $this->get_results_tags($this->counts, $numresps, $numrespondents, $prtotal, ''); + $pagetags = $this->get_results_tags($counts, $numresps, $numrespondents, $prtotal, ''); } else { $pagetags = new \stdClass(); } return $pagetags; } + /** + * Load the requested response into the object. Must be implemented by the subclass. + * + * @param int $rid The response id. + */ + public function load_response($rid) { + global $DB; + + $sql = 'SELECT a.id, c.id as cid, o.response ' . + 'FROM {'.static::response_table().'} a ' . + 'INNER JOIN {questionnaire_quest_choice} c ON a.choice_id = c.id ' . + 'LEFT JOIN {questionnaire_response_other} o ON a.response_id = o.response_id AND c.id = o.choice_id ' . + 'WHERE a.response_id = ? '; + $record = $DB->get_record_sql($sql, [$rid]); + if ($record) { + $this->responseid = $rid; + $this->choices[$record->cid] = new choice($record->cid, $record->response); + } + } + /** * Return an array of answers by question/choice for the given response. Must be implemented by the subclass. * diff --git a/classes/response/choice/choice.php b/classes/response/choice/choice.php new file mode 100644 index 00000000..9fa05bde --- /dev/null +++ b/classes/response/choice/choice.php @@ -0,0 +1,47 @@ +. + +/** + * This defines a structured class to hold response choice answers. + * + * @author Mike Churchward + * @license http://www.gnu.org/copyleft/gpl.html GNU Public License + * @package response + * @copyright 2019, onwards Poet + */ + +namespace mod_questionnaire\response\choice; +defined('MOODLE_INTERNAL') || die(); + +class choice { + + // Class properties. + /** @var int $choiceid The id of the question choice this applies to. */ + public $choiceid; + + /** @var string $answer Any entered text portion of this response. */ + public $answer; + + /** + * Choice constructor. + * @param null $choiceid + * @param null $answer + */ + public function __construct($choiceid = null, $answer = null) { + $this->choiceid = $choiceid; + $this->answer = $answer; + } +} \ No newline at end of file diff --git a/classes/response/date.php b/classes/response/date.php index 1678d827..36aee237 100644 --- a/classes/response/date.php +++ b/classes/response/date.php @@ -110,17 +110,17 @@ public function display_results($rids=false, $sort='', $anonymous=false) { $numresps = count($rids); if ($rows = $this->get_results($rids, $anonymous)) { $numrespondents = count($rows); + $counts = []; foreach ($rows as $row) { // Count identical answers (case insensitive). - $this->text = $row->response; - if (!empty($this->text)) { - $dateparts = preg_split('/-/', $this->text); - $this->text = make_timestamp($dateparts[0], $dateparts[1], $dateparts[2]); // Unix timestamp. - $textidx = clean_text($this->text); - $this->counts[$textidx] = !empty($this->counts[$textidx]) ? ($this->counts[$textidx] + 1) : 1; + if (!empty($row->response)) { + $dateparts = preg_split('/-/', $row->response); + $text = make_timestamp($dateparts[0], $dateparts[1], $dateparts[2]); // Unix timestamp. + $textidx = clean_text($text); + $counts[$textidx] = !empty($counts[$textidx]) ? ($counts[$textidx] + 1) : 1; } } - $pagetags = $this->get_results_tags($this->counts, $numresps, $numrespondents); + $pagetags = $this->get_results_tags($counts, $numresps, $numrespondents); } else { $pagetags = new \stdClass(); } diff --git a/classes/response/single.php b/classes/response/single.php index 2dd543cd..d92184b4 100644 --- a/classes/response/single.php +++ b/classes/response/single.php @@ -23,6 +23,8 @@ */ namespace mod_questionnaire\response; +use mod_questionnaire\response\choice\choice; + defined('MOODLE_INTERNAL') || die(); /** @@ -182,6 +184,7 @@ public function display_results($rids=false, $sort='', $anonymous=false) { $numrespondents = $DB->count_records_sql($responsecountsql, [$this->question->id]); if ($rows) { + $counts = []; foreach ($rows as $idx => $row) { if (strpos($idx, 'other') === 0) { $answer = $row->response; @@ -189,21 +192,41 @@ public function display_results($rids=false, $sort='', $anonymous=false) { $content = \mod_questionnaire\question\base::other_choice_display($ccontent); $content .= ' ' . clean_text($answer); $textidx = $content; - $this->counts[$textidx] = !empty($this->counts[$textidx]) ? ($this->counts[$textidx] + 1) : 1; + $counts[$textidx] = !empty($counts[$textidx]) ? ($counts[$textidx] + 1) : 1; } else { $contents = questionnaire_choice_values($row->content); - $this->choice = $contents->text.$contents->image; - $textidx = $this->choice; - $this->counts[$textidx] = !empty($this->counts[$textidx]) ? ($this->counts[$textidx] + 1) : 1; + $textidx = $contents->text.$contents->image; + $counts[$textidx] = !empty($counts[$textidx]) ? ($counts[$textidx] + 1) : 1; } } - $pagetags = $this->get_results_tags($this->counts, $numresps, $numrespondents, $prtotal, $sort); + $pagetags = $this->get_results_tags($counts, $numresps, $numrespondents, $prtotal, $sort); } else { $pagetags = new \stdClass(); } return $pagetags; } + /** + * Load the requested response into the object. Must be implemented by the subclass. + * + * @param int $rid The response id. + * @return array + */ + public function load_response($rid) { + global $DB; + + $sql = 'SELECT a.id, c.id as cid, o.response ' . + 'FROM {'.static::response_table().'} a ' . + 'INNER JOIN {questionnaire_quest_choice} c ON a.choice_id = c.id ' . + 'LEFT JOIN {questionnaire_response_other} o ON a.response_id = o.response_id AND c.id = o.choice_id ' . + 'WHERE a.response_id = ? '; + $record = $DB->get_record_sql($sql, [$rid]); + if ($record) { + $this->responseid = $rid; + $this->choices[$record->cid] = new choice($record->cid, $record->response); + } + } + /** * Return an array of answers by question/choice for the given response. Must be implemented by the subclass. * Array is indexed by question, and contains an array by choice code of selected choices. diff --git a/classes/response/text.php b/classes/response/text.php index c13094df..6a4ac08e 100644 --- a/classes/response/text.php +++ b/classes/response/text.php @@ -134,15 +134,14 @@ public function display_results($rids=false, $sort='', $anonymous=false) { $isnumeric = $this->question->type_id == QUESNUMERIC; // Count identical answers (numeric questions only). if ($isnumeric) { + $counts = []; foreach ($rows as $row) { if (!empty($row->response) || $row->response === "0") { - $this->text = $row->response; - $textidx = clean_text($this->text); - $this->counts[$textidx] = !empty($this->counts[$textidx]) ? ($this->counts[$textidx] + 1) : 1; - $this->userid[$textidx] = !empty($this->counts[$textidx]) ? ($this->counts[$textidx] + 1) : 1; + $textidx = clean_text($row->response); + $counts[$textidx] = !empty($counts[$textidx]) ? ($counts[$textidx] + 1) : 1; } } - $pagetags = $this->get_results_tags($this->counts, $numrespondents, $numresponses, $prtotal); + $pagetags = $this->get_results_tags($counts, $numrespondents, $numresponses, $prtotal); } else { $pagetags = $this->get_results_tags($rows, $numrespondents, $numresponses, $prtotal); } From 3ac9d35d8d77a92a3961e276e991b2225ef3091e Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Thu, 9 May 2019 17:30:47 -0400 Subject: [PATCH 071/341] GHI157 - Adding unit tests for privacy API, needed for refactoring. --- questionnaire.class.php | 2 +- tests/privacy_provider_test.php | 218 ++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 tests/privacy_provider_test.php diff --git a/questionnaire.class.php b/questionnaire.class.php index 0493a545..6ece61a6 100644 --- a/questionnaire.class.php +++ b/questionnaire.class.php @@ -1887,7 +1887,7 @@ private function get_full_submission_for_export($answers) { $choices = []; $cids = []; foreach ($question->choices as $cid => $choice) { - if (!empty($choice->value)) { + if (!empty($choice->value) && (strpos($choice->content, '=') !== false)) { $choices[$choice->value] = substr($choice->content, (strpos($choice->content, '=') + 1)); } else { $cids[$rqid . '_' . $cid] = $choice->content; diff --git a/tests/privacy_provider_test.php b/tests/privacy_provider_test.php new file mode 100644 index 00000000..3d19b3e2 --- /dev/null +++ b/tests/privacy_provider_test.php @@ -0,0 +1,218 @@ +. + +/** + * Privacy test for the mod questionnaire. + * + * @package mod_questionnaire + * @copyright 2019, onwards Poet + * @author Mike Churchward + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +use \mod_questionnaire\privacy\provider; + +/** + * Privacy test for the mod questionnaire. + * + * @package mod_questionnaire + * @copyright 2019, onwards Poet + * @author Mike Churchward + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @group mod_questionnaire + */ +class mod_questionnaire_privacy_testcase extends \core_privacy\tests\provider_testcase { + /** + * Tests set up. + */ + public function setUp() { + $this->resetAfterTest(); + $this->setAdminUser(); + } + + /** + * Check that the expected context is returned if there is any user data for this module. + */ + public function test_get_contexts_for_userid() { + global $DB; + + $this->resetAfterTest(); + $dg = $this->getDataGenerator(); + $qdg = $dg->get_plugin_generator('mod_questionnaire'); + $qdg->create_and_fully_populate(1, 1, 1, 1); + $user = $DB->get_record('user', ['firstname' => 'Testy']); + $questionnaires = $qdg->questionnaires(); + $questionnaire = current($questionnaires); + list ($course, $cm) = get_course_and_cm_from_instance($questionnaire->id, 'questionnaire', $questionnaire->course); + + $contextlist = provider::get_contexts_for_userid($user->id); + // Check that we only get back one context. + $this->assertCount(1, $contextlist); + + // Check that a context is returned and is the expected context. + $cmcontext = \context_module::instance($cm->id); + $this->assertEquals($cmcontext->id, $contextlist->get_contextids()[0]); + } + + /** + * Test that only users with a questionnaire context are fetched. + */ + public function test_get_users_in_context() { + global $DB; + + $this->resetAfterTest(); + $dg = $this->getDataGenerator(); + $qdg = $dg->get_plugin_generator('mod_questionnaire'); + $qdg->create_and_fully_populate(1, 1, 1, 1); + $user = $DB->get_record('user', ['firstname' => 'Testy']); + $questionnaires = $qdg->questionnaires(); + $questionnaire = current($questionnaires); + list ($course, $cm) = get_course_and_cm_from_instance($questionnaire->id, 'questionnaire', $questionnaire->course); + $cmcontext = context_module::instance($cm->id); + + $userlist = new \core_privacy\local\request\userlist($cmcontext, 'mod_questionnaire'); + + // The list of users for this context should return the user. + provider::get_users_in_context($userlist); + $this->assertCount(1, $userlist); + $expected = [$user->id]; + $actual = $userlist->get_userids(); + $this->assertEquals($expected, $actual); + + // The list of users for other contexts should not return any users. + $userlist = new \core_privacy\local\request\userlist(context_system::instance(), 'mod_questionnaire'); + provider::get_users_in_context($userlist); + $this->assertCount(0, $userlist); + } + + /** + * Test that user data is exported correctly. + */ + public function test_export_user_data() { + global $DB; + + $this->resetAfterTest(); + $dg = $this->getDataGenerator(); + $qdg = $dg->get_plugin_generator('mod_questionnaire'); + $qdg->create_and_fully_populate(1, 1, 1, 1); + $user = $DB->get_record('user', ['firstname' => 'Testy']); + $questionnaires = $qdg->questionnaires(); + $questionnaire = current($questionnaires); + list ($course, $cm) = get_course_and_cm_from_instance($questionnaire->id, 'questionnaire', $questionnaire->course); + $cmcontext = context_module::instance($cm->id); + + $writer = \core_privacy\local\request\writer::with_context($cmcontext); + $this->assertFalse($writer->has_any_data()); + + $approvedlist = new core_privacy\local\request\approved_contextlist($user, 'mod_questionnaire', [$cmcontext->id]); + provider::export_user_data($approvedlist); + $data = $writer->get_data([]); + + $this->assertContains($questionnaire->name, strip_tags($data->name)); + $this->assertEquals($questionnaire->intro, strip_tags($data->intro)); + $this->assertNotEmpty($data->responses[0]['questions']); + $this->assertEquals('1. Text Box 1000', $data->responses[0]['questions'][1]->questionname); + $this->assertEquals('Test answer', $data->responses[0]['questions'][1]->answers[0]); + $this->assertEquals('7. Numeric 1004', $data->responses[0]['questions'][7]->questionname); + $this->assertEquals(83, $data->responses[0]['questions'][7]->answers[0]); + $this->assertEquals('22. Rate Scale 1014', $data->responses[0]['questions'][22]->questionname); + $this->assertEquals('eleven = 1', $data->responses[0]['questions'][22]->answers[0]); + $this->assertEquals('eighteen = 3', $data->responses[0]['questions'][22]->answers[7]); + } + + /** + * Test deleting all user data for a specific context. + */ + public function test_delete_data_for_all_users_in_context() { + global $DB; + + $this->resetAfterTest(); + $dg = $this->getDataGenerator(); + $qdg = $dg->get_plugin_generator('mod_questionnaire'); + $qdg->create_and_fully_populate(1, 2, 1, 1); + $user = $DB->get_record('user', ['username' => 'username1']); + $questionnaires = $qdg->questionnaires(); + $questionnaire = current($questionnaires); + list ($course, $cm) = get_course_and_cm_from_instance($questionnaire->id, 'questionnaire', $questionnaire->course); + $cmcontext = context_module::instance($cm->id); + + // Get all accounts. There should be two. + $this->assertCount(2, $DB->get_records('questionnaire_response', [])); + + // Delete everything for the context. + provider::delete_data_for_all_users_in_context($cmcontext); + $this->assertCount(0, $DB->get_records('questionnaire_response', [])); + } + + /** + * This should work identical to the above test. + */ + public function test_delete_data_for_user() { + global $DB; + + $this->resetAfterTest(); + $dg = $this->getDataGenerator(); + $qdg = $dg->get_plugin_generator('mod_questionnaire'); + $qdg->create_and_fully_populate(1, 2, 1, 1); + $user = $DB->get_record('user', ['username' => 'username1']); + $questionnaires = $qdg->questionnaires(); + $questionnaire = current($questionnaires); + list ($course, $cm) = get_course_and_cm_from_instance($questionnaire->id, 'questionnaire', $questionnaire->course); + $cmcontext = context_module::instance($cm->id); + + // Get all accounts. There should be two. + $this->assertCount(2, $DB->get_records('questionnaire_response', [])); + + // Delete everything for the first user. + $approvedlist = new \core_privacy\local\request\approved_contextlist($user, 'questionnaire_response', [$cmcontext->id]); + provider::delete_data_for_user($approvedlist); + + $this->assertCount(0, $DB->get_records('questionnaire_response', ['userid' => $user->id])); + + // Get all accounts. There should be one. + $this->assertCount(1, $DB->get_records('questionnaire_response', [])); + } + + /** + * Test that data for users in approved userlist is deleted. + */ + public function test_delete_data_for_users() { + global $DB; + + $this->resetAfterTest(); + $dg = $this->getDataGenerator(); + $qdg = $dg->get_plugin_generator('mod_questionnaire'); + $qdg->create_and_fully_populate(1, 3, 1, 1); + $user = $DB->get_record('user', ['username' => 'username1']); + $user3 = $DB->get_record('user', ['username' => 'username3']); + $questionnaires = $qdg->questionnaires(); + $questionnaire = current($questionnaires); + list ($course, $cm) = get_course_and_cm_from_instance($questionnaire->id, 'questionnaire', $questionnaire->course); + $cmcontext = context_module::instance($cm->id); + + $approveduserlist = new \core_privacy\local\request\approved_userlist($cmcontext, 'questionnaire', [$user->id, $user3->id]); + + // Get all accounts. There should be three. + $this->assertCount(3, $DB->get_records('questionnaire_response', [])); + + provider::delete_data_for_users($approveduserlist); + + // Get all accounts. There should be one now. + $this->assertCount(1, $DB->get_records('questionnaire_response', [])); + } +} \ No newline at end of file From ef4ae42ec0f48c6b2c6972d74abdb3f15c8cb378 Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Mon, 13 May 2019 16:55:57 -0400 Subject: [PATCH 072/341] GHI157 - More sane choice code. --- classes/question/base.php | 50 +-------- classes/question/check.php | 20 ++-- classes/question/choice/choice.php | 175 +++++++++++++++++++++++++++++ classes/question/drop.php | 18 ++- classes/question/radio.php | 31 ++--- classes/response/choice/choice.php | 47 -------- classes/response/multiple.php | 9 +- classes/response/single.php | 10 +- questionnaire.class.php | 10 +- tests/responsetypes_test.php | 4 +- 10 files changed, 236 insertions(+), 138 deletions(-) create mode 100644 classes/question/choice/choice.php delete mode 100644 classes/response/choice/choice.php diff --git a/classes/question/base.php b/classes/question/base.php index f47e63b7..e6992bfb 100644 --- a/classes/question/base.php +++ b/classes/question/base.php @@ -204,52 +204,6 @@ static public function qtypename($qtype) { } } - /** - * Return true if the choice object or choice content string is an "other" choice. - * - * @param string | object $choice - * @return bool - */ - static public function other_choice($choice) { - if (is_object($choice)) { - $content = $choice->content; - } else { - $content = $choice; - } - return (strpos($content, '!other') === 0); - } - - /** - * Return the string to display for an "other" option. If the option is not an "other", return false. - * - * @param string | object $choice - * @return string | bool - */ - static public function other_choice_display($choice) { - if (!self::other_choice($choice)) { - return false; - } - - if (is_object($choice)) { - $content = $choice->content; - } else { - $content = $choice; - } - - // If there is a defined string display after the "=", return it. Otherwise the "other" language string. - return preg_replace(["/^!other=/", "/^!other/"], ['', get_string('other', 'questionnaire')], $content); - } - - /** - * Return the string to use as an input name for an other choice. - * - * @param int $choiceid - * @return string - */ - static public function other_choice_name($choiceid) { - return 'o' . $choiceid; - } - /** * Override and return true if the question has choices. */ @@ -265,9 +219,7 @@ private function get_choices() { if ($choices = $DB->get_records('questionnaire_quest_choice', ['question_id' => $this->id], 'id ASC')) { foreach ($choices as $choice) { - $this->choices[$choice->id] = new \stdClass(); - $this->choices[$choice->id]->content = $choice->content; - $this->choices[$choice->id]->value = $choice->value; + $this->choices[$choice->id] = choice\choice::create_from_data($choice); } } else { $this->choices = []; diff --git a/classes/question/check.php b/classes/question/check.php index 285e7307..0235cd5a 100644 --- a/classes/question/check.php +++ b/classes/question/check.php @@ -143,12 +143,12 @@ protected function question_survey_display($data, $dependants, $blankquestionnai if ($checked) { $checkbox->checked = $checked; } - if (self::other_choice($choice)) { - $checkbox->oname = 'q'.$this->id.'['.self::other_choice_name($id).']'; - $checkbox->ovalue = (isset($data->{'q'.$this->id}[self::other_choice_name($id)]) && - !empty($data->{'q'.$this->id}[self::other_choice_name($id)]) ? - stripslashes($data->{'q'.$this->id}[self::other_choice_name($id)]) : ''); - $checkbox->label = format_text(self::other_choice_display($choice).'', FORMAT_HTML, ['noclean' => true]); + if ($choice->is_other_choice()) { + $checkbox->oname = 'q'.$this->id.'['.$choice->other_choice_name().']'; + $checkbox->ovalue = (isset($data->{'q'.$this->id}[$choice->other_choice_name()]) && + !empty($data->{'q'.$this->id}[$choice->other_choice_name()]) ? + stripslashes($data->{'q'.$this->id}[$choice->other_choice_name()]) : ''); + $checkbox->label = format_text($choice->other_choice_display().'', FORMAT_HTML, ['noclean' => true]); } $choicetags->qelements[] = (object)['choice' => $checkbox]; } @@ -176,7 +176,7 @@ protected function response_survey_display($data) { foreach ($this->choices as $id => $choice) { $chobj = new \stdClass(); - if (!self::other_choice($choice)) { + if (!$choice->is_other_choice()) { $contents = questionnaire_choice_values($choice->content); $choice->content = $contents->text.$contents->image; if (isset($data->{'q'.$this->id}[$id])) { @@ -186,9 +186,9 @@ protected function response_survey_display($data) { $chobj->content = (($choice->content === '') ? $id : format_text($choice->content, FORMAT_HTML, ['noclean' => true])); } else { - $othertext = self::other_choice_display($choice); - if (isset($data->{'q'.$this->id}[self::other_choice_name($id)])) { - $oresp = $data->{'q'.$this->id}[self::other_choice_name($id)]; + $othertext = $choice->other_choice_display(); + if (isset($data->{'q'.$this->id}[$choice->other_choice_name()])) { + $oresp = $data->{'q'.$this->id}[$choice->other_choice_name()]; $chobj->selected = 1; $chobj->othercontent = (!empty($oresp) ? htmlspecialchars($oresp) : ' '); } diff --git a/classes/question/choice/choice.php b/classes/question/choice/choice.php new file mode 100644 index 00000000..7902d558 --- /dev/null +++ b/classes/question/choice/choice.php @@ -0,0 +1,175 @@ +. + +/** + * This defines a structured class to hold question choices. + * + * @author Mike Churchward + * @license http://www.gnu.org/copyleft/gpl.html GNU Public License + * @package response + * @copyright 2019, onwards Poet + */ + +namespace mod_questionnaire\question\choice; +defined('MOODLE_INTERNAL') || die(); + +class choice { + + // Class properties. + + /** The table name. */ + const TABLE = 'questionnaire_quest_choice'; + + /** @var int $id The id of the question choice this applies to. */ + public $id; + + /** @var int $questionid The id of the question this choice applies to. */ + public $questionid; + + /** @var string $content The display content for this choice. */ + public $content; + + /** @var string $value Optional value assigned to this choice. */ + public $value; + + /** + * Choice constructor. + * @param null $id + * @param null $questionid + * @param null $content + * @param null $value + */ + public function __construct($id = null, $questionid = null, $content = null, $value = null) { + $this->id = $id; + $this->questionid = $questionid; + $this->content = $content; + $this->value = $value; + } + + /** + * Create and return a choice object from a data id. If not found, an empty object is returned. + * + * @param int $id The data id to load. + * @return choice + */ + public static function create_from_id($id) { + global $DB; + + // Rename the data field question_id to questionid to conform with code conventions. Eventually, data table should be + // changed. + if ($record = $DB->get_record(self::tablename(), ['id' => $id], 'id,question_id as questionid,content,value')) { + return new choice($id, $record->questionid, $record->content, $record->value); + } else { + return new choice(); + } + } + + /** + * Create and return a choice object from data. + * + * @param object | array $choicedata The data to load. + * @return choice + */ + public static function create_from_data($choicedata) { + if (!is_array($choicedata)) { + $choicedata = (array)$choicedata; + } + + $properties = array_keys(get_class_vars(__CLASS__)); + foreach ($properties as $property) { + if (!isset($choicedata[$property])) { + $choicedata[$property] = null; + } + } + // Since the data table uses 'question_id' instead of 'questionid', look for that field as well. Hack that should be fixed + // by renaming the data table column. + if (!empty($choicedata['question_id'])) { + $choicedata['questionid'] = $choicedata['question_id']; + } + + return new choice($choicedata['id'], $choicedata['questionid'], $choicedata['content'], $choicedata['value']); + } + + /** + * Return the table name for choice. + */ + public static function tablename() { + return self::TABLE; + } + + /** + * Return true if the content string is an "other" choice. + * + * @param string $content + * @return bool + */ + static public function content_is_other_choice($content) { + return (strpos($content, '!other') === 0); + } + + /** + * Return true if the choice object is an "other" choice. + * + * @return bool + */ + public function is_other_choice() { + return (self::content_is_other_choice($this->content)); + } + + /** + * Return the string to display for an "other" option content string. If the option is not an "other", return false. + * + * @param string | object $choice + * @return string | bool + */ + static public function content_other_choice_display($content) { + if (!self::content_is_other_choice($content)) { + return false; + } + + // If there is a defined string display after the "=", return it. Otherwise the "other" language string. + return preg_replace(["/^!other=/", "/^!other/"], ['', get_string('other', 'questionnaire')], $content); + } + + /** + * Return the string to display for an "other" option for this object. If the option is not an "other", return false. + * + * @return string | bool + */ + public function other_choice_display() { + return self::content_other_choice_display($this->content); + } + + /** + * Return the string to use as an input name for an other choice. + * + * @param int $choiceid + * @return string + */ + static public function id_other_choice_name($choiceid) { + return 'o' . $choiceid; + } + + /** + * Return the string to use as an input name for an other choice. + * + * @param int $choiceid + * @return string + */ + public function other_choice_name() { + return self::id_other_choice_name($this->id); + } +} \ No newline at end of file diff --git a/classes/question/drop.php b/classes/question/drop.php index 8a96e9c7..86cd86df 100644 --- a/classes/question/drop.php +++ b/classes/question/drop.php @@ -25,6 +25,7 @@ namespace mod_questionnaire\question; defined('MOODLE_INTERNAL') || die(); use \html_writer; +use mod_questionnaire\question\choice\choice; class drop extends base { @@ -90,8 +91,9 @@ protected function question_survey_display($data, $dependants, $blankquestionnai if (isset($data->{'q'.$this->id}) && is_array($data->{'q'.$this->id})) { foreach ($data->{'q'.$this->id} as $cid => $cval) { $qdata->{'q' . $this->id} = $cid; - if (isset($data->{'q'.$this->id}[self::other_choice_name($cid)])) { - $qdata->{'q'.$this->id.self::other_choice_name($cid)} = $data->{'q'.$this->id}[self::other_choice_name($cid)]; + if (isset($data->{'q'.$this->id}[choice::id_other_choice_name($cid)])) { + $qdata->{'q'.$this->id.choice::id_other_choice_name($cid)} = + $data->{'q'.$this->id}[choice::id_other_choice_name($cid)]; } } } else if (isset($data->{'q'.$this->id})) { @@ -139,12 +141,22 @@ protected function response_survey_display($data) { $resptags->class = 'select custom-select ' . $resptags->id; $resptags->options = []; $resptags->options[] = (object)['value' => '', 'label' => get_string('choosedots')]; + + $qdata = new \stdClass(); + if (isset($data->{'q'.$this->id}) && is_array($data->{'q'.$this->id})) { + foreach ($data->{'q'.$this->id} as $cid => $cval) { + $qdata->{'q' . $this->id} = $cid; + } + } else if (isset($data->{'q'.$this->id})) { + $qdata->{'q'.$this->id} = $data->{'q'.$this->id}; + } + foreach ($this->choices as $id => $choice) { $contents = questionnaire_choice_values($choice->content); $chobj = new \stdClass(); $chobj->value = $id; $chobj->label = format_text($contents->text, FORMAT_HTML, ['noclean' => true]); - if (isset($data->{'q'.$this->id}) && ($id == $data->{'q'.$this->id})) { + if (isset($qdata->{'q'.$this->id}) && ($id == $qdata->{'q'.$this->id})) { $chobj->selected = 1; $resptags->selectedlabel = $chobj->label; } diff --git a/classes/question/radio.php b/classes/question/radio.php index b0cb514b..24dfac5b 100644 --- a/classes/question/radio.php +++ b/classes/question/radio.php @@ -23,6 +23,8 @@ */ namespace mod_questionnaire\question; +use mod_questionnaire\question\choice\choice; + defined('MOODLE_INTERNAL') || die(); class radio extends base { @@ -96,8 +98,9 @@ protected function question_survey_display($data, $dependants=[], $blankquestion if (isset($data->{'q'.$this->id}) && is_array($data->{'q'.$this->id})) { foreach ($data->{'q'.$this->id} as $cid => $cval) { $qdata->{'q' . $this->id} = $cid; - if (isset($data->{'q'.$this->id}[self::other_choice_name($cid)])) { - $qdata->{'q'.$this->id.self::other_choice_name($cid)} = $data->{'q'.$this->id}[self::other_choice_name($cid)]; + if (isset($data->{'q'.$this->id}[choice::id_other_choice_name($cid)])) { + $qdata->{'q'.$this->id.choice::id_other_choice_name($cid)} = + $data->{'q'.$this->id}[choice::id_other_choice_name($cid)]; } } } else if (isset($data->{'q'.$this->id})) { @@ -110,7 +113,7 @@ protected function question_survey_display($data, $dependants=[], $blankquestion $radio->horizontal = $horizontal; } - if (!self::other_choice($choice)) { // This is a normal radio button. + if (!$choice->is_other_choice()) { // This is a normal radio button. $htmlid = 'auto-rb'.sprintf('%04d', ++$idcounter); $radio->name = 'q'.$this->id; @@ -128,8 +131,8 @@ protected function question_survey_display($data, $dependants=[], $blankquestion $contents = questionnaire_choice_values($choice->content); $radio->label = $value.format_text($contents->text, FORMAT_HTML, ['noclean' => true]).$contents->image; } else { // Radio button with associated !other text field. - $othertext = self::other_choice_display($choice); - $cname = self::other_choice_name($id); + $othertext = $choice->other_choice_display(); + $cname = choice::id_other_choice_name($id); $odata = isset($qdata->{'q'.$this->id.$cname}) ? $qdata->{'q'.$this->id.$cname} : ''; $htmlid = 'auto-rb'.sprintf('%04d', ++$idcounter); @@ -142,7 +145,7 @@ protected function question_survey_display($data, $dependants=[], $blankquestion } $otherempty = !empty($radio->checked) && empty($odata); $radio->label = format_text($othertext, FORMAT_HTML, ['noclean' => true]); - $radio->oname = 'q'.$this->id.self::other_choice_name($id); + $radio->oname = 'q'.$this->id.choice::id_other_choice_name($id); $radio->oid = $htmlid.'-other'; if (isset($odata)) { $radio->ovalue = stripslashes($odata); @@ -196,8 +199,9 @@ protected function response_survey_display($data) { if (isset($data->{'q'.$this->id}) && is_array($data->{'q'.$this->id})) { foreach ($data->{'q'.$this->id} as $cid => $cval) { $qdata->{'q' . $this->id} = $cid; - if (isset($data->{'q'.$this->id}[self::other_choice_name($cid)])) { - $qdata->{'q'.$this->id.self::other_choice_name($cid)} = $data->{'q'.$this->id}[self::other_choice_name($cid)]; + if (isset($data->{'q'.$this->id}[choice::id_other_choice_name($cid)])) { + $qdata->{'q'.$this->id.choice::id_other_choice_name($cid)} = + $data->{'q'.$this->id}[choice::id_other_choice_name($cid)]; } } } else if (isset($data->{'q'.$this->id})) { @@ -212,7 +216,7 @@ protected function response_survey_display($data) { $chobj->horizontal = 1; } $chobj->name = $id.$uniquetag++; - if (!self::other_choice($choice)) { + if (!$choice->is_other_choice()) { $contents = questionnaire_choice_values($choice->content); $choice->content = $contents->text.$contents->image; if ($id == $checked) { @@ -220,8 +224,8 @@ protected function response_survey_display($data) { } $chobj->content = ($choice->content === '' ? $id : format_text($choice->content, FORMAT_HTML, ['noclean' => true])); } else { - $othertext = self::other_choice_display($choice); - $cid = 'q'.$this->id.self::other_choice_name($id); + $othertext = $choice->other_choice_display(); + $cid = 'q'.$this->id.choice::id_other_choice_name($id); if (isset($qdata->{$cid})) { $chobj->selected = 1; $chobj->othercontent = (!empty($qdata->{$cid}) ? htmlspecialchars($qdata->{$cid}) : ' '); @@ -256,9 +260,10 @@ public function response_complete($responsedata) { * @return boolean */ public function response_valid($responsedata) { - if (isset($responsedata->{'q'.$this->id}) && self::other_choice($this->choices[$responsedata->{'q'.$this->id}])) { + if (isset($responsedata->{'q'.$this->id}) && isset($this->choices[$responsedata->{'q'.$this->id}]) && + $this->choices[$responsedata->{'q'.$this->id}]->is_other_choice()) { // False if "other" choice is checked but text box is empty. - return !empty($responsedata->{'q'.$this->id.self::other_choice_name($responsedata->{'q'.$this->id})}); + return !empty($responsedata->{'q'.$this->id.choice::id_other_choice_name($responsedata->{'q'.$this->id})}); } else { return parent::response_valid($responsedata); } diff --git a/classes/response/choice/choice.php b/classes/response/choice/choice.php deleted file mode 100644 index 9fa05bde..00000000 --- a/classes/response/choice/choice.php +++ /dev/null @@ -1,47 +0,0 @@ -. - -/** - * This defines a structured class to hold response choice answers. - * - * @author Mike Churchward - * @license http://www.gnu.org/copyleft/gpl.html GNU Public License - * @package response - * @copyright 2019, onwards Poet - */ - -namespace mod_questionnaire\response\choice; -defined('MOODLE_INTERNAL') || die(); - -class choice { - - // Class properties. - /** @var int $choiceid The id of the question choice this applies to. */ - public $choiceid; - - /** @var string $answer Any entered text portion of this response. */ - public $answer; - - /** - * Choice constructor. - * @param null $choiceid - * @param null $answer - */ - public function __construct($choiceid = null, $answer = null) { - $this->choiceid = $choiceid; - $this->answer = $answer; - } -} \ No newline at end of file diff --git a/classes/response/multiple.php b/classes/response/multiple.php index b56eceea..bac004b0 100644 --- a/classes/response/multiple.php +++ b/classes/response/multiple.php @@ -57,8 +57,8 @@ public function insert_response($responsedata) { $cid = clean_param($cid, PARAM_CLEAN); if (isset($this->question->choices[$cid])) { // If this choice is an "other" choice, look for the added input. - if (\mod_questionnaire\question\base::other_choice($this->question->choices[$cid])) { - $cname = \mod_questionnaire\question\base::other_choice_name($cid); + if ($this->question->choices[$cid]->is_other_choice()) { + $cname = \mod_questionnaire\question\choice\choice::id_other_choice_name($cid); $other = isset($val[$cname]) ? $val[$cname] : ''; // If no input specified, ignore this choice. @@ -120,8 +120,9 @@ static public function response_select($rid) { $newrow['responses'] = []; } $newrow['responses'][$row->cid] = $row->cid; - if (\mod_questionnaire\question\base::other_choice($row->ccontent)) { - $newrow['responses'][\mod_questionnaire\question\base::other_choice_name($row->cid)] = $row->response; + if (\mod_questionnaire\question\choice\choice::content_is_other_choice($row->ccontent)) { + $newrow['responses'][\mod_questionnaire\question\choice\choice::id_other_choice_name($row->cid)] = + $row->response; } } $values[$qid] = $newrow; diff --git a/classes/response/single.php b/classes/response/single.php index d92184b4..290743d2 100644 --- a/classes/response/single.php +++ b/classes/response/single.php @@ -60,8 +60,8 @@ public function insert_response($responsedata) { $cid = clean_param($cid, PARAM_CLEAN); if (isset($this->question->choices[$cid])) { // If this choice is an "other" choice, look for the added input. - if (\mod_questionnaire\question\base::other_choice($this->question->choices[$cid])) { - $cname = 'q' . $this->question->id . \mod_questionnaire\question\base::other_choice_name($cid); + if ($this->question->choices[$cid]->is_other_choice()) { + $cname = 'q' . $this->question->id . \mod_questionnaire\question\choice\choice::id_other_choice_name($cid); $other = isset($responsedata->{$cname}) ? $responsedata->{$cname} : ''; // If no input specified, ignore this choice. @@ -189,7 +189,7 @@ public function display_results($rids=false, $sort='', $anonymous=false) { if (strpos($idx, 'other') === 0) { $answer = $row->response; $ccontent = $row->content; - $content = \mod_questionnaire\question\base::other_choice_display($ccontent); + $content = \mod_questionnaire\question\choice\choice::content_other_choice_display($ccontent); $content .= ' ' . clean_text($answer); $textidx = $content; $counts[$textidx] = !empty($counts[$textidx]) ? ($counts[$textidx] + 1) : 1; @@ -250,8 +250,8 @@ static public function response_select($rid) { $newrow['ccontent'] = $row->ccontent; $newrow['responses'] = []; $newrow['responses'][$row->cid] = $row->cid; - if (\mod_questionnaire\question\base::other_choice($row->ccontent)) { - $newrow['responses'][\mod_questionnaire\question\base::other_choice_name($row->cid)] = $row->response; + if (\mod_questionnaire\question\choice\choice::content_is_other_choice($row->ccontent)) { + $newrow['responses'][\mod_questionnaire\question\choice\choice::id_other_choice_name($row->cid)] = $row->response; } $values[$row->qid] = $newrow; } diff --git a/questionnaire.class.php b/questionnaire.class.php index 8a11ee0b..9f1bf10e 100644 --- a/questionnaire.class.php +++ b/questionnaire.class.php @@ -2953,7 +2953,7 @@ public function generate_csv($rid='', $userid='', $choicecodes=1, $choicetext=0, foreach ($choices as $choice) { $content = $choice->content; // If "Other" add a column for the actual "other" text entered. - if (\mod_questionnaire\question\base::other_choice($content)) { + if (\mod_questionnaire\question\choice\choice::content_is_other_choice($content)) { $col = $choice->name.'_'.$stringother; $columns[][$qpos] = $col; $questionidcols[][$qpos] = null; @@ -2981,7 +2981,7 @@ public function generate_csv($rid='', $userid='', $choicecodes=1, $choicetext=0, array_push($types, '0'); // If "Other" add a column for the "other" checkbox. // Then add a column for the actual "other" text entered. - if (\mod_questionnaire\question\base::other_choice($content)) { + if (\mod_questionnaire\question\choice\choice::content_is_other_choice($content)) { $content = $stringother; $col = $choice->name.'->['.$content.']'; $columns[][$qpos] = $col; @@ -3111,7 +3111,7 @@ public function generate_csv($rid='', $userid='', $choicecodes=1, $choicetext=0, $choicetxt = $responserow->rankvalue + 1; } else { $content = $choicesbyqid[$qid][$responserow->choice_id]->content; - if (\mod_questionnaire\question\base::other_choice($content)) { + if (\mod_questionnaire\question\choice\choice::content_is_other_choice($content)) { // If this is an "other" column, put the text entered in the next position. $row[$position + 1] = $responserow->response; $choicetxt = empty($responserow->choice_id) ? '0' : '1'; @@ -3140,9 +3140,9 @@ public function generate_csv($rid='', $userid='', $choicecodes=1, $choicetext=0, } $content = $choicesbyqid[$qid][$responserow->choice_id]->content; - if (\mod_questionnaire\question\base::other_choice($content)) { + if (\mod_questionnaire\question\choice\choice::content_is_other_choice($content)) { // If this has an "other" text, use it. - $responsetxt = \mod_questionnaire\question\base::other_choice_display($content); + $responsetxt = \mod_questionnaire\question\choice\choice::content_other_choice_display($content); $responsetxt1 = $responserow->response; } else if (($choicecodes == 1) && ($choicetext == 1)) { $responsetxt = $c.' : '.$content; diff --git a/tests/responsetypes_test.php b/tests/responsetypes_test.php index 8a9691e6..94fcccd8 100644 --- a/tests/responsetypes_test.php +++ b/tests/responsetypes_test.php @@ -164,7 +164,7 @@ public function test_create_response_single() { } } $vals = ['q'.$question->id => $val, - 'q'.$question->id.\mod_questionnaire\question\base::other_choice_name($val) => 'Forty-four']; + 'q'.$question->id.\mod_questionnaire\question\choice\choice::id_other_choice_name($val) => 'Forty-four']; $userid = 2; $response = $generator->create_question_response($questionnaire, $question, $vals, $userid); @@ -213,7 +213,7 @@ public function test_create_response_multiple() { $val[$cid] = $cid; } else if ($choice->content == '!other=Another number') { $val[$cid] = $cid; - $val[\mod_questionnaire\question\base::other_choice_name($cid)] = 'Forty-four'; + $val[\mod_questionnaire\question\choice\choice::id_other_choice_name($cid)] = 'Forty-four'; $ocid = $cid; } } From 03532ae8b696793cbb81f91a8c75ef967ad930fd Mon Sep 17 00:00:00 2001 From: Mike Churchward Date: Tue, 14 May 2019 09:43:19 -0400 Subject: [PATCH 073/341] GHI195 - Display required indicator without question number. --- templates/question_container.mustache | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/question_container.mustache b/templates/question_container.mustache index 96ee622d..a0c2e181 100644 --- a/templates/question_container.mustache +++ b/templates/question_container.mustache @@ -44,15 +44,15 @@
{{{dependencylist}}} +
{{#qnum}} {{# str }}questionnum, mod_questionnaire{{/ str}}{{{qnum}}} -

{{{qnum}}}

+ {{/qnum}} {{{required}}}
- {{/qnum}}
{{#label}}