PageRenderTime 4ms CodeModel.GetById 66ms app.highlight 52ms RepoModel.GetById 1ms app.codeStats 1ms

/locallib.php

https://github.com/KieranRBriggs/moodle-mod_hotpot
PHP | 2023 lines | 1133 code | 255 blank | 635 comment | 231 complexity | 314aabfc644e9b57b97bf0274a86a537 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   2
   3// This file is part of Moodle - http://moodle.org/
   4//
   5// Moodle is free software: you can redistribute it and/or modify
   6// it under the terms of the GNU General Public License as published by
   7// the Free Software Foundation, either version 3 of the License, or
   8// (at your option) any later version.
   9//
  10// Moodle is distributed in the hope that it will be useful,
  11// but WITHOUT ANY WARRANTY; without even the implied warranty of
  12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13// GNU General Public License for more details.
  14//
  15// You should have received a copy of the GNU General Public License
  16// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  17
  18/**
  19 * Library of internal classes and functions for module hotpot
  20 *
  21 * All the hotpot specific functions, needed to implement the module
  22 * logic, should go to here. Instead of having bunch of function named
  23 * hotpot_something() taking the hotpot instance as the first
  24 * parameter, we use a class hotpot that provides all methods.
  25 *
  26 * @package   mod-hotpot
  27 * @copyright 2010 Gordon Bateson <gordon.bateson@gmail.com>
  28 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  29 */
  30
  31defined('MOODLE_INTERNAL') || die();
  32
  33require_once(dirname(__FILE__).'/lib.php');     // we extend this library here
  34require_once($CFG->libdir . '/gradelib.php');   // we use some rounding and comparing routines here
  35
  36/**
  37 * Full-featured hotpot API
  38 *
  39 * This wraps the hotpot database record with a set of methods that are called
  40 * from the module itself. The class should be initialized right after you get
  41 * $hotpot, $cm and $course records at the begining of the script.
  42 */
  43class hotpot {
  44
  45    /**
  46     * internal codes to indicate what text is to be used
  47     * for the name and introduction of a HotPot instance
  48     */
  49    const TEXTSOURCE_FILE           = 0; // was TEXTSOURCE_QUIZ
  50    const TEXTSOURCE_FILENAME       = 1;
  51    const TEXTSOURCE_FILEPATH       = 2;
  52    const TEXTSOURCE_SPECIFIC       = 3;
  53
  54    /**
  55     * database codes to indicate what navigation aids are used
  56     * when the quiz apears in the browser
  57     */
  58    const NAVIGATION_NONE           = 0; // was 6
  59    const NAVIGATION_MOODLE         = 1; // was NAVIGATION_BAR
  60    const NAVIGATION_FRAME          = 2;
  61    const NAVIGATION_EMBED          = 3; // was NAVIGATION_IFRAME
  62    const NAVIGATION_ORIGINAL       = 4;
  63    const NAVIGATION_TOPBAR         = 5; // was NAVIGATION_GIVEUP but that was replaced by stopbutton
  64
  65    /**
  66     * database codes to indicate the grading method for a HotPot instance
  67     */
  68    const GRADEMETHOD_HIGHEST       = 1;
  69    const GRADEMETHOD_AVERAGE       = 2;
  70    const GRADEMETHOD_FIRST         = 3;
  71    const GRADEMETHOD_LAST          = 4;
  72
  73    /**
  74     * database codes to indicate the source/config location for a HotPot instance
  75     */
  76    const LOCATION_COURSEFILES      = 0;
  77    const LOCATION_SITEFILES        = 1;
  78    const LOCATION_WWW              = 2;
  79
  80    /**
  81     * bit-masks used to extract bits from the hotpot "title" setting
  82     */
  83    const TITLE_SOURCE              = 0x03; // 1st - 2nd bits
  84    const TITLE_UNITNAME            = 0x04; // 3rd bit
  85    const TITLE_SORTORDER           = 0x08; // 4th bit
  86
  87    /**
  88     * database codes for the following time fields
  89     *  - timelimit : the maximum length of one attempt
  90     *  - delay3 : the delay after end of quiz before control returns to Moodle
  91     */
  92    const TIME_SPECIFIC             = 0;
  93    const TIME_TEMPLATE             = -1;
  94    const TIME_AFTEROK              = -2;
  95    const TIME_DISABLE              = -3;
  96
  97    const CONTINUE_RESUMEQUIZ       = 1;
  98    const CONTINUE_RESTARTQUIZ      = 2;
  99    const CONTINUE_RESTARTUNIT      = 3;
 100    const CONTINUE_ABANDONUNIT      = 4;
 101
 102    const STATUS_INPROGRESS         = 1;
 103    const STATUS_TIMEDOUT           = 2;
 104    const STATUS_ABANDONED          = 3;
 105    const STATUS_COMPLETED          = 4;
 106    const STATUS_PAUSED             = 5;
 107
 108    const FEEDBACK_NONE             = 0;
 109    const FEEDBACK_WEBPAGE          = 1;
 110    const FEEDBACK_FORMMAIL         = 2;
 111    const FEEDBACK_MOODLEFORUM      = 3;
 112    const FEEDBACK_MOODLEMESSAGING  = 4;
 113
 114    const STOPBUTTON_NONE           = 0;
 115    const STOPBUTTON_LANGPACK       = 1;
 116    const STOPBUTTON_SPECIFIC       = 2;
 117
 118    const ACTIVITY_NONE             = 0;
 119    const ACTIVITY_COURSE_ANY       = -1;
 120    const ACTIVITY_SECTION_ANY      = -2;
 121    const ACTIVITY_COURSE_HOTPOT    = -3;
 122    const ACTIVITY_SECTION_HOTPOT   = -4;
 123
 124    const ENTRYOPTIONS_TITLE        = 0x01;
 125    const ENTRYOPTIONS_GRADING      = 0x02;
 126    const ENTRYOPTIONS_DATES        = 0x04;
 127    const ENTRYOPTIONS_ATTEMPTS     = 0x08;
 128
 129    const EXITOPTIONS_TITLE         = 0x01;
 130    const EXITOPTIONS_ENCOURAGEMENT = 0x02;
 131    const EXITOPTIONS_ATTEMPTSCORE  = 0x04;
 132    const EXITOPTIONS_HOTPOTGRADE   = 0x08;
 133    const EXITOPTIONS_RETRY         = 0x10;
 134    const EXITOPTIONS_INDEX         = 0x20;
 135    const EXITOPTIONS_COURSE        = 0x40;
 136    const EXITOPTIONS_GRADES        = 0x80;
 137
 138    const BODYSTYLES_BACKGROUND     = 0x01;
 139    const BODYSTYLES_COLOR          = 0x02;
 140    const BODYSTYLES_FONT           = 0x04;
 141    const BODYSTYLES_MARGIN         = 0x08;
 142
 143    /**
 144     * three sets of 6 bits define the times at which a quiz may be reviewed
 145     * e.g. 0x3f = 0011 1111 (i.e. right most 6 bits)
 146     */
 147    const REVIEW_DURINGATTEMPT = 0x0003f; // 1st set of 6 bits : during attempt
 148    const REVIEW_AFTERATTEMPT  = 0x00fc0; // 2nd set of 6 bits : after attempt (but before quiz closes)
 149    const REVIEW_AFTERCLOSE    = 0x3f000; // 3rd set of 6 bits : after the quiz closes
 150
 151    /**
 152     * within each group of 6 bits we determine what should be shown
 153     * e.g. 0x1041 = 00-0001 0000-01 00-0001 (i.e. 3 sets of 6 bits)
 154     */
 155    const REVIEW_RESPONSES = 0x1041; // 1*0x1041 : 1st bit of each 6-bit set : Show student responses
 156    const REVIEW_ANSWERS   = 0x2082; // 2*0x1041 : 2nd bit of each 6-bit set : Show correct answers
 157    const REVIEW_SCORES    = 0x4104; // 3*0x1041 : 3rd bit of each 6-bit set : Show scores
 158    const REVIEW_FEEDBACK  = 0x8208; // 4*0x1041 : 4th bit of each 6-bit set : Show feedback
 159
 160    /** @var stdclass course module record */
 161    public $cm;
 162
 163    /** @var stdclass course record */
 164    public $course;
 165
 166    /** @var stdclass context object */
 167    public $context;
 168
 169    /** @var int hotpot instance identifier */
 170    public $id;
 171
 172    /** @var string hotpot activity name */
 173    public $name;
 174
 175    /** @var string url or path of the source file for this HotPot instance */
 176    public $sourcefile;
 177
 178    /** @var string the type of the source file for this HotPot instance */
 179    public $sourcetype;
 180
 181    /** @var int the file itemid of the sourcefile for this HotPot instance */
 182    public $sourcelocation;
 183
 184    /** @var string url or path of the config file for this HotPot instance */
 185    public $configfile;
 186
 187    /** @var int the location of the configfile for this HotPot instance */
 188    public $configlocation;
 189
 190    /** @var xxx */
 191    public $entrycm;
 192
 193    /** @var xxx */
 194    public $entrygrade;
 195
 196    /** @var xxx */
 197    public $entrypage;
 198
 199    /** @var xxx */
 200    public $entrytext;
 201
 202    /** @var xxx */
 203    public $entryformat;
 204
 205    /** @var xxx */
 206    public $entryoptions;
 207
 208    /** @var xxx */
 209    public $exitpage;
 210
 211    /** @var xxx */
 212    public $exittext;
 213
 214    /** @var xxx */
 215    public $exitformat;
 216
 217    /** @var xxx */
 218    public $exitoptions;
 219
 220    /** @var xxx */
 221    public $exitcm;
 222
 223    /** @var xxx */
 224    public $exitgrade;
 225
 226    /** @var string the output format to be used when generating browser content */
 227    public $outputformat;
 228
 229    /** @var int navigation aids to be used when this HotPot instance appears in the browser */
 230    public $navigation;
 231
 232    /** @var int defines what will be displayed as the title in the browser */
 233    public $title;
 234
 235    /** @var int indicates what kind of of stop button, if any, will be displayed */
 236    public $stopbutton;
 237
 238    /** @var string the string to be displayed on the stop button */
 239    public $stoptext;
 240
 241    /** @var boolean flag to indicate quiz content should be run processed by Moodle filters */
 242    public $usefilters;
 243
 244    /** @var boolean flag to indicate quiz content should be linked to Moodle glossaries */
 245    public $useglossary;
 246
 247    /** @var string name, if any, of mediaplayer filter to be used to replace media players  */
 248    public $usemediafilter;
 249
 250    /** @var int what kind of popup, if any, should be shown for student feedback */
 251    public $studentfeedback;
 252
 253    /** @var string url, if any, to which student feedback will be sent */
 254    public $studentfeedbackurl;
 255
 256    /** @var int time after which this HotPot becomes available */
 257    public $timeopen;
 258
 259    /** @var int time after which this HotPot is no longer available */
 260    public $timeclose;
 261
 262    /** @var int the time limit for a single attempt at this HotPot */
 263    public $timelimit;
 264
 265    /** @var int minimum time delay, in seconds, between first and second attempt */
 266    public $delay1;
 267
 268    /** @var int minimum time delay, in seconds, between attempts after the second attempt */
 269    public $delay2;
 270
 271    /** @var int delay, in seconds, between finishing a quiz and returning control to Moodle */
 272    public $delay3;
 273
 274    /** @var string optional password required to access this HotPot instance */
 275    public $password;
 276
 277    /** @var string optional IP mask to limit access this HotPot instance */
 278    public $subnet;
 279
 280    /** @var xxx */
 281    public $reviewoptions;
 282
 283    /** @var int 0-100 to show maximum number of attempts allowed at this HotPot instance */
 284    public $attemptlimit;
 285
 286    /** @var int code denoting the grading method for this HotPot instance */
 287    public $grademethod;
 288
 289    /** @var int 0-100 to show maximum grade for this HotPot instance */
 290    public $gradeweighting;
 291
 292    /** @var boolean if true, every click of "hint", "clue" or "check" will be stored in the Moodle database */
 293    public $clickreporting;
 294
 295    /** @var boolean if true, the raw xml returned form the attempt will be stored in the Moodle database  */
 296    public $discarddetails;
 297
 298    /** @var int timestamp of when the module was modified */
 299    public $timemodified;
 300
 301    /** @var int timestamp of when the module was created */
 302    public $timecreated;
 303
 304    /** @var int timestamp of when this object was created */
 305    public $time;
 306
 307    /** @var object representing the source file */
 308    public $source;
 309
 310    /** @var object representing the config file */
 311    public $config;
 312
 313    /** @var object representing a record from the hotpot_attempt table */
 314    public $attempt;
 315
 316    /** @var array cache of all attempts by current user at this HotPot activity */
 317    public $attempts;
 318
 319    /** @var object representing the grade_grade this HotPot activity */
 320    public $gradeitem;
 321
 322    /** @var boolean cache for hotpot:attempt capability */
 323    public $canattempt;
 324
 325    /** @var boolean cache for hotpot:deleteallattempts capability */
 326    public $candeleteallattempts;
 327
 328    /** @var boolean cache for hotpot:deletemyattempts capability */
 329    public $candeletemyattempts;
 330
 331    /** @var boolean cache for hotpot:manage capability */
 332    public $canmanage;
 333
 334    /** @var boolean cache for hotpot:preview capability */
 335    public $canpreview;
 336
 337    /** @var boolean cache for hotpot:reviewallattempts capability */
 338    public $canreviewallattempts;
 339
 340    /** @var boolean cache for hotpot:reviewmyattempts capability */
 341    public $canreviewmyattempts;
 342
 343    /** @var boolean cache for hotpot:view capability */
 344    public $canview;
 345
 346    /** @var boolean cache of swithc to show if user can start this hotpot */
 347    public $canstart;
 348
 349    /**
 350     * Initializes the hotpot API instance using the data from DB
 351     *
 352     * Makes deep copy of all passed records properties. Replaces integer $course attribute
 353     * with a full database record (course should not be stored in instances table anyway).
 354     *
 355     * The method is "protected" to prevent it being called directly. To create a new
 356     * instance of this class please use the self::create() method (see below).
 357     *
 358     * @param stdclass $dbrecord HotPot instance data from the {hotpot} table
 359     * @param stdclass $cm       Course module record as returned by {@link get_coursemodule_from_id()}
 360     * @param stdclass $course   Course record from {course} table
 361     * @param stdclass $context  The context of the hotpot instance
 362     * @param stdclass $attempt  attempt data from the {hotpot_attempts} table
 363     */
 364    private function __construct(stdclass $dbrecord, stdclass $cm, stdclass $course, stdclass $context=null, stdclass $attempt=null) {
 365        foreach ($dbrecord as $field => $value) {
 366            if (property_exists('hotpot', $field)) {
 367                $this->$field = $value;
 368            }
 369        }
 370        $this->cm = $cm;
 371        $this->course = $course;
 372        if (is_null($context)) {
 373            $this->context = hotpot_get_context(CONTEXT_MODULE, $this->cm->id);
 374        } else {
 375            $this->context = $context;
 376        }
 377        if (is_null($attempt)) {
 378            // do nothing
 379        } else {
 380            $this->attempt = $attempt;
 381        }
 382        $this->time = time();
 383    }
 384
 385    ////////////////////////////////////////////////////////////////////////////////
 386    // Static methods                                                             //
 387    ////////////////////////////////////////////////////////////////////////////////
 388
 389    /**
 390     * Creates a new HotPot object
 391     *
 392     * @param stdclass $dbrecord a row from the hotpot table
 393     * @param stdclass $cm a row from the course_modules table
 394     * @param stdclass $course a row from the course table
 395     * @return hotpot the new hotpot object
 396     */
 397    static public function create(stdclass $dbrecord, stdclass $cm, stdclass $course, stdclass $context=null, stdclass $attempt=null) {
 398        return new hotpot($dbrecord, $cm, $course, $context, $attempt);
 399    }
 400
 401    /**
 402     * set_user_editing
 403     */
 404    static public function set_user_editing() {
 405        global $USER;
 406        $editmode = optional_param('editmode', null, PARAM_BOOL);
 407        if (! is_null($editmode)) {
 408            $USER->editing = $editmode;
 409        }
 410    }
 411
 412    /**
 413     * Returns the localized list of navigation settings for a HotPot instance
 414     *
 415     * @return array
 416     */
 417    public static function available_navigations_list() {
 418        return array (
 419            self::NAVIGATION_MOODLE   => get_string('navigation_moodle', 'hotpot'),
 420            self::NAVIGATION_TOPBAR   => get_string('navigation_topbar', 'hotpot'),
 421            self::NAVIGATION_FRAME    => get_string('navigation_frame', 'hotpot'),
 422            self::NAVIGATION_EMBED    => get_string('navigation_embed', 'hotpot'),
 423            self::NAVIGATION_ORIGINAL => get_string('navigation_original', 'hotpot'),
 424            self::NAVIGATION_NONE     => get_string('navigation_none', 'hotpot')
 425        );
 426    }
 427
 428    /**
 429     * Returns the localized list of feedback settings for a HotPot instance
 430     *
 431     * @return array
 432     */
 433    public static function available_feedbacks_list() {
 434        global $CFG;
 435        $list = array (
 436            self::FEEDBACK_NONE        => get_string('none'),
 437            self::FEEDBACK_WEBPAGE     => get_string('feedbackwebpage',  'hotpot'),
 438            self::FEEDBACK_FORMMAIL    => get_string('feedbackformmail', 'hotpot'),
 439            self::FEEDBACK_MOODLEFORUM => get_string('feedbackmoodleforum', 'hotpot')
 440        );
 441        if ($CFG->messaging) {
 442            $list[self::FEEDBACK_MOODLEMESSAGING] = get_string('feedbackmoodlemessaging', 'hotpot');
 443        }
 444        return $list;
 445    }
 446
 447    /**
 448     * Returns the list of media players for the HotPot module
 449     *
 450     * @return array
 451     */
 452    public static function available_mediafilters_list() {
 453        $plugins = get_list_of_plugins('mod/hotpot/mediafilter'); // sorted
 454
 455        if (in_array('moodle', $plugins)) {
 456            // make 'moodle' the first element in the plugins array
 457            unset($plugins[array_search('moodle', $plugins)]);
 458            array_unshift($plugins, 'moodle');
 459        }
 460
 461        // define element type for list of mediafilters (select, radio, checkbox)
 462        $options = array('' => get_string('none'));
 463        foreach ($plugins as $plugin) {
 464            $options[$plugin] = get_string('mediafilter_'.$plugin, 'hotpot');
 465        }
 466        return $options;
 467    }
 468
 469    /**
 470     * Returns the localized list of output format setings for a given HotPot sourcetype
 471     *
 472     * @return array
 473     */
 474    public static function available_outputformats_list($sourcetype) {
 475
 476        $outputformats = array(
 477            '0' => get_string('outputformat_best', 'hotpot')
 478        );
 479        if ($sourcetype) {
 480            $classes = self::get_classes('hotpotattempt', 'renderer.php', 'mod_', '_renderer');
 481            foreach ($classes as $class) {
 482                // use call_user_func() to prevent syntax error in PHP 5.2.x
 483                $sourcetypes = call_user_func(array($class, 'sourcetypes'));
 484                if (in_array($sourcetype, $sourcetypes)) {
 485                    // strip prefix, "mod_hotpot_attempt_", and suffix, "_renderer"
 486                    $outputformat = substr($class, 19, -9);
 487                    $outputformats[$outputformat] = get_string('outputformat_'.$outputformat, 'hotpot');
 488                }
 489            }
 490            // remove "best" if there is only one compatible output format
 491            // if (count($outputformats)==2) {
 492            //     unset($outputformats[0]);
 493            // }
 494        }
 495        return $outputformats;
 496    }
 497
 498    /**
 499     * Returns the localized list of attempt limit settings for a HotPot instance
 500     *
 501     * @return array
 502     */
 503    public static function available_attemptlimits_list() {
 504        $options = array(
 505            0 => get_string('attemptsunlimited', 'hotpot'),
 506        );
 507        for ($i=1; $i<=10; $i++) {
 508            $options[$i] = "$i";
 509        }
 510        return $options;
 511    }
 512
 513    /**
 514     * Returns the localized list of grade method settings for a HotPot instance
 515     *
 516     * @return array
 517     */
 518    public static function available_grademethods_list() {
 519        return array (
 520            self::GRADEMETHOD_HIGHEST => get_string('highestscore', 'hotpot'),
 521            self::GRADEMETHOD_AVERAGE => get_string('averagescore', 'hotpot'),
 522            self::GRADEMETHOD_FIRST   => get_string('firstattempt', 'hotpot'),
 523            self::GRADEMETHOD_LAST    => get_string('lastattempt', 'hotpot'),
 524        );
 525    }
 526
 527    /**
 528     * Returns the localized list of status settings for a HotPot attempt
 529     *
 530     * @return array
 531     */
 532    public static function available_statuses_list() {
 533        return array (
 534            self::STATUS_INPROGRESS => get_string('inprogress', 'hotpot'),
 535            self::STATUS_TIMEDOUT   => get_string('timedout', 'hotpot'),
 536            self::STATUS_ABANDONED  => get_string('abandoned', 'hotpot'),
 537            self::STATUS_COMPLETED  => get_string('completed', 'hotpot')
 538        );
 539    }
 540
 541    /**
 542     * Returns the localized list of grade method settings for a HotPot instance
 543     *
 544     * @return array
 545     */
 546    public static function available_namesources_list() {
 547        return array (
 548            self::TEXTSOURCE_FILE     => get_string('textsourcefile', 'hotpot'),
 549            self::TEXTSOURCE_FILENAME => get_string('textsourcefilename', 'hotpot'),
 550            self::TEXTSOURCE_FILEPATH => get_string('textsourcefilepath', 'hotpot'),
 551            self::TEXTSOURCE_SPECIFIC => get_string('textsourcespecific', 'hotpot')
 552        );
 553    }
 554
 555    /**
 556     * Returns the localized list of grade method settings for a HotPot instance
 557     *
 558     * @return array
 559     */
 560    public static function available_titles_list() {
 561        return array (
 562            self::TEXTSOURCE_SPECIFIC => get_string('hotpotname', 'hotpot'),
 563            self::TEXTSOURCE_FILE     => get_string('textsourcefile', 'hotpot'),
 564            self::TEXTSOURCE_FILENAME => get_string('textsourcefilename', 'hotpot'),
 565            self::TEXTSOURCE_FILEPATH => get_string('textsourcefilepath', 'hotpot')
 566        );
 567    }
 568
 569    /**
 570     * Returns the localized list of maximum grade settings for a HotPot instance
 571     *
 572     * @return array
 573     */
 574    public static function available_gradeweightings_list() {
 575        $options = array();
 576        for ($i=100; $i>=1; $i--) {
 577            $options[$i] = $i;
 578        }
 579        $options[0] = get_string('nograde');
 580        return $options;
 581    }
 582
 583    /**
 584     * Detects the type of the source file
 585     *
 586     * @param stored_file $sourcefile the file that has just been uploaded and stored
 587     * @return string the type of the source file (e.g. hp_6_jcloze_xml)
 588     */
 589    public static function get_sourcetype($sourcefile) {
 590        // include all the hotpot_source classes
 591        $classes = self::get_classes('hotpotsource');
 592
 593        // loop through the classes checking to see if this file is recognized
 594        // use call_user_func() to prevent syntax error in PHP 5.2.x
 595        foreach ($classes as $class) {
 596            if (call_user_func(array($class, 'is_quizfile'), $sourcefile)) {
 597                return call_user_func(array($class, 'get_type'), $class);
 598            }
 599        }
 600
 601        // file is not a recognized quiz type :-(
 602        return '';
 603    }
 604
 605    /**
 606     * Returns a js module object for the HotPot module
 607     *
 608     * @param array $requires
 609     *    e.g. array('base', 'dom', 'event-delegate', 'event-key')
 610     * @return array $strings
 611     *    e.g. array(
 612     *        array('timesup', 'quiz'),
 613     *        array('functiondisabledbysecuremode', 'quiz'),
 614     *        array('flagged', 'question')
 615     *    )
 616     */
 617    public static function get_js_module(array $requires = null, array $strings = null) {
 618        return array(
 619            'name' => 'mod_hotpot',
 620            'fullpath' => '/mod/hotpot/module.js',
 621            'requires' => $requires,
 622            'strings' => $strings,
 623        );
 624    }
 625
 626    /**
 627     * get_version_info
 628     *
 629     * @param xxx $info
 630     * @return xxx
 631     */
 632    public static function get_version_info($info)  {
 633        global $CFG;
 634
 635        static $module = null;
 636        if (is_null($module)) {
 637            $module = new stdClass();
 638            require($CFG->dirroot.'/mod/hotpot/version.php');
 639        }
 640
 641        if (isset($module->$info)) {
 642            return $module->$info;
 643        } else {
 644            return "no $info found";
 645        }
 646    }
 647
 648   /**
 649    * load_mediafilter_filter
 650    *
 651    * @param xxx $classname
 652    */
 653   public static function load_mediafilter_filter($classname)  {
 654        global $CFG;
 655        $path = $CFG->dirroot.'/mod/hotpot/mediafilter/'.$classname.'/class.php';
 656
 657        // check the filter exists
 658        if (! file_exists($path)) {
 659            debugging('hotpot mediafilter class is not accessible: '.$classname, DEBUG_DEVELOPER);
 660            return false;
 661        }
 662
 663        return require_once($path);
 664    }
 665
 666    /**
 667     * sourcefile_options
 668     *
 669     * @param xxx $context
 670     * @return xxx
 671     */
 672    public static function sourcefile_options() {
 673        return array('subdirs' => 1, 'maxbytes' => 0, 'maxfiles' => -1);
 674    }
 675
 676    /**
 677     * text_editors_options
 678     *
 679     * @param xxx $context
 680     * @return xxx
 681     */
 682    public static function text_editors_options($context)  {
 683        return array('subdirs' => 1, 'maxbytes' => 0, 'maxfiles' => EDITOR_UNLIMITED_FILES,
 684                     'changeformat' => 1, 'context' => $context, 'noclean' => 1, 'trusttext' => 0);
 685    }
 686
 687    /**
 688     * text_page_types
 689     *
 690     * @return xxx
 691     */
 692    public static function text_page_types() {
 693        return array('entry', 'exit');
 694    }
 695
 696    /**
 697     * text_page_options
 698     *
 699     * @param xxx $type
 700     * @return xxx
 701     */
 702    public static function text_page_options($type)  {
 703        if ($type=='entry') {
 704            return array(
 705                'title'         => self::ENTRYOPTIONS_TITLE,
 706                'grading'       => self::ENTRYOPTIONS_GRADING,
 707                'dates'         => self::ENTRYOPTIONS_DATES,
 708                'attempts'      => self::ENTRYOPTIONS_ATTEMPTS
 709            );
 710        }
 711        if ($type=='exit') {
 712            return array(
 713                'title'         => self::ENTRYOPTIONS_TITLE,
 714                'encouragement' => self::EXITOPTIONS_ENCOURAGEMENT,
 715                'attemptscore'  => self::EXITOPTIONS_ATTEMPTSCORE,
 716                'hotpotgrade'   => self::EXITOPTIONS_HOTPOTGRADE,
 717                'retry'         => self::EXITOPTIONS_RETRY,
 718                'index'         => self::EXITOPTIONS_INDEX,
 719                'course'        => self::EXITOPTIONS_COURSE,
 720                'grades'        => self::EXITOPTIONS_GRADES
 721            );
 722        }
 723        return array();
 724    }
 725
 726    /**
 727     * user_preferences_fields
 728     *
 729     * @return array of user_preferences used by the HotPot module
 730     */
 731    public static function user_preferences_fieldnames() {
 732        return array(
 733            // fields used only when adding a new HotPot
 734            'namesource','entrytextsource','exittextsource','quizchain',
 735
 736            // source/config files
 737            'sourcefile','sourcelocation','configfile','configlocation',
 738
 739            // entry/exit pages
 740            'entrypage','entryformat','entryoptions',
 741            'exitpage','exitformat','exitoptions',
 742            'entrycm','entrygrade','exitcm','exitgrade',
 743
 744            // display
 745            'outputformat','navigation','title','stopbutton','stoptext',
 746            'usefilters','useglossary','usemediafilter','studentfeedback','studentfeedbackurl',
 747
 748            // access restrictions
 749            'timeopen','timeclose','timelimit','delay1','delay2','delay3',
 750            'password','subnet','reviewoptions','attemptlimit',
 751
 752            // grading and reporting
 753            'grademethod','gradeweighting','clickreporting','discarddetails'
 754        );
 755    }
 756
 757    /**
 758     * string_ids
 759     *
 760     * @param xxx $field_value
 761     * @return xxx
 762     */
 763    public static function string_ids($field_value, $max_field_length=255)  {
 764        $ids = array();
 765
 766        $strings = explode(',', $field_value);
 767        foreach($strings as $str) {
 768            if ($id = self::string_id($str)) {
 769                $ids[] = $id;
 770            }
 771        }
 772        $ids = implode(',', $ids);
 773
 774        // we have to make sure that the list of $ids is no longer
 775        // than the maximum allowable length for this field
 776        if (strlen($ids) > $max_field_length) {
 777
 778            // truncate $ids just before last comma in allowable field length
 779            // Note: largest possible id is something like 9223372036854775808
 780            //       so we must leave space for that in the $ids string
 781            $ids = substr($ids, 0, $max_field_length - 20);
 782            $ids = substr($ids, 0, strrpos($ids, ','));
 783
 784            // create single $str(ing) containing all $strings not included in $ids
 785            $str = implode(',', array_slice($strings, substr_count($ids, ',') + 1));
 786
 787            // append the id of the string containing all the strings not yet in $ids
 788            if ($id = self::string_id($str)) {
 789                $ids .= ','.$id;
 790            }
 791        }
 792
 793        // return comma separated list of string $ids
 794        return $ids;
 795    }
 796
 797    /**
 798     * string_id
 799     *
 800     * @param xxx $str
 801     * @return xxx
 802     */
 803    public static function string_id($str)  {
 804        global $DB;
 805
 806        if (! isset($str) || ! is_string($str) || trim($str)=='') {
 807            // invalid input string
 808            return false;
 809        }
 810
 811        // create md5 key
 812        $md5key = md5($str);
 813
 814        if ($id = $DB->get_field('hotpot_strings', 'id', array('md5key'=>$md5key))) {
 815            // string already exists
 816            return $id;
 817        }
 818
 819        // create a new string record
 820        $record = (object)array('string'=>$str, 'md5key'=>$md5key);
 821        if (! $id = $DB->insert_record('hotpot_strings', $record)) {
 822            print_error('error_insertrecord', 'hotpot', '', 'hotpot_strings');
 823        }
 824
 825        // new string was successfully added
 826        return $id;
 827    }
 828
 829    /**
 830     * get_strings
 831     *
 832     * @param xxx $ids
 833     * @return xxx
 834     */
 835    public static function get_strings($ids)  {
 836        global $DB;
 837
 838        // convert $ids to an array, if necessary
 839        if (is_string($ids)) {
 840            $ids = explode(',', $ids);
 841            $ids = array_filter($ids);
 842        }
 843
 844        // return strings, if any
 845        if (empty($ids)) {
 846            return array();
 847        } else {
 848            list($filter, $params) = $DB->get_in_or_equal($ids);
 849            return $DB->get_records_select('hotpot_strings', "id $filter", $params, '', 'id,string');
 850        }
 851    }
 852
 853    /**
 854     * get_question_text
 855     *
 856     * @param xxx $question
 857     * @return xxx
 858     */
 859    static public function get_question_text($question)   {
 860        global $DB;
 861
 862        if (empty($question->text)) {
 863            // JMatch, JMix and JQuiz
 864            return $question->name;
 865        } else {
 866            // JCloze and JCross
 867            return $DB->get_field('hotpot_strings', 'string', array('id' => $question->text));
 868        }
 869    }
 870
 871    ////////////////////////////////////////////////////////////////////////////////
 872    // Hotpot API                                                                 //
 873    ////////////////////////////////////////////////////////////////////////////////
 874
 875    /**
 876     * @return moodle_url of this hotpot's view page
 877     */
 878    public function view_url($cm=null) {
 879        if (is_null($cm)) {
 880            $cm = $this->cm;
 881        }
 882        return new moodle_url('/mod/'.$cm->modname.'/view.php', array('id' => $cm->id));
 883    }
 884
 885    /**
 886     * @return moodle_url of this hotpot's view page
 887     */
 888    public function report_url($mode='', $cm=null) {
 889        if (is_null($cm)) {
 890            $cm = $this->cm;
 891        }
 892        $params = array('id' => $cm->id);
 893        if ($mode) {
 894            $params['mode'] = $mode;
 895        }
 896        return new moodle_url('/mod/hotpot/report.php', $params);
 897    }
 898
 899    /**
 900     * @return moodle_url of this hotpot's attempt page
 901     */
 902    public function attempt_url($framename='', $cm=null) {
 903        if (is_null($cm)) {
 904            $cm = $this->cm;
 905        }
 906        $params = array('id' => $cm->id);
 907        if ($framename) {
 908            $params['framename'] = $framename;
 909        }
 910        return new moodle_url('/mod/hotpot/attempt.php', $params);
 911    }
 912
 913    /**
 914     * @return moodle_url of this hotpot's attempt page
 915     */
 916    public function submit_url($attempt=null) {
 917        if (is_null($attempt)) {
 918            $attempt = $this->attempt;
 919        }
 920        return new moodle_url('/mod/hotpot/submit.php', array('id' => $attempt->id));
 921    }
 922
 923    /**
 924     * @return moodle_url of the review page for an attempt at this hotpot
 925     */
 926    public function review_url($attempt=null) {
 927        if (is_null($attempt)) {
 928            $attempt = $this->attempt;
 929        }
 930        return new moodle_url('/mod/hotpot/review.php', array('id' => $attempt->id));
 931    }
 932
 933    /**
 934     * @return moodle_url of this course's hotpot index page
 935     */
 936    public function index_url($course=null) {
 937        if (is_null($course)) {
 938            $course = $this->course;
 939        }
 940        return new moodle_url('/mod/hotpot/index.php', array('id' => $course->id));
 941    }
 942
 943    /**
 944     * @return moodle_url of this hotpot's course page
 945     */
 946    public function course_url($course=null) {
 947        if (is_null($course)) {
 948            $course = $this->course;
 949        }
 950        $params = array('id' => $course->id);
 951        $sectionnum = 0;
 952        if (isset($course->coursedisplay) && defined('COURSE_DISPLAY_MULTIPAGE')) {
 953            // Moodle >= 2.3
 954            if ($course->coursedisplay==COURSE_DISPLAY_MULTIPAGE) {
 955                $courseid = $course->id;
 956                $sectionid = $this->cm->section;
 957                if ($modinfo = get_fast_modinfo($this->course)) {
 958                    $sections = $modinfo->get_section_info_all();
 959                    foreach ($sections as $section) {
 960                        if ($section->id==$sectionid) {
 961                            $sectionnum = $section->section;
 962                            break;
 963                        }
 964                    }
 965                }
 966                unset($modinfo, $sections, $section);
 967            }
 968        }
 969        if ($sectionnum) {
 970            $params['section'] = $sectionnum;
 971        }
 972        return new moodle_url('/course/view.php', $params);
 973    }
 974
 975    /**
 976     * @return moodle_url of this hotpot's course grade page
 977     */
 978    public function grades_url($course=null) {
 979        if (is_null($course)) {
 980            $course = $this->course;
 981        }
 982        return new moodle_url('/grade/index.php', array('id' => $course->id));
 983    }
 984
 985    /**
 986     * @return source object representing the source file for this HotPot
 987     */
 988    public function get_source() {
 989        global $CFG, $DB;
 990        if (empty($this->source)) {
 991            // get sourcetype e.g. hp_6_jcloze_xml
 992            $sourcefile = $this->get_sourcefile();
 993            if (! $sourcetype = clean_param($this->sourcetype, PARAM_SAFEDIR)) {
 994                if ($sourcetype = hotpot::get_sourcetype($sourcefile)) {
 995                    $DB->set_field('hotpot', 'sourcetype', $sourcetype, array('id' => $this->id));
 996                    $this->sourcetype = $sourcetype;
 997                } else {
 998                    throw new moodle_exception('missingsourcetype', 'hotpot');
 999                }
1000            }
1001
1002            $dir = str_replace('_', '/', $sourcetype);
1003            require_once($CFG->dirroot.'/mod/hotpot/source/'.$dir.'/class.php');
1004
1005            $classname = 'hotpot_source_'.$sourcetype;
1006            $this->source = new $classname($sourcefile, $this);
1007        }
1008        return $this->source;
1009    }
1010
1011    /**
1012     * Returns the localized description of the grade method
1013     *
1014     * @return string
1015     */
1016    public function format_grademethod() {
1017        $options = $this->available_grademethods_list();
1018        if (array_key_exists($this->grademethod, $options)) {
1019            return $options[$this->grademethod];
1020        } else {
1021            return $this->grademethod; // shouldn't happen
1022        }
1023    }
1024
1025    /**
1026     * Returns the localized description of the attempt status
1027     *
1028     * @return string
1029     */
1030    public static function format_status($status) {
1031        $options = self::available_statuses_list();
1032        if (array_key_exists($status, $options)) {
1033            return $options[$status];
1034        } else {
1035            return $status; // shouldn't happen
1036        }
1037    }
1038
1039    /**
1040     * Returns a formatted version of the $time
1041     *
1042     * @param in $time the time to format
1043     * @param string $format time format string
1044     * @param string $notime return value if $time==0
1045     * @return string
1046     */
1047    public static function format_time($time, $format=null, $notime='&nbsp;') {
1048        if ($time>0) {
1049            return format_time($time, $format);
1050        } else {
1051            return $notime;
1052        }
1053    }
1054
1055    /**
1056     * Returns a formatted version of an (attempt) $record's score
1057     *
1058     * @param object $record from the Moodle database
1059     * @param string $noscore return value if $record->score is not set
1060     * @return string
1061     */
1062    function format_score($record, $default='&nbsp;') {
1063        if (isset($record->score)) {
1064            return $record->score;
1065        } else {
1066            return $default;
1067        }
1068    }
1069
1070    /**
1071     * Returns the stored_file object for this HotPot's source file
1072     *
1073     * @return stored_file
1074     */
1075    public function get_sourcefile() {
1076        global $CFG, $DB;
1077        $fs = get_file_storage();
1078
1079        $filename = basename($this->sourcefile);
1080        $filepath = dirname($this->sourcefile);
1081
1082        // require leading and trailing slash on $filepath
1083        if (substr($filepath, 0, 1)=='/' && substr($filepath, -1)=='/') {
1084            // do nothing - $filepath is valid
1085        } else {
1086            // fix filepath - shouldn't happen !!
1087            // maybe leftover from a messy upgrade
1088            if ($filepath=='.' || $filepath=='') {
1089                $filepath = '/';
1090            } else {
1091                $filepath = '/'.ltrim($filepath, '/');
1092                $filepath = rtrim($filepath, '/').'/';
1093            }
1094            $this->sourcefile = $filepath.$filename;
1095            $DB->set_field('hotpot', 'sourcefile', $this->sourcefile, array('id' => $this->id));
1096        }
1097
1098        if ($file = $fs->get_file($this->context->id, 'mod_hotpot', 'sourcefile', 0, $filepath, $filename)) {
1099            return $file;
1100        }
1101
1102        // the source file is missing, probably this HotPot
1103        // has recently been upgraded/imported from Moodle 1.9
1104        // so we are going to try to create the missing stored file
1105
1106        $file_record = array(
1107            'contextid'=>$this->context->id, 'component'=>'mod_hotpot', 'filearea'=>'sourcefile',
1108            'sortorder'=>1, 'itemid'=>0, 'filepath'=>$filepath, 'filename'=>$filename
1109        );
1110
1111        $coursecontext  = hotpot_get_context(CONTEXT_COURSE, $this->course->id);
1112        $filehash = sha1('/'.$coursecontext->id.'/course/legacy/0'.$filepath.$filename);
1113
1114        if ($file = $fs->get_file_by_hash($filehash)) {
1115            // file exists in legacy course files
1116            if ($file = $fs->create_file_from_storedfile($file_record, $file)) {
1117                return $file;
1118            }
1119        }
1120
1121        $oldfilepath = $CFG->dataroot.'/'.$this->course->id.$filepath.$filename;
1122        if (file_exists($oldfilepath)) {
1123            // file exists on server's filesystem
1124            if ($file = $fs->create_file_from_pathname($file_record, $oldfilepath)) {
1125                return $file;
1126            }
1127        }
1128
1129        // source file not found - shouldn't happen !!
1130        throw new moodle_exception('sourcefilenotfound', 'hotpot', '', $this->sourcefile);
1131    }
1132
1133    /**
1134     * Returns the output format to be used for an attempt at this HotPot
1135     * If the outputformat is not given, the "best" outupt format is returned
1136     * which is the one with the same name as "sourcetype" for this HotPot
1137     *
1138     * @return string $subtype
1139     */
1140    public function get_outputformat() {
1141        if (empty($this->outputformat)) {
1142            return $this->get_source()->get_best_outputformat();
1143        } else {
1144            return clean_param($this->outputformat, PARAM_SAFEDIR);
1145        }
1146    }
1147
1148    /**
1149     * can_attempt
1150     *
1151     * @return xxx
1152     */
1153    function can_attempt() {
1154        if (is_null($this->canattempt)) {
1155            $this->canattempt = has_capability('mod/hotpot:attempt', $this->context);
1156        }
1157        return $this->canattempt;
1158    }
1159
1160    /**
1161     * can_deleteattempts
1162     *
1163     * @return xxx
1164     */
1165    function can_deleteattempts() {
1166        return $this->can_deletemyattempts() || $this->can_deleteallattempts();
1167    }
1168
1169    /**
1170     * can_deleteallattempts
1171     *
1172     * @return xxx
1173     */
1174    function can_deleteallattempts() {
1175        if (is_null($this->candeleteallattempts)) {
1176            $this->candeleteallattempts = has_capability('mod/hotpot:deleteallattempts', $this->context);
1177        }
1178        return $this->candeleteallattempts;
1179    }
1180
1181    /**
1182     * can_deletemyattempts
1183     *
1184     * @return xxx
1185     */
1186    function can_deletemyattempts() {
1187        if (is_null($this->candeletemyattempts)) {
1188            $this->candeletemyattempts = has_capability('mod/hotpot:deletemyattempts', $this->context);
1189        }
1190        return $this->candeletemyattempts;
1191    }
1192
1193    /**
1194     * can_manage
1195     *
1196     * @return xxx
1197     */
1198    function can_manage() {
1199        if (is_null($this->canmanage)) {
1200            $this->canmanage = has_capability('mod/hotpot:manage', $this->context);
1201        }
1202        return $this->canmanage;
1203    }
1204
1205    /**
1206     * can_preview
1207     *
1208     * @return xxx
1209     */
1210    function can_preview() {
1211        if (is_null($this->canpreview)) {
1212            $this->canpreview = has_capability('mod/hotpot:preview', $this->context);
1213        }
1214        return $this->canpreview;
1215    }
1216
1217    /**
1218     * can_reviewallattempts
1219     *
1220     * @return xxx
1221     */
1222    function can_reviewattempts() {
1223        return $this->can_reviewmyattempts() || $this->can_reviewallattempts();
1224    }
1225
1226    /**
1227     * can_reviewallattempts
1228     *
1229     * @return xxx
1230     */
1231    function can_reviewallattempts() {
1232        if (is_null($this->canreviewallattempts)) {
1233            $this->canreviewallattempts = has_capability('mod/hotpot:reviewallattempts', $this->context);
1234        }
1235        return $this->canreviewallattempts;
1236    }
1237
1238    /**
1239     * can_reviewmyattempts
1240     *
1241     * @return xxx
1242     */
1243    function can_reviewmyattempts() {
1244        if (is_null($this->canreviewmyattempts)) {
1245            $this->canreviewmyattempts = has_capability('mod/hotpot:reviewmyattempts', $this->context);
1246        }
1247        return $this->canreviewmyattempts;
1248    }
1249
1250    /**
1251     * can_view
1252     *
1253     * @return xxx
1254     */
1255     function can_view() {
1256        if (is_null($this->canview)) {
1257            $this->canview = has_capability('mod/hotpot:view', $this->context);
1258        }
1259        return $this->canview;
1260     }
1261
1262    /**
1263     * can_start
1264     *
1265     * @param xxx $canstart (optional, default=null)
1266     * @return xxx
1267     */
1268    function can_start($canstart=null)  {
1269        if (is_null($canstart)) {
1270            if (is_null($this->canstart)) {
1271                // set automatically
1272                if (! $this->can_attempt()) {
1273                    $this->canstart = false;
1274                } else if ($this->require_isopen()) {
1275                    $this->canstart = false;
1276                } else if ($this->require_notclosed()) {
1277                    $this->canstart = false;
1278                } else if ($this->require_entrycm()) {
1279                    $this->canstart = false;
1280                } else if ($this->require_delay('delay1')) {
1281                    $this->canstart = false;
1282                } else if ($this->require_delay('delay2')) {
1283                    $this->canstart = false;
1284                } else if ($this->require_moreattempts(true)) {
1285                    $this->canstart = false;
1286                } else { // no errors so far
1287                    $this->canstart = true;
1288                }
1289            }
1290            // get
1291            return $this->canstart;
1292        } else {
1293            // set manually
1294            $this->canstart = $canstart;
1295        }
1296    }
1297
1298    /**
1299     * Returns the subtype to be used to get a renderer for an attempt at this HotPot
1300     *
1301     * @return string $subtype
1302     */
1303    public function get_attempt_renderer_subtype() {
1304        return 'attempt_'.$this->get_outputformat();
1305    }
1306
1307    /**
1308     * set_preferred_pagelayout
1309     *
1310     * @param xxx $PAGE
1311     */
1312    public function set_preferred_pagelayout($PAGE)  {
1313        // page layouts are defined in theme/xxx/config.php
1314
1315        switch ($this->navigation) {
1316
1317            case self::NAVIGATION_ORIGINAL:
1318            case self::NAVIGATION_NONE:
1319                // $PAGE->set_pagelayout('popup');
1320                $PAGE->set_pagelayout('embedded');
1321                break;
1322
1323            case self::NAVIGATION_FRAME:
1324            case self::NAVIGATION_EMBED:
1325                $framename = optional_param('framename', '', PARAM_ALPHA);
1326                if ($framename=='top') {
1327                    $PAGE->set_pagelayout('frametop');
1328                }
1329                if ($framename=='main') {
1330                    $PAGE->set_pagelayout('embedded');
1331                }
1332                break;
1333
1334            case self::NAVIGATION_TOPBAR:
1335                $PAGE->set_pagelayout('login'); // no nav menu
1336                break;
1337        }
1338    }
1339
1340    /**
1341     * to_stdclass
1342     *
1343     * @return xxx
1344     */
1345    public function to_stdclass() {
1346        $stdclass = new stdclass();
1347        $vars = get_object_vars($this);
1348        foreach ($vars as $name => $value) {
1349            if (is_object($this->$name) || is_array($this->$name)) {
1350                continue;
1351            }
1352            $stdclass->$name = $value;
1353        }
1354        // extra fields required for grades
1355        if (isset($this->course) && is_object($this->course)) {
1356            $stdclass->course = $this->course->id;
1357        }
1358        if (isset($this->cm) && is_object($this->cm)) {
1359            $stdclass->cmidnumber = $this->cm->id;
1360        }
1361        $stdclass->modname = 'hotpot';
1362        return $stdclass;
1363    }
1364
1365    /**
1366     * Returns the subtype to be used to get a report renderer for this HotPot
1367     *
1368     * @return string $mode
1369     * @return string $subtype
1370     */
1371    public function get_report_renderer_subtype($mode) {
1372        if ($mode=='') {
1373            $mode = 'overview';
1374        }
1375        return 'report_'.$mode;
1376    }
1377
1378    ////////////////////////////////////////////////////////////////////////////////
1379    // Internal methods (implementation details)                                  //
1380    ////////////////////////////////////////////////////////////////////////////////
1381
1382    /**
1383     * This function will "include" all the files matching $classfilename for a given a plugin type
1384     * (e.g. hotpotsource), and return a list of classes that were included
1385     *
1386     * @param string $plugintype one of the plugintypes specified in mod/hotpot/db/subplugins.php
1387     */
1388    static public function get_classes($plugintype, $classfilename='class.php', $prefix='', $suffix='') {
1389        global $CFG;
1390
1391        // initialize array to hold class names
1392        $classes = array();
1393
1394        // get list of all subplugins
1395        $subplugins = array();
1396        include($CFG->dirroot.'/mod/hotpot/db/subplugins.php');
1397
1398        // extract the $plugintype we are interested in
1399        $types = array();
1400        if (isset($subplugins[$plugintype])) {
1401            $types[$plugintype] = $subplugins[$plugintype];
1402        }
1403        unset($subplugins);
1404
1405        // we are not interested in these directories
1406        $ignored = array('CVS', '_vti_cnf', 'simpletest', 'db', 'yui', 'phpunit');
1407
1408        // get all the subplugins for this $plugintype
1409        while (list($type, $dir) = each($types)) {
1410            $fulldir = $CFG->dirroot.'/'.$dir;
1411            if (is_dir($fulldir) && file_exists($fulldir.'/'.$classfilename)) {
1412
1413                // include the class
1414                require_once($fulldir.'/'.$classfilename);
1415
1416                // extract class name, e.g. hotpot_source_hp_6_jcloze_xml
1417                // from $subdir, e.g. mod/hotpot/file/h6/6/jcloze/xml
1418                // by removing leading "mod/" and converting all "/" to "_"
1419                $classes[] = $prefix.str_replace('/', '_', substr($dir, 4)).$suffix;
1420
1421                // get subplugins in this $dir
1422                $items = new DirectoryIterator($fulldir);
1423                foreach ($items as $item) {
1424                    if (substr($item, 0, 1)=='.' || in_array($item, $ignored)) {
1425                        continue;
1426                    }
1427                    if ($item->isDir()) {
1428                        $types[$type.$item] = $dir.'/'.$item;
1429                    }
1430                }
1431            }
1432        }
1433        sort($classes);
1434        return $classes;
1435    }
1436
1437    /**
1438     * get_report_modes
1439     *
1440     * @return xxx
1441     */
1442    function get_report_modes() {
1443        $modes = array('overview', 'scores', 'responses', 'analysis');
1444        if ($this->clickreporting) {
1445            $modes[] = 'clicktrail';
1446        }
1447        return $modes;
1448    }
1449
1450    ////////////////////////////////////////////////////////////////////////////////
1451    // methods to access database records connected to this HotPot                //
1452    ////////////////////////////////////////////////////////////////////////////////
1453
1454    /*
1455     * create an db record for an attempt at this HotPot activity
1456     *
1457     * @return int the id of the newly created attempt record
1458     */
1459    function create_attempt() {
1460        global $DB, $USER;
1461        if (empty($this->attempt)) {
1462
1463            // get max attempt number so far
1464            $sql = "SELECT MAX(attempt) FROM {hotpot_attempts} WHERE hotpotid=? AND userid=?";
1465            if ($max_attempt = $DB->get_field_sql($sql, array($this->id, $USER->id))) {
1466                $max_attempt ++;
1467            } else {
1468                $max_attempt = 1;
1469            }
1470
1471            // create attempt record
1472            $this->attempt = new stdClass();
1473            $this->attempt->hotpotid       = $this->id;
1474            $this->attempt->userid         = $USER->id;
1475            $this->attempt->starttime      = 0;
1476            $this->attempt->endtime        = 0;
1477            $this->attempt->score          = 0;
1478            $this->attempt->penalties      = 0;
1479            $this->attempt->attempt        = $max_attempt;
1480            $this->attempt->timestart      = $this->time;
1481            $this->attempt->timefinish     = 0;
1482            $this->attempt->status         = self::STATUS_INPROGRESS;
1483            $this->attempt->clickreportid  = 0;
1484            $this->attempt->timemodified   = $this->time;
1485
1486            // insert attempt record into database
1487            if (! $this->attempt->id = $DB->insert_record('hotpot_attempts', $this->attempt)) {
1488                throw new moodle_exception('error_insertrecord', 'hotpot', '', 'hotpot_attempts');
1489            }
1490
1491            // set previous "in progress" attempt(s) to adandoned
1492            $select = 'hotpotid=? AND userid=? AND attempt<=? AND status=?';
1493            $params = array($this->id, $USER->id, $max_attempt, self::STATUS_INPROGRESS);
1494            if ($attempts = $DB->get_records_select('hotpot_attempts', $select, $params)) {
1495                foreach ($attempts as $attempt) {
1496                    $attempt->timemodified = $this->time;
1497                    $attempt->status = self::STATUS_ABANDONED;
1498   

Large files files are truncated, but you can click here to view the full file