def collect_assets_from_text(text, course_id, include_content=False): """ Yield dicts of asset content and path from static asset paths found in the given text. Make sure to have replaced the URLs with rewrite_absolute_static_urls first. If include_content is True, the result will include a contentstore StaticContent file object which wraps the actual binary content of the file. """ # Replace static urls like '/static/foo.png' static_paths = [] # Drag-and-drop-v2 has # "/static/blah.png" # which must be changed to "/static/blah.png" for replace_static_urls to work: text2 = text.replace(""", '"') replace_static_urls(text=text2, course_id=course_id, static_paths_out=static_paths) for (path, uri) in static_paths: if path.startswith('/static/'): path = path[8:] info = { 'path': path, 'url': '/' + str(course_id.make_asset_key("asset", path)), } if include_content: content = get_asset_content_from_path(course_id, path) if content is None: log.error("Static asset not found: (%s, %s)", path, uri) else: info['content'] = content yield info
def test_multi_replace(): course_source = '"/course/file.png"' assert replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY) == \ replace_static_urls(replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY), DATA_DIRECTORY) assert replace_course_urls(course_source, COURSE_KEY) == \ replace_course_urls(replace_course_urls(course_source, COURSE_KEY), COURSE_KEY)
def replace_urls(self, text, static_replace_only=False): """ Replaces all static/course/jump-to-id URLs in provided text/html. Args: text: String containing the URL to be replaced static_replace_only: If True, only static urls will be replaced """ if self.lookup_asset_url: text = replace_static_urls(text, xblock=self.xblock(), lookup_asset_url=self.lookup_asset_url) else: text = replace_static_urls( text, data_directory=self.data_directory, course_id=self.course_id, static_asset_path=self.static_asset_path, static_paths_out=self.static_paths_out) if not static_replace_only: text = replace_course_urls(text, self.course_id) if self.jump_to_id_base_url: text = replace_jump_to_id_urls(text, self.course_id, self.jump_to_id_base_url) return text
def test_raw_static_check(): """ Make sure replace_static_urls leaves alone things that end in '.raw' """ path = '"/static/foo.png?raw"' assert replace_static_urls(path, DATA_DIRECTORY) == path text = 'text <tag a="/static/js/capa/protex/protex.nocache.js?raw"/><div class="' assert replace_static_urls(path, text) == path
def test_data_dir_fallback(mock_storage, mock_modulestore, mock_settings): # lint-amnesty, pylint: disable=unused-argument mock_modulestore.return_value = Mock(XMLModuleStore) mock_storage.url.side_effect = Exception mock_storage.exists.return_value = True assert replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY) == '"/static/data_dir/file.png"' mock_storage.exists.return_value = False assert replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY) == '"/static/data_dir/file.png"'
def test_static_paths_out(mock_modulestore, mock_storage): """ Tests the side-effect of passing an array to collect static_paths_out. * if a static URL is changed, then its changed URL is returned. * if a static URL is unchanged, then the unchanged URL is returned. * xblock paths are not included in the static_paths_out array. """ mock_storage.exists.return_value = False mock_modulestore.return_value = Mock(MongoModuleStore) static_url = '/static/LAlec04_controller.swf?csConfigFile=/static/LAlec04_config.xml&name1=value1&name2=value2' static_course_url = '/c4x/org/course/asset/LAlec04_controller.swf?csConfigFile=%2Fc4x%2Forg%2Fcourse%2Fasset%2FLAlec04_config.xml&name1=value1&name2=value2' # lint-amnesty, pylint: disable=line-too-long raw_url = '/static/js/capa/protex/protex.nocache.js?raw' xblock_url = '/static/xblock/resources/babys_first.lil_xblock/public/images/pacifier.png' # xss-lint: disable=python-wrap-html pre_text = f'EMBED src ="{static_url}" xblock={xblock_url} text <tag a="{raw_url}"/><div class="' # xss-lint: disable=python-wrap-html post_text = f'EMBED src ="{static_course_url}" xblock={xblock_url} text <tag a="{raw_url}"/><div class="' # lint-amnesty, pylint: disable=line-too-long static_paths = [] assert replace_static_urls(pre_text, DATA_DIRECTORY, COURSE_KEY, static_paths_out=static_paths) == post_text assert static_paths == [(static_url, static_course_url), (raw_url, raw_url)]
def get_course_syllabus_section(course, section_key): """ This returns the snippet of html to be rendered on the syllabus page, given the key for the section. Valid keys: - syllabus - guest_syllabus """ # Many of these are stored as html files instead of some semantic # markup. This can change without effecting this interface when we find a # good format for defining so many snippets of text/html. if section_key in ['syllabus', 'guest_syllabus']: try: filesys = course.system.resources_fs # first look for a run-specific version dirs = [path("syllabus") / course.url_name, path("syllabus")] filepath = find_file(filesys, dirs, section_key + ".html") with filesys.open(filepath) as html_file: return replace_static_urls( html_file.read().decode('utf-8'), getattr(course, 'data_dir', None), course_id=course.id, static_asset_path=course.static_asset_path, ) except ResourceNotFound: log.exception("Missing syllabus section %s in course %s", section_key, str(course.location)) return "! Syllabus missing !" raise KeyError("Invalid about key " + str(section_key))
def test_relative_url_for_split_course(self): """ Test relative path for split courses assets """ with modulestore().default_store(ModuleStoreEnum.Type.split): module_store = modulestore() course_id = module_store.make_course_key('edX', 'toy', '2012_Fall') import_course_from_xml(module_store, self.user.id, TEST_DATA_DIR, ['toy'], static_content_store=contentstore(), target_id=course_id, create_if_not_present=True) course = module_store.get_course(course_id) filename = 'sample_static.html' html_src_attribute = '"/static/{}"'.format(filename) asset_url = replace_static_urls(html_src_attribute, course_id=course.id) url = asset_url.replace('"', '') base_url = url.replace(filename, '') self.assertIn("/{}".format(filename), url) resp = self.client.get(url) self.assertEqual(resp.status_code, 200) # simulation of html page where base_url is up-to asset's main directory # and relative_path is dom element with its src relative_path = 'just_a_test.jpg' # browser append relative_path with base_url absolute_path = base_url + relative_path self.assertIn("/{}".format(relative_path), absolute_path) resp = self.client.get(absolute_path) self.assertEqual(resp.status_code, 200)
def test_storage_url_exists(mock_storage): mock_storage.exists.return_value = True mock_storage.url.return_value = '/static/file.png' assert replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY) == '"/static/file.png"' mock_storage.exists.assert_called_once_with('file.png') mock_storage.url.assert_called_once_with('file.png')
def test_mongo_filestore(mock_get_excluded_extensions, mock_get_base_url, mock_modulestore, mock_static_content): mock_modulestore.return_value = Mock(MongoModuleStore) mock_static_content.get_canonicalized_asset_path.return_value = "c4x://mock_url" mock_get_base_url.return_value = '' mock_get_excluded_extensions.return_value = ['foobar'] # No namespace => no change to path assert replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY) == '"/static/data_dir/file.png"' # Namespace => content url assert '"' + mock_static_content.get_canonicalized_asset_path.return_value + '"' == \ replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY, course_id=COURSE_KEY) mock_static_content.get_canonicalized_asset_path.assert_called_once_with( COURSE_KEY, 'file.png', '', ['foobar'])
def remap_static_url(original_url, course): """Remap a URL in the ways the course requires.""" # Ick: this should be possible without having to quote and unquote the URL... input_url = "'" + original_url + "'" output_url = replace_static_urls( input_url, getattr(course, 'data_dir', None), course_id=course.id, static_asset_path=course.static_asset_path) # strip off the quotes again... return output_url[1:-1]
def test_static_url_with_xblock_resource(mock_modulestore, mock_storage): """ Make sure that for URLs with XBlock resource URL, which start with /static/, we don't rewrite them. """ mock_storage.exists.return_value = False mock_modulestore.return_value = Mock(MongoModuleStore) pre_text = 'EMBED src ="/static/xblock/resources/babys_first.lil_xblock/public/images/pacifier.png"' post_text = pre_text assert replace_static_urls(pre_text, DATA_DIRECTORY, COURSE_KEY) == post_text
def replace_static_urls(data_dir, block, view, frag, context, course_id=None, static_asset_path=''): # pylint: disable=unused-argument """ Updates the supplied module with a new get_html function that wraps the old get_html function and substitutes urls of the form /static/... with urls that are /static/<prefix>/... """ return wrap_fragment(frag, static_replace.replace_static_urls( frag.content, data_dir, course_id, static_asset_path=static_asset_path ))
def test_static_url_with_query(mock_modulestore, mock_storage): """ Make sure that for urls with query params: query params that contain "^/static/" are converted to full location urls query params that do not contain "^/static/" are left unchanged """ mock_storage.exists.return_value = False mock_modulestore.return_value = Mock(MongoModuleStore) pre_text = 'EMBED src ="/static/LAlec04_controller.swf?csConfigFile=/static/LAlec04_config.xml&name1=value1&name2=value2"' # lint-amnesty, pylint: disable=line-too-long post_text = 'EMBED src ="/c4x/org/course/asset/LAlec04_controller.swf?csConfigFile=%2Fc4x%2Forg%2Fcourse%2Fasset%2FLAlec04_config.xml&name1=value1&name2=value2"' # lint-amnesty, pylint: disable=line-too-long assert replace_static_urls(pre_text, DATA_DIRECTORY, COURSE_KEY) == post_text
def test_static_url_with_xblock_resource_on_cdn(mock_modulestore, mock_storage): """ Make sure that for URLs with XBlock resource URL, which start with /static/, we don't rewrite them, even if these are served from an absolute URL like a CDN. """ mock_storage.exists.return_value = False mock_modulestore.return_value = Mock(MongoModuleStore) pre_text = 'EMBED src ="https://example.com/static/xblock/resources/tehehe.xblock/public/images/woo.png"' post_text = pre_text assert replace_static_urls(pre_text, DATA_DIRECTORY, COURSE_KEY) == post_text
def _expand_static_url(self, url): """ This is required to make URLs like '/static/dnd-test-image.png' work (note: that is the only portable URL format for static files that works across export/import and reruns). This method is unfortunately a bit hackish since XBlock does not provide a low-level API for this. """ if hasattr(self.runtime, 'replace_urls'): url = self.runtime.replace_urls(u'"{}"'.format(url))[1:-1] elif hasattr(self.runtime, 'course_id'): # edX Studio uses a different runtime for 'studio_view' than 'student_view', # and the 'studio_view' runtime doesn't provide the replace_urls API. try: from common.djangoapps.static_replace import replace_static_urls # pylint: disable=import-error url = replace_static_urls( u'"{}"'.format(url), None, course_id=self.runtime.course_id)[1:-1] except ImportError: pass return url
def test_replace_urls(self): html = '<a href="/static/id">' assert self.runtime.replace_urls(html) == \ static_replace.replace_static_urls(html, course_id=self.runtime.course_id)