Skip to content
Snippets Groups Projects
Commit c2a49eea authored by Matti Harjula's avatar Matti Harjula
Browse files

Move the logic for inclusion of remote content to be a VLE specific part so that should a VLE

want to apply some sort of proxy settings they now can.

In preparation for future also include special paths for shared CASText2 templates in addition
to the current contrib for shared CAS content.
parent 843c39dc
No related branches found
No related tags found
No related merge requests found
...@@ -40,19 +40,8 @@ require_once(__DIR__ . '/../utils.php'); ...@@ -40,19 +40,8 @@ require_once(__DIR__ . '/../utils.php');
*/ */
class stack_cas_castext2_include extends stack_cas_castext2_block { class stack_cas_castext2_include extends stack_cas_castext2_block {
// Avoid retrieving the same file multiple times during the same request.
private static $extcache = [];
private static function file_get_contents($url) {
if (isset(self::$extcache[$url])) {
return self::$extcache[$url];
}
self::$extcache[$url] = file_get_contents($url);
return self::$extcache[$url];
}
public function compile($format, $options): ?MP_Node { public function compile($format, $options): ?MP_Node {
$src = self::file_get_contents($this->params['src']); $src = stack_fetch_included_content($this->params['src']);
if (isset($options['in include'])) { if (isset($options['in include'])) {
// We will need to rethink the validate_extract_attributes()-logic // We will need to rethink the validate_extract_attributes()-logic
// to extract casstrings from nested inclusions. Also loops... // to extract casstrings from nested inclusions. Also loops...
...@@ -79,7 +68,7 @@ class stack_cas_castext2_include extends stack_cas_castext2_block { ...@@ -79,7 +68,7 @@ class stack_cas_castext2_include extends stack_cas_castext2_block {
public function validate_extract_attributes(): array { public function validate_extract_attributes(): array {
// This is tricky, we need to validate the attributes of the included content. // This is tricky, we need to validate the attributes of the included content.
// To do that we need to retrieve it and process it again, luckily this gets cached. // To do that we need to retrieve it and process it again, luckily this gets cached.
$src = self::file_get_contents($this->params['src']); $src = stack_fetch_included_content($this->params['src']);
if ($src === false) { if ($src === false) {
throw new stack_exception('Include block source not accessible: ' . $this->params['src']); throw new stack_exception('Include block source not accessible: ' . $this->params['src']);
} }
......
...@@ -24,6 +24,7 @@ defined('MOODLE_INTERNAL') || die(); ...@@ -24,6 +24,7 @@ defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/autogen/parser.mbstring.php'); require_once(__DIR__ . '/autogen/parser.mbstring.php');
// Also needs stack_string(). // Also needs stack_string().
require_once(__DIR__ . '/../../locallib.php'); require_once(__DIR__ . '/../../locallib.php');
require_once(__DIR__ . '/../../vle_specific.php');
require_once(__DIR__ . '/../utils.class.php'); require_once(__DIR__ . '/../utils.class.php');
require_once(__DIR__ . '/MP_classes.php'); require_once(__DIR__ . '/MP_classes.php');
...@@ -247,8 +248,6 @@ class maxima_parser_utils { ...@@ -247,8 +248,6 @@ class maxima_parser_utils {
// Generates errors if inclusions within inclusions or inclusions in unexpected places. // Generates errors if inclusions within inclusions or inclusions in unexpected places.
// Returns either the AST or some form of an exception. // Returns either the AST or some form of an exception.
public static function parse_and_insert_missing_semicolons_with_includes($str) { public static function parse_and_insert_missing_semicolons_with_includes($str) {
static $remotes = [];
$root = self::parse_and_insert_missing_semicolons($str); $root = self::parse_and_insert_missing_semicolons($str);
if ($root instanceof MP_Root) { if ($root instanceof MP_Root) {
if (isset($root->position['fixedsemicolons'])) { if (isset($root->position['fixedsemicolons'])) {
...@@ -259,7 +258,7 @@ class maxima_parser_utils { ...@@ -259,7 +258,7 @@ class maxima_parser_utils {
// Ok now seek for the inclusions if any are there. // Ok now seek for the inclusions if any are there.
$includecount = 0; $includecount = 0;
$errors = []; $errors = [];
$include = function($node) use (&$includecount, &$errors, &$remotes) { $include = function($node) use (&$includecount, &$errors) {
if ($node instanceof MP_FunctionCall && $node->name instanceof MP_Atom && if ($node instanceof MP_FunctionCall && $node->name instanceof MP_Atom &&
($node->name->value === 'stack_include' || $node->name->value === 'stack_include_contrib')) { ($node->name->value === 'stack_include' || $node->name->value === 'stack_include_contrib')) {
// Now the first requirement for this is that this must be a top level item // Now the first requirement for this is that this must be a top level item
...@@ -275,19 +274,9 @@ class maxima_parser_utils { ...@@ -275,19 +274,9 @@ class maxima_parser_utils {
// such stuff. // such stuff.
$remoteurl = $node->arguments[0]->value; $remoteurl = $node->arguments[0]->value;
if ($node->name->value === 'stack_include_contrib') { if ($node->name->value === 'stack_include_contrib') {
$remoteurl = 'https://raw.githubusercontent.com/maths/moodle-qtype_stack/' . $remoteurl = 'contrib://' . $remoteurl;
'master/stack/maxima/contrib/' . $remoteurl;
}
if (isset($remotes[$remoteurl])) {
$srccode = $remotes[$remoteurl];
} else {
$fileheaders = get_headers($remoteurl);
$srccode = false;
if (strpos($fileheaders[0], '404') === false) {
$srccode = file_get_contents($remoteurl);
$remotes[$remoteurl] = $srccode;
}
} }
$srccode = stack_fetch_included_content($remoteurl);
if ($srccode === false) { if ($srccode === false) {
// Do not give the address in the output. // Do not give the address in the output.
$errors[] = 'stack_include or stack_include_contrib, could not retrieve: ' . $remoteurl; $errors[] = 'stack_include or stack_include_contrib, could not retrieve: ' . $remoteurl;
......
...@@ -210,3 +210,86 @@ function question_display_options() { ...@@ -210,3 +210,86 @@ function question_display_options() {
$options->suppressruntestslink = true; $options->suppressruntestslink = true;
return $options; 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 fetchign a file from a particular
* GitHub side folder. If the "l" suffix is there then the file will be red
* 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): string | bool {
static $cache = [];
$lc = trim(strtolower($url));
$good = false;
$islocalfile = false;
$error = 'Not a fetchable URL type.';
$translated = $url;
if (strpos($lc, 'http://') === 0 || strpos($lc, 'https://') === 0) {
$good = true;
} else if (strpos($lc, 'contrib://') === 0 || strpos($lc, 'contribl://') === 0) {
$path = explode('://', $url, 2)[1];
if (strpos('..', $path) !== false || strpos('/', $path) === 0) {
$error = 'Traversing the directory tree is forbidden.';
} else {
$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) {
$path = explode('://', $url, 2)[1];
if (strpos('..', $path) !== false || strpos('/', $path) === 0) {
$error = 'Traversing the directory tree is forbidden.';
} else {
$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;
}
}
}
// Not actually passing the $error out now, it is here for documentation
// and possible future use.
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 {
$headers = get_headers($translated);
if (strpos($headers[0], '404') === false) {
$cache[$translated] = file_get_contents($translated);
} else {
$cache[$translated] = false;
}
}
}
return $cache[$translated];
}
$cache[$translated] = false;
return false;
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment