Skip to content
Snippets Groups Projects
Select Git revision
  • fix-remote-url_v4.8.3
  • fix-remote-url_v4.8.x
  • fix-remote-url_v4.7.x
  • fix-remote-url_v4.6.0
  • fix-remote-urls
  • master default
6 results

vle_specific.php

Blame
  • vle_specific.php 12.00 KiB
    <?php
    // This file is part of Stack - http://stack.maths.ed.ac.uk/
    //
    // Stack 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.
    //
    // Stack 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 Stack.  If not, see <http://www.gnu.org/licenses/>.
    
    defined('MOODLE_INTERNAL') || die();
    
    // This file defines question_display_options which the next class extends.
    require_once(__DIR__.'/../../../lib/questionlib.php');
    require_once('questiondisplayoptions.php');
    
    /**
     * A collection of things that are a bit VLE specific and have been
     * extracted from the general logic.
     *
     * If you are porting to another platform you should check these out
     * these are not going to stop you from progressing but you will need
     * these at some point.
     *
     * There are two main things here:
     *
     *   1. Permission checking, the future error message system will tune its
     *      verbosity based on whether the user is a teacher or not.
     *
     *   2. Attached file management, any links that need rewriting to
     *      access attached fiels should be handled by this. This is relevant
     *      for all bits of CASText.
     *
     *
     * Elsewhere there are other major things:
     *
     *   1. The JSXGraph block in the CASText system uses JavaScript and loads
     *      it through the system, you will probably need to replace the block,
     *      should be enough to replace the portion of the block pushing out
     *      the script and the script itself may require some tuning related
     *      to the JavaScript Module system. If you don't want to support
     *      binding of inputs to JSXGraphs, just throw the block away.
     *
     *      CASText blocks can be replaced during execution so you do not even
     *      need to touch the original file. Simply use the `register`-function
     *      in the block-factory to replace the class handling that particular
     *      block. Same logic can be used to add blocks if for example your file
     *      management would need a new one.
     *
     *   2. The inputs and their related JavaScripts, these are the difficult
     *      ones. Again replacing scripts and the loading logic for them can
     *      prove to be hard and you may even choose to live without
     *      the instant validation feature those scripts provide. Other than
     *      that the recommended way is to map whatever way you deal with
     *      $_POST or even $_GET data so that those inputs receive similar
     *      $response arrays as they would in Moodle. Mapping functions for
     *      dealing with the script handling would be a good idea, or dummy
     *      functions if one does not care about those.
     *
     *   3. Storage of the question, you can freely store thigns as you wish
     *      but it would be nice to have unique identifiers for all
     *      the things that the original Moodle database model has separated
     *      to tables. And naturally mapping to similar arrays/objects on
     *      on the code side will help.
     *
     */
    
    /**
     * This answers the question whether the currently active user is
     * able to edit this question. Basically, editing user.
     *
     * If you are unable to answer this question simply return FALSE.
     */
    function stack_user_can_edit_question($question): bool {
        // In Moodle we can get this directly from the question itself.
        return $question->has_cap('edit');
    }
    
    /**
     * This answers the question whether the currently active user is
     * able to view this question. Basically, have it present in something
     * that they are supposed to see.
     *
     * If you are unable to answer this question simply return TRUE.
     * This is currently used for [[textdownload]] and being able to figure
     * out a link to some other persons attempt is not really a problem.
     */
    function stack_user_can_view_question($question): bool {
        // In Moodle we can get this directly from the question itself.
        return $question->has_cap('view');
    }
    
    /**
     * Attachement files and CASText2 compilation note:
     *  1. If your attacment url is entirelly static after the question
     *     has received its database IDs please write it open here.
     *  2. In Moodle the url includes usage specific identifiers and must
     *     therefore be written open at the point of usage.
     *  3. Due to that we use the [[pfs]]-block to carry relevant details
     *     around in the code so that the writing open step can access these
     *     details when need be and in Moodle the [[pfs]]-block does that.
     *  4. If you have simillar needs for urls being specific to the user
     *     or usage crate your own block like [[pfs]] and register it to
     *     the CASText system to do the rewriting.
     *  5! If you have that type of variance in handling you must not
     *     rewrite at this point as the result of these is stored as compiled
     *     CASText and will be used for all future users of this question.
     *  6. You may need to deal with permissions here as well if you track
     *     access separately based on the part of the question the file
     *     exists in.
     *
     *  7. Note that rewriting to static urls during question import is also
     *     an option but it means that one needs to do more complex things
     *     during export if one wants to export those questiosn with those
     *     files.
     */
    
    /**
     * Rewrites or wraps in rewriting logic a given CASText string if it
     * includes placeholders for urls that need to be rewritten.
     *
     * If your system does not support any such urls just return the string
     * as is.
     */
    function stack_castext_file_filter(string $castext, array $identifiers): string {
        if ($castext === '') {
            // Nothing to do with empty strings.
            return $castext;
        }
    
        // In Moodle these are easy to spot.
        if (mb_strpos($castext, '@@PLUGINFILE@@') !== false) {
            // We use the PFS block that has been specicifally
            // built for Moodle to pass on the relevant details.
            $block = '[[pfs';
            switch ($identifiers['field']) {
                case 'questiontext':
                case 'generalfeedback':
                    $block .= ' component="question"';
                    $block .= ' filearea="' . $identifiers['field'] . '"';
                    $block .= ' itemid="' . $identifiers['questionid'] . '"';
                    break;
                case 'specificfeedback':
                case 'prtcorrect': // These three are not in actual use.
                case 'prtpartiallycorrect':
                case 'prtincorrect':
                    $block .= ' component="qtype_stack"';
                    $block .= ' filearea="' . $identifiers['field'] . '"';
                    $block .= ' itemid="' . $identifiers['questionid'] . '"';
                    break;
                case 'prtnodetruefeedback':
                case 'prtnodefalsefeedback':
                    $block .= ' component="qtype_stack"';
                    $block .= ' filearea="' . $identifiers['field'] . '"';
                    $block .= ' itemid="' . $identifiers['prtnodeid'] . '"';
                    break;
            }
            $block .= ']]';
            return $block . $castext . '[[/pfs]]';
        }
        return $castext;
    }
    
    /*
     * This function returns the version number of the current Moodle.
     */
    function stack_determine_moodle_version() {
        $v = get_config('moodle');
        return($v->branch);
    }
    
    /*
     * This function returns fully defined URL for a file present in
     * the `corsscripts` directory. Either mapped through logic that
     * modifies headers or a direct link.
     */
    function stack_cors_link(string $filename): string {
        return (new moodle_url(
                '/question/type/stack/corsscripts/cors.php', ['name' => $filename]))->out(false);
    }
    
    /*
     * Gets the URL used for MathJax, might be VLE local.
     */
    function stack_get_mathjax_url(): string {
        // TODO: figure out how to support VLE local with CORS.
        return 'https://cdn.jsdelivr.net/npm/mathjax@2.7.9/MathJax.js?config=TeX-AMS-MML_HTMLorMML';
    }
    
    /*
     * Gets the url for MathJax 3.
     */
    function stack_get_mathjax3_url() {
        return 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js';
    }
    
    /*
     * Give the VLE a chance to clear any question cache.
     */
    function stack_clear_vle_question_cache(int $questionid) {
        question_bank::notify_question_edited($questionid);
    }
    
    /*
     * This is needed to put links to the STACK question dashboard into the question.
     */
    function question_display_options() {
        $options = new qtype_stack_question_display_options();
        $options->readonly = true;
        $options->flags = question_display_options::HIDDEN;
        $options->suppressruntestslink = true;
        return $options;
    }
    
    /*
     * This uses whatever methods the VLE wants to use to fetch included urls
     * for the inclusion methods and can do caching at the request level.
     *
     * The requirements are as follows:
     *  1. Must not cache, over multiple requests, the inclusion must use
     *     remote version at the time of inclusion.
     *  2. Supports inclusion from http(s)://, contrib(l):// and template(l)://
     *     URLs.
     *  3. contrib:// is special shorthand for fetching a file from a particular
     *     GitHub side folder. If the "l" suffix is there then the file will be read
     *     from a matching local folder, if fetching from GitHub fails we do not
     *     automatically fall-back to the local version.
     *  4. template:// is similalr but has a different folder.
     *
     *  contrib:// is for CAS side stuff and template:// is for CASText side stuff.
     *
     *  Returns the string content of the URL/file. If failign return false.
     */
    function stack_fetch_included_content(string $url) {
        static $cache = [];
        $lc = trim(strtolower($url));
        $good = false;
        $islocalfile = false;
        // Not actually passing the $error out now, it is here for documentation
        // and possible future use.
        $error = 'Not a fetchable URL type.';
        $translated = $url;
        if (strpos($url, '://') === false) {
            $good = false;
            return false;
        }
        $path = explode('://', $url, 2)[1];
        if (strpos($lc, 'http://') === 0 || strpos($lc, 'https://') === 0) {
            $good = true;
        } else {
            if (strpos($path, '..') !== false || strpos($path, '/') === 0 || strpos($path, '~') === 0) {
                $error = 'Traversing the directory tree is forbidden.';
                $good = false;
                return false;
            }
        }
    
        if (strpos($lc, 'contrib://') === 0 || strpos($lc, 'contribl://') === 0) {
            $good = true;
            if (strpos($lc, 'contrib://') === 0) {
                $translated = 'https://raw.githubusercontent.com/maths/moodle-qtype_stack/' .
                                        'master/stack/maxima/contrib/' . $path;
            } else {
                $islocalfile = true;
                $translated = __DIR__ . '/stack/maxima/contrib/' . $path;
            }
        } else if (strpos($lc, 'template://') === 0 || strpos($lc, 'templatel://') === 0) {
            $good = true;
            if (strpos($lc, 'template://') === 0) {
                $translated = 'https://raw.githubusercontent.com/maths/moodle-qtype_stack/' .
                                        'master/stack/cas/castext2/template/' . $path;
            } else {
                $islocalfile = true;
                $translated = __DIR__ . '/stack/cas/castext2/template/' . $path;
            }
        }
    
        if ($good) {
            if (!isset($cache[$translated])) {
                // Feel free to apply any proxying here if you want.
                // Just remember that $islocalfile might be true and you might do
                // something else then.
                if ($islocalfile) {
                    $cache[$translated] = file_get_contents($translated);
                } else {
                    $translated = clean_param($translated, PARAM_URL);
                    $headers = get_headers($translated);
                    if (strpos($headers[0], '404') === false) {
                        $cache[$translated] = download_file_content($translated);
                    } else {
                        $cache[$translated] = false;
                    }
                }
            }
            return $cache[$translated];
        }
        $cache[$translated] = false;
        return false;
    }