diff --git a/openedx/features/offline_mode/assets_management.py b/openedx/features/offline_mode/assets_management.py index e90d00fe20d6..225990449e5f 100644 --- a/openedx/features/offline_mode/assets_management.py +++ b/openedx/features/offline_mode/assets_management.py @@ -161,3 +161,18 @@ def save_mathjax_to_xblock_assets(temp_dir): file.write(response.content) log.info(f"Successfully saved MathJax to {file_path}") + + +def save_external_file(temp_dir, link, filename): + """ + Save external file to the local directory. + """ + file_path = os.path.join(temp_dir, filename) + try: + response = requests.get(link) + except requests.exceptions.RequestException as e: + log.error(f"Failed to download {link}: {e}") + else: + create_subdirectories_for_asset(file_path) + with open(file_path, 'wb') as file: + file.write(response.content) diff --git a/openedx/features/offline_mode/html_manipulator.py b/openedx/features/offline_mode/html_manipulator.py index fbbca2af6494..88d8a75b3884 100644 --- a/openedx/features/offline_mode/html_manipulator.py +++ b/openedx/features/offline_mode/html_manipulator.py @@ -3,12 +3,13 @@ """ import os import re +import uuid from bs4 import BeautifulSoup from django.conf import settings -from .assets_management import save_asset_file, save_mathjax_to_xblock_assets +from .assets_management import save_asset_file, save_external_file, save_mathjax_to_xblock_assets from .constants import MATHJAX_CDN_URL, MATHJAX_STATIC_PATH @@ -33,6 +34,8 @@ def process_html(self): self._replace_asset_links() self._replace_static_links() self._replace_mathjax_link() + self._replace_external_links() + self._copy_platform_fonts() soup = BeautifulSoup(self.html_data, 'html.parser') self._replace_iframe(soup) @@ -79,6 +82,39 @@ def _replace_link(self, match): save_asset_file(self.temp_dir, self.xblock, link, filename) return f'assets/{filename}' + def _replace_external_links(self): + """ + Replace external links to images and js files with local links. + """ + pattern = re.compile(r'https:\/\/[^"\s]+?\.(js|jpe?g|png|gif|bmp|svg)') + self.html_data = pattern.sub(self._replace_external_link, self.html_data) + + def _replace_external_link(self, match): + """ + Returns the local path of the external file. + + Downloads the external file and saves it to the local directory. + """ + link = match.group() + file_extension = match.group(1) + unique_id = uuid.uuid4() + filename = f"assets/external/{unique_id}.{file_extension}" + save_external_file(self.temp_dir, link, filename) + + return filename + + def _copy_platform_fonts(self): + """ + Copy platform fonts to the block temp directory. + """ + platform_fonts_dir = "xmodule/js/common_static/fonts/vendor/" + block_fonts_path = os.path.join(self.temp_dir, "assets", "fonts", "vendor") + os.makedirs(block_fonts_path) + for font in os.listdir(platform_fonts_dir): + font_path = os.path.join(platform_fonts_dir, font) + if os.path.isfile(font_path): + os.system(f'cp {font_path} {block_fonts_path}') + @staticmethod def _replace_iframe(soup): """