Select Git revision
vle_specific.php
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;
}