From bcdf87a877b13e1acf02cdc3660112533c1ca769 Mon Sep 17 00:00:00 2001 From: Ilya Tregubov Date: Mon, 4 Jan 2021 15:40:06 +0200 Subject: [PATCH] MDL-70009 h5pactivity: Split get_pluginfile_hash in two methods. --- h5p/classes/api.php | 77 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 64 insertions(+), 13 deletions(-) diff --git a/h5p/classes/api.php b/h5p/classes/api.php index cab34764a1a..d864e79f98b 100644 --- a/h5p/classes/api.php +++ b/h5p/classes/api.php @@ -194,7 +194,10 @@ class api { global $DB; // Deconstruct the URL and get the pathname associated. - $pathnamehash = self::get_pluginfile_hash($url, $preventredirect); + if (self::can_access_pluginfile_hash($url, $preventredirect)) { + $pathnamehash = self::get_pluginfile_hash($url); + } + if (!$pathnamehash) { return [false, false]; } @@ -337,22 +340,28 @@ class api { * @param factory $factory The \core_h5p\factory object */ public static function delete_content_from_pluginfile_url(string $url, factory $factory): void { + global $DB; + // Get the H5P to delete. - list($file, $h5p) = self::get_content_from_pluginfile_url($url); + $pathnamehash = self::get_pluginfile_hash($url); + $h5p = $DB->get_record('h5p', ['pathnamehash' => $pathnamehash]); if ($h5p) { self::delete_content($h5p, $factory); } } /** - * Get the pathnamehash from an H5P internal URL. + * If user can access pathnamehash from an H5P internal URL. * * @param string $url H5P pluginfile URL poiting to an H5P file. * @param bool $preventredirect Set to true in scripts that can not redirect (CLI, RSS feeds, etc.), throws exceptions * - * @return string|false pathnamehash for the file in the internal URL. + * @return bool if user can access pluginfile hash. + * @throws \moodle_exception + * @throws \coding_exception + * @throws \require_login_exception */ - protected static function get_pluginfile_hash(string $url, bool $preventredirect = true) { + protected static function can_access_pluginfile_hash(string $url, bool $preventredirect = true): bool { global $USER, $CFG; // Decode the URL before start processing it. @@ -365,7 +374,6 @@ class api { // We only need the slasharguments. $path = substr($path, strpos($path, '.php/') + 5); $parts = explode('/', $path); - $filename = array_pop($parts); // If the request is made by tokenpluginfile.php we need to avoid userprivateaccesskey. if (strpos($url, '/tokenpluginfile.php')) { @@ -377,11 +385,6 @@ class api { $component = array_shift($parts); $filearea = array_shift($parts); - // Ignore draft files, because they are considered temporary files, so shouldn't be displayed. - if ($filearea == 'draft') { - return false; - } - // Get the context. try { list($context, $course, $cm) = get_context_info_array($contextid); @@ -415,7 +418,7 @@ class api { $parentcontext = $context->get_parent_context(); if ($parentcontext->contextlevel === CONTEXT_COURSECAT) { // Check if category is visible and user can view this category. - if (!core_course_category::get($parentcontext->instanceid, IGNORE_MISSING)) { + if (!\core_course_category::get($parentcontext->instanceid, IGNORE_MISSING)) { send_file_not_found(); } } else if ($parentcontext->contextlevel === CONTEXT_USER && $parentcontext->instanceid != $USER->id) { @@ -431,7 +434,7 @@ class api { // For CONTEXT_MODULE and CONTEXT_COURSE check if the user is enrolled in the course. // And for CONTEXT_MODULE has permissions view this .h5p file. if ($context->contextlevel == CONTEXT_MODULE || - $context->contextlevel == CONTEXT_COURSE) { + $context->contextlevel == CONTEXT_COURSE) { // Require login to the course first (without login to the module). require_course_login($course, true, null, !$preventredirect, $preventredirect); @@ -448,6 +451,54 @@ class api { } } + return true; + } + + /** + * Get the pathnamehash from an H5P internal URL. + * + * @param string $url H5P pluginfile URL poiting to an H5P file. + * + * @return string|false pathnamehash for the file in the internal URL. + * + * @throws \moodle_exception + */ + protected static function get_pluginfile_hash(string $url) { + + // Decode the URL before start processing it. + $url = new \moodle_url(urldecode($url)); + + // Remove params from the URL (such as the 'forcedownload=1'), to avoid errors. + $url->remove_params(array_keys($url->params())); + $path = $url->out_as_local_url(); + + // We only need the slasharguments. + $path = substr($path, strpos($path, '.php/') + 5); + $parts = explode('/', $path); + $filename = array_pop($parts); + + // If the request is made by tokenpluginfile.php we need to avoid userprivateaccesskey. + if (strpos($url, '/tokenpluginfile.php')) { + array_shift($parts); + } + + // Get the contextid, component and filearea. + $contextid = array_shift($parts); + $component = array_shift($parts); + $filearea = array_shift($parts); + + // Ignore draft files, because they are considered temporary files, so shouldn't be displayed. + if ($filearea == 'draft') { + return false; + } + + // Get the context. + try { + list($context, $course, $cm) = get_context_info_array($contextid); + } catch (\moodle_exception $e) { + throw new \moodle_exception('invalidcontextid', 'core_h5p'); + } + // Some components, such as mod_page or mod_resource, add the revision to the URL to prevent caching problems. // So the URL contains this revision number as itemid but a 0 is always stored in the files table. // In order to get the proper hash, a callback should be done (looking for those exceptions).