// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
- * Sets up the tabs used by the quiz pages based on the users capabilites.
+ * Classes to enforce the various access rules that can apply to a quiz.
*
* @package mod
* @subpackage quiz
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* This class keeps track of the various access rules that apply to a particular
* quiz, with convinient methods for seeing whether access is allowed.
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once($CFG->libdir.'/formslib.php');
*/
-if (!defined('MOODLE_INTERNAL')) {
- die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page.
-}
+defined('MOODLE_INTERNAL') || die();
/**
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+
+defined('MOODLE_INTERNAL') || die();
+
require_once($CFG->dirroot . '/mod/quiz/backup/moodle2/backup_quiz_stepslib.php'); // Because it exists (must)
+
/**
* quiz backup task that provides all the settings and steps to perform one
* complete backup of the activity
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-/**
- * Define all the backup steps that will be used by the backup_quiz_activity_task
- */
+
+defined('MOODLE_INTERNAL') || die();
+
/**
- * Define the complete quiz structure for backup, with file and id annotations
+ * Define all the backup steps that will be used by the backup_quiz_activity_task
*/
class backup_quiz_activity_structure_step extends backup_questions_activity_structure_step {
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/quiz/backup/moodle2/restore_quiz_stepslib.php'); // Because it exists (must)
+
/**
* quiz restore task that provides all the settings and steps to perform one
* complete restore of the activity
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-/**
- * Define all the restore steps that will be used by the restore_quiz_activity_task
- */
+
+defined('MOODLE_INTERNAL') || die();
+
/**
* Structure step to restore one quiz activity
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+defined('MOODLE_INTERNAL') || die();
+
$capabilities = array(
// Ability to see that the quiz exists, and the basic information
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Code run after the quiz module database tables have been created.
*/
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+defined('MOODLE_INTERNAL') || die();
+
$messageproviders = array (
// Notify teacher that a student has submitted a quiz attempt
'submission' => array (
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+defined('MOODLE_INTERNAL') || die();
+
$subplugins = array('quiz' => 'mod/quiz/report');
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Quiz module upgrade function.
* @param string $oldversion the version we are upgrading from.
*/
-require_once($CFG->dirroot . '/mod/quiz/locallib.php');
+defined('MOODLE_INTERNAL') || die();
+require_once($CFG->dirroot . '/mod/quiz/locallib.php');
define('NUM_QS_TO_SHOW_IN_RANDOM', 3);
*/
-/** Require {@link eventslib.php} */
+defined('MOODLE_INTERNAL') || die();
+
require_once($CFG->libdir . '/eventslib.php');
-/** Require {@link calendar/lib.php} */
require_once($CFG->dirroot . '/calendar/lib.php');
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-if (!defined('MOODLE_INTERNAL')) {
- die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page.
-}
-/**
- * Include those library functions that are also used by core Moodle or other modules
- */
+defined('MOODLE_INTERNAL') || die();
+
require_once($CFG->dirroot . '/mod/quiz/lib.php');
require_once($CFG->dirroot . '/mod/quiz/accessrules.php');
require_once($CFG->dirroot . '/mod/quiz/attemptlib.php');
require_once($CFG->libdir . '/eventslib.php');
require_once($CFG->libdir . '/filelib.php');
-/// Constants ///////////////////////////////////////////////////////////////////
/**#@+
* Options determining how the grades from individual attempts are combined to give
*/
-if (!defined('MOODLE_INTERNAL')) {
- die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
-}
+defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/course/moodleform_mod.php');
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
*/
-if (!defined('MOODLE_INTERNAL')) {
- die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
-}
+defined('MOODLE_INTERNAL') || die();
require_once $CFG->libdir.'/formslib.php';
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once($CFG->libdir.'/tablelib.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Base class for quiz report plugins.
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+defined('MOODLE_INTERNAL') || die();
+
$capabilities = array(
// Is the user allowed to see the student's real names while grading?
'quiz/grading:viewstudentnames' => array(
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once($CFG->libdir . '/formslib.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once($CFG->dirroot . '/mod/quiz/report/grading/gradingsettings_form.php');
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+defined('MOODLE_INTERNAL') || die();
+
$plugin->version = 2011021400;
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Quiz overview report upgrade function.
* @param number $oldversion
- */function xmldb_quiz_overview_upgrade($oldversion) {
+ */
+function xmldb_quiz_overview_upgrade($oldversion) {
global $CFG, $DB;
$dbman = $DB->get_manager();
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* This is a table subclass for displaying the quiz grades report.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once($CFG->libdir . '/formslib.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once($CFG->dirroot.'/mod/quiz/report/attemptsreport.php');
require_once($CFG->dirroot.'/mod/quiz/report/overview/overviewsettings_form.php');
require_once($CFG->dirroot.'/mod/quiz/report/overview/overview_table.php');
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+defined('MOODLE_INTERNAL') || die();
+
$plugin->version = 2011021600; // The (date) version of this module
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+
+defined('MOODLE_INTERNAL') || die();
+
require_once($CFG->dirroot . '/mod/quiz/lib.php');
define('QUIZ_REPORT_DEFAULT_PAGE_SIZE', 30);
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once($CFG->dirroot.'/mod/quiz/report/attemptsreport.php');
require_once($CFG->dirroot.'/mod/quiz/report/responses/responsessettings_form.php');
require_once($CFG->dirroot.'/mod/quiz/report/responses/responses_table.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* This is a table subclass for displaying the quiz responses report.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once($CFG->libdir . '/formslib.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../../../../config.php');
global $CFG;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+
+defined('MOODLE_INTERNAL') || die();
+
require_once($CFG->dirroot . '/question/engine/compatibility.php');
/**
* @copyright 2008 Jamie Pratt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+
+defined('MOODLE_INTERNAL') || die();
+
$capabilities = array(
'quiz/statistics:view' => array(
'captype' => 'read',
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Post-install script
*/
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Quiz statistics report upgrade code.
*/
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* This class has methods to compute the question statistics from the raw data.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once($CFG->dirroot . '/mod/quiz/report/statistics/statistics_form.php');
require_once($CFG->dirroot . '/mod/quiz/report/statistics/statistics_table.php');
require_once($CFG->dirroot . '/mod/quiz/report/statistics/statistics_question_table.php');
require_once($CFG->dirroot . '/mod/quiz/report/statistics/qstats.php');
require_once($CFG->dirroot . '/mod/quiz/report/statistics/responseanalysis.php');
+
/**
* The quiz statistics report provides summary information about each question in
* a quiz, compared to the whole quiz. It also provides a drill-down to more
* detailed information about each question.
*
- * @copyright 2008 Jamie Pratt
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @copyright 2008 Jamie Pratt
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class quiz_statistics_report extends quiz_default_report {
/** @var integer Time after which statistics are automatically recomputed. */
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* This class can store and compute the analysis of the responses to a particular
* question.
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once($CFG->libdir . '/questionlib.php');
require_once($CFG->dirroot . '/mod/quiz/report/statistics/qstats.php');
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once($CFG->libdir . '/formslib.php');
+
/**
* This is the settings form for the quiz statistics report.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once($CFG->libdir . '/tablelib.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once($CFG->libdir.'/tablelib.php');
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+defined('MOODLE_INTERNAL') || die();
+
$plugin->version = 2011021500;
*/
-defined('MOODLE_INTERNAL') || die;
+defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/quiz/lib.php');
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
*/
-if (!defined('MOODLE_INTERNAL')) {
- die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page.
-}
+defined('MOODLE_INTERNAL') || die();
/**
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-if (!defined('MOODLE_INTERNAL')) {
- die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page.
-}
+
+defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
*/
-if (!defined('MOODLE_INTERNAL')) {
- die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page.
-}
+defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/quiz/editlib.php');
*/
-if (!defined('MOODLE_INTERNAL')) {
- die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page.
-}
+defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/quiz/lib.php');
*/
-if (!defined('MOODLE_INTERNAL')) {
- die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page.
-}
+defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
*/
-if (!defined('MOODLE_INTERNAL')) {
- die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page.
-}
+defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+defined('MOODLE_INTERNAL') || die();
+
$module->version = 2010122304; // The (date) version of this module
$module->requires = 2010080300; // Requires this Moodle version
$module->cron = 0; // How often should cron check this module (seconds)?
-
-
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
-
/**
* Question behaviour for the old adaptive mode.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Question behaviour for adaptive mode.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Renderer for outputting parts of a question belonging to the legacy
* adaptive behaviour.
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../../../engine/lib.php');
require_once(dirname(__FILE__) . '/../../../engine/simpletest/helpers.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../adaptive/behaviour.php');
+
/**
* Question behaviour for adaptive mode, with no penalties.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../adaptive/renderer.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../../../engine/lib.php');
require_once(dirname(__FILE__) . '/../../../engine/simpletest/helpers.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* The base class for question behaviours.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../deferredfeedback/behaviour.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Renderer for outputting parts of a question belonging to the deferred
* feedback with certainty based marking behaviour.
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../../../engine/lib.php');
require_once(dirname(__FILE__) . '/../../../engine/simpletest/helpers.php');
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
-
/**
* Question behaviour for the case when the student's answer is just
* saved until they submit the whole attempt, and then it is graded.
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Question behaviour for deferred feedback.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Renderer for outputting parts of a question belonging to the deferred
* feedback behaviour.
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../../../engine/lib.php');
require_once(dirname(__FILE__) . '/../../../engine/simpletest/helpers.php');
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
-
/**
* Question behaviour where the student can submit questions one at a
* time for immediate feedback, with certainty based marking.
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../immediatefeedback/behaviour.php');
+
/**
* Question behaviour for immediate feedback with CBM.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../deferredcbm/renderer.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../../../engine/lib.php');
require_once(dirname(__FILE__) . '/../../../engine/simpletest/helpers.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Question behaviour for immediate feedback.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Renderer for outputting parts of a question belonging to the immediate
* feedback behaviour.
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
-
/**
* This file contains tests that walks a question through the immediate feedback
* behaviour.
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../../../engine/lib.php');
require_once(dirname(__FILE__) . '/../../../engine/simpletest/helpers.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Question behaviour informaiton items.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Renderer for outputting parts of a question belonging to the information
* item behaviour.
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../../../engine/lib.php');
require_once(dirname(__FILE__) . '/../../../engine/simpletest/helpers.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Question behaviour for the interactive model.
*
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
-
/**
* Renderer for outputting parts of a question belonging to the interactive
* behaviour.
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Interactive behaviour renderer.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../../../engine/lib.php');
require_once(dirname(__FILE__) . '/../../../engine/simpletest/helpers.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../interactive/behaviour.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../interactive/renderer.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../../../engine/lib.php');
require_once(dirname(__FILE__) . '/../../../engine/simpletest/helpers.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Question behaviour for questions that can only be graded manually.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Renderer for outputting parts of a question belonging to the manual
* graded behaviour.
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../../../engine/lib.php');
require_once(dirname(__FILE__) . '/../../../engine/simpletest/helpers.php');
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
-
/**
* Fake question behaviour that is used when the actual qim was not
* available.
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Fake question behaviour that is used when the actual behaviour
* is not available.
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Renderer for outputting parts of a question when the actual behaviour
* used is not available.
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../../../engine/lib.php');
require_once(dirname(__FILE__) . '/../../../engine/simpletest/helpers.php');
require_once(dirname(__FILE__) . '/../behaviour.php');
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
-
/**
* This behaviour that is used when the actual qim was not
* available.
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* This behaviour is specifically for use with the Opaque question type.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Renderer for outputting parts of a question when the actual behaviour
* used is not available.
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../../../engine/lib.php');
require_once(dirname(__FILE__) . '/../../../engine/simpletest/helpers.php');
require_once(dirname(__FILE__) . '/../behaviour.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Renderer base class for question behaviours.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
// number of categories to display on page
define('QUESTION_PAGE_LENGTH', 25);
*/
-if (!defined('MOODLE_INTERNAL')) {
- die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
-}
+defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/formslib.php');
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
-
/**
* Functions used to show question editing interface
*
*/
-require_once($CFG->libdir . '/questionlib.php');
+defined('MOODLE_INTERNAL') || die();
+require_once($CFG->libdir . '/questionlib.php');
define('DEFAULT_QUESTIONS_PER_PAGE', 20);
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
-
/**
* More object oriented wrappers around parts of the Moodle question bank.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* This static class provides access to the other question bank.
*
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
-
/**
* Code for loading and saving quiz attempts to and from the database.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* This class controls the loading and saving of question engine data to and from
* the database.
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/states.php');
require_once(dirname(__FILE__) . '/datalib.php');
require_once(dirname(__FILE__) . '/renderer.php');
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
-
/**
* Renderers for outputting parts of the question engine.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* This renderer controls the overall output of questions. It works with a
* {@link qbehaviour_renderer} and a {@link qtype_renderer} to output the
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
-
/**
* This file contains helper classes for testing the question engine.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../lib.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../lib.php');
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
-
/**
* This file contains tests for the question_attempt class.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../lib.php');
require_once(dirname(__FILE__) . '/helpers.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../lib.php');
require_once(dirname(__FILE__) . '/helpers.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../lib.php');
require_once(dirname(__FILE__) . '/helpers.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../lib.php');
require_once(dirname(__FILE__) . '/helpers.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../lib.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../lib.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../lib.php');
require_once($CFG->libdir . '/questionlib.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../lib.php');
require_once(dirname(__FILE__) . '/helpers.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once(dirname(__FILE__) . '/../lib.php');
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
-
/**
* This defines the states a question can be in.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* An enumeration representing the states a question can be in after a
* {@link question_attempt_step}.
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
-
/**
* This file contains the code required to upgrade all the attempt data from
* old versions of Moodle into the tables used by the new question engine.
*/
+defined('MOODLE_INTERNAL') || die();
+
global $CFG;
require_once($CFG->libdir . '/questionlib.php');
*/
-if (!defined('MOODLE_INTERNAL')) {
- die('Direct access to this script is forbidden.');
-}
+defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/formslib.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Base class for question import and export formats.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Aiken format - a simple format for creating multiple choice questions (with
* only one correct choice, and no feedback).
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once ($CFG->libdir . '/xmlize.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once ($CFG->libdir . '/xmlize.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once ($CFG->libdir . '/xmlize.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* The GIFT import filter was designed as an easy to use method
* for teachers writing questions as a text file. It supports most
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
-
/**
* Unit tests for the Moodle GIFT format.
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+
+defined('MOODLE_INTERNAL') || die();
+
require_once($CFG->libdir . '/questionlib.php');
require_once($CFG->dirroot . '/question/format.php');
require_once($CFG->dirroot . '/question/format/gift/format.php');
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Examview question importer.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Missing word question importer.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Importer that imports a text file containing a single Multianswer question
* from a text file.
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Modified from the original filter/mediaplugin/filter.php
*/
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once("$CFG->dirroot/question/format/qti_two/qt_common.php");
define('CLOZE_TRAILING_TEXT_ID', 9999999);
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* gets a list of all the media files for the given course
*
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* Web CT question importer.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
+
/**
* XHTML question exporter.
*
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
-
/**
* Code for exporting questions as Moodle XML.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once($CFG->dirroot . '/question/format.php');
require_once($CFG->libdir . '/xmlize.php');
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
-
/**
* Unit tests for the Moodle XML format.
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+
+defined('MOODLE_INTERNAL') || die();
+
require_once($CFG->libdir . '/questionlib.php');
require_once($CFG->dirroot . '/question/format/xml/format.php');
*/
-if (!defined('MOODLE_INTERNAL')) {
- die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
-}
+defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/formslib.php');
*/
-if (!defined('MOODLE_INTERNAL')) {
- die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
-}
+defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/formslib.php');
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
-
/**
* Library functions used by question/preview.php.
*
*/
+defined('MOODLE_INTERNAL') || die();
+
require_once($CFG->libdir . '/formslib.php');
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
-
/**
* Unit tests for the question import and export system.
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+
+defined('MOODLE_INTERNAL') || die();
+
require_once($CFG->libdir . '/questionlib.php');
require_once($CFG->dirroot . '/question/format.php');
DONE question/type/truefalse/simpletest/testquestion.php | 99 +
DONE question/type/truefalse/simpletest/testquestiontype.php | 73 +
DONE question/type/truefalse/version.php | 4 +-
-
-Missing @package
-================
-
-DONE mod/quiz/addrandomform.php
-DONE mod/quiz/db/access.php
-DONE mod/quiz/db/install.php
-DONE mod/quiz/db/install.xml
-DONE mod/quiz/db/subplugins.php
-DONE mod/quiz/db/upgrade.php
-DONE mod/quiz/grade.php
-DONE mod/quiz/module.js
-DONE mod/quiz/pix/icon.gif
-DONE mod/quiz/pix/navflagged.png
-DONE mod/quiz/report/grading/styles.css
-DONE mod/quiz/report/overview/db/install.xml
-DONE mod/quiz/report/statistics/db/install.xml
-DONE mod/quiz/report/statistics/simpletest/mdl_question.csv
-DONE mod/quiz/report/statistics/simpletest/mdl_question_states.csv
-DONE mod/quiz/settingslib.php
-DONE mod/quiz/styles.css
-DONE mod/quiz/version.php
-DONE question/behaviour/adaptive/lang/en/qbehaviour_adaptive.php
-DONE question/behaviour/adaptivenopenalty/lang/en/qbehaviour_adaptivenopenalty.php
-DONE question/behaviour/deferredcbm/lang/en/qbehaviour_deferredcbm.php
-DONE question/behaviour/deferredfeedback/lang/en/qbehaviour_deferredfeedback.php
-DONE question/behaviour/immediatecbm/lang/en/qbehaviour_immediatecbm.php
-DONE question/behaviour/immediatefeedback/lang/en/qbehaviour_immediatefeedback.php
-DONE question/behaviour/informationitem/lang/en/qbehaviour_informationitem.php
-DONE question/behaviour/interactive/lang/en/qbehaviour_interactive.php
-DONE question/behaviour/interactivecountback/lang/en/qbehaviour_interactivecountback.php
-DONE question/behaviour/manualgraded/lang/en/qbehaviour_manualgraded.php
-DONE question/behaviour/missing/lang/en/qbehaviour_missing.php
-DONE question/behaviour/opaque/lang/en/qbehaviour_opaque.php
-DONE question/category_form.php
-DONE question/export_form.php
-DONE question/flags.js
-DONE question/format/aiken/format.php
-DONE question/import_form.php
-DONE question/qbank.js
-DONE question/qengine.js
-DONE question/type/calculated/datasetitems_form.php
-DONE question/type/calculated/db/upgrade.php
-DONE question/type/calculated/questiontype.php
-DONE question/type/calculated/version.php
-DONE question/type/calculatedmulti/questiontype.php
-DONE question/type/calculatedsimple/questiontype.php
-DONE question/type/ddwtos/version.php
-DONE question/type/description/question.html
-DONE question/type/essay/display.html
-DONE question/type/essay/version.php
-DONE question/type/gapselect/edit_form_base.php
-DONE question/type/gapselect/rendererbase.php
-DONE question/type/match/db/upgrade.php
-DONE question/type/match/version.php
-DONE question/type/multianswer/db/upgrade.php
-DONE question/type/multianswer/version.php
-DONE question/type/multichoice/db/upgrade.php
-DONE question/type/multichoice/version.php
-DONE question/type/numerical/db/upgrade.php
-DONE question/type/numerical/display.html
-DONE question/type/numerical/version.php
-DONE question/type/opaque/lang/en/qtype_opaque.php
-DONE question/type/opaque/settings.php
-DONE question/type/randomsamatch/version.php
-DONE question/type/truefalse/version.php
-
-Missing boiler-plate
-====================
-
-DONE question/addquestion.php
-DONE question/category.php
-DONE question/category_class.php
-DONE question/edit.php
-DONE question/export.php
-DONE question/format/blackboard/format.php
-DONE question/format/blackboard_six/format.php
-DONE question/format/examview/format.php
-DONE question/format/learnwise/format.php
-DONE question/format/missingword/format.php
-DONE question/format/qti_two/custommediafilter.php
-DONE question/format/qti_two/format.php
-DONE question/format/qti_two/qt_common.php
-DONE question/format/webct/format.php
-DONE question/format/xhtml/format.php
-DONE question/format.php
-DONE question/question.php
-DONE question/type/calculated/datasetdefinitions_form.php
-DONE question/type/calculatedmulti/edit_calculatedmulti_form.php
-DONE question/type/ddwtos/script.js
-DONE question/type/multianswer/edit_multianswer_form.php
-DONE question/type/multianswer/questiontype.php
-DONE question/type/numerical/display.html
-DONE question/type/randomsamatch/edit_randomsamatch_form.php
-DONE question/type/randomsamatch/questiontype.php
-DONE question/upgrade.php
-
+++ /dev/null
-
- /**
- * Prints a question
- *
- * Simply calls the question type specific print_question() method.
-+ *
-+ * @global array
- * @param object $question The question to be rendered.
- * @param object $state The state to render the question in.
- * @param integer $number The number for this question.
- * @param object $cmoptions The options specified by the course module
- * @param object $options An object specifying the rendering options.
- */
--function print_question(&$question, &$state, $number, $cmoptions, $options=null) {
-+function print_question(&$question, &$state, $number, $cmoptions, $options=null, $context=null) {
- global $QTYPES;
-- $QTYPES[$question->qtype]->print_question($question, $state, $number, $cmoptions, $options);
-+ $QTYPES[$question->qtype]->print_question($question, $state, $number, $cmoptions, $options, $context);
- }
- /**
- * Saves question options
- *
- * Simply calls the question type specific save_question_options() method.
-+ *
-+ * @global array
- */
- function save_question_options($question) {
- global $QTYPES;
-@@ -2075,8 +2255,9 @@ function sort_categories_by_tree(&$categories, $id = 0, $level = 1) {
- //If level = 1, we have finished, try to look for non processed categories (bad parent) and sort them too
- if ($level == 1) {
- foreach ($keys as $key) {
-- //If not processed and it's a good candidate to start (because its parent doesn't exist in the course)
-- if (!isset($categories[$key]->processed) && !$DB->record_exists('question_categories', array('course'=>$categories[$key]->course, 'id'=>$categories[$key]->parent))) {
-+ // If not processed and it's a good candidate to start (because its parent doesn't exist in the course)
-+ if (!isset($categories[$key]->processed) && !$DB->record_exists(
-+ 'question_categories', array('contextid'=>$categories[$key]->contextid, 'id'=>$categories[$key]->parent))) {
- $children[$key] = $categories[$key];
- $categories[$key]->processed = true;
- $children = $children + sort_categories_by_tree($categories, $children[$key]->id, $level+1);
-@@ -2167,16 +2348,23 @@ function add_indented_names($categories, $nochildrenof = -1) {
- * @param integer $selected optionally, the id of a category to be selected by default in the dropdown.
- */
- function question_category_select_menu($contexts, $top = false, $currentcat = 0, $selected = "", $nochildrenof = -1) {
-+ global $OUTPUT;
- $categoriesarray = question_category_options($contexts, $top, $currentcat, false, $nochildrenof);
- if ($selected) {
-- $nothing = '';
-+ $choose = '';
- } else {
-- $nothing = 'choose';
-+ $choose = 'choosedots';
-+ }
-+ $options = array();
-+ foreach($categoriesarray as $group=>$opts) {
-+ $options[] = array($group=>$opts);
- }
-- choose_from_menu_nested($categoriesarray, 'category', $selected, $nothing);
-+
-+ echo html_writer::select($options, 'category', $selected, $choose);
- }
-@@ -2216,23 +2406,31 @@ function question_edit_url($context) {
- /**
- * Gets the default category in the most specific context.
- * If no categories exist yet then default ones are created in all contexts.
- *
-+ * @global object
- * @param array $contexts The context objects for this context and all parent contexts.
- * @return object The default category - the category in the course context
- */
- function question_make_default_categories($contexts) {
- global $DB;
-+ static $preferredlevels = array(
-+ CONTEXT_COURSE => 4,
-+ CONTEXT_MODULE => 3,
-+ CONTEXT_COURSECAT => 2,
-+ CONTEXT_SYSTEM => 1,
-+ );
-
- $toreturn = null;
-+ $preferredness = 0;
- // If it already exists, just return it.
- foreach ($contexts as $key => $context) {
-- if (!$exists = $DB->record_exists("question_categories", array('contextid'=>$context->id))){
-+ if (!$exists = $DB->record_exists("question_categories", array('contextid'=>$context->id))) {
- // Otherwise, we need to make one
- $category = new stdClass;
- $contextname = print_context_name($context, false, true);
-@@ -2242,19 +2440,20 @@ function question_make_default_categories($contexts) {
- $category->parent = 0;
- $category->sortorder = 999; // By default, all categories get this number, and are sorted alphabetically.
- $category->stamp = make_unique_id_code();
-- if (!$category->id = $DB->insert_record('question_categories', $category)) {
-- print_error('cannotcreatedefaultcat', '', '', print_context_name($context));
-- }
-+ $category->id = $DB->insert_record('question_categories', $category);
- } else {
- $category = question_get_default_category($context->id);
- }
--
-- if ($context->contextlevel == CONTEXT_COURSE){
-- $toreturn = clone($category);
-+ if ($preferredlevels[$context->contextlevel] > $preferredness &&
-+ has_any_capability(array('moodle/question:usemine', 'moodle/question:useall'), $context)) {
-+ $toreturn = $category;
-+ $preferredness = $preferredlevels[$context->contextlevel];
- }
- }
-
--
-+ if (!is_null($toreturn)) {
-+ $toreturn = clone($toreturn);
-+ }
- return $toreturn;
- }
-
-@@ -2313,9 +2514,12 @@ function question_category_options($contexts, $top = false, $currentcat = 0, $po
- if ($popupform){
- $popupcats = array();
- foreach ($categoriesarray as $contextstring => $optgroup){
-- $popupcats[] = '--'.$contextstring;
-- $popupcats = array_merge($popupcats, $optgroup);
-- $popupcats[] = '--';
-+ $group = array();
-+ foreach ($optgroup as $key=>$value) {
-+ $key = str_replace($CFG->wwwroot, '', $key);
-+ $group[$key] = $value;
-+ }
-+ $popupcats[] = array($contextstring=>$group);
- }
- return $popupcats;
- } else {
-@@ -2335,7 +2539,7 @@ function question_add_context_in_key($categories){
- function question_add_tops($categories, $pcontexts){
- $topcats = array();
- foreach ($pcontexts as $context){
-- $newcat = new object();
-+ $newcat = new stdClass();
- $newcat->id = "0,$context";
- $newcat->name = get_string('top');
- $newcat->parent = -1;
- function get_import_export_formats( $type ) {
-
- global $CFG;
-- $fileformats = get_list_of_plugins("question/format");
-+ $fileformats = get_plugin_list("qformat");
-
- $fileformatname=array();
- require_once( "{$CFG->dirroot}/question/format.php" );
-- foreach ($fileformats as $key => $fileformat) {
-- $format_file = $CFG->dirroot . "/question/format/$fileformat/format.php";
-- if (file_exists( $format_file ) ) {
-- require_once( $format_file );
-+ foreach ($fileformats as $fileformat=>$fdir) {
-+ $format_file = "$fdir/format.php";
-+ if (file_exists($format_file) ) {
-+ require_once($format_file);
- }
- else {
- continue;
-@@ -2400,7 +2607,10 @@ function get_import_export_formats( $type ) {
- if ($provided) {
- $formatname = get_string($fileformat, 'quiz');
- if ($formatname == "[[$fileformat]]") {
-- $formatname = $fileformat; // Just use the raw folder name
-+ $formatname = get_string($fileformat, 'qformat_'.$fileformat);
-+ if ($formatname == "[[$fileformat]]") {
-+ $formatname = $fileformat; // Just use the raw folder name
-+ }
- }
- $fileformatnames[$fileformat] = $formatname;
- }
-@@ -2412,50 +2622,39 @@ function get_import_export_formats( $type ) {
-
-
- /**
--* Create default export filename
--*
--* @return string default export filename
--* @param object $course
--* @param object $category
-+* Create a reasonable default file name for exporting questions from a particular
-+* category.
-+* @param object $course the course the questions are in.
-+* @param object $category the question category.
-+* @return string the filename.
- */
--function default_export_filename($course,$category) {
-- //Take off some characters in the filename !!
-- $takeoff = array(" ", ":", "/", "\\", "|");
-- $export_word = str_replace($takeoff,"_",moodle_strtolower(get_string("exportfilename","quiz")));
-- //If non-translated, use "export"
-- if (substr($export_word,0,1) == "[") {
-- $export_word= "export";
-- }
--
-- //Calculate the date format string
-- $export_date_format = str_replace(" ","_",get_string("exportnameformat","quiz"));
-- //If non-translated, use "%Y%m%d-%H%M"
-- if (substr($export_date_format,0,1) == "[") {
-- $export_date_format = "%%Y%%m%%d-%%H%%M";
-- }
--
-- //Calculate the shortname
-- $export_shortname = clean_filename($course->shortname);
-- if (empty($export_shortname) or $export_shortname == '_' ) {
-- $export_shortname = $course->id;
-- }
--
-- //Calculate the category name
-- $export_categoryname = clean_filename($category->name);
--
-- //Calculate the final export filename
-- //The export word
-- $export_name = $export_word."-";
-- //The shortname
-- $export_name .= moodle_strtolower($export_shortname)."-";
-- //The category name
-- $export_name .= moodle_strtolower($export_categoryname)."-";
-- //The date format
-- $export_name .= userdate(time(),$export_date_format,99,false);
-- //Extension is supplied by format later.
-+function question_default_export_filename($course, $category) {
-+ // We build a string that is an appropriate name (questions) from the lang pack,
-+ // then the corse shortname, then the question category name, then a timestamp.
-+
-+ $base = clean_filename(get_string('exportfilename', 'question'));
-+
-+ $dateformat = str_replace(' ', '_', get_string('exportnameformat', 'question'));
-+ $timestamp = clean_filename(userdate(time(), $dateformat, 99, false));
-+
-+ $shortname = clean_filename($course->shortname);
-+ if ($shortname == '' || $shortname == '_' ) {
-+ $shortname = $course->id;
-+ }
-+
-+ $categoryname = clean_filename(format_string($category->name));
-+
-+ return "{$base}-{$shortname}-{$categoryname}-{$timestamp}";
-
- return $export_name;
- }
-+
-+/**
-+ * @package moodlecore
-+ * @subpackage question
-+ * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
-+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
-+ */
- class context_to_string_translator{
- /**
- * @var array used to translate between contextids and strings for this context.
-@@ -2549,13 +2751,13 @@ function question_has_capability_on($question, $cap, $cachecat = -1){
- static $questions = array();
- static $categories = array();
- static $cachedcat = array();
-- if ($cachecat != -1 && (array_search($cachecat, $cachedcat)===FALSE)){
-- $questions += $DB->get_records('question', array('category'=>$cachecat));
-+ if ($cachecat != -1 && array_search($cachecat, $cachedcat) === false) {
-+ $questions += $DB->get_records('question', array('category' => $cachecat));
- $cachedcat[] = $cachecat;
- }
- if (!is_object($question)){
- if (!isset($questions[$question])){
-- if (!$questions[$question] = $DB->get_record('question', array('id'=>$question), 'id,category,createdby')) {
-+ if (!$questions[$question] = $DB->get_record('question', array('id' => $question), 'id,category,createdby')) {
- print_error('questiondoesnotexist', 'question');
- }
- }
-@@ -2567,11 +2769,12 @@ function question_has_capability_on($question, $cap, $cachecat = -1){
- }
- }
- $category = $categories[$question->category];
-+ $context = get_context_instance_by_id($category->contextid);
-
- if (array_search($cap, $question_questioncaps)!== FALSE){
-- if (!has_capability('moodle/question:'.$cap.'all', get_context_instance_by_id($category->contextid))){
-+ if (!has_capability('moodle/question:'.$cap.'all', $context)){
- if ($question->createdby == $USER->id){
-- return has_capability('moodle/question:'.$cap.'mine', get_context_instance_by_id($category->contextid));
-+ return has_capability('moodle/question:'.$cap.'mine', $context);
- } else {
- return false;
- }
-@@ -2579,7 +2782,7 @@ function question_has_capability_on($question, $cap, $cachecat = -1){
- return true;
- }
- } else {
-- return has_capability('moodle/question:'.$cap, get_context_instance_by_id($category->contextid));
-+ return has_capability('moodle/question:'.$cap, $context);
- }
-
- }
-@@ -2594,107 +2797,6 @@ function question_require_capability_on($question, $cap){
- return true;
- }
-
--function question_file_links_base_url($courseid){
-- global $CFG;
-- $baseurl = preg_quote("$CFG->wwwroot/file.php", '!');
-- $baseurl .= '('.preg_quote('?file=', '!').')?';//may or may not
-- //be using slasharguments, accept either
-- $baseurl .= "/$courseid/";//course directory
-- return $baseurl;
--}
--
--/*
-- * Find all course / site files linked to in a piece of html.
-- * @param string html the html to search
-- * @param int course search for files for courseid course or set to siteid for
-- * finding site files.
-- * @return array files with keys being files.
-- */
--function question_find_file_links_from_html($html, $courseid){
-- global $CFG;
-- $baseurl = question_file_links_base_url($courseid);
-- $searchfor = '!'.
-- '(<\s*(a|img)\s[^>]*(href|src)\s*=\s*")'.$baseurl.'([^"]*)"'.
-- '|'.
-- '(<\s*(a|img)\s[^>]*(href|src)\s*=\s*\')'.$baseurl.'([^\']*)\''.
-- '!i';
-- $matches = array();
-- $no = preg_match_all($searchfor, $html, $matches);
-- if ($no){
-- $rawurls = array_filter(array_merge($matches[5], $matches[10]));//array_filter removes empty elements
-- //remove any links that point somewhere they shouldn't
-- foreach (array_keys($rawurls) as $rawurlkey){
-- if (!$cleanedurl = question_url_check($rawurls[$rawurlkey])){
-- unset($rawurls[$rawurlkey]);
-- } else {
-- $rawurls[$rawurlkey] = $cleanedurl;
-- }
--
-- }
-- $urls = array_flip($rawurls);// array_flip removes duplicate files
-- // and when we merge arrays will continue to automatically remove duplicates
-- } else {
-- $urls = array();
-- }
-- return $urls;
--}
--
--/**
-- * Check that url doesn't point anywhere it shouldn't
-- *
-- * @param $url string relative url within course files directory
-- * @return mixed boolean false if not OK or cleaned URL as string if OK
-- */
--function question_url_check($url){
-- global $CFG;
-- if ((substr(strtolower($url), 0, strlen($CFG->moddata)) == strtolower($CFG->moddata)) ||
-- (substr(strtolower($url), 0, 10) == 'backupdata')){
-- return false;
-- } else {
-- return clean_param($url, PARAM_PATH);
-- }
--}
--
--/**
-- * Find all course / site files linked to in a piece of html.
-- * @param string html the html to search
-- * @param int course search for files for courseid course or set to siteid for
-- * finding site files.
-- * @return array files with keys being files.
-- */
--function question_replace_file_links_in_html($html, $fromcourseid, $tocourseid, $url, $destination, &$changed){
-- global $CFG;
-- require_once($CFG->libdir .'/filelib.php');
-- $tourl = get_file_url("$tocourseid/$destination");
-- $fromurl = question_file_links_base_url($fromcourseid).preg_quote($url, '!');
-- $searchfor = array('!(<\s*(a|img)\s[^>]*(href|src)\s*=\s*")'.$fromurl.'(")!i',
-- '!(<\s*(a|img)\s[^>]*(href|src)\s*=\s*\')'.$fromurl.'(\')!i');
-- $newhtml = preg_replace($searchfor, '\\1'.$tourl.'\\5', $html);
-- if ($newhtml != $html){
-- $changed = true;
-- }
-- return $newhtml;
--}
--
--function get_filesdir_from_context($context){
-- global $DB;
--
-- switch ($context->contextlevel){
-- case CONTEXT_COURSE :
-- $courseid = $context->instanceid;
-- break;
-- case CONTEXT_MODULE :
-- $courseid = $DB->get_field('course_modules', 'course', array('id'=>$context->instanceid));
-- break;
-- case CONTEXT_COURSECAT :
-- case CONTEXT_SYSTEM :
-- $courseid = SITEID;
-- break;
-- default :
-- print_error('invalidcontext');
-- }
-- return $courseid;
--}
- /**
- * Get the real state - the correct question id and answer - for a random
- * question.
-@@ -2702,11 +2804,12 @@ function get_filesdir_from_context($context){
- * @return mixed return integer real question id or false if there was an
- * error..
- */
--function question_get_real_state($state){
-+function question_get_real_state($state) {
-+ global $OUTPUT;
- $realstate = clone($state);
- $matches = array();
- if (!preg_match('|^random([0-9]+)-(.*)|', $state->answer, $matches)){
-- notify(get_string('errorrandom', 'quiz_statistics'));
-+ echo $OUTPUT->notification(get_string('errorrandom', 'quiz_statistics'));
- return false;
- } else {
- $realstate->question = $matches[1];
-@@ -2770,4 +2877,389 @@ function question_get_toggleflag_checksum($attemptid, $questionid, $sessionid, $
- return md5($attemptid . "_" . $user->secret . "_" . $questionid . "_" . $sessionid);
- }
-
--?>
-+/**
-+ * Adds question bank setting links to the given navigation node if caps are met.
-+ *
-+ * @param navigation_node $navigationnode The navigation node to add the question branch to
-+ * @param stdClass $context
-+ * @return navigation_node Returns the question branch that was added
-+ */
-+function question_extend_settings_navigation(navigation_node $navigationnode, $context) {
-+ global $PAGE;
-+
-+ if ($context->contextlevel == CONTEXT_COURSE) {
-+ $params = array('courseid'=>$context->instanceid);
-+ } else if ($context->contextlevel == CONTEXT_MODULE) {
-+ $params = array('cmid'=>$context->instanceid);
-+ } else {
-+ return;
-+ }
-+
-+ $questionnode = $navigationnode->add(get_string('questionbank','question'), new moodle_url('/question/edit.php', $params), navigation_node::TYPE_CONTAINER);
-+
-+ $contexts = new question_edit_contexts($context);
-+ if ($contexts->have_one_edit_tab_cap('questions')) {
-+ $questionnode->add(get_string('questions', 'quiz'), new moodle_url('/question/edit.php', $params), navigation_node::TYPE_SETTING);
-+ }
-+ if ($contexts->have_one_edit_tab_cap('categories')) {
-+ $questionnode->add(get_string('categories', 'quiz'), new moodle_url('/question/category.php', $params), navigation_node::TYPE_SETTING);
-+ }
-+ if ($contexts->have_one_edit_tab_cap('import')) {
-+ $questionnode->add(get_string('import', 'quiz'), new moodle_url('/question/import.php', $params), navigation_node::TYPE_SETTING);
-+ }
-+ if ($contexts->have_one_edit_tab_cap('export')) {
-+ $questionnode->add(get_string('export', 'quiz'), new moodle_url('/question/export.php', $params), navigation_node::TYPE_SETTING);
-+ }
-+
-+ return $questionnode;
-+}
-+
-+class question_edit_contexts {
-+
-+ public static $CAPS = array(
-+ 'editq' => array('moodle/question:add',
-+ 'moodle/question:editmine',
-+ 'moodle/question:editall',
-+ 'moodle/question:viewmine',
-+ 'moodle/question:viewall',
-+ 'moodle/question:usemine',
-+ 'moodle/question:useall',
-+ 'moodle/question:movemine',
-+ 'moodle/question:moveall'),
-+ 'questions'=>array('moodle/question:add',
-+ 'moodle/question:editmine',
-+ 'moodle/question:editall',
-+ 'moodle/question:viewmine',
-+ 'moodle/question:viewall',
-+ 'moodle/question:movemine',
-+ 'moodle/question:moveall'),
-+ 'categories'=>array('moodle/question:managecategory'),
-+ 'import'=>array('moodle/question:add'),
-+ 'export'=>array('moodle/question:viewall', 'moodle/question:viewmine'));
-+
-+ protected $allcontexts;
-+
-+ /**
-+ * @param current context
-+ */
-+ public function question_edit_contexts($thiscontext){
-+ $pcontextids = get_parent_contexts($thiscontext);
-+ $contexts = array($thiscontext);
-+ foreach ($pcontextids as $pcontextid){
-+ $contexts[] = get_context_instance_by_id($pcontextid);
-+ }
-+ $this->allcontexts = $contexts;
-+ }
-+ /**
-+ * @return array all parent contexts
-+ */
-+ public function all(){
-+ return $this->allcontexts;
-+ }
-+ /**
-+ * @return object lowest context which must be either the module or course context
-+ */
-+ public function lowest(){
-+ return $this->allcontexts[0];
-+ }
-+ /**
-+ * @param string $cap capability
-+ * @return array parent contexts having capability, zero based index
-+ */
-+ public function having_cap($cap){
-+ $contextswithcap = array();
-+ foreach ($this->allcontexts as $context){
-+ if (has_capability($cap, $context)){
-+ $contextswithcap[] = $context;
-+ }
-+ }
-+ return $contextswithcap;
-+ }
-+ /**
-+ * @param array $caps capabilities
-+ * @return array parent contexts having at least one of $caps, zero based index
-+ */
-+ public function having_one_cap($caps){
-+ $contextswithacap = array();
-+ foreach ($this->allcontexts as $context){
-+ foreach ($caps as $cap){
-+ if (has_capability($cap, $context)){
-+ $contextswithacap[] = $context;
-+ break; //done with caps loop
-+ }
-+ }
-+ }
-+ return $contextswithacap;
-+ }
-+ /**
-+ * @param string $tabname edit tab name
-+ * @return array parent contexts having at least one of $caps, zero based index
-+ */
-+ public function having_one_edit_tab_cap($tabname){
-+ return $this->having_one_cap(self::$CAPS[$tabname]);
-+ }
-+ /**
-+ * Has at least one parent context got the cap $cap?
-+ *
-+ * @param string $cap capability
-+ * @return boolean
-+ */
-+ public function have_cap($cap){
-+ return (count($this->having_cap($cap)));
-+ }
-+
-+ /**
-+ * Has at least one parent context got one of the caps $caps?
-+ *
-+ * @param array $caps capability
-+ * @return boolean
-+ */
-+ public function have_one_cap($caps){
-+ foreach ($caps as $cap) {
-+ if ($this->have_cap($cap)) {
-+ return true;
-+ }
-+ }
-+ return false;
-+ }
-+ /**
-+ * Has at least one parent context got one of the caps for actions on $tabname
-+ *
-+ * @param string $tabname edit tab name
-+ * @return boolean
-+ */
-+ public function have_one_edit_tab_cap($tabname){
-+ return $this->have_one_cap(self::$CAPS[$tabname]);
-+ }
-+ /**
-+ * Throw error if at least one parent context hasn't got the cap $cap
-+ *
-+ * @param string $cap capability
-+ */
-+ public function require_cap($cap){
-+ if (!$this->have_cap($cap)){
-+ print_error('nopermissions', '', '', $cap);
-+ }
-+ }
-+ /**
-+ * Throw error if at least one parent context hasn't got one of the caps $caps
-+ *
-+ * @param array $cap capabilities
-+ */
-+ public function require_one_cap($caps) {
-+ if (!$this->have_one_cap($caps)) {
-+ $capsstring = join($caps, ', ');
-+ print_error('nopermissions', '', '', $capsstring);
-+ }
-+ }
-+
-+ /**
-+ * Throw error if at least one parent context hasn't got one of the caps $caps
-+ *
-+ * @param string $tabname edit tab name
-+ */
-+ public function require_one_edit_tab_cap($tabname){
-+ if (!$this->have_one_edit_tab_cap($tabname)) {
-+ print_error('nopermissions', '', '', 'access question edit tab '.$tabname);
-+ }
-+ }
-+}
-+
-+/**
-+ * 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
-+ *
-+ * @param string $text text being processed
-+ * @param string $file the php script used to serve files
-+ * @param int $contextid
-+ * @param string $component component
-+ * @param string $filearea filearea
-+ * @param array $ids other IDs will be used to check file permission
-+ * @param int $itemid
-+ * @param array $options
-+ * @return string
-+ */
-+function quiz_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/";
-+
-+ if (!empty($ids)) {
-+ $baseurl .= (implode('/', $ids) . '/');
-+ }
-+
-+ if ($itemid !== null) {
-+ $baseurl .= "$itemid/";
-+ }
-+
-+ if ($options['forcehttps']) {
-+ $baseurl = str_replace('http://', 'https://', $baseurl);
-+ }
-+
-+ return str_replace('@@PLUGINFILE@@/', $baseurl, $text);
-+}
-+
-+/**
-+ * Called by pluginfile.php to serve files related to the 'question' core
-+ * component and for files belonging to qtypes.
-+ *
-+ * For files that relate to questions in a question_attempt, then we delegate to
-+ * a function in the component that owns the attempt (for example in the quiz,
-+ * or in core question preview) to get necessary inforation.
-+ *
-+ * (Note that, at the moment, all question file areas relate to questions in
-+ * attempts, so the If at the start of the last paragraph is always true.)
-+ *
-+ * Does not return, either calls send_file_not_found(); or serves the file.
-+ *
-+ * @param object $course course settings object
-+ * @param object $context context object
-+ * @param string $component the name of the component we are serving files for.
-+ * @param string $filearea the name of the file area.
-+ * @param array $args the remaining bits of the file path.
-+ * @param bool $forcedownload whether the user must be forced to download the file.
-+ */
-+function question_pluginfile($course, $context, $component, $filearea, $args, $forcedownload) {
-+ global $DB, $CFG;
-+
-+ list($context, $course, $cm) = get_context_info_array($context->id);
-+ require_login($course, false, $cm);
-+
-+ if ($filearea === 'export') {
-+ require_once($CFG->dirroot . '/question/editlib.php');
-+ $contexts = new question_edit_contexts($context);
-+ // check export capability
-+ $contexts->require_one_edit_tab_cap('export');
-+ $category_id = (int)array_shift($args);
-+ $format = array_shift($args);
-+ $cattofile = array_shift($args);
-+ $contexttofile = array_shift($args);
-+ $filename = array_shift($args);
-+
-+ // load parent class for import/export
-+ require_once($CFG->dirroot . '/question/format.php');
-+ require_once($CFG->dirroot . '/question/editlib.php');
-+ require_once($CFG->dirroot . '/question/format/' . $format . '/format.php');
-+
-+ $classname = 'qformat_' . $format;
-+ if (!class_exists($classname)) {
-+ send_file_not_found();
-+ }
-+
-+ $qformat = new $classname();
-+
-+ if (!$category = $DB->get_record('question_categories', array('id' => $category_id))) {
-+ send_file_not_found();
-+ }
-+
-+ $qformat->setCategory($category);
-+ $qformat->setContexts($contexts->having_one_edit_tab_cap('export'));
-+ $qformat->setCourse($course);
-+
-+ if ($cattofile == 'withcategories') {
-+ $qformat->setCattofile(true);
-+ } else {
-+ $qformat->setCattofile(false);
-+ }
-+
-+ if ($contexttofile == 'withcontexts') {
-+ $qformat->setContexttofile(true);
-+ } else {
-+ $qformat->setContexttofile(false);
-+ }
-+
-+ if (!$qformat->exportpreprocess()) {
-+ send_file_not_found();
-+ print_error('exporterror', 'question', $thispageurl->out());
-+ }
-+
-+ // export data to moodle file pool
-+ if (!$content = $qformat->exportprocess(true)) {
-+ send_file_not_found();
-+ }
-+
-+ //DEBUG
-+ //echo '<textarea cols=90 rows=20>';
-+ //echo $content;
-+ //echo '</textarea>';
-+ //die;
-+ send_file($content, $filename, 0, 0, true, true, $qformat->mime_type());
-+ }
-+
-+ $attemptid = (int)array_shift($args);
-+ $questionid = (int)array_shift($args);
-+
-+
-+ if ($attemptid === 0) {
-+ // preview
-+ require_once($CFG->dirroot . '/question/previewlib.php');
-+ return question_preview_question_pluginfile($course, $context,
-+ $component, $filearea, $attemptid, $questionid, $args, $forcedownload);
-+
-+ } else {
-+ $module = $DB->get_field('question_attempts', 'modulename',
-+ array('id' => $attemptid));
-+
-+ $dir = get_component_directory($module);
-+ if (!file_exists("$dir/lib.php")) {
-+ send_file_not_found();
-+ }
-+ include_once("$dir/lib.php");
-+
-+ $filefunction = $module . '_question_pluginfile';
-+ if (!function_exists($filefunction)) {
-+ send_file_not_found();
-+ }
-+
-+ $filefunction($course, $context, $component, $filearea, $attemptid, $questionid,
-+ $args, $forcedownload);
-+
-+ send_file_not_found();
-+ }
-+}
-+
-+/**
-+ * Final test for whether a studnet should be allowed to see a particular file.
-+ * This delegates the decision to the question type plugin.
-+ *
-+ * @param object $question The question to be rendered.
-+ * @param object $state The state to render the question in.
-+ * @param object $options An object specifying the rendering options.
-+ * @param string $component the name of the component we are serving files for.
-+ * @param string $filearea the name of the file area.
-+ * @param array $args the remaining bits of the file path.
-+ * @param bool $forcedownload whether the user must be forced to download the file.
-+ */
-+function question_check_file_access($question, $state, $options, $contextid, $component,
-+ $filearea, $args, $forcedownload) {
-+ global $QTYPES;
-+ return $QTYPES[$question->qtype]->check_file_access($question, $state, $options, $contextid, $component,
-+ $filearea, $args, $forcedownload);
-+}
-+
-+/**
-+ * Create url for question export
-+ *
-+ * @param int $contextid, current context
-+ * @param int $categoryid, categoryid
-+ * @param string $format
-+ * @param string $withcategories
-+ * @param string $ithcontexts
-+ * @param moodle_url export file url
-+ */
-+function question_make_export_url($contextid, $categoryid, $format, $withcategories, $withcontexts, $filename) {
-+ global $CFG;
-+ $urlbase = "$CFG->httpswwwroot/pluginfile.php";
-+ return moodle_url::make_file_url($urlbase, "/$contextid/question/export/{$categoryid}/{$format}/{$withcategories}/{$withcontexts}/{$filename}", true);
-+}
+++ /dev/null
-diff --git a/lib/questionlib.php b/lib/questionlib.php
-index 04752b2..3211e92 100644
---- a/lib/questionlib.php
-+++ b/lib/questionlib.php
-@@ -1,4 +1,20 @@
--<?php // $Id$
-+<?php
-+
-+// This file is part of Moodle - http://moodle.org/
-+//
-+// Moodle is free software: you can redistribute it and/or modify
-+// it under the terms of the GNU General Public License as published by
-+// the Free Software Foundation, either version 3 of the License, or
-+// (at your option) any later version.
-+//
-+// Moodle is distributed in the hope that it will be useful,
-+// but WITHOUT ANY WARRANTY; without even the implied warranty of
-+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-+// GNU General Public License for more details.
-+//
-+// You should have received a copy of the GNU General Public License
-+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
-+
- /**
- * Code for handling and processing questions
- *
-@@ -10,34 +26,18 @@
- * TODO: separate those functions which form part of the API
- * from the helper functions.
- *
-- * @author Martin Dougiamas and many others. This has recently been completely
-- * rewritten by Alex Smith, Julian Sedding and Gustav Delius as part of
-- * the Serving Mathematics project
-- * {@link http://maths.york.ac.uk/serving_maths}
-- * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
-- * @package question
-+ * @package moodlecore
-+ * @subpackage questionbank
-+ * @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com}
-+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
--/// CONSTANTS ///////////////////////////////////
-
--/**#@+
-- * The different types of events that can create question states
-- */
--define('QUESTION_EVENTOPEN', '0'); // The state was created by Moodle
--define('QUESTION_EVENTNAVIGATE', '1'); // The responses were saved because the student navigated to another page (this is not currently used)
--define('QUESTION_EVENTSAVE', '2'); // The student has requested that the responses should be saved but not submitted or validated
--define('QUESTION_EVENTGRADE', '3'); // Moodle has graded the responses. A SUBMIT event can be changed to a GRADE event by Moodle.
--define('QUESTION_EVENTDUPLICATE', '4'); // The responses submitted were the same as previously
--define('QUESTION_EVENTVALIDATE', '5'); // The student has requested a validation. This causes the responses to be saved as well, but not graded.
--define('QUESTION_EVENTCLOSEANDGRADE', '6'); // Moodle has graded the responses. A CLOSE event can be changed to a CLOSEANDGRADE event by Moodle.
--define('QUESTION_EVENTSUBMIT', '7'); // The student response has been submitted but it has not yet been marked
--define('QUESTION_EVENTCLOSE', '8'); // The response has been submitted and the session has been closed, either because the student requested it or because Moodle did it (e.g. because of a timelimit). The responses have not been graded.
--define('QUESTION_EVENTMANUALGRADE', '9'); // Grade was entered by teacher
--
--define('QUESTION_EVENTS_GRADED', QUESTION_EVENTGRADE.','.
-- QUESTION_EVENTCLOSEANDGRADE.','.
-- QUESTION_EVENTMANUALGRADE);
--/**#@-*/
-+require_once($CFG->dirroot . '/question/engine/lib.php');
-+require_once($CFG->dirroot . '/question/type/questiontype.php');
-+
-+
-+/// CONSTANTS ///////////////////////////////////
-
- /**#@+
- * The core question types.
-@@ -59,7 +59,7 @@ define("ESSAY", "essay");
- * Constant determines the number of answer boxes supplied in the editing
- * form for multiple choice and similar question types.
- */
--define("QUESTION_NUMANS", "10");
-+define("QUESTION_NUMANS", 10);
-
- /**
- * Constant determines the number of answer boxes supplied in the editing
-@@ -78,22 +78,10 @@ define("QUESTION_NUMANS_ADD", 3);
- /**
- * The options used when popping up a question preview window in Javascript.
- */
--define('QUESTION_PREVIEW_POPUP_OPTIONS', 'scrollbars=yes,resizable=yes,width=700,height=540');
-+define('QUESTION_PREVIEW_POPUP_OPTIONS', 'scrollbars=yes,resizable=yes,width=800,height=600');
-
- /**#@+
-- * Option flags for ->optionflags
-- * The options are read out via bitwise operation using these constants
-- */
--/**
-- * Whether the questions is to be run in adaptive mode. If this is not set then
-- * a question closes immediately after the first submission of responses. This
-- * is how question is Moodle always worked before version 1.5
-- */
--define('QUESTION_ADAPTIVE', 1);
--
--/**
- * options used in forms that move files.
-- *
- */
- define('QUESTION_FILENOTHINGSELECTED', 0);
- define('QUESTION_FILEDONOTHING', 1);
-@@ -103,64 +91,13 @@ define('QUESTION_FILEMOVELINKSONLY', 4);
-
- /**#@-*/
-
--/// QTYPES INITIATION //////////////////
--// These variables get initialised via calls to question_register_questiontype
--// as the question type classes are included.
--global $QTYPES, $QTYPE_MANUAL, $QTYPE_EXCLUDE_FROM_RANDOM;
--/**
-- * Array holding question type objects
-- */
--$QTYPES = array();
- /**
-- * String in the format "'type1','type2'" that can be used in SQL clauses like
-- * "WHERE q.type IN ($QTYPE_MANUAL)".
-+ * @global array holding question type objects
-+ * @deprecated
- */
--$QTYPE_MANUAL = '';
--/**
-- * String in the format "'type1','type2'" that can be used in SQL clauses like
-- * "WHERE q.type NOT IN ($QTYPE_EXCLUDE_FROM_RANDOM)".
-- */
--$QTYPE_EXCLUDE_FROM_RANDOM = '';
--
--/**
-- * Add a new question type to the various global arrays above.
-- *
-- * @param object $qtype An instance of the new question type class.
-- */
--function question_register_questiontype($qtype) {
-- global $QTYPES, $QTYPE_MANUAL, $QTYPE_EXCLUDE_FROM_RANDOM;
--
-- $name = $qtype->name();
-- $QTYPES[$name] = $qtype;
-- if ($qtype->is_manual_graded()) {
-- if ($QTYPE_MANUAL) {
-- $QTYPE_MANUAL .= ',';
-- }
-- $QTYPE_MANUAL .= "'$name'";
-- }
-- if (!$qtype->is_usable_by_random()) {
-- if ($QTYPE_EXCLUDE_FROM_RANDOM) {
-- $QTYPE_EXCLUDE_FROM_RANDOM .= ',';
-- }
-- $QTYPE_EXCLUDE_FROM_RANDOM .= "'$name'";
-- }
--}
--
--require_once("$CFG->dirroot/question/type/questiontype.php");
-+global $QTYPES;
-+$QTYPES = question_bank::get_all_qtypes();
-
--// Load the questiontype.php file for each question type
--// These files in turn call question_register_questiontype()
--// with a new instance of each qtype class.
--$qtypenames= get_list_of_plugins('question/type');
--foreach($qtypenames as $qtypename) {
-- // Instanciates all plug-in question types
-- $qtypefilepath= "$CFG->dirroot/question/type/$qtypename/questiontype.php";
--
-- // echo "Loading $qtypename<br/>"; // Uncomment for debugging
-- if (is_readable($qtypefilepath)) {
-- require_once($qtypefilepath);
-- }
--}
-
- /**
- * An array of question type names translated to the user's language, suitable for use when
-@@ -173,11 +110,10 @@ foreach($qtypenames as $qtypename) {
- * @return array an array of question type names translated to the user's language.
- */
- function question_type_menu() {
-- global $QTYPES;
- static $menu_options = null;
- if (is_null($menu_options)) {
- $menu_options = array();
-- foreach ($QTYPES as $name => $qtype) {
-+ foreach (question_bank::get_all_qtypes() as $name => $qtype) {
- $menuname = $qtype->menu_name();
- if ($menuname) {
- $menu_options[$name] = $menuname;
-@@ -187,86 +123,58 @@ function question_type_menu() {
- return $menu_options;
- }
-
--/// OTHER CLASSES /////////////////////////////////////////////////////////
--
--/**
-- * This holds the options that are set by the course module
-- */
--class cmoptions {
-- /**
-- * Whether a new attempt should be based on the previous one. If true
-- * then a new attempt will start in a state where all responses are set
-- * to the last responses from the previous attempt.
-- */
-- var $attemptonlast = false;
--
-- /**
-- * Various option flags. The flags are accessed via bitwise operations
-- * using the constants defined in the CONSTANTS section above.
-- */
-- var $optionflags = QUESTION_ADAPTIVE;
--
-- /**
-- * Determines whether in the calculation of the score for a question
-- * penalties for earlier wrong responses within the same attempt will
-- * be subtracted.
-- */
-- var $penaltyscheme = true;
--
-- /**
-- * The maximum time the user is allowed to answer the questions withing
-- * an attempt. This is measured in minutes so needs to be multiplied by
-- * 60 before compared to timestamps. If set to 0 no timelimit will be applied
-- */
-- var $timelimit = 0;
--
-- /**
-- * Timestamp for the closing time. Responses submitted after this time will
-- * be saved but no credit will be given for them.
-- */
-- var $timeclose = 9999999999;
--
-- /**
-- * The id of the course from withing which the question is currently being used
-- */
-- var $course = SITEID;
--
-- /**
-- * Whether the answers in a multiple choice question should be randomly
-- * shuffled when a new attempt is started.
-- */
-- var $shuffleanswers = true;
--
-- /**
-- * The number of decimals to be shown when scores are printed
-- */
-- var $decimalpoints = 2;
--}
--
--
- /// FUNCTIONS //////////////////////////////////////////////////////
-
- /**
- * Returns an array of names of activity modules that use this question
- *
-+ * @deprecated since Moodle 2.1. Use {@link questions_in_use} instead.
-+
- * @param object $questionid
- * @return array of strings
- */
- function question_list_instances($questionid) {
-+ throw new coding_exception('question_list_instances has been deprectated. Please use questions_in_use instead.');
-+}
-+
-+/**
-+ * @param array $questionids of question ids.
-+ * @return boolean whether any of these questions are being used by any part of Moodle.
-+ */
-+function questions_in_use($questionids) {
- global $CFG;
-- $instances = array();
-- $modules = get_records('modules');
-- foreach ($modules as $module) {
-- $fullmod = $CFG->dirroot . '/mod/' . $module->name;
-- if (file_exists($fullmod . '/lib.php')) {
-- include_once($fullmod . '/lib.php');
-- $fn = $module->name.'_question_list_instances';
-+
-+ if (question_engine::questions_in_use($questionids)) {
-+ return true;
-+ }
-+
-+ foreach (get_records('modules') as $module) {
-+ $lib = $CFG->dirroot . '/mod/' . $module->name . '/lib.php';
-+ if (file_exists($lib)) {
-+ include_once($lib);
-+
-+ $fn = $module->name . '_questions_in_use';
- if (function_exists($fn)) {
-- $instances = $instances + $fn($questionid);
-+ if ($fn($questionids)) {
-+ return true;
-+ }
-+ } else {
-+
-+ // Fallback for legacy modules.
-+ $fn = $module->name.'_question_list_instances';
-+ foreach ($questionids as $questionid) {
-+ if (function_exists($fn)) {
-+ $instances = $fn($questionid);
-+ if (!empty($instances)) {
-+ return true;
-+ }
-+ }
-+ }
- }
- }
- }
-- return $instances;
-+
-+ return false;
- }
-
- /**
-@@ -298,39 +206,42 @@ function question_context_has_any_questions($context) {
- * @return object ->gradeoptionsfull full array ->gradeoptions +ve only
- */
- function get_grade_options() {
-- // define basic array of grades
-+ // define basic array of grades. This list comprises all fractions of the form:
-+ // a. p/q for q <= 6, 0 <= p <= q
-+ // b. p/10 for 0 <= p <= 10
-+ // c. 1/q for 1 <= q <= 10
-+ // d. 1/20
- $grades = array(
-- 1.00,
-- 0.90,
-- 0.83333,
-- 0.80,
-- 0.75,
-- 0.70,
-- 0.66666,
-- 0.60,
-- 0.50,
-- 0.40,
-- 0.33333,
-- 0.30,
-- 0.25,
-- 0.20,
-- 0.16666,
-- 0.142857,
-- 0.125,
-- 0.11111,
-- 0.10,
-- 0.05,
-- 0);
-+ 1.0000000,
-+ 0.9000000,
-+ 0.8333333,
-+ 0.8000000,
-+ 0.7500000,
-+ 0.7000000,
-+ 0.6666667,
-+ 0.6000000,
-+ 0.5000000,
-+ 0.4000000,
-+ 0.3333333,
-+ 0.3000000,
-+ 0.2500000,
-+ 0.2000000,
-+ 0.1666667,
-+ 0.1428571,
-+ 0.1250000,
-+ 0.1111111,
-+ 0.1000000,
-+ 0.0500000,
-+ 0.0000000);
-
- // iterate through grades generating full range of options
- $gradeoptionsfull = array();
- $gradeoptions = array();
- foreach ($grades as $grade) {
- $percentage = 100 * $grade;
-- $neggrade = -$grade;
-- $gradeoptions["$grade"] = "$percentage %";
-- $gradeoptionsfull["$grade"] = "$percentage %";
-- $gradeoptionsfull["$neggrade"] = -$percentage." %";
-+ $gradeoptions["$grade"] = $percentage . '%';
-+ $gradeoptionsfull["$grade"] = $percentage . '%';
-+ $gradeoptionsfull['' . (-$grade)] = (-$percentage) . '%';
- }
- $gradeoptionsfull["0"] = $gradeoptions["0"] = get_string("none");
-
-@@ -386,60 +297,43 @@ function match_grade_options($gradeoptionsfull, $grade, $matchgrades='error') {
- }
-
- /**
-- * Tests whether a category is in use by any activity module
-- *
-- * @return boolean
-- * @param integer $categoryid
-- * @param boolean $recursive Whether to examine category children recursively
-+ * @deprecated Since Moodle 2.1. Use {@link question_category_in_use} instead.
-+ * @param integer $categoryid a question category id.
-+ * @param boolean $recursive whether to check child categories too.
-+ * @return boolean whether any question in this category is in use.
- */
- function question_category_isused($categoryid, $recursive = false) {
--
-- //Look at each question in the category
-- if ($questions = get_records('question', 'category', $categoryid)) {
-- foreach ($questions as $question) {
-- if (count(question_list_instances($question->id))) {
-- return true;
-- }
-- }
-- }
--
-- //Look under child categories recursively
-- if ($recursive) {
-- if ($children = get_records('question_categories', 'parent', $categoryid)) {
-- foreach ($children as $child) {
-- if (question_category_isused($child->id, $recursive)) {
-- return true;
-- }
-- }
-- }
-- }
--
-- return false;
-+ throw new coding_exception('question_category_isused has been deprectated. Please use question_category_in_use instead.');
- }
-
- /**
-- * Deletes all data associated to an attempt from the database
-+ * Tests whether any question in a category is used by any part of Moodle.
- *
-- * @param integer $attemptid The id of the attempt being deleted
-+ * @param integer $categoryid a question category id.
-+ * @param boolean $recursive whether to check child categories too.
-+ * @return boolean whether any question in this category is in use.
- */
--function delete_attempt($attemptid) {
-- global $QTYPES;
-+function question_category_in_use($categoryid, $recursive = false) {
-
-- $states = get_records('question_states', 'attempt', $attemptid);
-- if ($states) {
-- $stateslist = implode(',', array_keys($states));
--
-- // delete question-type specific data
-- foreach ($QTYPES as $qtype) {
-- $qtype->delete_states($stateslist);
-+ // Look at each question in the category
-+ if ($questions = get_records('question', 'category', $categoryid)) {
-+ if (questions_in_use($questions)) {
-+ return true;
- }
- }
-+ if (!$recursive) {
-+ return false;
-+ }
-
-- // delete entries from all other question tables
-- // It is important that this is done only after calling the questiontype functions
-- delete_records("question_states", "attempt", $attemptid);
-- delete_records("question_sessions", "attemptid", $attemptid);
-- delete_records("question_attempts", "id", $attemptid);
-+ // Look under child categories recursively
-+ if ($children = get_records('question_categories', 'parent', $categoryid)) {
-+ foreach ($children as $child) {
-+ if (question_category_in_use($child->id, $recursive)) {
-+ return true;
-+ }
-+ }
-+ }
-+ return false;
- }
-
- /**
-@@ -448,7 +342,7 @@ function delete_attempt($attemptid) {
- * It will not delete a question if it is used by an activity module
- * @param object $question The question being deleted
- */
--function delete_question($questionid) {
-+function question_delete_question($questionid) {
- global $QTYPES;
-
- if (!$question = get_record('question', 'id', $questionid)) {
-@@ -459,12 +353,17 @@ function delete_question($questionid) {
- }
-
- // Do not delete a question if it is used by an activity module
-- if (count(question_list_instances($questionid))) {
-+ if (questions_in_use(array($questionid))) {
- return;
- }
-
-- // delete questiontype-specific data
-+ // Check permissions.
- question_require_capability_on($question, 'edit');
-+
-+ $dm = new question_engine_data_mapper();
-+ $dm->delete_previews($questionid);
-+
-+ // Delete questiontype-specific data
- if ($question) {
- if (isset($QTYPES[$question->qtype])) {
- $QTYPES[$question->qtype]->delete_question($questionid);
-@@ -473,26 +372,11 @@ function delete_question($questionid) {
- echo "Question with id $questionid does not exist.<br />";
- }
-
-- if ($states = get_records('question_states', 'question', $questionid)) {
-- $stateslist = implode(',', array_keys($states));
--
-- // delete questiontype-specific data
-- foreach ($QTYPES as $qtype) {
-- $qtype->delete_states($stateslist);
-- }
-- }
--
-- // delete entries from all other question tables
-- // It is important that this is done only after calling the questiontype functions
-- delete_records("question_answers", "question", $questionid);
-- delete_records("question_states", "question", $questionid);
-- delete_records("question_sessions", "questionid", $questionid);
--
-- // Now recursively delete all child questions
-+ // Recursively delete all child questions
- if ($children = get_records('question', 'parent', $questionid)) {
- foreach ($children as $child) {
- if ($child->id != $questionid) {
-- delete_question($child->id);
-+ question_delete_question($child->id);
- }
- }
- }
-@@ -517,7 +401,7 @@ function question_delete_course($course, $feedback=true) {
- //Cache some strings
- $strcatdeleted = get_string('unusedcategorydeleted', 'quiz');
- $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
-- $categoriescourse = get_records('question_categories', 'contextid', $coursecontext->id, 'parent', 'id, parent, name');
-+ $categoriescourse = get_records('question_categories', 'contextid', $coursecontext->id, 'parent', 'id, parent, name, contextid');
-
- if ($categoriescourse) {
-
-@@ -531,7 +415,7 @@ function question_delete_course($course, $feedback=true) {
- //deleting questions
- if ($questions = get_records("question", "category", $category->id)) {
- foreach ($questions as $question) {
-- delete_question($question->id);
-+ question_delete_question($question->id);
- }
- delete_records("question", "category", $category->id);
- }
-@@ -578,7 +462,7 @@ function question_delete_course_category($category, $newcategory, $feedback=true
-
- // Try to delete each question.
- foreach ($questions as $question) {
-- delete_question($question->id);
-+ question_delete_question($question->id);
- }
-
- // Check to see if there were any questions that were kept because they are
-@@ -676,7 +560,7 @@ function question_delete_activity($cm, $feedback=true) {
- //Cache some strings
- $strcatdeleted = get_string('unusedcategorydeleted', 'quiz');
- $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
-- if ($categoriesmods = get_records('question_categories', 'contextid', $modcontext->id, 'parent', 'id, parent, name')){
-+ if ($categoriesmods = get_records('question_categories', 'contextid', $modcontext->id, 'parent', 'id, parent, name, contextid')){
- //Sort categories following their tree (parent-child) relationships
- //this will make the feedback more readable
- $categoriesmods = sort_categories_by_tree($categoriesmods);
-@@ -687,7 +571,7 @@ function question_delete_activity($cm, $feedback=true) {
- //deleting questions
- if ($questions = get_records("question", "category", $category->id)) {
- foreach ($questions as $question) {
-- delete_question($question->id);
-+ question_delete_question($question->id);
- }
- delete_records("question", "category", $category->id);
- }
-@@ -751,725 +635,148 @@ function questionbank_navigation_tabs(&$row, $contexts, $querystring) {
- }
-
- /**
-- * Private function to factor common code out of get_question_options().
-- *
-- * @param object $question the question to tidy.
-- * @return boolean true if successful, else false.
-+ * Generate the URL for starting a new preview of a given question with the given options.
-+ * @param integer $questionid the question to preview.
-+ * @param string $preferredbehaviour the behaviour to use for the preview.
-+ * @param float $maxmark the maximum to mark the question out of.
-+ * @param question_display_options $displayoptions the display options to use.
-+ * @return string the URL.
- */
--function _tidy_question(&$question) {
-- global $QTYPES;
-- if (!array_key_exists($question->qtype, $QTYPES)) {
-- $question->qtype = 'missingtype';
-- $question->questiontext = '<p>' . get_string('warningmissingtype', 'quiz') . '</p>' . $question->questiontext;
-- }
-- $question->name_prefix = question_make_name_prefix($question->id);
-- return $QTYPES[$question->qtype]->get_question_options($question);
--}
--
--/**
-- * Updates the question objects with question type specific
-- * information by calling {@link get_question_options()}
-+function question_preview_url($questionid, $preferredbehaviour, $maxmark, $displayoptions) {
-+ global $CFG;
-+ return $CFG->wwwroot . '/question/preview.php?id=' . $questionid .
-+ '&behaviour=' . $preferredbehaviour .
-+ '&maxmark=' . $maxmark .
-+ '&correctness=' . $displayoptions->correctness .
-+ '&marks=' . $displayoptions->marks .
-+ '&markdp=' . $displayoptions->markdp .
-+ '&feedback=' . (bool) $displayoptions->feedback .
-+ '&generalfeedback=' . (bool) $displayoptions->generalfeedback .
-+ '&rightanswer=' . (bool) $displayoptions->rightanswer .
-+ '&history=' . (bool) $displayoptions->history;
-+}
-+
-+/**
-+ * Given a list of ids, load the basic information about a set of questions from the questions table.
-+ * The $join and $extrafields arguments can be used together to pull in extra data.
-+ * See, for example, the usage in mod/quiz/attemptlib.php, and
-+ * read the code below to see how the SQL is assembled. Throws exceptions on error.
- *
-- * Can be called either with an array of question objects or with a single
-- * question object.
-+ * @global object
-+ * @global object
-+ * @param array $questionids array of question ids.
-+ * @param string $extrafields extra SQL code to be added to the query.
-+ * @param string $join extra SQL code to be added to the query.
-+ * @param array $extraparams values for any placeholders in $join.
-+ * You are strongly recommended to use named placeholder.
- *
-- * @param mixed $questions Either an array of question objects to be updated
-- * or just a single question object
-- * @return bool Indicates success or failure.
-+ * @return array partially complete question objects. You need to call get_question_options
-+ * on them before they can be properly used.
- */
--function get_question_options(&$questions) {
-- if (is_array($questions)) { // deal with an array of questions
-- foreach ($questions as $i => $notused) {
-- if (!_tidy_question($questions[$i])) {
-- return false;
-- }
-- }
-- return true;
-- } else { // deal with single question
-- return _tidy_question($questions);
-- }
--}
--
--/**
--* Loads the most recent state of each question session from the database
--* or create new one.
--*
--* For each question the most recent session state for the current attempt
--* is loaded from the question_states table and the question type specific data and
--* responses are added by calling {@link restore_question_state()} which in turn
--* calls {@link restore_session_and_responses()} for each question.
--* If no states exist for the question instance an empty state object is
--* created representing the start of a session and empty question
--* type specific information and responses are created by calling
--* {@link create_session_and_responses()}.
--*
--* @return array An array of state objects representing the most recent
--* states of the question sessions.
--* @param array $questions The questions for which sessions are to be restored or
--* created.
--* @param object $cmoptions
--* @param object $attempt The attempt for which the question sessions are
--* to be restored or created.
--* @param mixed either the id of a previous attempt, if this attmpt is
--* building on a previous one, or false for a clean attempt.
--*/
--function get_question_states(&$questions, $cmoptions, $attempt, $lastattemptid = false) {
-- global $CFG, $QTYPES;
--
-- // get the question ids
-- $ids = array_keys($questions);
-- $questionlist = implode(',', $ids);
--
-- // The question field must be listed first so that it is used as the
-- // array index in the array returned by get_records_sql
-- $statefields = 'n.questionid as question, s.id, s.attempt, s.originalquestion, ' .
-- 's.seq_number, s.answer, s.timestamp, s.event, s.grade, s.raw_grade, ' .
-- 's.penalty, n.sumpenalty, n.manualcomment';
-- // Load the newest states for the questions
-- $sql = "SELECT $statefields".
-- " FROM {$CFG->prefix}question_states s,".
-- " {$CFG->prefix}question_sessions n".
-- " WHERE s.id = n.newest".
-- " AND n.attemptid = '$attempt->uniqueid'".
-- " AND n.questionid IN ($questionlist)";
-- $states = get_records_sql($sql);
--
-- // Load the newest graded states for the questions
-- $sql = "SELECT $statefields".
-- " FROM {$CFG->prefix}question_states s,".
-- " {$CFG->prefix}question_sessions n".
-- " WHERE s.id = n.newgraded".
-- " AND n.attemptid = '$attempt->uniqueid'".
-- " AND n.questionid IN ($questionlist)";
-- $gradedstates = get_records_sql($sql);
--
-- // loop through all questions and set the last_graded states
-- foreach ($ids as $i) {
-- if (isset($states[$i])) {
-- restore_question_state($questions[$i], $states[$i]);
-- if (isset($gradedstates[$i])) {
-- restore_question_state($questions[$i], $gradedstates[$i]);
-- $states[$i]->last_graded = $gradedstates[$i];
-- } else {
-- $states[$i]->last_graded = clone($states[$i]);
-- }
-- } else {
-- if ($lastattemptid) {
-- // If the new attempt is to be based on this previous attempt.
-- // Find the responses from the previous attempt and save them to the new session
--
-- // Load the last graded state for the question. Note, $statefields is
-- // the same as above, except that we don't want n.manualcomment.
-- $statefields = 'n.questionid as question, s.id, s.attempt, s.originalquestion, ' .
-- 's.seq_number, s.answer, s.timestamp, s.event, s.grade, s.raw_grade, ' .
-- 's.penalty, n.sumpenalty';
-- $sql = "SELECT $statefields".
-- " FROM {$CFG->prefix}question_states s,".
-- " {$CFG->prefix}question_sessions n".
-- " WHERE s.id = n.newest".
-- " AND n.attemptid = '$lastattemptid'".
-- " AND n.questionid = '$i'";
-- if (!$laststate = get_record_sql($sql)) {
-- // Only restore previous responses that have been graded
-- continue;
-- }
-- // Restore the state so that the responses will be restored
-- restore_question_state($questions[$i], $laststate);
-- $states[$i] = clone($laststate);
-- unset($states[$i]->id);
-- } else {
-- // create a new empty state
-- $states[$i] = new object;
-- $states[$i]->question = $i;
-- $states[$i]->responses = array('' => '');
-- $states[$i]->raw_grade = 0;
-- }
--
-- // now fill/overide initial values
-- $states[$i]->attempt = $attempt->uniqueid;
-- $states[$i]->seq_number = 0;
-- $states[$i]->timestamp = $attempt->timestart;
-- $states[$i]->event = ($attempt->timefinish) ? QUESTION_EVENTCLOSE : QUESTION_EVENTOPEN;
-- $states[$i]->grade = 0;
-- $states[$i]->penalty = 0;
-- $states[$i]->sumpenalty = 0;
-- $states[$i]->manualcomment = '';
--
-- // Prevent further changes to the session from incrementing the
-- // sequence number
-- $states[$i]->changed = true;
--
-- if ($lastattemptid) {
-- // prepare the previous responses for new processing
-- $action = new stdClass;
-- $action->responses = $laststate->responses;
-- $action->timestamp = $laststate->timestamp;
-- $action->event = QUESTION_EVENTSAVE; //emulate save of questions from all pages MDL-7631
--
-- // Process these responses ...
-- question_process_responses($questions[$i], $states[$i], $action, $cmoptions, $attempt);
--
-- // Fix for Bug #5506: When each attempt is built on the last one,
-- // preserve the options from any previous attempt.
-- if ( isset($laststate->options) ) {
-- $states[$i]->options = $laststate->options;
-- }
-- } else {
-- // Create the empty question type specific information
-- if (!$QTYPES[$questions[$i]->qtype]->create_session_and_responses(
-- $questions[$i], $states[$i], $cmoptions, $attempt)) {
-- return false;
-- }
-- }
-- $states[$i]->last_graded = clone($states[$i]);
-- }
-+function question_preload_questions($questionids, $extrafields = '', $join = '') {
-+ global $CFG;
-+ if (empty($questionids)) {
-+ return array();
- }
-- return $states;
--}
--
--
--/**
--* Creates the run-time fields for the states
--*
--* Extends the state objects for a question by calling
--* {@link restore_session_and_responses()}
--* @param object $question The question for which the state is needed
--* @param object $state The state as loaded from the database
--* @return boolean Represents success or failure
--*/
--function restore_question_state(&$question, &$state) {
-- global $QTYPES;
--
-- // initialise response to the value in the answer field
-- $state->responses = array('' => addslashes($state->answer));
-- unset($state->answer);
-- $state->manualcomment = isset($state->manualcomment) ? addslashes($state->manualcomment) : '';
--
-- // Set the changed field to false; any code which changes the
-- // question session must set this to true and must increment
-- // ->seq_number. The save_question_session
-- // function will save the new state object to the database if the field is
-- // set to true.
-- $state->changed = false;
--
-- // Load the question type specific data
-- return $QTYPES[$question->qtype]
-- ->restore_session_and_responses($question, $state);
--
--}
--
--/**
--* Saves the current state of the question session to the database
--*
--* The state object representing the current state of the session for the
--* question is saved to the question_states table with ->responses[''] saved
--* to the answer field of the database table. The information in the
--* question_sessions table is updated.
--* The question type specific data is then saved.
--* @return mixed The id of the saved or updated state or false
--* @param object $question The question for which session is to be saved.
--* @param object $state The state information to be saved. In particular the
--* most recent responses are in ->responses. The object
--* is updated to hold the new ->id.
--*/
--function save_question_session(&$question, &$state) {
-- global $QTYPES;
-- // Check if the state has changed
-- if (!$state->changed && isset($state->id)) {
-- return $state->id;
-+ if ($join) {
-+ $join = ' JOIN '.$join;
- }
-- // Set the legacy answer field
-- $state->answer = isset($state->responses['']) ? $state->responses[''] : '';
--
-- // Save the state
-- if (!empty($state->update)) { // this forces the old state record to be overwritten
-- update_record('question_states', $state);
-- } else {
-- if (!$state->id = insert_record('question_states', $state)) {
-- unset($state->id);
-- unset($state->answer);
-- return false;
-- }
-+ if ($extrafields) {
-+ $extrafields = ', ' . $extrafields;
- }
-+ $sql = 'SELECT q.*' . $extrafields . " FROM {$CFG->prefix}question q" . $join .
-+ ' WHERE q.id IN (' . implode(',', $questionids) . ')';
-
-- // create or update the session
-- if (!$session = get_record('question_sessions', 'attemptid',
-- $state->attempt, 'questionid', $question->id)) {
-- $session->attemptid = $state->attempt;
-- $session->questionid = $question->id;
-- $session->newest = $state->id;
-- // The following may seem weird, but the newgraded field needs to be set
-- // already even if there is no graded state yet.
-- $session->newgraded = $state->id;
-- $session->sumpenalty = $state->sumpenalty;
-- $session->manualcomment = $state->manualcomment;
-- if (!insert_record('question_sessions', $session)) {
-- error('Could not insert entry in question_sessions');
-- }
-- } else {
-- $session->newest = $state->id;
-- if (question_state_is_graded($state) or $state->event == QUESTION_EVENTOPEN) {
-- // this state is graded or newly opened, so it goes into the lastgraded field as well
-- $session->newgraded = $state->id;
-- $session->sumpenalty = $state->sumpenalty;
-- $session->manualcomment = $state->manualcomment;
-- } else {
-- $session->manualcomment = addslashes($session->manualcomment);
-- }
-- update_record('question_sessions', $session);
-+ // Load the questions
-+ if (!$questions = get_records_sql($sql)) {
-+ return array();
- }
-
-- unset($state->answer);
--
-- // Save the question type specific state information and responses
-- if (!$QTYPES[$question->qtype]->save_session_and_responses(
-- $question, $state)) {
-- return false;
-+ foreach ($questions as $question) {
-+ $question->_partiallyloaded = true;
- }
-- // Reset the changed flag
-- $state->changed = false;
-- return $state->id;
--}
-
--/**
--* Determines whether a state has been graded by looking at the event field
--*
--* @return boolean true if the state has been graded
--* @param object $state
--*/
--function question_state_is_graded($state) {
-- $gradedevents = explode(',', QUESTION_EVENTS_GRADED);
-- return (in_array($state->event, $gradedevents));
--}
-+ // Note, a possible optimisation here would be to not load the TEXT fields
-+ // (that is, questiontext and generalfeedback) here, and instead load them in
-+ // question_load_questions. That would add one DB query, but reduce the amount
-+ // of data transferred most of the time. I am not going to do this optimisation
-+ // until it is shown to be worthwhile.
-
--/**
--* Determines whether a state has been closed by looking at the event field
--*
--* @return boolean true if the state has been closed
--* @param object $state
--*/
--function question_state_is_closed($state) {
-- return ($state->event == QUESTION_EVENTCLOSE
-- or $state->event == QUESTION_EVENTCLOSEANDGRADE
-- or $state->event == QUESTION_EVENTMANUALGRADE);
-+ return $questions;
- }
-
--
- /**
-- * Extracts responses from submitted form
-+ * Load a set of questions, given a list of ids. The $join and $extrafields arguments can be used
-+ * together to pull in extra data. See, for example, the usage in mod/quiz/attempt.php, and
-+ * read the code below to see how the SQL is assembled. Throws exceptions on error.
- *
-- * This can extract the responses given to one or several questions present on a page
-- * It returns an array with one entry for each question, indexed by question id
-- * Each entry is an object with the properties
-- * ->event The event that has triggered the submission. This is determined by which button
-- * the user has pressed.
-- * ->responses An array holding the responses to an individual question, indexed by the
-- * name of the corresponding form element.
-- * ->timestamp A unix timestamp
-- * @return array array of action objects, indexed by question ids.
-- * @param array $questions an array containing at least all questions that are used on the form
-- * @param array $formdata the data submitted by the form on the question page
-- * @param integer $defaultevent the event type used if no 'mark' or 'validate' is submitted
-+ * @param array $questionids array of question ids.
-+ * @param string $extrafields extra SQL code to be added to the query.
-+ * @param string $join extra SQL code to be added to the query.
-+ * @param array $extraparams values for any placeholders in $join.
-+ * You are strongly recommended to use named placeholder.
-+ *
-+ * @return array question objects.
- */
--function question_extract_responses($questions, $formdata, $defaultevent=QUESTION_EVENTSAVE) {
--
-- $time = time();
-- $actions = array();
-- foreach ($formdata as $key => $response) {
-- // Get the question id from the response name
-- if (false !== ($quid = question_get_id_from_name_prefix($key))) {
-- // check if this is a valid id
-- if (!isset($questions[$quid])) {
-- error('Form contained question that is not in questionids');
-- }
--
-- // Remove the name prefix from the name
-- //decrypt trying
-- $key = substr($key, strlen($questions[$quid]->name_prefix));
-- if (false === $key) {
-- $key = '';
-- }
-- // Check for question validate and mark buttons & set events
-- if ($key === 'validate') {
-- $actions[$quid]->event = QUESTION_EVENTVALIDATE;
-- } else if ($key === 'submit') {
-- $actions[$quid]->event = QUESTION_EVENTSUBMIT;
-- } else {
-- $actions[$quid]->event = $defaultevent;
-- }
--
-- // Update the state with the new response
-- $actions[$quid]->responses[$key] = $response;
-+function question_load_questions($questionids, $extrafields = '', $join = '') {
-+ $questions = question_preload_questions($questionids, $extrafields, $join);
-
-- // Set the timestamp
-- $actions[$quid]->timestamp = $time;
-- }
-- }
-- foreach ($actions as $quid => $notused) {
-- ksort($actions[$quid]->responses);
-+ // Load the question type specific information
-+ if (!get_question_options($questions)) {
-+ return 'Could not load the question options';
- }
-- return $actions;
--}
--
--
--/**
-- * Returns the html for question feedback image.
-- * @param float $fraction value representing the correctness of the user's
-- * response to a question.
-- * @param boolean $selected whether or not the answer is the one that the
-- * user picked.
-- * @return string
-- */
--function question_get_feedback_image($fraction, $selected=true) {
--
-- global $CFG;
-
-- if ($fraction >= 1.0) {
-- if ($selected) {
-- $feedbackimg = '<img src="'.$CFG->pixpath.'/i/tick_green_big.gif" '.
-- 'alt="'.get_string('correct', 'quiz').'" class="icon" />';
-- } else {
-- $feedbackimg = '<img src="'.$CFG->pixpath.'/i/tick_green_small.gif" '.
-- 'alt="'.get_string('correct', 'quiz').'" class="icon" />';
-- }
-- } else if ($fraction > 0.0 && $fraction < 1.0) {
-- if ($selected) {
-- $feedbackimg = '<img src="'.$CFG->pixpath.'/i/tick_amber_big.gif" '.
-- 'alt="'.get_string('partiallycorrect', 'quiz').'" class="icon" />';
-- } else {
-- $feedbackimg = '<img src="'.$CFG->pixpath.'/i/tick_amber_small.gif" '.
-- 'alt="'.get_string('partiallycorrect', 'quiz').'" class="icon" />';
-- }
-- } else {
-- if ($selected) {
-- $feedbackimg = '<img src="'.$CFG->pixpath.'/i/cross_red_big.gif" '.
-- 'alt="'.get_string('incorrect', 'quiz').'" class="icon" />';
-- } else {
-- $feedbackimg = '<img src="'.$CFG->pixpath.'/i/cross_red_small.gif" '.
-- 'alt="'.get_string('incorrect', 'quiz').'" class="icon" />';
-- }
-- }
-- return $feedbackimg;
-+ return $questions;
- }
-
--
- /**
-- * Returns the class name for question feedback.
-- * @param float $fraction value representing the correctness of the user's
-- * response to a question.
-- * @return string
-+ * Private function to factor common code out of get_question_options().
-+ *
-+ * @param object $question the question to tidy.
-+ * @param boolean $loadtags load the question tags from the tags table. Optional, default false.
-+ * @return boolean true if successful, else false.
- */
--function question_get_feedback_class($fraction) {
--
-- global $CFG;
--
-- if ($fraction >= 1.0) {
-- $class = 'correct';
-- } else if ($fraction > 0.0 && $fraction < 1.0) {
-- $class = 'partiallycorrect';
-- } else {
-- $class = 'incorrect';
-+function _tidy_question(&$question, $loadtags = false) {
-+ global $CFG, $QTYPES;
-+ if (!array_key_exists($question->qtype, $QTYPES)) {
-+ $question->qtype = 'missingtype';
-+ $question->questiontext = '<p>' . get_string('warningmissingtype', 'quiz') . '</p>' . $question->questiontext;
- }
-- return $class;
--}
--
--
--/**
--* For a given question in an attempt we walk the complete history of states
--* and recalculate the grades as we go along.
--*
--* This is used when a question is changed and old student
--* responses need to be marked with the new version of a question.
--*
--* TODO: Make sure this is not quiz-specific
--*
--* @return boolean Indicates whether the grade has changed
--* @param object $question A question object
--* @param object $attempt The attempt, in which the question needs to be regraded.
--* @param object $cmoptions
--* @param boolean $verbose Optional. Whether to print progress information or not.
--*/
--function regrade_question_in_attempt($question, $attempt, $cmoptions, $verbose=false) {
--
-- // load all states for this question in this attempt, ordered in sequence
-- if ($states = get_records_select('question_states',
-- "attempt = '{$attempt->uniqueid}' AND question = '{$question->id}'",
-- 'seq_number ASC')) {
-- $states = array_values($states);
--
-- // Subtract the grade for the latest state from $attempt->sumgrades to get the
-- // sumgrades for the attempt without this question.
-- $attempt->sumgrades -= $states[count($states)-1]->grade;
--
-- // Initialise the replaystate
-- $state = clone($states[0]);
-- $state->manualcomment = get_field('question_sessions', 'manualcomment', 'attemptid',
-- $attempt->uniqueid, 'questionid', $question->id);
-- restore_question_state($question, $state);
-- $state->sumpenalty = 0.0;
-- $replaystate = clone($state);
-- $replaystate->last_graded = $state;
--
-- $changed = false;
-- for($j = 1; $j < count($states); $j++) {
-- restore_question_state($question, $states[$j]);
-- $action = new stdClass;
-- $action->responses = $states[$j]->responses;
-- $action->timestamp = $states[$j]->timestamp;
--
-- // Change event to submit so that it will be reprocessed
-- if (QUESTION_EVENTCLOSE == $states[$j]->event
-- or QUESTION_EVENTGRADE == $states[$j]->event
-- or QUESTION_EVENTCLOSEANDGRADE == $states[$j]->event) {
-- $action->event = QUESTION_EVENTSUBMIT;
--
-- // By default take the event that was saved in the database
-- } else {
-- $action->event = $states[$j]->event;
-- }
--
-- if ($action->event == QUESTION_EVENTMANUALGRADE) {
-- // Ensure that the grade is in range - in the past this was not checked,
-- // but now it is (MDL-14835) - so we need to ensure the data is valid before
-- // proceeding.
-- if ($states[$j]->grade < 0) {
-- $states[$j]->grade = 0;
-- } else if ($states[$j]->grade > $question->maxgrade) {
-- $states[$j]->grade = $question->maxgrade;
-- }
-- $error = question_process_comment($question, $replaystate, $attempt,
-- $replaystate->manualcomment, $states[$j]->grade);
-- if (is_string($error)) {
-- notify($error);
-- }
-- } else {
--
-- // Reprocess (regrade) responses
-- if (!question_process_responses($question, $replaystate,
-- $action, $cmoptions, $attempt)) {
-- $verbose && notify("Couldn't regrade state #{$state->id}!");
-- }
-- }
--
-- // We need rounding here because grades in the DB get truncated
-- // e.g. 0.33333 != 0.3333333, but we want them to be equal here
-- if ((round((float)$replaystate->raw_grade, 5) != round((float)$states[$j]->raw_grade, 5))
-- or (round((float)$replaystate->penalty, 5) != round((float)$states[$j]->penalty, 5))
-- or (round((float)$replaystate->grade, 5) != round((float)$states[$j]->grade, 5))) {
-- $changed = true;
-- }
--
-- $replaystate->id = $states[$j]->id;
-- $replaystate->changed = true;
-- $replaystate->update = true; // This will ensure that the existing database entry is updated rather than a new one created
-- save_question_session($question, $replaystate);
-+ if ($success = $QTYPES[$question->qtype]->get_question_options($question)) {
-+ if (isset($question->_partiallyloaded)) {
-+ unset($question->_partiallyloaded);
- }
-- if ($changed) {
-- // TODO, call a method in quiz to do this, where 'quiz' comes from
-- // the question_attempts table.
-- update_record('quiz_attempts', $attempt);
-- }
--
-- return $changed;
-- }
-- return false;
--}
--
--/**
--* Processes an array of student responses, grading and saving them as appropriate
--*
--* @param object $question Full question object, passed by reference
--* @param object $state Full state object, passed by reference
--* @param object $action object with the fields ->responses which
--* is an array holding the student responses,
--* ->action which specifies the action, e.g., QUESTION_EVENTGRADE,
--* and ->timestamp which is a timestamp from when the responses
--* were submitted by the student.
--* @param object $cmoptions
--* @param object $attempt The attempt is passed by reference so that
--* during grading its ->sumgrades field can be updated
--* @return boolean Indicates success/failure
--*/
--function question_process_responses(&$question, &$state, $action, $cmoptions, &$attempt) {
-- global $QTYPES;
--
-- // if no responses are set initialise to empty response
-- if (!isset($action->responses)) {
-- $action->responses = array('' => '');
- }
--
-- // make sure these are gone!
-- unset($action->responses['submit'], $action->responses['validate']);
--
-- // Check the question session is still open
-- if (question_state_is_closed($state)) {
-- return true;
-- }
--
-- // If $action->event is not set that implies saving
-- if (! isset($action->event)) {
-- debugging('Ambiguous action in question_process_responses.' , DEBUG_DEVELOPER);
-- $action->event = QUESTION_EVENTSAVE;
-- }
-- // If submitted then compare against last graded
-- // responses, not last given responses in this case
-- if (question_isgradingevent($action->event)) {
-- $state->responses = $state->last_graded->responses;
-- }
--
-- // Check for unchanged responses (exactly unchanged, not equivalent).
-- // We also have to catch questions that the student has not yet attempted
-- $sameresponses = $QTYPES[$question->qtype]->compare_responses($question, $action, $state);
-- if (!empty($state->last_graded) && $state->last_graded->event == QUESTION_EVENTOPEN &&
-- question_isgradingevent($action->event)) {
-- $sameresponses = false;
-- }
--
-- // If the response has not been changed then we do not have to process it again
-- // unless the attempt is closing or validation is requested
-- if ($sameresponses and QUESTION_EVENTCLOSE != $action->event
-- and QUESTION_EVENTVALIDATE != $action->event) {
-- return true;
-- }
--
-- // Roll back grading information to last graded state and set the new
-- // responses
-- $newstate = clone($state->last_graded);
-- $newstate->responses = $action->responses;
-- $newstate->seq_number = $state->seq_number + 1;
-- $newstate->changed = true; // will assure that it gets saved to the database
-- $newstate->last_graded = clone($state->last_graded);
-- $newstate->timestamp = $action->timestamp;
-- $state = $newstate;
--
-- // Set the event to the action we will perform. The question type specific
-- // grading code may override this by setting it to QUESTION_EVENTCLOSE if the
-- // attempt at the question causes the session to close
-- $state->event = $action->event;
--
-- if (!question_isgradingevent($action->event)) {
-- // Grade the response but don't update the overall grade
-- if (!$QTYPES[$question->qtype]->grade_responses($question, $state, $cmoptions)) {
-- return false;
-- }
--
-- // Temporary hack because question types are not given enough control over what is going
-- // on. Used by Opaque questions.
-- // TODO fix this code properly.
-- if (!empty($state->believeevent)) {
-- // If the state was graded we need to ...
-- if (question_state_is_graded($state)) {
-- question_apply_penalty_and_timelimit($question, $state, $attempt, $cmoptions);
--
-- // update the attempt grade
-- $attempt->sumgrades -= (float)$state->last_graded->grade;
-- $attempt->sumgrades += (float)$state->grade;
--
-- // and update the last_graded field.
-- unset($state->last_graded);
-- $state->last_graded = clone($state);
-- unset($state->last_graded->changed);
-- }
-- } else {
-- // Don't allow the processing to change the event type
-- $state->event = $action->event;
-- }
--
-- } else { // grading event
--
-- // Unless the attempt is closing, we want to work out if the current responses
-- // (or equivalent responses) were already given in the last graded attempt.
-- if(QUESTION_EVENTCLOSE != $action->event && QUESTION_EVENTOPEN != $state->last_graded->event &&
-- $QTYPES[$question->qtype]->compare_responses($question, $state, $state->last_graded)) {
-- $state->event = QUESTION_EVENTDUPLICATE;
-- }
--
-- // If we did not find a duplicate or if the attempt is closing, perform grading
-- if ((!$sameresponses and QUESTION_EVENTDUPLICATE != $state->event) or
-- QUESTION_EVENTCLOSE == $action->event) {
-- if (!$QTYPES[$question->qtype]->grade_responses($question, $state, $cmoptions)) {
-- return false;
-- }
--
-- // Calculate overall grade using correct penalty method
-- question_apply_penalty_and_timelimit($question, $state, $attempt, $cmoptions);
-- }
--
-- // If the state was graded we need to ...
-- if (question_state_is_graded($state)) {
-- // update the attempt grade
-- $attempt->sumgrades -= (float)$state->last_graded->grade;
-- $attempt->sumgrades += (float)$state->grade;
--
-- // and update the last_graded field.
-- unset($state->last_graded);
-- $state->last_graded = clone($state);
-- unset($state->last_graded->changed);
-- }
-+ if ($loadtags && !empty($CFG->usetags)) {
-+ require_once($CFG->dirroot . '/tag/lib.php');
-+ $question->tags = tag_get_tags_array('question', $question->id);
- }
-- $attempt->timemodified = $action->timestamp;
--
-- return true;
-+ return $success;
- }
-
- /**
--* Determine if event requires grading
--*/
--function question_isgradingevent($event) {
-- return (QUESTION_EVENTSUBMIT == $event || QUESTION_EVENTCLOSE == $event);
--}
--
--/**
--* Applies the penalty from the previous graded responses to the raw grade
--* for the current responses
--*
--* The grade for the question in the current state is computed by subtracting the
--* penalty accumulated over the previous graded responses at the question from the
--* raw grade. If the timestamp is more than 1 minute beyond the end of the attempt
--* the grade is set to zero. The ->grade field of the state object is modified to
--* reflect the new grade but is never allowed to decrease.
--* @param object $question The question for which the penalty is to be applied.
--* @param object $state The state for which the grade is to be set from the
--* raw grade and the cumulative penalty from the last
--* graded state. The ->grade field is updated by applying
--* the penalty scheme determined in $cmoptions to the ->raw_grade and
--* ->last_graded->penalty fields.
--* @param object $cmoptions The options set by the course module.
--* The ->penaltyscheme field determines whether penalties
--* for incorrect earlier responses are subtracted.
--*/
--function question_apply_penalty_and_timelimit(&$question, &$state, $attempt, $cmoptions) {
-- // TODO. Quiz dependancy. The fact that the attempt that is passed in here
-- // is from quiz_attempts, and we use things like $cmoptions->timelimit.
--
-- // deal with penalty
-- if ($cmoptions->penaltyscheme) {
-- $state->grade = $state->raw_grade - $state->sumpenalty;
-- $state->sumpenalty += (float) $state->penalty;
-- } else {
-- $state->grade = $state->raw_grade;
-- }
--
-- // deal with timelimit
-- if ($cmoptions->timelimit) {
-- // We allow for 5% uncertainty in the following test
-- if ($state->timestamp - $attempt->timestart > $cmoptions->timelimit * 63) {
-- $cm = get_coursemodule_from_instance('quiz', $cmoptions->id);
-- if (!has_capability('mod/quiz:ignoretimelimits', get_context_instance(CONTEXT_MODULE, $cm->id),
-- $attempt->userid, false)) {
-- $state->grade = 0;
-+ * Updates the question objects with question type specific
-+ * information by calling {@link get_question_options()}
-+ *
-+ * Can be called either with an array of question objects or with a single
-+ * question object.
-+ *
-+ * @param mixed $questions Either an array of question objects to be updated
-+ * or just a single question object
-+ * @param boolean $loadtags load the question tags from the tags table. Optional, default false.
-+ * @return bool Indicates success or failure.
-+ */
-+function get_question_options(&$questions, $loadtags = false) {
-+ if (is_array($questions)) { // deal with an array of questions
-+ foreach ($questions as $i => $notused) {
-+ if (!_tidy_question($questions[$i], $loadtags)) {
-+ return false;
- }
- }
-+ return true;
-+ } else { // deal with single question
-+ return _tidy_question($questions, $loadtags);
- }
--
-- // deal with closing time
-- if ($cmoptions->timeclose and $state->timestamp > ($cmoptions->timeclose + 60) // allowing 1 minute lateness
-- and !$attempt->preview) { // ignore closing time for previews
-- $state->grade = 0;
-- }
--
-- // Ensure that the grade does not go down
-- $state->grade = max($state->grade, $state->last_graded->grade);
- }
-
- /**
-@@ -1497,168 +804,6 @@ function print_question_icon($question, $return = false) {
- }
-
- /**
--* Returns a html link to the question image if there is one
--*
--* @return string The html image tag or the empy string if there is no image.
--* @param object $question The question object
--*/
--function get_question_image($question) {
--
-- global $CFG;
-- $img = '';
--
-- if (!$category = get_record('question_categories', 'id', $question->category)){
-- error('invalid category id '.$question->category);
-- }
-- $coursefilesdir = get_filesdir_from_context(get_context_instance_by_id($category->contextid));
--
-- if ($question->image) {
--
-- if (substr(strtolower($question->image), 0, 7) == 'http://') {
-- $img .= $question->image;
--
-- } else {
-- require_once($CFG->libdir .'/filelib.php');
-- $img = get_file_url("$coursefilesdir/{$question->image}");
-- }
-- }
-- return $img;
--}
--
--function question_print_comment_box($question, $state, $attempt, $url) {
-- global $CFG, $QTYPES;
--
-- $prefix = 'response';
-- $usehtmleditor = can_use_richtext_editor();
-- if (!question_state_is_graded($state) && $QTYPES[$question->qtype]->is_question_manual_graded($question, $attempt->layout)) {
-- $grade = '';
-- } else {
-- $grade = round($state->last_graded->grade, 3);
-- }
-- echo '<form method="post" action="'.$url.'">';
-- include($CFG->dirroot.'/question/comment.html');
-- echo '<input type="hidden" name="attempt" value="'.$attempt->uniqueid.'" />';
-- echo '<input type="hidden" name="question" value="'.$question->id.'" />';
-- echo '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
-- echo '<input type="submit" name="submit" value="'.get_string('save', 'quiz').'" />';
-- echo '</form>';
--
-- if ($usehtmleditor) {
-- use_html_editor();
-- }
--}
--
--/**
-- * Process a manual grading action. That is, use $comment and $grade to update
-- * $state and $attempt. The attempt and the comment text are stored in the
-- * database. $state is only updated in memory, it is up to the call to store
-- * that, if appropriate.
-- *
-- * @param object $question the question
-- * @param object $state the state to be updated.
-- * @param object $attempt the attempt the state belongs to, to be updated.
-- * @param string $comment the new comment from the teacher.
-- * @param mixed $grade the grade the teacher assigned, or '' to not change the grade.
-- * @return mixed true on success, a string error message if a problem is detected
-- * (for example score out of range).
-- */
--function question_process_comment($question, &$state, &$attempt, $comment, $grade) {
-- $grade = trim($grade);
-- if ($grade < 0 || $grade > $question->maxgrade) {
-- $a = new stdClass;
-- $a->grade = $grade;
-- $a->maxgrade = $question->maxgrade;
-- $a->name = $question->name;
-- return get_string('errormanualgradeoutofrange', 'question', $a);
-- }
--
-- // Update the comment and save it in the database
-- $comment = trim($comment);
-- $state->manualcomment = $comment;
-- if (!set_field('question_sessions', 'manualcomment', $comment, 'attemptid', $attempt->uniqueid, 'questionid', $question->id)) {
-- return get_string('errorsavingcomment', 'question', $question);
-- }
--
-- // Update the attempt if the score has changed.
-- if ($grade !== '' && (abs($state->last_graded->grade - $grade) > 0.002 || $state->last_graded->event != QUESTION_EVENTMANUALGRADE)) {
-- $attempt->sumgrades = $attempt->sumgrades - $state->last_graded->grade + $grade;
-- $attempt->timemodified = time();
-- if (!update_record('quiz_attempts', $attempt)) {
-- return get_string('errorupdatingattempt', 'question', $attempt);
-- }
--
-- // We want to update existing state (rather than creating new one) if it
-- // was itself created by a manual grading event.
-- $state->update = $state->event == QUESTION_EVENTMANUALGRADE;
--
-- // Update the other parts of the state object.
-- $state->raw_grade = $grade;
-- $state->grade = $grade;
-- $state->penalty = 0;
-- $state->timestamp = time();
-- $state->seq_number++;
-- $state->event = QUESTION_EVENTMANUALGRADE;
--
-- // Update the last graded state (don't simplify!)
-- unset($state->last_graded);
-- $state->last_graded = clone($state);
--
-- // We need to indicate that the state has changed in order for it to be saved.
-- $state->changed = 1;
-- }
--
-- return true;
--}
--
--/**
--* Construct name prefixes for question form element names
--*
--* Construct the name prefix that should be used for example in the
--* names of form elements created by questions.
--* This is called by {@link get_question_options()}
--* to set $question->name_prefix.
--* This name prefix includes the question id which can be
--* extracted from it with {@link question_get_id_from_name_prefix()}.
--*
--* @return string
--* @param integer $id The question id
--*/
--function question_make_name_prefix($id) {
-- return 'resp' . $id . '_';
--}
--
--/**
--* Extract question id from the prefix of form element names
--*
--* @return integer The question id
--* @param string $name The name that contains a prefix that was
--* constructed with {@link question_make_name_prefix()}
--*/
--function question_get_id_from_name_prefix($name) {
-- if (!preg_match('/^resp([0-9]+)_/', $name, $matches))
-- return false;
-- return (integer) $matches[1];
--}
--
--/**
-- * Returns the unique id for a new attempt
-- *
-- * Every module can keep their own attempts table with their own sequential ids but
-- * the question code needs to also have a unique id by which to identify all these
-- * attempts. Hence a module, when creating a new attempt, calls this function and
-- * stores the return value in the 'uniqueid' field of its attempts table.
-- */
--function question_new_attempt_uniqueid($modulename='quiz') {
-- global $CFG;
-- $attempt = new stdClass;
-- $attempt->modulename = $modulename;
-- if (!$id = insert_record('question_attempts', $attempt)) {
-- error('Could not create new entry in question_attempts table');
-- }
-- return $id;
--}
--
--/**
- * Creates a stamp that uniquely identifies this version of the question
- *
- * In future we want this to use a hash of the question data to guarantee that
-@@ -1674,33 +819,8 @@ function question_hash($question) {
-
- /// FUNCTIONS THAT SIMPLY WRAP QUESTIONTYPE METHODS //////////////////////////////////
- /**
-- * Get the HTML that needs to be included in the head tag when the
-- * questions in $questionlist are printed in the gives states.
-- *
-- * @param array $questionlist a list of questionids of the questions what will appear on this page.
-- * @param array $questions an array of question objects, whose keys are question ids.
-- * Must contain all the questions in $questionlist
-- * @param array $states an array of question state objects, whose keys are question ids.
-- * Must contain the state of all the questions in $questionlist
-- *
-- * @return string some HTML code that can go inside the head tag.
-- */
--function get_html_head_contributions(&$questionlist, &$questions, &$states) {
-- global $QTYPES;
--
-- $contributions = array();
-- foreach ($questionlist as $questionid) {
-- $question = $questions[$questionid];
-- $contributions = array_merge($contributions,
-- $QTYPES[$question->qtype]->get_html_head_contributions(
-- $question, $states[$questionid]));
-- }
-- return implode("\n", array_unique($contributions));
--}
--
--/**
-- * Like @see{get_html_head_contributions} but for the editing page
-- * question/question.php.
-+ * Get anything that needs to be included in the head of the question editing page
-+ * for a particular question type. This function is called by question/question.php.
- *
- * @param $question A question object. Only $question->qtype is used.
- * @return string some HTML code that can go inside the head tag.
-@@ -1712,20 +832,6 @@ function get_editing_head_contributions($question) {
- }
-
- /**
-- * Prints a question
-- *
-- * Simply calls the question type specific print_question() method.
-- * @param object $question The question to be rendered.
-- * @param object $state The state to render the question in.
-- * @param integer $number The number for this question.
-- * @param object $cmoptions The options specified by the course module
-- * @param object $options An object specifying the rendering options.
-- */
--function print_question(&$question, &$state, $number, $cmoptions, $options=null) {
-- global $QTYPES;
-- $QTYPES[$question->qtype]->print_question($question, $state, $number, $cmoptions, $options);
--}
--/**
- * Saves question options
- *
- * Simply calls the question type specific save_question_options() method.
-@@ -1736,44 +842,6 @@ function save_question_options($question) {
- $QTYPES[$question->qtype]->save_question_options($question);
- }
-
--/**
--* Gets all teacher stored answers for a given question
--*
--* Simply calls the question type specific get_all_responses() method.
--*/
--// ULPGC ecastro
--function get_question_responses($question, $state) {
-- global $QTYPES;
-- $r = $QTYPES[$question->qtype]->get_all_responses($question, $state);
-- return $r;
--}
--
--
--/**
--* Gets the response given by the user in a particular state
--*
--* Simply calls the question type specific get_actual_response() method.
--*/
--// ULPGC ecastro
--function get_question_actual_response($question, $state) {
-- global $QTYPES;
--
-- $r = $QTYPES[$question->qtype]->get_actual_response($question, $state);
-- return $r;
--}
--
--/**
--* TODO: document this
--*/
--// ULPGc ecastro
--function get_question_fraction_grade($question, $state) {
-- global $QTYPES;
--
-- $r = $QTYPES[$question->qtype]->get_fractional_grade($question, $state);
-- return $r;
--}
--
--
- /// CATEGORY FUNCTIONS /////////////////////////////////////////////////////////////////
-
- /**
-@@ -1796,7 +864,7 @@ function sort_categories_by_tree(&$categories, $id = 0, $level = 1) {
- if ($level == 1) {
- foreach ($keys as $key) {
- //If not processed and it's a good candidate to start (because its parent doesn't exist in the course)
-- if (!isset($categories[$key]->processed) && !record_exists('question_categories', 'course', $categories[$key]->course, 'id', $categories[$key]->parent)) {
-+ if (!isset($categories[$key]->processed) && !record_exists('question_categories', 'contextid', $categories[$key]->contextid, 'id', $categories[$key]->parent)) {
- $children[$key] = $categories[$key];
- $categories[$key]->processed = true;
- $children = $children + sort_categories_by_tree($categories, $children[$key]->id, $level+1);
-@@ -2019,6 +1087,7 @@ function question_add_context_in_key($categories){
- }
- return $newcatarray;
- }
-+
- function question_add_tops($categories, $pcontexts){
- $topcats = array();
- foreach ($pcontexts as $context){
-@@ -2048,8 +1117,6 @@ function question_categorylist($categoryid) {
- }
-
-
--
--
- //===========================
- // Import/Export Functions
- //===========================
-@@ -2098,7 +1165,6 @@ function get_import_export_formats( $type ) {
- return $fileformatnames;
- }
-
--
- /**
- * Create default export filename
- *
-@@ -2144,6 +1210,7 @@ function default_export_filename($course,$category) {
-
- return $export_name;
- }
-+
- class context_to_string_translator{
- /**
- * @var array used to translate between contextids and strings for this context.
-@@ -2352,4 +1419,3 @@ function get_filesdir_from_context($context){
- }
- return $courseid;
- }
--?>
+++ /dev/null
-diff --git a/question/backuplib.php b/question/backuplib.php
-index 6d37298..0e2cf4b 100644
---- a/question/backuplib.php
-+++ b/question/backuplib.php
-@@ -192,7 +192,7 @@
- fwrite ($bf,full_tag("QUESTIONTEXTFORMAT",$level + 2,false,$question->questiontextformat));
- fwrite ($bf,full_tag("IMAGE",$level + 2,false,$question->image));
- fwrite ($bf,full_tag("GENERALFEEDBACK",$level + 2,false,$question->generalfeedback));
-- fwrite ($bf,full_tag("DEFAULTGRADE",$level + 2,false,$question->defaultgrade));
-+ fwrite ($bf,full_tag("DEFAULTGRADE",$level + 2,false,$question->defaultmark));
- fwrite ($bf,full_tag("PENALTY",$level + 2,false,$question->penalty));
- fwrite ($bf,full_tag("QTYPE",$level + 2,false,$question->qtype));
- fwrite ($bf,full_tag("LENGTH",$level + 2,false,$question->length));
-@@ -205,6 +205,9 @@
- fwrite ($bf,full_tag("MODIFIEDBY",$level + 2,false,$question->modifiedby));
- // Backup question type specific data
- $status = $status && $QTYPES[$question->qtype]->backup($bf,$preferences,$question->id, $level + 2);
-+
-+ $status = $status && backup_question_hints($bf, $preferences, $question->id, $level + 2);
-+
- //End question
- $status = $status && fwrite ($bf,end_tag("QUESTION",$level + 1,true));
- //Do some output
-@@ -223,6 +226,27 @@
- return $status;
- }
-
-+ function question_backup_hints($bf, $preferences, $questionid, $level = 6) {
-+ $hints = get_records('question_hints', 'questionid', $questionid, 'id');
-+ if (!$hints) {
-+ return true;
-+ }
-+
-+ $status = true;
-+ $status = $status && fwrite($bf, start_tag("HINTS", $level, true));
-+ foreach ($hints as $hint) {
-+ $status = $status && fwrite($bf, start_tag("HINT", $level + 1, true));
-+ $status = $status && fwrite($bf, full_tag("HINT_TEXT", $level + 2, false, $hint->hint));
-+ $status = $status && fwrite($bf, full_tag("SHOWNUMCORRECT", $level + 2, false, $hint->shownumcorrect));
-+ $status = $status && fwrite($bf, full_tag("CLEARWRONG", $level + 2, false, $hint->clearwrong));
-+ $status = $status && fwrite($bf, full_tag("OPTIONS", $level + 2, false, $hint->options));
-+ $status = $status && fwrite($bf, end_tag("HINT", $level + 1, true));
-+ }
-+ $status = $status && fwrite($bf, end_tag("HINTS", $level, true));
-+
-+ return $status;
-+ }
-+
- //This function backups the answers data in some question types
- //(truefalse, shortanswer,multichoice,numerical,calculated)
- function question_backup_answers($bf,$preferences,$question, $level = 6) {
-diff --git a/question/editlib.php b/question/editlib.php
-index c18daf9..e21a4ce 100644
---- a/question/editlib.php
-+++ b/question/editlib.php
-@@ -1,17 +1,33 @@
--<?php // $Id$
-+<?php
-+
-+// This file is part of Moodle - http://moodle.org/
-+//
-+// Moodle is free software: you can redistribute it and/or modify
-+// it under the terms of the GNU General Public License as published by
-+// the Free Software Foundation, either version 3 of the License, or
-+// (at your option) any later version.
-+//
-+// Moodle is distributed in the hope that it will be useful,
-+// but WITHOUT ANY WARRANTY; without even the implied warranty of
-+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-+// GNU General Public License for more details.
-+//
-+// You should have received a copy of the GNU General Public License
-+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
-+
-+
- /**
- * Functions used to show question editing interface
- *
-- *
-- * @author Martin Dougiamas and many others. This has recently been extensively
-- * rewritten by members of the Serving Mathematics project
-- * {@link http://maths.york.ac.uk/serving_maths}
-- * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
-- * @package questionbank
-+ * @package mod_quiz
-+ * @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com}
-+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-+
- require_once($CFG->libdir.'/questionlib.php');
-
-+
- define('DEFAULT_QUESTIONS_PER_PAGE', 20);
-
- function get_module_from_cmid($cmid){
-@@ -492,13 +508,13 @@ function question_showbank_actions($pageurl, $cm){
- // for each question either hide it if it is in use or delete it
- foreach ($questionlist as $questionid) {
- question_require_capability_on($questionid, 'edit');
-- if (record_exists('quiz_question_instances', 'question', $questionid)) {
-+ if (questions_in_use(array($questionid))) {
- if (!set_field('question', 'hidden', 1, 'id', $questionid)) {
- question_require_capability_on($questionid, 'edit');
- error('Was not able to hide question');
- }
- } else {
-- delete_question($questionid);
-+ question_delete_question($questionid);
- }
- }
- }
-@@ -551,7 +567,7 @@ function question_showbank($tabname, $contexts, $pageurl, $cm, $page, $perpage,
- $key = $matches[1];
- $questionlist .= $key.',';
- question_require_capability_on($key, 'edit');
-- if (record_exists('quiz_question_instances', 'question', $key)) {
-+ if (questions_in_use(array($key))) {
- $questionnames .= '* ';
- $inuse = true;
- }
-diff --git a/question/exportfile.php b/question/exportfile.php
-index 1a40c5f..67503e5 100644
---- a/question/exportfile.php
-+++ b/question/exportfile.php
-@@ -1,17 +1,43 @@
--<?php // $Id$
-- require_once(dirname(__FILE__) . '/../config.php');
-- require_once($CFG->libdir . '/filelib.php');
-+<?php
-
-- // Note: file.php always calls require_login() with $setwantsurltome=false
-- // in order to avoid messing redirects. MDL-14495
-- require_login(0, true, null, false);
-+// 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 <http://www.gnu.org/licenses/>.
-
-- $relativepath = get_file_argument('question/exportfile.php');
-- if (!$relativepath) {
-- error('No valid arguments supplied or incorrect server configuration');
-- }
-
-- $pathname = $CFG->dataroot . '/temp/questionexport/' . $USER->id . '/' . $relativepath;
-+/**
-+ * This script sends question exports to users who do not have permisison to
-+ * view the course files.
-+ *
-+ * @package moodlecore
-+ * @subpackage questionbank
-+ * @copyright 2008 The Open University
-+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
-+ */
-
-- send_temp_file($pathname, $relativepath);
--?>
-\ No newline at end of file
-+require_once(dirname(__FILE__) . '/../config.php');
-+require_once($CFG->libdir . '/filelib.php');
-+
-+// Note: file.php always calls require_login() with $setwantsurltome=false
-+// in order to avoid messing redirects. MDL-14495
-+require_login(0, true, null, false);
-+
-+$relativepath = get_file_argument('question/exportfile.php');
-+if (!$relativepath) {
-+ error('No valid arguments supplied or incorrect server configuration');
-+}
-+
-+$pathname = $CFG->dataroot . '/temp/questionexport/' . $USER->id . '/' . $relativepath;
-+
-+send_temp_file($pathname, $relativepath);
-diff --git a/question/file.php b/question/file.php
-index cd4789a..8774e7d 100644
---- a/question/file.php
-+++ b/question/file.php
-@@ -1,86 +1,109 @@
--<?php // $Id$
-- // This script fetches files from the dataroot/questionattempt directory
-- // It is based on the top-level file.php
-- //
-- // On a module-by-module basis (currently only implemented for quiz), it checks
-- // whether the user has permission to view the file.
-- //
-- // Syntax: question/file.php/attemptid/questionid/filename.ext
-- // Workaround: question/file.php?file=/attemptid/questionid/filename.ext
--
-- require_once('../config.php');
-- require_once('../lib/filelib.php');
--
-- // disable moodle specific debug messages
-- disable_debugging();
--
-- $relativepath = get_file_argument('file.php');
-- // force download for any student-submitted files to prevent XSS attacks.
-- $forcedownload = 1;
--
-- // relative path must start with '/', because of backup/restore!!!
-- if (!$relativepath) {
-- error('No valid arguments supplied or incorrect server configuration');
-- } else if ($relativepath{0} != '/') {
-- error('No valid arguments supplied, path does not start with slash!');
-- }
-+<?php
-
-- $pathname = $CFG->dataroot.'/questionattempt'.$relativepath;
-+// 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 <http://www.gnu.org/licenses/>.
-
-- // extract relative path components
-- $args = explode('/', trim($relativepath, '/'));
-
-- // check for the right number of directories in the path
-- if (count($args) != 3) {
-- error('Invalid arguments supplied');
-- }
-+/**
-+ * This script fetches files from the dataroot/questionattempt directory
-+ * It is based on the top-level file.php
-+ *
-+ * On a module-by-module basis (currently only implemented for quiz), it checks
-+ * whether the user has permission to view the file.
-+ *
-+ * Syntax: question/file.php/attemptid/questionid/filename.ext
-+ * Workaround: question/file.php?file=/attemptid/questionid/filename.ext
-+ *
-+ * @package moodlecore
-+ * @subpackage questionengine
-+ * @copyright 2007 Adriane Boyd
-+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
-+ */
-
-- // security: require login
-- require_login();
-+require_once('../config.php');
-+require_once('../lib/filelib.php');
-
-- // security: do not return directory node!
-- if (is_dir($pathname)) {
-- question_attempt_not_found();
-- }
-+// disable moodle specific debug messages
-+disable_debugging();
-
-- $lifetime = 0; // do not cache because students may reupload files
--
-- // security: check that the user has permission to access this file
-- $haspermission = false;
-- if ($attempt = get_record("question_attempts", "id", $args[0])) {
-- $modfile = $CFG->dirroot .'/mod/'. $attempt->modulename .'/lib.php';
-- $modcheckfileaccess = $attempt->modulename .'_check_file_access';
-- if (file_exists($modfile)) {
-- @require_once($modfile);
-- if (function_exists($modcheckfileaccess)) {
-- $haspermission = $modcheckfileaccess($args[0], $args[1]);
-- }
-- }
-- } else if ($args[0][0] == 0) {
-- global $USER;
-- $list = explode('_', $args[0]);
-- if ($list[1] == $USER->id) {
-- $haspermission = true;
-- }
-- }
-+$relativepath = get_file_argument('file.php');
-+// force download for any student-submitted files to prevent XSS attacks.
-+$forcedownload = 1;
-+
-+// relative path must start with '/', because of backup/restore!!!
-+if (!$relativepath) {
-+ error('No valid arguments supplied or incorrect server configuration');
-+} else if ($relativepath{0} != '/') {
-+ error('No valid arguments supplied, path does not start with slash!');
-+}
-+
-+$pathname = $CFG->dataroot.'/questionattempt'.$relativepath;
-
-- if ($haspermission) {
-- // check that file exists
-- if (!file_exists($pathname)) {
-- question_attempt_not_found();
-+// extract relative path components
-+$args = explode('/', trim($relativepath, '/'));
-+
-+// check for the right number of directories in the path
-+if (count($args) != 3) {
-+ error('Invalid arguments supplied');
-+}
-+
-+// security: require login
-+require_login();
-+
-+// security: do not return directory node!
-+if (is_dir($pathname)) {
-+ question_attempt_not_found();
-+}
-+
-+$lifetime = 0; // do not cache because students may reupload files
-+
-+// security: check that the user has permission to access this file
-+$haspermission = false;
-+if ($attempt = get_record("question_attempts", "id", $args[0])) {
-+ $modfile = $CFG->dirroot .'/mod/'. $attempt->modulename .'/lib.php';
-+ $modcheckfileaccess = $attempt->modulename .'_check_file_access';
-+ if (file_exists($modfile)) {
-+ @require_once($modfile);
-+ if (function_exists($modcheckfileaccess)) {
-+ $haspermission = $modcheckfileaccess($args[0], $args[1]);
- }
-+ }
-+} else if ($args[0][0] == 0) {
-+ global $USER;
-+ $list = explode('_', $args[0]);
-+ if ($list[1] == $USER->id) {
-+ $haspermission = true;
-+ }
-+}
-
-- // send the file
-- session_write_close(); // unlock session during fileserving
-- $filename = $args[count($args)-1];
-- send_file($pathname, $filename, $lifetime, $CFG->filteruploadedfiles, false, $forcedownload);
-- } else {
-+if ($haspermission) {
-+ // check that file exists
-+ if (!file_exists($pathname)) {
- question_attempt_not_found();
- }
-
-- function question_attempt_not_found() {
-- global $CFG;
-- header('HTTP/1.0 404 not found');
-- print_error('filenotfound', 'error', $CFG->wwwroot); //this is not displayed on IIS??
-- }
--?>
-+ // send the file
-+ session_write_close(); // unlock session during fileserving
-+ $filename = $args[count($args)-1];
-+ send_file($pathname, $filename, $lifetime, $CFG->filteruploadedfiles, false, $forcedownload);
-+} else {
-+ question_attempt_not_found();
-+}
-+
-+function question_attempt_not_found() {
-+ global $CFG;
-+ header('HTTP/1.0 404 not found');
-+ print_error('filenotfound', 'error', $CFG->wwwroot); //this is not displayed on IIS??
-+}
-diff --git a/question/format.php b/question/format.php
-index 61ba333..410b275 100644
---- a/question/format.php
-+++ b/question/format.php
-@@ -184,8 +184,8 @@ class qformat_default {
- * @param qtypehint hint about a question type from format
- * @return object question object suitable for save_options() or false if cannot handle
- */
-- function try_importing_using_qtypes( $data, $question=null, $extra=null, $qtypehint='') {
-- global $QTYPES;
-+ function try_importing_using_qtypes($data, $question = null, $extra = null,
-+ $qtypehint = '') {
-
- // work out what format we are using
- $formatname = substr(get_class($this), strlen('qformat_'));
-@@ -193,7 +193,7 @@ class qformat_default {
-
- //first try importing using a hint from format
- if (!empty($qtypehint)) {
-- $qtype = $QTYPES[$qtypehint];
-+ $qtype = question_bank::get_qtype($qtypehint, false);
- if (is_object($qtype) && method_exists($qtype, $methodname)) {
- $question = $qtype->$methodname($data, $question, $this, $extra);
- if ($question) {
-@@ -204,7 +204,7 @@ class qformat_default {
-
- // loop through installed questiontypes checking for
- // function to handle this question
-- foreach ($QTYPES as $qtype) {
-+ foreach (question_bank::get_all_qtypes() as $qtype) {
- if (method_exists( $qtype, $methodname)) {
- if ($question = $qtype->$methodname( $data, $question, $this, $extra )) {
- return $question;
-@@ -242,6 +242,9 @@ class qformat_default {
- }
-
- if (! $questions = $this->readquestions($lines)) { // Extract all the questions
-+ if (!is_array($questions)) {
-+ return false;
-+ }
- notify( get_string('noquestionsinfile','quiz') );
- return false;
- }
-@@ -501,7 +504,7 @@ class qformat_default {
-
- $question = new stdClass();
- $question->shuffleanswers = $CFG->quiz_shuffleanswers;
-- $question->defaultgrade = 1;
-+ $question->defaultmark = 1;
- $question->image = "";
- $question->usecase = 0;
- $question->multiplier = array();
-@@ -510,7 +513,7 @@ class qformat_default {
- $question->partiallycorrectfeedback = '';
- $question->incorrectfeedback = '';
- $question->answernumbering = 'abc';
-- $question->penalty = 0.1;
-+ $question->penalty = 0.3333333;
- $question->length = 1;
-
- // this option in case the questiontypes class wants
-diff --git a/question/import_form.php b/question/import_form.php
-index 108846b..cb876af 100644
---- a/question/import_form.php
-+++ b/question/import_form.php
-@@ -90,5 +90,24 @@ class question_import_form extends moodleform {
- return NULL;
- }
- }
-+
-+
-+
-+ public function validation($data, $files){
-+ $errors = parent::validation($data, $files);
-+ // If a file is uploaded return
-+ if ($data['choosefile'] || $files['newfile']) {
-+ return $errors;
-+ }
-+ // If file is not uploaded from file upload
-+ if (!$files['newfile']) {
-+ $errors['newfile'] = get_string('importfromupload', 'question');
-+ }
-+ // If filke is not imported from course files
-+ if (!$data['choosefile'] && !$files['newfile']) {
-+ $errors['choosefile'] = get_string('importfromcoursefiles', 'question');
-+ }
-+ return $errors;
-+ }
- }
- ?>
-diff --git a/question/move_form.php b/question/move_form.php
-index d2b6445..4965167 100644
---- a/question/move_form.php
-+++ b/question/move_form.php
-@@ -1,4 +1,29 @@
--<?php // $Id$
-+<?php
-+
-+// This file is part of Moodle - http://moodle.org/
-+//
-+// Moodle is free software: you can redistribute it and/or modify
-+// it under the terms of the GNU General Public License as published by
-+// the Free Software Foundation, either version 3 of the License, or
-+// (at your option) any later version.
-+//
-+// Moodle is distributed in the hope that it will be useful,
-+// but WITHOUT ANY WARRANTY; without even the implied warranty of
-+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-+// GNU General Public License for more details.
-+//
-+// You should have received a copy of the GNU General Public License
-+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
-+
-+
-+/**
-+ * Form for moving questions between categories.
-+ *
-+ * @package moodlecore
-+ * @subpackage questionbank
-+ * @copyright 2008 The Open University
-+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
-+ */
-
- if (!defined('MOODLE_INTERNAL')) {
- die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
-@@ -13,16 +38,11 @@ class question_move_form extends moodleform {
-
- $currentcat = $this->_customdata['currentcat'];
- $contexts = $this->_customdata['contexts'];
--//--------------------------------------------------------------------------------
-
- $mform->addElement('questioncategory', 'category', get_string('category','quiz'), compact('contexts', 'currentcat'));
-
--
--//--------------------------------------------------------------------------------
- $this->add_action_buttons(true, get_string('categorymoveto', 'quiz'));
--//--------------------------------------------------------------------------------
- $mform->addElement('hidden', 'delete', $currentcat);
- $mform->setType('delete', PARAM_INT);
- }
- }
--?>
-diff --git a/question/preview.php b/question/preview.php
-index 4335165..f3c7baf 100644
---- a/question/preview.php
-+++ b/question/preview.php
-@@ -1,4 +1,21 @@
--<?php // $Id$
-+<?php
-+
-+// This file is part of Moodle - http://moodle.org/
-+//
-+// Moodle is free software: you can redistribute it and/or modify
-+// it under the terms of the GNU General Public License as published by
-+// the Free Software Foundation, either version 3 of the License, or
-+// (at your option) any later version.
-+//
-+// Moodle is distributed in the hope that it will be useful,
-+// but WITHOUT ANY WARRANTY; without even the implied warranty of
-+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-+// GNU General Public License for more details.
-+//
-+// You should have received a copy of the GNU General Public License
-+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
-+
-+
- /**
- * This page displays a preview of a question
- *
-@@ -7,231 +24,178 @@
- * information is stored in the session as an array of subsequent states rather
- * than in the database.
- *
-- * TODO: make this work with activities other than quiz
-- *
-- * @author Alex Smith as part of the Serving Mathematics project
-- * {@link http://maths.york.ac.uk/serving_maths}
-- * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
-- * @package questionbank
-+ * @package core
-+ * @subpackage questionbank
-+ * @copyright Alex Smith {@link http://maths.york.ac.uk/serving_maths} and
-+ * numerous contributors.
-+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-- require_once("../config.php");
-- require_once($CFG->libdir.'/questionlib.php');
-- require_once($CFG->dirroot.'/mod/quiz/locallib.php'); // We really want to get rid of this
--
-- $id = required_param('id', PARAM_INT); // question id
-- // if no quiz id is specified then a dummy quiz with default options is used
-- $quizid = optional_param('quizid', 0, PARAM_INT);
-- // if no quiz id is specified then tell us the course
-- if (empty($quizid)) {
-- $courseid = required_param('courseid', PARAM_INT);
-- }
--
-- // Test if we are continuing an attempt at a question
-- $continue = optional_param('continue', 0, PARAM_BOOL);
-- // Check for any of the submit buttons
-- $fillcorrect = optional_param('fillcorrect', 0, PARAM_BOOL);
-- $markall = optional_param('markall', 0, PARAM_BOOL);
-- $finishattempt = optional_param('finishattempt', 0, PARAM_BOOL);
-- $back = optional_param('back', 0, PARAM_BOOL);
-- $startagain = optional_param('startagain', 0, PARAM_BOOL);
-- // We are always continuing an attempt if a submit button was pressed with the
-- // exception of the start again button
-- if ($fillcorrect || $markall || $finishattempt || $back) {
-- $continue = true;
-- } else if ($startagain) {
-- $continue = false;
-- }
--
-- $url = new moodle_url($CFG->wwwroot . '/question/preview.php');
-- $url->param('id', $id);
-- if ($quizid) {
-- $url->param('quizid', $quizid);
-- } else {
-- $url->param('courseid', $courseid);
-- }
-- $url->param('continue', 1);
-- if (!$continue) {
-- // Start a new attempt; delete the old session
-- unset($SESSION->quizpreview);
-- // Redirect to ourselves but with continue=1; prevents refreshing the page
-- // from restarting an attempt (needed so that random questions don't change)
-- redirect($url->out());
-- }
-- // Load the question information
-- if (!$questions = get_records('question', 'id', $id)) {
-- error('Could not load question');
-- }
-- if (empty($quizid)) {
-- $quiz = new cmoptions;
-- $quiz->id = 0;
-- $quiz->review = $CFG->quiz_review;
-- require_login($courseid, false);
-- $quiz->course = $courseid;
-- } else if (!$quiz = get_record('quiz', 'id', $quizid)) {
-- error("Quiz id $quizid does not exist");
-- } else {
-- require_login($quiz->course, false, get_coursemodule_from_instance('quiz', $quizid, $quiz->course));
-- }
--
--
--
-- if ($maxgrade = get_field('quiz_question_instances', 'grade', 'quiz', $quiz->id, 'question', $id)) {
-- $questions[$id]->maxgrade = $maxgrade;
-- } else {
-- $questions[$id]->maxgrade = $questions[$id]->defaultgrade;
-- }
--
-- $quiz->id = 0; // just for safety
-- $quiz->questions = $id;
--
-- if (!$category = get_record("question_categories", "id", $questions[$id]->category)) {
-- error("This question doesn't belong to a valid category!");
-- }
--
-- if (!question_has_capability_on($questions[$id], 'use', $questions[$id]->category)){
-- error("You can't preview these questions!");
-- }
-- if (isset($COURSE)){
-- $quiz->course = $COURSE->id;
-- }
--
-- // Load the question type specific information
-- if (!get_question_options($questions)) {
-- print_error('newattemptfail', 'quiz');
-- }
--
-- // Create a dummy quiz attempt
-- // TODO: find out what of the following we really need. What is $attempt
-- // really used for?
-- $timenow = time();
-- $attempt->quiz = $quiz->id;
-- $attempt->userid = $USER->id;
-- $attempt->attempt = 0;
-- $attempt->sumgrades = 0;
-- $attempt->timestart = $timenow;
-- $attempt->timefinish = 0;
-- $attempt->timemodified = $timenow;
-- $attempt->uniqueid = 0;
-- $attempt->id = 0;
--
-- // Restore the history of question sessions from the moodle session or create
-- // new sessions. Make $states a reference to the states array in the moodle
-- // session.
-- if (isset($SESSION->quizpreview->states) and $SESSION->quizpreview->questionid == $id) {
-- // Reload the question session history from the moodle session
-- $states =& $SESSION->quizpreview->states;
-- $historylength = count($states) - 1;
-- if ($back && $historylength > 0) {
-- // Go back one step in the history
-- unset($states[$historylength]);
-- $historylength--;
-+require_once(dirname(__FILE__) . '/../config.php');
-+require_once($CFG->libdir . '/questionlib.php');
-+require_once($CFG->libdir . '/formslib.php');
-+require_once(dirname(__FILE__) . '/previewlib.php');
-+require_js('yui_dom-event');
-+require_js($CFG->httpswwwroot . '/question/preview.js');
-+
-+// Get and validate question id.
-+$id = required_param('id', PARAM_INT); // Question id
-+$question = question_bank::load_question($id);
-+require_login();
-+if (!$category = get_record('question_categories', 'id', $question->category)) {
-+ print_error('unknownquestioncategory', 'question', $question->category);
-+}
-+question_require_capability_on($question, 'use');
-+
-+// Get and validate display options.
-+$options = new question_preview_options($question);
-+$options->load_user_defaults();
-+$options->set_from_request();
-+
-+// Get and validate exitsing preview, or start a new one.
-+$previewid = optional_param('previewid', 0, PARAM_ALPHANUM);
-+if ($previewid) {
-+ if (!isset($SESSION->question_previews[$previewid])) {
-+ print_error('notyourpreview', 'question');
-+ }
-+ try {
-+ $quba = question_engine::load_questions_usage_by_activity($previewid);
-+ } catch (Exception $e){
-+ print_error('submissionoutofsequencefriendlymessage', 'question',
-+ question_preview_url($question->id, $options->behaviour,
-+ $options->maxmark, $options));
-+ }
-+ $slot = $quba->get_first_question_number();
-+ $usedquestion = $quba->get_question($slot);
-+ if ($usedquestion->id != $question->id) {
-+ print_error('questionidmismatch', 'question');
-+ }
-+ $question = $usedquestion;
-+
-+} else {
-+ $quba = question_engine::make_questions_usage_by_activity('core_question_preview',
-+ get_context_instance_by_id($category->contextid));
-+ $quba->set_preferred_behaviour($options->behaviour);
-+ $slot = $quba->add_question($question, $options->maxmark);
-+ $quba->start_all_questions();
-+ begin_sql();
-+ question_engine::save_questions_usage_by_activity($quba);
-+ commit_sql();
-+
-+ $SESSION->question_previews[$quba->get_id()] = true;
-+}
-+$options->behaviour = $quba->get_preferred_behaviour();
-+$options->maxmark = $quba->get_question_max_mark($slot);
-+
-+// Create the settings form, and initialise the fields.
-+$optionsform = new preview_options_form($CFG->wwwroot . '/question/preview.php?id=' . $question->id, $quba);
-+$optionsform->set_data($options);
-+
-+// Process change of settings, if that was requested.
-+if ($newoptions = $optionsform->get_submitted_data()) {
-+ // Set user preferences
-+ $options->save_user_preview_options($newoptions);
-+ restart_preview($previewid, $question->id, $newoptions);
-+}
-+
-+// Prepare a URL that is used in various places.
-+$actionurl = question_preview_action_url($question->id, $quba->get_id(), $options);
-+
-+// Process any actions from the buttons at the bottom of the form.
-+if (data_submitted() && confirm_sesskey()) {
-+ if (optional_param('restart', false, PARAM_BOOL)) {
-+ restart_preview($previewid, $question->id, $options);
-+
-+ } else if (optional_param('fill', null, PARAM_BOOL)) {
-+ $correctresponse = $quba->get_correct_response($slot);
-+ $quba->process_action($slot, $correctresponse);
-+ begin_sql();
-+ question_engine::save_questions_usage_by_activity($quba);
-+ commit_sql();
-+ redirect($actionurl);
-+
-+ } else if (optional_param('finish', null, PARAM_BOOL)) {
-+ try {
-+ $quba->process_all_actions();
-+ } catch (question_out_of_sequence_exception $e){
-+ print_error('submissionoutofsequencefriendlymessage', 'question', $actionurl);
- }
-+ $quba->finish_all_questions();
-+ begin_sql();
-+ question_engine::save_questions_usage_by_activity($quba);
-+ commit_sql();
-+ redirect($actionurl);
-+
- } else {
-- // Record the question id in the moodle session
-- $SESSION->quizpreview->questionid = $id;
-- // Create an empty session for the question
-- if (!$newstates =
-- get_question_states($questions, $quiz, $attempt)) {
-- print_error('newattemptfail', 'quiz');
-+ try {
-+ $quba->process_all_actions();
-+ } catch (question_out_of_sequence_exception $e){
-+ print_error('submissionoutofsequencefriendlymessage', 'question', $actionurl);
- }
-- $SESSION->quizpreview->states = array($newstates);
-- $states =& $SESSION->quizpreview->states;
-- $historylength = 0;
-- }
--
-- if (!$fillcorrect && !$back && ($form = data_submitted())) {
-- $form = (array)$form;
-- $submitted = true;
--
-- // Create a new item in the history of question states (don't simplify!)
-- $states[$historylength + 1] = array();
-- $states[$historylength + 1][$id] = clone($states[$historylength][$id]);
-- $historylength++;
-- $curstate =& $states[$historylength][$id];
-- $curstate->changed = false;
--
-- // Process the responses
-- unset($form['id']);
-- unset($form['quizid']);
-- unset($form['continue']);
-- unset($form['markall']);
-- unset($form['finishattempt']);
-- unset($form['back']);
-- unset($form['startagain']);
--
-- $event = $finishattempt ? QUESTION_EVENTCLOSE : QUESTION_EVENTSUBMIT;
-- if ($actions = question_extract_responses($questions, $form, $event)) {
-- $actions[$id]->timestamp = 0; // We do not care about timelimits here
-- if (!question_process_responses($questions[$id], $curstate, $actions[$id], $quiz, $attempt)) {
-- unset($SESSION->quizpreview);
-- print_error('errorprocessingresponses', 'question', $url->out());
-- }
-- if (!$curstate->changed) {
-- // Update the current state rather than creating a new one
-- $historylength--;
-- unset($states[$historylength]);
-- $states = array_values($states);
-- $curstate =& $states[$historylength][$id];
-- }
-+ begin_sql();
-+ question_engine::save_questions_usage_by_activity($quba);
-+ commit_sql();
-+ $scrollpos = optional_param('scrollpos', '', PARAM_RAW);
-+ if ($scrollpos !== '') {
-+ $actionurl .= '&scrollpos=' . ((int) $scrollpos);
- }
-- } else {
-- $submitted = false;
-- $curstate =& $states[$historylength][$id];
-- }
--
-- // TODO: should not use quiz-specific function here
-- $options = quiz_get_renderoptions($quiz->review, $curstate);
-- $options->noeditlink = true;
--
-- // Fill in the correct responses (unless the question is in readonly mode)
-- if ($fillcorrect && !$options->readonly) {
-- $curstate->responses = $QTYPES[$questions[$id]->qtype]
-- ->get_correct_responses($questions[$id], $curstate);
-- }
--
-- $strpreview = get_string('preview', 'quiz').' '.format_string($questions[$id]->name);
-- $questionlist = array($id);
-- $headtags = get_html_head_contributions($questionlist, $questions, $states[$historylength]);
-- print_header($strpreview, '', '', '', $headtags);
-- print_heading($strpreview);
-+ redirect($actionurl);
-+ }
-+}
-+
-+if ($question->length) {
-+ $displaynumber = '1';
-+} else {
-+ $displaynumber = 'i';
-+}
-+$restartdisabled = '';
-+$finishdisabled = '';
-+$filldisabled = '';
-+if ($quba->get_question_state($slot)->is_finished()) {
-+ $finishdisabled = ' disabled="disabled"';
-+ $filldisabled = ' disabled="disabled"';
-+}
-+if (!$previewid) {
-+ $restartdisabled = ' disabled="disabled"';
-+}
-+// Output
-+$title = get_string('previewquestion', 'question', format_string($question->name));
-+$headtags = question_engine::initialise_js() . $quba->render_question_head_html($slot);
-+print_header($title, '', '', '', $headtags);
-+print_heading($title);
-+
-+// Start the question form.
-+echo '<form method="post" action="' . s($actionurl) .
-+ '" enctype="multipart/form-data" id="responseform">', "\n";
-+print_js_call('question_init_form', array('responseform'));
-+echo '<input type="hidden" name="sesskey" value="' . sesskey() . '" />', "\n";
-+echo '<input type="hidden" name="slots" value="' . $slot . '" />', "\n";
-+
-+// Output the question.
-+echo $quba->render_question($slot, $options, $displaynumber);
-+
-+echo '<p class="notifytiny">' . get_string('behaviourbeingused', 'question',
-+ question_engine::get_behaviour_name(
-+ $quba->get_question_attempt($slot)->get_behaviour_name())) . '</p>';
-+// Finish the question form.
-+echo '<div id="previewcontrols" class="controls">';
-+echo '<input type="submit" name="restart"' . $restartdisabled .
-+ ' value="' . get_string('restart', 'question') . '" />', "\n";
-+echo '<input type="submit" name="fill"' . $filldisabled .
-+ ' value="' . get_string('fillincorrect', 'question') . '" />', "\n";
-+echo '<input type="submit" name="finish"' . $finishdisabled .
-+ ' value="' . get_string('submitandfinish', 'question') . '" />', "\n";
-+echo '<input type="hidden" name="scrollpos" id="scrollpos" value="" />';
-+echo '</div>';
-+echo '</form>';
-+
-+// Display the settings form.
-+$optionsform->display();
-+
-+echo '<script type="text/javascript">question_preview_init("' .
-+ get_string('closepreview', 'question') . '", "previewcontrols");</script>', "\n";
-+
-+// Finish output.
-+use_html_editor();
-+print_footer('empty');
-
-- if (!empty($quizid)) {
-- echo '<p class="quemodname">'.get_string('modulename', 'quiz') . ': ';
-- p(format_string($quiz->name));
-- echo "</p>\n";
-- }
-- $number = 1;
-- echo '<form method="post" action="'.$url->out(true).'" enctype="multipart/form-data" id="responseform">', "\n";
-- print_question($questions[$id], $curstate, $number, $quiz, $options);
--
-- echo '<div class="controls">';
-- echo $url->hidden_params_out();
--
-- // Print the mark and finish attempt buttons
-- echo '<input name="markall" type="submit" value="' . get_string('markall',
-- 'quiz') . "\" />\n";
-- echo '<input name="finishattempt" type="submit" value="' .
-- get_string('finishattempt', 'quiz') . "\" />\n";
-- echo '<br />';
-- echo '<br />';
-- // Print the fill correct button (unless the question is in readonly mode)
-- if (!$options->readonly) {
-- echo '<input name="fillcorrect" type="submit" value="' .
-- get_string('fillcorrect', 'quiz') . "\" />\n";
-- }
-- // Print the navigation buttons
-- if ($historylength > 0) {
-- echo '<input name="back" type="submit" value="' . get_string('previous',
-- 'quiz') . "\" />\n";
-- }
-- // Print the start again button
-- echo '<input name="startagain" type="submit" value="' .
-- get_string('startagain', 'quiz') . "\" />\n";
-- // Print the close window button
-- echo '<input type="button" onclick="window.close()" value="' .
-- get_string('closepreview', 'quiz') . "\" />";
-- echo '</div>';
-- echo '</form>';
-- print_footer();
--?>
-diff --git a/question/previewlib.php b/question/previewlib.php
-new file mode 100644
-index 0000000..f507a38
---- /dev/null
-+++ b/question/previewlib.php
-@@ -0,0 +1,214 @@
-+<?php
-+
-+// This file is part of Moodle - http://moodle.org/
-+//
-+// Moodle is free software: you can redistribute it and/or modify
-+// it under the terms of the GNU General Public License as published by
-+// the Free Software Foundation, either version 3 of the License, or
-+// (at your option) any later version.
-+//
-+// Moodle is distributed in the hope that it will be useful,
-+// but WITHOUT ANY WARRANTY; without even the implied warranty of
-+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-+// GNU General Public License for more details.
-+//
-+// You should have received a copy of the GNU General Public License
-+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
-+
-+
-+/**
-+ * Helper code for the question preview UI.
-+ *
-+ * @package core
-+ * @subpackage questionbank
-+ * @copyright 2009 The Open University
-+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
-+ */
-+
-+/**
-+ * Settings form for the preview options.
-+ *
-+ * @copyright 2009 The Open University
-+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
-+ */
-+class preview_options_form extends moodleform {
-+ public function definition() {
-+ $mform = $this->_form;
-+
-+ $hiddenofvisible = array(
-+ question_display_options::HIDDEN => get_string('notshown', 'question'),
-+ question_display_options::VISIBLE => get_string('shown', 'question'),
-+ );
-+
-+ $mform->addElement('header', 'optionsheader', get_string('changeoptions', 'question'));
-+
-+ $behaviours = question_engine::get_behaviour_options($this->_customdata->get_preferred_behaviour());
-+ $mform->addElement('select', 'behaviour', get_string('howquestionsbehave', 'question'), $behaviours);
-+ $mform->setHelpButton('behaviour', array('howquestionsbehave', get_string('howquestionsbehave', 'question'), 'question'));
-+
-+ $mform->addElement('text', 'maxmark', get_string('markedoutof', 'question'), array('size' => '5'));
-+ $mform->setType('maxmark', PARAM_NUMBER);
-+
-+ $mform->addElement('select', 'correctness', get_string('whethercorrect', 'question'), $hiddenofvisible);
-+
-+ $marksoptions = array(
-+ question_display_options::HIDDEN => get_string('notshown', 'question'),
-+ question_display_options::MAX_ONLY => get_string('showmaxmarkonly', 'question'),
-+ question_display_options::MARK_AND_MAX => get_string('showmarkandmax', 'question'),
-+ );
-+ $mform->addElement('select', 'marks', get_string('marks', 'question'), $marksoptions);
-+
-+ $mform->addElement('select', 'markdp', get_string('decimalplacesingrades', 'question'),
-+ question_engine::get_dp_options());
-+
-+ $mform->addElement('select', 'feedback', get_string('specificfeedback', 'question'), $hiddenofvisible);
-+
-+ $mform->addElement('select', 'generalfeedback', get_string('generalfeedback', 'question'), $hiddenofvisible);
-+
-+ $mform->addElement('select', 'rightanswer', get_string('rightanswer', 'question'), $hiddenofvisible);
-+
-+ $mform->addElement('select', 'history', get_string('responsehistory', 'question'), $hiddenofvisible);
-+
-+ $mform->addElement('submit', 'submit', get_string('restartwiththeseoptions', 'question'), $hiddenofvisible);
-+ }
-+}
-+
-+
-+/**
-+ * Displays question preview options as default and set the options
-+ * Setting default, getting and setting user preferences in question preview options.
-+ *
-+ * @copyright 2010 The Open University
-+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
-+ */
-+class question_preview_options extends question_display_options {
-+ /** @var string the behaviour to use for this preview. */
-+ public $behaviour;
-+
-+ /** @var number the maximum mark to use for this preview. */
-+ public $maxmark;
-+
-+ /** @var string prefix to append to field names to get user_preference names. */
-+ const OPTIONPREFIX = 'question_preview_options_';
-+
-+ /**
-+ * Constructor.
-+ */
-+ public function __construct($question) {
-+ global $CFG;
-+ $this->behaviour = 'deferredfeedback';
-+ $this->maxmark = $question->defaultmark;
-+ $this->correctness = self::VISIBLE;
-+ $this->marks = self::MARK_AND_MAX;
-+ $this->markdp = $CFG->quiz_decimalpoints;
-+ $this->feedback = self::VISIBLE;
-+ $this->numpartscorrect = $this->feedback;
-+ $this->generalfeedback = self::VISIBLE;
-+ $this->rightanswer = self::VISIBLE;
-+ $this->history = self::HIDDEN;
-+ $this->flags = self::HIDDEN;
-+ $this->manualcomment = self::HIDDEN;
-+ }
-+
-+ /**
-+ * @return array names of the options we store in the user preferences table.
-+ */
-+ protected function get_user_pref_fields() {
-+ return array('behaviour', 'correctness', 'marks', 'markdp', 'feedback',
-+ 'generalfeedback', 'rightanswer', 'history');
-+ }
-+
-+ /**
-+ * @return array names and param types of the options we read from the request.
-+ */
-+ protected function get_field_types() {
-+ return array(
-+ 'behaviour' => PARAM_ALPHA,
-+ 'maxmark' => PARAM_NUMBER,
-+ 'correctness' => PARAM_BOOL,
-+ 'marks' => PARAM_INT,
-+ 'markdp' => PARAM_INT,
-+ 'feedback' => PARAM_BOOL,
-+ 'generalfeedback' => PARAM_BOOL,
-+ 'rightanswer' => PARAM_BOOL,
-+ 'history' => PARAM_BOOL,
-+ );
-+ }
-+
-+ /**
-+ * Load the value of the options from the user_preferences table.
-+ */
-+ public function load_user_defaults() {
-+ foreach ($this->get_user_pref_fields() as $field) {
-+ $this->$field = get_user_preferences(
-+ self::OPTIONPREFIX . $field, $this->$field);
-+ }
-+ $this->numpartscorrect = $this->feedback;
-+ }
-+
-+ /**
-+ * Save a change to the user's preview options to the database.
-+ * @param object $newoptions
-+ */
-+ public function save_user_preview_options($newoptions) {
-+ foreach ($this->get_user_pref_fields() as $field) {
-+ if (isset($newoptions->$field)) {
-+ set_user_preference(self::OPTIONPREFIX . $field, $newoptions->$field);
-+ }
-+ }
-+ }
-+
-+ /**
-+ * Set the value of any fields included in the request.
-+ */
-+ public function set_from_request() {
-+ foreach ($this->get_field_types() as $field => $type) {
-+ $this->$field = optional_param($field, $this->$field, $type);
-+ }
-+ $this->numpartscorrect = $this->feedback;
-+ }
-+
-+ /**
-+ * @return string URL fragment. Parameters needed in the URL when continuing
-+ * this preview.
-+ */
-+ public function get_query_string() {
-+ $querystring = array();
-+ foreach ($this->get_field_types() as $field => $notused) {
-+ if ($field == 'behaviour' || $field == 'maxmark') {
-+ continue;
-+ }
-+ $querystring[] = $field . '=' . $this->$field;
-+ }
-+ return implode('&', $querystring);
-+ }
-+}
-+
-+
-+/**
-+ * The the URL to use for actions relating to this preview.
-+ * @param integer $questionid the question being previewed.
-+ * @param integer $qubaid the id of the question usage for this preview.
-+ * @param question_preview_options $options the options in use.
-+ */
-+function question_preview_action_url($questionid, $qubaid,
-+ question_preview_options $options) {
-+ global $CFG;
-+ $url = $CFG->wwwroot . '/question/preview.php?id=' . $questionid . '&previewid=' . $qubaid;
-+ return $url . '&' . $options->get_query_string();
-+}
-+
-+/**
-+ * Delete the current preview, if any, and redirect to start a new preview.
-+ * @param integer $previewid
-+ * @param integer $questionid
-+ * @param object $displayoptions
-+ */
-+function restart_preview($previewid, $questionid, $displayoptions) {
-+ if ($previewid) {
-+ begin_sql();
-+ question_engine::delete_questions_usage_by_activity($previewid);
-+ commit_sql();
-+ }
-+ redirect(question_preview_url($questionid, $displayoptions->behaviour, $displayoptions->maxmark, $displayoptions));
-+}
-diff --git a/question/question.php b/question/question.php
-index c67c6fb..a0a9e49 100644
---- a/question/question.php
-+++ b/question/question.php
-@@ -142,7 +142,6 @@ if ($mform->is_cancelled()){
- close_window();
- } else {
- $nexturl = new moodle_url($returnurl);
-- $nexturl->param('lastchanged', $question->id);
- redirect($nexturl->out());
- }
- } elseif ($fromform = $mform->get_data()) {
-@@ -222,7 +221,7 @@ if ($mform->is_cancelled()){
- }
- } else {
-
-- list($streditingquestion,) = $QTYPES[$question->qtype]->get_heading();
-+ $streditingquestion = $QTYPES[$question->qtype]->get_heading();
- $headtags = get_editing_head_contributions($question);
- if ($cm !== null) {
- $strmodule = get_string('modulename', $cm->modname);
-diff --git a/question/restorelib.php b/question/restorelib.php
-index ca8d940..aa37793 100644
---- a/question/restorelib.php
-+++ b/question/restorelib.php
-@@ -325,7 +325,7 @@
- $question->questiontextformat = backup_todb($que_info['#']['QUESTIONTEXTFORMAT']['0']['#']);
- $question->image = backup_todb($que_info['#']['IMAGE']['0']['#']);
- $question->generalfeedback = backup_todb_optional_field($que_info, 'GENERALFEEDBACK', '');
-- $question->defaultgrade = backup_todb($que_info['#']['DEFAULTGRADE']['0']['#']);
-+ $question->defaultmark = backup_todb($que_info['#']['DEFAULTGRADE']['0']['#']);
- $question->penalty = backup_todb($que_info['#']['PENALTY']['0']['#']);
- $question->qtype = backup_todb($que_info['#']['QTYPE']['0']['#']);
- $question->length = backup_todb($que_info['#']['LENGTH']['0']['#']);
-@@ -335,6 +335,17 @@
- $question->timecreated = backup_todb_optional_field($que_info, 'TIMECREATED', 0);
- $question->timemodified = backup_todb_optional_field($que_info, 'TIMEMODIFIED', 0);
-
-+ // Update old, badly rounded penalties, to the new equivalents.
-+ if ($question->penalty >= 0.33 && $question->penalty <= 0.34) {
-+ $question->penalty = 0.3333333;
-+ }
-+ if ($question->penalty >= 0.66 && $question->penalty <= 0.67) {
-+ $question->penalty = 0.6666667;
-+ }
-+ if ($question->penalty >= 1) {
-+ $question->penalty = 1;
-+ }
-+
- // Set the createdby field, if the user was in the backup, or if we are on the same site.
- $createdby = backup_todb_optional_field($que_info, 'CREATEDBY', null);
- if (!empty($createdby)) {
-@@ -466,7 +477,8 @@
- }
-
- //Now, restore every question_answers in this question
-- $status = question_restore_answers($oldid,$newid,$que_info,$restore);
-+ $status = $status && question_restore_answers($oldid,$newid,$que_info,$restore);
-+ $status = $status && question_restore_hints($oldid,$newid,$que_info,$restore);
- // Restore questiontype specific data
- if (array_key_exists($question->qtype, $QTYPES)) {
- $status = $QTYPES[$question->qtype]->restore($oldid,$newid,$que_info,$restore);
-@@ -510,6 +522,61 @@
- }
- }
-
-+ function question_restore_hints($old_question_id, $new_question_id, $info, $restore) {
-+ if (!isset($info['#']['HINTS']['0']['#']['HINT'])) {
-+ return true;
-+ }
-+ $hints = $info['#']['HINTS']['0']['#']['HINT'];
-+
-+ $status = true;
-+ $count = 0;
-+ foreach ($hints as $hint_info) {
-+ if (isset($hint_info['#']['REST'])) {
-+ // Backwards compatibility.
-+ $hintoptions = backup_todb($hint_info['#']['REST']['0']['#']);
-+ if ($hintoptions) {
-+ $hintoptions = unserialize($hintoptions);
-+ } else {
-+ $hintoptions = array(0, 0);
-+ }
-+ $numoptions = count($hintoptions);
-+