From 8c03be8c71a6e0dbaef8b98996efcccfc4248eee Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Mon, 5 Dec 2011 17:13:04 +0000 Subject: [PATCH 1/3] MDL-30592 moodlelib: new helper component_callback I chose to be slighly inconsistent with plugin_callback in order to make a nicer API. --- lib/moodlelib.php | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/lib/moodlelib.php b/lib/moodlelib.php index d6bba110a09..4ee3a61ed9d 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -7766,35 +7766,48 @@ function get_list_of_plugins($directory='mod', $exclude='', $basedir='') { return $plugins; } +/** +* invoke plugin's callback functions +* +* @param string $type Plugin type e.g. 'mod' +* @param string $name Plugin name +* @param string $feature Feature name +* @param string $action Feature's action +* @param array $params parameters of callback function, should be an array +* @param mixed $default default value if callback function hasn't been defined, or if it retursn null. +* @return mixed +*/ +function plugin_callback($type, $name, $feature, $action, $params = null, $default = null) { + return component_callback($type . '_' . $name, $feature . '_' . $action, (array) $params, $default); +} /** * invoke plugin's callback functions * - * @param string $type Plugin type e.g. 'mod' - * @param string $name Plugin name - * @param string $feature Feature name - * @param string $action Feature's action - * @param string $options parameters of callback function, should be an array - * @param mixed $default default value if callback function hasn't been defined + * @param string $component frankenstyle component name, e.g. 'mod_quiz' + * @param string $function the rest of the function name, e.g. 'cron' will end up calling 'mod_quiz_cron' + * @param array $params parameters of callback function + * @param mixed $default default value if callback function hasn't been defined, or if it retursn null. * @return mixed */ -function plugin_callback($type, $name, $feature, $action, $options = null, $default=null) { +function component_callback($component, $function, array $params = array(), $default = null) { global $CFG; // this is needed for require_once() bellow - $component = clean_param($type . '_' . $name, PARAM_COMPONENT); - if (empty($component)) { - throw new coding_exception('Invalid component used in plugin_callback():' . $type . '_' . $name); + $cleancomponent = clean_param($component, PARAM_COMPONENT); + if (empty($cleancomponent)) { + throw new coding_exception('Invalid component used in plugin_callback():' . $component); } + $component = $cleancomponent; list($type, $name) = normalize_component($component); $component = $type . '_' . $name; - $function = $component.'_'.$feature.'_'.$action; - $oldfunction = $name.'_'.$feature.'_'.$action; + $oldfunction = $name.'_'.$function; + $function = $component.'_'.$function; $dir = get_component_directory($component); if (empty($dir)) { - throw new coding_exception('Invalid component used in plugin_callback():' . $type . '_' . $name); + throw new coding_exception('Invalid component used in plugin_callback():' . $component); } // Load library and look for function @@ -7811,7 +7824,7 @@ function plugin_callback($type, $name, $feature, $action, $options = null, $defa if (function_exists($function)) { // Function exists, so just return function result - $ret = call_user_func_array($function, (array)$options); + $ret = call_user_func_array($function, $params); if (is_null($ret)) { return $default; } else { From fdb5bc03f582e69ece6df0757b55ab8ad83cde68 Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Mon, 5 Dec 2011 18:46:25 +0000 Subject: [PATCH 2/3] MDL-30592 quiz statistics, ensure images in the question text appear. --- lib/questionlib.php | 82 +++++++++++++++++++-------- mod/quiz/report/statistics/lib.php | 46 +++++++++++++++ mod/quiz/report/statistics/report.php | 30 ++++++++-- 3 files changed, 128 insertions(+), 30 deletions(-) create mode 100644 mod/quiz/report/statistics/lib.php diff --git a/lib/questionlib.php b/lib/questionlib.php index 6e605dbcac5..06c68712041 100644 --- a/lib/questionlib.php +++ b/lib/questionlib.php @@ -1636,10 +1636,7 @@ class question_edit_contexts { } /** - * Rewrite question url, file_rewrite_pluginfile_urls always build url by - * $file/$contextid/$component/$filearea/$itemid/$pathname_in_text, so we cannot add - * extra questionid and attempted in url by it, so we create quiz_rewrite_question_urls - * to build url here + * Helps call file_rewrite_pluginfile_urls with the right parameters. * * @param string $text text being processed * @param string $file the php script used to serve files @@ -1653,32 +1650,59 @@ class question_edit_contexts { */ function question_rewrite_question_urls($text, $file, $contextid, $component, $filearea, array $ids, $itemid, array $options=null) { - global $CFG; - - $options = (array)$options; - if (!isset($options['forcehttps'])) { - $options['forcehttps'] = false; - } - - if (!$CFG->slasharguments) { - $file = $file . '?file='; - } - - $baseurl = "$CFG->wwwroot/$file/$contextid/$component/$filearea/"; + $idsstr = ''; if (!empty($ids)) { - $baseurl .= (implode('/', $ids) . '/'); + $idsstr .= implode('/', $ids); } - if ($itemid !== null) { - $baseurl .= "$itemid/"; + $idsstr .= '/' . $itemid; + } + return file_rewrite_pluginfile_urls($text, $file, $contextid, $component, + $filearea, $idsstr, $options); +} + +/** + * Rewrite the PLUGINFILE urls in the questiontext, when viewing the question + * text outside and attempt (for example, in the question bank listing or in the + * quiz statistics report). + * + * @param string $questiontext the question text. + * @param int $contextid the context the text is being displayed in. + * @param string $component component + * @param array $ids other IDs will be used to check file permission + * @param array $options + * @return string $questiontext with URLs rewritten. + */ +function question_rewrite_questiontext_preview_urls($questiontext, $contextid, + $component, $questionid, $options=null) { + + return file_rewrite_pluginfile_urls($questiontext, 'pluginfile.php', $contextid, + 'question', 'questiontext_preview', "$component/$questionid", $options); +} + +/** + * Send a file from the question text of a question. + * @param int $questionid the question id + * @param array $args the remaining file arguments (file path). + * @param bool $forcedownload whether the user must be forced to download the file. + */ +function question_send_questiontext_file($questionid, $args, $forcedownload) { + global $DB; + + $question = $DB->get_record_sql(' + SELECT q.id, qc.contextid + FROM {question} q + JOIN {question_categories} qc ON qc.id = q.category + WHERE q.id = :id', array('id' => $questionid), MUST_EXIST); + + $fs = get_file_storage(); + $fullpath = "/$question->contextid/question/questiontext/$question->id/" . implode('/', $args); + if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { + send_file_not_found(); } - if ($options['forcehttps']) { - $baseurl = str_replace('http://', 'https://', $baseurl); - } - - return str_replace('@@PLUGINFILE@@/', $baseurl, $text); + send_stored_file($file, 0, 0, $forcedownload); } /** @@ -1704,6 +1728,16 @@ function question_rewrite_question_urls($text, $file, $contextid, $component, function question_pluginfile($course, $context, $component, $filearea, $args, $forcedownload) { global $DB, $CFG; + if ($filearea === 'questiontext_preview') { + $component = array_shift($args); + $questionid = array_shift($args); + + component_callback($component, 'questiontext_preview_pluginfile', array( + $context, $questionid, $args, $forcedownload)); + + send_file_not_found(); + } + list($context, $course, $cm) = get_context_info_array($context->id); require_login($course, false, $cm); diff --git a/mod/quiz/report/statistics/lib.php b/mod/quiz/report/statistics/lib.php new file mode 100644 index 00000000000..06d7fd97b3e --- /dev/null +++ b/mod/quiz/report/statistics/lib.php @@ -0,0 +1,46 @@ +. + +/** + * Standard plugin entry points of the quiz statistics report. + * + * @package quiz + * @subpackage statistics + * @copyright 2011 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +/** + * Serve questiontext files in the question text when they are displayed in this report. + * @param context $context the context + * @param int $questionid the question id + * @param array $args remaining file args + * @param bool $forcedownload + */ +function quiz_statistics_questiontext_preview_pluginfile($context, $questionid, $args, $forcedownload) { + global $CFG; + require_once($CFG->dirroot . '/mod/quiz/locallib.php'); + + list($context, $course, $cm) = get_context_info_array($context->id); + require_login($course, false, $cm); + + // Assume only trusted people can see this report. There is no real way to + // validate questionid, becuase of the complexity of random quetsions. + require_capability('quiz/statistics:view', $context); + + question_send_questiontext_file($questionid, $args, $forcedownload); +} diff --git a/mod/quiz/report/statistics/report.php b/mod/quiz/report/statistics/report.php index f29d2205ee6..87c0e9332fa 100644 --- a/mod/quiz/report/statistics/report.php +++ b/mod/quiz/report/statistics/report.php @@ -54,7 +54,7 @@ class quiz_statistics_report extends quiz_default_report { public function display($quiz, $cm, $course) { global $CFG, $DB, $OUTPUT, $PAGE; - $context = get_context_instance(CONTEXT_MODULE, $cm->id); + $this->context = get_context_instance(CONTEXT_MODULE, $cm->id); // Work out the display options. $download = optional_param('download', '', PARAM_ALPHA); @@ -94,7 +94,7 @@ class quiz_statistics_report extends quiz_default_report { } else { // All users who can attempt quizzes and who are in the currently selected group - $groupstudents = get_users_by_capability($context, + $groupstudents = get_users_by_capability($this->context, array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'), '', '', '', '', $currentgroup, '', false); if (!$groupstudents) { @@ -160,7 +160,7 @@ class quiz_statistics_report extends quiz_default_report { } if (!quiz_questions_in_quiz($quiz->questions)) { - echo quiz_no_questions_message($quiz, $cm, $context); + echo quiz_no_questions_message($quiz, $cm, $this->context); } else if (!$this->table->is_downloading() && $s == 0) { echo $OUTPUT->notification(get_string('noattempts', 'quiz')); } @@ -317,15 +317,33 @@ class quiz_statistics_report extends quiz_default_report { echo $OUTPUT->heading(get_string('questionstatistics', 'quiz_statistics')); echo html_writer::table($questionstatstable); } + public function format_text($text, $format, $qa, $component, $filearea, $itemid, + $clean = false) { + $formatoptions = new stdClass(); + $formatoptions->noclean = !$clean; + $formatoptions->para = false; + $text = $qa->rewrite_pluginfile_urls($text, $component, $filearea, $itemid); + return format_text($text, $format, $formatoptions); + } + + /** @return the result of applying {@link format_text()} to the question text. */ + public function format_questiontext($qa) { + return $this->format_text($this->questiontext, $this->questiontextformat, + $qa, 'question', 'questiontext', $this->id); + } /** * @param object $question question data. * @return string HTML of question text, ready for display. */ - protected function render_question_text($question){ + protected function render_question_text($question) { global $OUTPUT; - return $OUTPUT->box(format_text($question->questiontext, $question->questiontextformat, - array('overflowdiv' => true)), + + $text = question_rewrite_questiontext_preview_urls($question->questiontext, + $this->context->id, 'quiz_statistics', $question->id); + + return $OUTPUT->box(format_text($text, $question->questiontextformat, + array('noclean' => true, 'para' => false, 'overflowdiv' => true)), 'questiontext boxaligncenter generalbox boxwidthnormal mdl-align'); } From 0019a9b7aa9afae3053d1b0ef61eb80a44acfffe Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Mon, 5 Dec 2011 19:07:12 +0000 Subject: [PATCH 3/3] MDL-30592 question_bank: make files in qtext work in the question list. --- lib/questionlib.php | 27 +++++++++++++++++++++++++++ question/editlib.php | 16 +++++++++++----- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/lib/questionlib.php b/lib/questionlib.php index 06c68712041..2b3b6aca5f7 100644 --- a/lib/questionlib.php +++ b/lib/questionlib.php @@ -1827,6 +1827,33 @@ function question_pluginfile($course, $context, $component, $filearea, $args, $f } } +/** + * Serve questiontext files in the question text when they are displayed in this report. + * @param context $context the context + * @param int $questionid the question id + * @param array $args remaining file args + * @param bool $forcedownload + */ +function core_question_questiontext_preview_pluginfile($context, $questionid, $args, $forcedownload) { + global $DB; + + // Verify that contextid matches the question. + $question = $DB->get_record_sql(' + SELECT q.*, qc.contextid + FROM {question} q + JOIN {question_categories} qc ON qc.id = q.category + WHERE q.id = :id AND qc.contextid = :contextid', + array('id' => $questionid, 'contextid' => $context->id), MUST_EXIST); + + // Check the capability. + list($context, $course, $cm) = get_context_info_array($context->id); + require_login($course, false, $cm); + + question_require_capability_on($question, 'use'); + + question_send_questiontext_file($questionid, $args, $forcedownload); +} + /** * Create url for question export * diff --git a/question/editlib.php b/question/editlib.php index 2b541a6306c..5f2487e1e09 100644 --- a/question/editlib.php +++ b/question/editlib.php @@ -791,16 +791,22 @@ class question_bank_question_text_row extends question_bank_row_base { } protected function display_content($question, $rowclasses) { - $text = format_text($question->questiontext, $question->questiontextformat, - $this->formatoptions, $this->qbank->get_courseid()); + $text = question_rewrite_questiontext_preview_urls($question->questiontext, + $question->contextid, 'question', $question->id); + $text = format_text($text, $question->questiontextformat, + $this->formatoptions); if ($text == '') { $text = ' '; } echo $text; } + public function get_extra_joins() { + return array('qc' => 'JOIN {question_categories} qc ON qc.id = q.category'); + } + public function get_required_fields() { - return array('q.questiontext', 'q.questiontextformat'); + return array('q.id', 'q.questiontext', 'q.questiontextformat', 'qc.contextid'); } } @@ -1100,10 +1106,10 @@ class question_bank_view { } /// Build the where clause. - $tests = array('parent = 0'); + $tests = array('q.parent = 0'); if (!$showhidden) { - $tests[] = 'hidden = 0'; + $tests[] = 'q.hidden = 0'; } if ($recurse) {