/** * Return an instance of the mustache class. * * @since 2.9 * @return Mustache_Engine */ protected function get_mustache() { global $CFG; if ($this->mustache === null) { $themename = $this->page->theme->name; $themerev = theme_get_revision(); $cachedir = make_localcache_directory("mustache/{$themerev}/{$themename}"); $loader = new \core\output\mustache_filesystem_loader(); $stringhelper = new \core\output\mustache_string_helper(); $quotehelper = new \core\output\mustache_quote_helper(); $jshelper = new \core\output\mustache_javascript_helper($this->page->requires); $pixhelper = new \core\output\mustache_pix_helper($this); // We only expose the variables that are exposed to JS templates. $safeconfig = $this->page->requires->get_config_for_javascript($this->page, $this); $helpers = array('config' => $safeconfig, 'str' => array($stringhelper, 'str'), 'quote' => array($quotehelper, 'quote'), 'js' => array($jshelper, 'help'), 'pix' => array($pixhelper, 'pix')); $this->mustache = new Mustache_Engine(array('cache' => $cachedir, 'escape' => 's', 'loader' => $loader, 'helpers' => $helpers, 'pragmas' => [Mustache_Engine::PRAGMA_BLOCKS])); } return $this->mustache; }
/** * Return an instance of the mustache class. * * @since 2.9 * @return Mustache_Engine */ protected function get_mustache() { global $CFG; if ($this->mustache === null) { require_once $CFG->dirroot . '/lib/mustache/src/Mustache/Autoloader.php'; Mustache_Autoloader::register(); $themename = $this->page->theme->name; $themerev = theme_get_revision(); $cachedir = make_localcache_directory("mustache/{$themerev}/{$themename}"); $loader = new \core\output\mustache_filesystem_loader(); $stringhelper = new \core\output\mustache_string_helper(); $jshelper = new \core\output\mustache_javascript_helper($this->page->requires); $pixhelper = new \core\output\mustache_pix_helper($this); // We only expose the variables that are exposed to JS templates. $safeconfig = $this->page->requires->get_config_for_javascript($this->page, $this); $helpers = array('config' => $safeconfig, 'str' => array($stringhelper, 'str'), 'js' => array($jshelper, 'help'), 'pix' => array($pixhelper, 'pix')); $this->mustache = new Mustache_Engine(array('cache' => $cachedir, 'escape' => 's', 'loader' => $loader, 'helpers' => $helpers)); } return $this->mustache; }
public function test_localcachedir() { global $CFG; $this->resetAfterTest(true); // Test default location - can not be modified in phpunit tests because we override everything in config.php. $this->assertSame("{$CFG->dataroot}/localcache", $CFG->localcachedir); $this->setCurrentTimeStart(); $timestampfile = "{$CFG->localcachedir}/.lastpurged"; // Delete existing localcache directory, as this is testing first call // to make_localcache_directory. remove_dir($CFG->localcachedir, true); $dir = make_localcache_directory('', false); $this->assertSame($CFG->localcachedir, $dir); $this->assertFileNotExists("{$CFG->localcachedir}/.htaccess"); $this->assertFileExists($timestampfile); $this->assertTimeCurrent(filemtime($timestampfile)); $dir = make_localcache_directory('test/test', false); $this->assertSame("{$CFG->localcachedir}/test/test", $dir); // Test custom location. $CFG->localcachedir = "{$CFG->dataroot}/testlocalcache"; $this->setCurrentTimeStart(); $timestampfile = "{$CFG->localcachedir}/.lastpurged"; $this->assertFileNotExists($timestampfile); $dir = make_localcache_directory('', false); $this->assertSame($CFG->localcachedir, $dir); $this->assertFileExists("{$CFG->localcachedir}/.htaccess"); $this->assertFileExists($timestampfile); $this->assertTimeCurrent(filemtime($timestampfile)); $dir = make_localcache_directory('test', false); $this->assertSame("{$CFG->localcachedir}/test", $dir); $prevtime = filemtime($timestampfile); $dir = make_localcache_directory('pokus', false); $this->assertSame("{$CFG->localcachedir}/pokus", $dir); $this->assertSame($prevtime, filemtime($timestampfile)); // Test purging. $testfile = "{$CFG->localcachedir}/test/test.txt"; $this->assertTrue(touch($testfile)); $now = $this->setCurrentTimeStart(); set_config('localcachedirpurged', $now - 2); purge_all_caches(); $this->assertFileNotExists($testfile); $this->assertFileNotExists(dirname($testfile)); $this->assertFileExists($timestampfile); $this->assertTimeCurrent(filemtime($timestampfile)); $this->assertTimeCurrent($CFG->localcachedirpurged); // Simulates purge_all_caches() on another server node. make_localcache_directory('test', false); $this->assertTrue(touch($testfile)); set_config('localcachedirpurged', $now - 1); $this->assertTrue(touch($timestampfile, $now - 2)); clearstatcache(); $this->assertSame($now - 2, filemtime($timestampfile)); $this->setCurrentTimeStart(); $dir = make_localcache_directory('', false); $this->assertSame("{$CFG->localcachedir}", $dir); $this->assertFileNotExists($testfile); $this->assertFileNotExists(dirname($testfile)); $this->assertFileExists($timestampfile); $this->assertTimeCurrent(filemtime($timestampfile)); }
/** * Invalidates browser caches and cached data in temp. * * IMPORTANT - If you are adding anything here to do with the cache directory you should also have a look at * {@link phpunit_util::reset_dataroot()} * * @return void */ function purge_all_caches() { global $CFG, $DB; reset_text_filters_cache(); js_reset_all_caches(); theme_reset_all_caches(); get_string_manager()->reset_caches(); core_text::reset_caches(); if (class_exists('core_plugin_manager')) { core_plugin_manager::reset_caches(); } // Bump up cacherev field for all courses. try { increment_revision_number('course', 'cacherev', ''); } catch (moodle_exception $e) { // Ignore exception since this function is also called before upgrade script when field course.cacherev does not exist yet. } $DB->reset_caches(); cache_helper::purge_all(); // Purge all other caches: rss, simplepie, etc. clearstatcache(); remove_dir($CFG->cachedir . '', true); // Make sure cache dir is writable, throws exception if not. make_cache_directory(''); // This is the only place where we purge local caches, we are only adding files there. // The $CFG->localcachedirpurged flag forces local directories to be purged on cluster nodes. remove_dir($CFG->localcachedir, true); set_config('localcachedirpurged', time()); make_localcache_directory('', true); \core\task\manager::clear_static_caches(); }
/** * Install core moodle tables and initialize * @param float $version target version * @param bool $verbose * @return void, may throw exception */ function install_core($version, $verbose) { global $CFG, $DB; // We can not call purge_all_caches() yet, make sure the temp and cache dirs exist and are empty. remove_dir($CFG->cachedir.'', true); make_cache_directory('', true); remove_dir($CFG->localcachedir.'', true); make_localcache_directory('', true); remove_dir($CFG->tempdir.'', true); make_temp_directory('', true); remove_dir($CFG->dataroot.'/muc', true); make_writable_directory($CFG->dataroot.'/muc', true); try { core_php_time_limit::raise(600); print_upgrade_part_start('moodle', true, $verbose); // does not store upgrade running flag $DB->get_manager()->install_from_xmldb_file("$CFG->libdir/db/install.xml"); upgrade_started(); // we want the flag to be stored in config table ;-) // set all core default records and default settings require_once("$CFG->libdir/db/install.php"); xmldb_main_install(); // installs the capabilities too // store version upgrade_main_savepoint(true, $version, false); // Continue with the installation log_update_descriptions('moodle'); external_update_descriptions('moodle'); events_update_definition('moodle'); \core\task\manager::reset_scheduled_tasks_for_component('moodle'); message_update_providers('moodle'); \core\message\inbound\manager::update_handlers_for_component('moodle'); // Write default settings unconditionally admin_apply_default_settings(NULL, true); print_upgrade_part_end(null, true, $verbose); // Purge all caches. They're disabled but this ensures that we don't have any persistent data just in case something // during installation didn't use APIs. cache_helper::purge_all(); } catch (exception $ex) { upgrade_handle_exception($ex); } }
/** * KSES replacement cleaning function - uses HTML Purifier. * * @param string $text The (X)HTML string to purify * @param array $options Array of options; currently only option supported is 'allowid' (if set, * does not remove id attributes when cleaning) * @return string */ function purify_html($text, $options = array()) { global $CFG; $text = (string) $text; static $purifiers = array(); static $caches = array(); // Purifier code can change only during major version upgrade. $version = empty($CFG->version) ? 0 : $CFG->version; $cachedir = "{$CFG->localcachedir}/htmlpurifier/{$version}"; if (!file_exists($cachedir)) { // Purging of caches may remove the cache dir at any time, // luckily file_exists() results should be cached for all existing directories. $purifiers = array(); $caches = array(); gc_collect_cycles(); make_localcache_directory('htmlpurifier', false); check_dir_exists($cachedir); } $allowid = empty($options['allowid']) ? 0 : 1; $allowobjectembed = empty($CFG->allowobjectembed) ? 0 : 1; $type = 'type_' . $allowid . '_' . $allowobjectembed; if (!array_key_exists($type, $caches)) { $caches[$type] = cache::make('core', 'htmlpurifier', array('type' => $type)); } $cache = $caches[$type]; // Add revision number and all options to the text key so that it is compatible with local cluster node caches. $key = "|{$version}|{$allowobjectembed}|{$allowid}|{$text}"; $filteredtext = $cache->get($key); if ($filteredtext === true) { // The filtering did not change the text last time, no need to filter anything again. return $text; } else { if ($filteredtext !== false) { return $filteredtext; } } if (empty($purifiers[$type])) { require_once $CFG->libdir . '/htmlpurifier/HTMLPurifier.safe-includes.php'; require_once $CFG->libdir . '/htmlpurifier/locallib.php'; $config = HTMLPurifier_Config::createDefault(); $config->set('HTML.DefinitionID', 'moodlehtml'); $config->set('HTML.DefinitionRev', 2); $config->set('Cache.SerializerPath', $cachedir); $config->set('Cache.SerializerPermissions', $CFG->directorypermissions); $config->set('Core.NormalizeNewlines', false); $config->set('Core.ConvertDocumentToFragment', true); $config->set('Core.Encoding', 'UTF-8'); $config->set('HTML.Doctype', 'XHTML 1.0 Transitional'); $config->set('URI.AllowedSchemes', array('http' => true, 'https' => true, 'ftp' => true, 'irc' => true, 'nntp' => true, 'news' => true, 'rtsp' => true, 'rtmp' => true, 'teamspeak' => true, 'gopher' => true, 'mms' => true, 'mailto' => true)); $config->set('Attr.AllowedFrameTargets', array('_blank')); if ($allowobjectembed) { $config->set('HTML.SafeObject', true); $config->set('Output.FlashCompat', true); $config->set('HTML.SafeEmbed', true); } if ($allowid) { $config->set('Attr.EnableID', true); } if ($def = $config->maybeGetRawHTMLDefinition()) { $def->addElement('nolink', 'Block', 'Flow', array()); // Skip our filters inside. $def->addElement('tex', 'Inline', 'Inline', array()); // Tex syntax, equivalent to $$xx$$. $def->addElement('algebra', 'Inline', 'Inline', array()); // Algebra syntax, equivalent to @@xx@@. $def->addElement('lang', 'Block', 'Flow', array(), array('lang' => 'CDATA')); // Original multilang style - only our hacked lang attribute. $def->addAttribute('span', 'xxxlang', 'CDATA'); // Current very problematic multilang. } $purifier = new HTMLPurifier($config); $purifiers[$type] = $purifier; } else { $purifier = $purifiers[$type]; } $multilang = strpos($text, 'class="multilang"') !== false; $filteredtext = $text; if ($multilang) { $filteredtextregex = '/<span(\\s+lang="([a-zA-Z0-9_-]+)"|\\s+class="multilang"){2}\\s*>/'; $filteredtext = preg_replace($filteredtextregex, '<span xxxlang="${2}">', $filteredtext); } $filteredtext = (string) $purifier->purify($filteredtext); if ($multilang) { $filteredtext = preg_replace('/<span xxxlang="([a-zA-Z0-9_-]+)">/', '<span lang="${1}" class="multilang">', $filteredtext); } if ($text === $filteredtext) { // No need to store the filtered text, next time we will just return unfiltered text // because it was not changed by purifying. $cache->set($key, true); } else { $cache->set($key, $filteredtext); } return $filteredtext; }
/** * Purge dataroot directory * @static * @return void */ public static function reset_dataroot() { global $CFG; $childclassname = self::get_framework() . '_util'; // Do not delete automatically installed files. self::skip_original_data_files($childclassname); // Clear file status cache, before checking file_exists. clearstatcache(); // Clean up the dataroot folder. $handle = opendir(self::get_dataroot()); while (false !== ($item = readdir($handle))) { if (in_array($item, $childclassname::$datarootskiponreset)) { continue; } if (is_dir(self::get_dataroot() . "/{$item}")) { remove_dir(self::get_dataroot() . "/{$item}", false); } else { unlink(self::get_dataroot() . "/{$item}"); } } closedir($handle); // Clean up the dataroot/filedir folder. if (file_exists(self::get_dataroot() . '/filedir')) { $handle = opendir(self::get_dataroot() . '/filedir'); while (false !== ($item = readdir($handle))) { if (in_array('filedir/' . $item, $childclassname::$datarootskiponreset)) { continue; } if (is_dir(self::get_dataroot() . "/filedir/{$item}")) { remove_dir(self::get_dataroot() . "/filedir/{$item}", false); } else { unlink(self::get_dataroot() . "/filedir/{$item}"); } } closedir($handle); } make_temp_directory(''); make_cache_directory(''); make_localcache_directory(''); // Reset the cache API so that it recreates it's required directories as well. cache_factory::reset(); // Purge all data from the caches. This is required for consistency. // Any file caches that happened to be within the data root will have already been clearer (because we just deleted cache) // and now we will purge any other caches as well. cache_helper::purge_all(); }
if ($rev > 0 and file_exists($candidate)) { if (!empty($_SERVER['HTTP_IF_NONE_MATCH']) || !empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { // we do not actually need to verify the etag value because our files // never change in cache because we increment the rev parameter js_send_unmodified(filemtime($candidate), $etag); } js_send_cached($candidate, $etag); } //================================================================================= // ok, now we need to start normal moodle script, we need to load all libs and $DB define('ABORT_AFTER_CONFIG_CANCEL', true); define('NO_MOODLE_COOKIES', true); // Session not used here define('NO_UPGRADE_CHECK', true); // Ignore upgrade check require "{$CFG->dirroot}/lib/setup.php"; $theme = theme_config::load($themename); $themerev = theme_get_revision(); if ($themerev <= 0 or $rev != $themerev) { // Do not send caching headers if they do not request current revision, // we do not want to pollute browser caches with outdated JS. js_send_uncached($theme->javascript_content($type)); } make_localcache_directory('theme', false); js_write_cache_file_content($candidate, core_minify::js_files($theme->javascript_files($type))); // Verify nothing failed in cache file creation. clearstatcache(); if (file_exists($candidate)) { js_send_cached($candidate, $etag); } js_send_uncached($theme->javascript_content($type));
/** * Return an instance of the mustache class. * * @since 2.9 * @return Mustache_Engine */ protected function get_mustache() { global $CFG; if ($this->mustache === null) { require_once $CFG->dirroot . '/lib/mustache/src/Mustache/Autoloader.php'; Mustache_Autoloader::register(); $themename = $this->page->theme->name; $themerev = theme_get_revision(); $target = $this->target; $cachedir = make_localcache_directory("mustache/{$themerev}/{$themename}/{$target}"); $loaderoptions = array(); // Where are all the places we should look for templates? $suffix = $this->component; if ($this->subtype !== null) { $suffix .= '_' . $this->subtype; } // Start with an empty list. $loader = new Mustache_Loader_CascadingLoader(array()); $loaderdir = $CFG->dirroot . '/theme/' . $themename . '/templates/' . $suffix; if (is_dir($loaderdir)) { $loader->addLoader(new \core\output\mustache_filesystem_loader($loaderdir, $loaderoptions)); } // Search each of the parent themes second. foreach ($this->page->theme->parents as $parent) { $loaderdir = $CFG->dirroot . '/theme/' . $parent . '/templates/' . $suffix; if (is_dir($loaderdir)) { $loader->addLoader(new \core\output\mustache_filesystem_loader($loaderdir, $loaderoptions)); } } // Look in a components templates dir for a base implementation. $compdirectory = core_component::get_component_directory($suffix); if ($compdirectory) { $loaderdir = $compdirectory . '/templates'; if (is_dir($loaderdir)) { $loader->addLoader(new \core\output\mustache_filesystem_loader($loaderdir, $loaderoptions)); } } // Look in the core templates dir as a final fallback. $compdirectory = $CFG->libdir; if ($compdirectory) { $loaderdir = $compdirectory . '/templates'; if (is_dir($loaderdir)) { $loader->addLoader(new \core\output\mustache_filesystem_loader($loaderdir, $loaderoptions)); } } $stringhelper = new \core\output\mustache_string_helper(); $jshelper = new \core\output\mustache_javascript_helper($this->page->requires); $pixhelper = new \core\output\mustache_pix_helper($this); // We only expose the variables that are exposed to JS templates. $safeconfig = $this->page->requires->get_config_for_javascript($this->page, $this); $helpers = array('config' => $safeconfig, 'str' => array($stringhelper, 'str'), 'js' => array($jshelper, 'help'), 'pix' => array($pixhelper, 'pix')); $this->mustache = new Mustache_Engine(array('cache' => $cachedir, 'escape' => 's', 'loader' => $loader, 'helpers' => $helpers)); } return $this->mustache; }
/** * Purge dataroot directory * @static * @return void */ public static function reset_dataroot() { global $CFG; $childclassname = self::get_framework() . '_util'; $handle = opendir($CFG->dataroot); while (false !== ($item = readdir($handle))) { if (in_array($item, $childclassname::$datarootskiponreset)) { continue; } if (is_dir("{$CFG->dataroot}/{$item}")) { remove_dir("{$CFG->dataroot}/{$item}", false); } else { unlink("{$CFG->dataroot}/{$item}"); } } closedir($handle); make_temp_directory(''); make_cache_directory(''); make_localcache_directory(''); // Reset the cache API so that it recreates it's required directories as well. cache_factory::reset(); // Purge all data from the caches. This is required for consistency. // Any file caches that happened to be within the data root will have already been clearer (because we just deleted cache) // and now we will purge any other caches as well. cache_helper::purge_all(); }
/** * KSES replacement cleaning function - uses HTML Purifier. * * @param string $text The (X)HTML string to purify * @param array $options Array of options; currently only option supported is 'allowid' (if set, * does not remove id attributes when cleaning) * @return string */ function purify_html($text, $options = array()) { global $CFG; $text = (string)$text; static $purifiers = array(); static $caches = array(); // Purifier code can change only during major version upgrade. $version = empty($CFG->version) ? 0 : $CFG->version; $cachedir = "$CFG->localcachedir/htmlpurifier/$version"; if (!file_exists($cachedir)) { // Purging of caches may remove the cache dir at any time, // luckily file_exists() results should be cached for all existing directories. $purifiers = array(); $caches = array(); gc_collect_cycles(); make_localcache_directory('htmlpurifier', false); check_dir_exists($cachedir); } $allowid = empty($options['allowid']) ? 0 : 1; $allowobjectembed = empty($CFG->allowobjectembed) ? 0 : 1; $type = 'type_'.$allowid.'_'.$allowobjectembed; if (!array_key_exists($type, $caches)) { $caches[$type] = cache::make('core', 'htmlpurifier', array('type' => $type)); } $cache = $caches[$type]; // Add revision number and all options to the text key so that it is compatible with local cluster node caches. $key = "|$version|$allowobjectembed|$allowid|$text"; $filteredtext = $cache->get($key); if ($filteredtext === true) { // The filtering did not change the text last time, no need to filter anything again. return $text; } else if ($filteredtext !== false) { return $filteredtext; } if (empty($purifiers[$type])) { require_once $CFG->libdir.'/htmlpurifier/HTMLPurifier.safe-includes.php'; require_once $CFG->libdir.'/htmlpurifier/locallib.php'; $config = HTMLPurifier_Config::createDefault(); $config->set('HTML.DefinitionID', 'moodlehtml'); $config->set('HTML.DefinitionRev', 6); $config->set('Cache.SerializerPath', $cachedir); $config->set('Cache.SerializerPermissions', $CFG->directorypermissions); $config->set('Core.NormalizeNewlines', false); $config->set('Core.ConvertDocumentToFragment', true); $config->set('Core.Encoding', 'UTF-8'); $config->set('HTML.Doctype', 'XHTML 1.0 Transitional'); $config->set('URI.AllowedSchemes', array( 'http' => true, 'https' => true, 'ftp' => true, 'irc' => true, 'nntp' => true, 'news' => true, 'rtsp' => true, 'rtmp' => true, 'teamspeak' => true, 'gopher' => true, 'mms' => true, 'mailto' => true )); $config->set('Attr.AllowedFrameTargets', array('_blank')); if ($allowobjectembed) { $config->set('HTML.SafeObject', true); $config->set('Output.FlashCompat', true); $config->set('HTML.SafeEmbed', true); } if ($allowid) { $config->set('Attr.EnableID', true); } if ($def = $config->maybeGetRawHTMLDefinition()) { $def->addElement('nolink', 'Block', 'Flow', array()); // Skip our filters inside. $def->addElement('tex', 'Inline', 'Inline', array()); // Tex syntax, equivalent to $$xx$$. $def->addElement('algebra', 'Inline', 'Inline', array()); // Algebra syntax, equivalent to @@xx@@. $def->addElement('lang', 'Block', 'Flow', array(), array('lang'=>'CDATA')); // Original multilang style - only our hacked lang attribute. $def->addAttribute('span', 'xxxlang', 'CDATA'); // Current very problematic multilang. // Media elements. // https://html.spec.whatwg.org/#the-video-element $def->addElement('video', 'Block', 'Optional: #PCDATA | Flow | source | track', 'Common', [ 'src' => 'URI', 'crossorigin' => 'Enum#anonymous,use-credentials', 'poster' => 'URI', 'preload' => 'Enum#auto,metadata,none', 'autoplay' => 'Bool', 'playsinline' => 'Bool', 'loop' => 'Bool', 'muted' => 'Bool', 'controls' => 'Bool', 'width' => 'Length', 'height' => 'Length', ]); // https://html.spec.whatwg.org/#the-audio-element $def->addElement('audio', 'Block', 'Optional: #PCDATA | Flow | source | track', 'Common', [ 'src' => 'URI', 'crossorigin' => 'Enum#anonymous,use-credentials', 'preload' => 'Enum#auto,metadata,none', 'autoplay' => 'Bool', 'loop' => 'Bool', 'muted' => 'Bool', 'controls' => 'Bool' ]); // https://html.spec.whatwg.org/#the-source-element $def->addElement('source', false, 'Empty', null, [ 'src' => 'URI', 'type' => 'Text' ]); // https://html.spec.whatwg.org/#the-track-element $def->addElement('track', false, 'Empty', null, [ 'src' => 'URI', 'kind' => 'Enum#subtitles,captions,descriptions,chapters,metadata', 'srclang' => 'Text', 'label' => 'Text', 'default' => 'Bool', ]); // Use the built-in Ruby module to add annotation support. $def->manager->addModule(new HTMLPurifier_HTMLModule_Ruby()); } $purifier = new HTMLPurifier($config); $purifiers[$type] = $purifier; } else { $purifier = $purifiers[$type]; } $multilang = (strpos($text, 'class="multilang"') !== false); $filteredtext = $text; if ($multilang) { $filteredtextregex = '/<span(\s+lang="([a-zA-Z0-9_-]+)"|\s+class="multilang"){2}\s*>/'; $filteredtext = preg_replace($filteredtextregex, '<span xxxlang="${2}">', $filteredtext); } $filteredtext = (string)$purifier->purify($filteredtext); if ($multilang) { $filteredtext = preg_replace('/<span xxxlang="([a-zA-Z0-9_-]+)">/', '<span lang="${1}" class="multilang">', $filteredtext); } if ($text === $filteredtext) { // No need to store the filtered text, next time we will just return unfiltered text // because it was not changed by purifying. $cache->set($key, true); } else { $cache->set($key, $filteredtext); } return $filteredtext; }