diff --git a/cms/envs/common.py b/cms/envs/common.py index 7a4cd7b77f..356fb2d7ff 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -2309,3 +2309,10 @@ VERIFY_STUDENT = { # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2020-11-02 ORGANIZATIONS_AUTOCREATE = True + +################# Settings for brand logos. ################# +LOGO_URL = None +LOGO_URL_PNG = None +LOGO_TRADEMARK_URL = None +FAVICON_URL = None +DEFAULT_EMAIL_LOGO_URL = 'https://edx-cdn.org/v3/default/logo.png' diff --git a/cms/envs/production.py b/cms/envs/production.py index cd515fcb56..8cf5473e43 100644 --- a/cms/envs/production.py +++ b/cms/envs/production.py @@ -568,3 +568,9 @@ if FEATURES.get('ENABLE_CORS_HEADERS'): CORS_ALLOW_HEADERS = corsheaders_default_headers + ( 'use-jwt-cookie', ) + +################# Settings for brand logos. ################# +LOGO_URL = ENV_TOKENS.get('LOGO_URL', LOGO_URL) +LOGO_URL_PNG = ENV_TOKENS.get('LOGO_URL_PNG', LOGO_URL_PNG) +LOGO_TRADEMARK_URL = ENV_TOKENS.get('LOGO_TRADEMARK_URL', LOGO_TRADEMARK_URL) +FAVICON_URL = ENV_TOKENS.get('FAVICON_URL', FAVICON_URL) diff --git a/lms/djangoapps/branding/api.py b/lms/djangoapps/branding/api.py index e532240dfe..4f4ade9abc 100644 --- a/lms/djangoapps/branding/api.py +++ b/lms/djangoapps/branding/api.py @@ -26,6 +26,7 @@ from six.moves.urllib.parse import urljoin from lms.djangoapps.branding.models import BrandingApiConfig from common.djangoapps.edxmako.shortcuts import marketing_link from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers +from .toggles import app_logs_enabled log = logging.getLogger("edx.footer") EMPTY_URL = '#' @@ -443,15 +444,32 @@ def _footer_mobile_links(is_secure): def _footer_logo_img(is_secure): - """Return the logo used for footer about link + """ + Return the logo used for footer about link - Args: + Arguments: is_secure (bool): Whether the request is using TLS. Returns: - Absolute url to logo + URL of the brand logo """ - logo_name = configuration_helpers.get_value('FOOTER_ORGANIZATION_IMAGE', settings.FOOTER_ORGANIZATION_IMAGE) + default_local_path = 'images/logo.png' + brand_footer_logo_url = settings.LOGO_TRADEMARK_URL + footer_url_from_site_config = configuration_helpers.get_value( + 'FOOTER_ORGANIZATION_IMAGE', + settings.FOOTER_ORGANIZATION_IMAGE + ) + + if app_logs_enabled(): + log.info( + ("[Branding][footer_logo_img]: site_config:%s, footer_org_img:%s," + "brand_url:%s, default:%s"), + footer_url_from_site_config, + settings.FOOTER_ORGANIZATION_IMAGE, + brand_footer_logo_url, + default_local_path + ) + # `logo_name` is looked up from the configuration, # which falls back on the Django settings, which loads it from # `lms.yml`, which is created and managed by Ansible. Because of @@ -461,25 +479,22 @@ def _footer_logo_img(is_secure): # EdX needs the FOOTER_ORGANIZATION_IMAGE value to point to edX's # logo by default, so that it can display properly on edx.org -- both # within the LMS, and on the Drupal marketing site, which uses this API. - try: - return _absolute_url_staticfile(is_secure, logo_name) - except ValueError: - # However, if the edx.org comprehensive theme is not activated, - # Django's staticfiles system will be unable to find this footer, - # and will throw a ValueError. Since the edx.org comprehensive theme - # is not activated by default, we will end up entering this block - # of code on new Open edX installations, and on sandbox installations. - # We can log when this happens: - default_logo = "images/logo.png" - log.info( - u"Failed to find footer logo at '%s', using '%s' instead", - logo_name, - default_logo, - ) - # And we'll use the default logo path of "images/logo.png" instead. - # There is a core asset that corresponds to this logo, so this should - # always succeed. - return staticfiles_storage.url(default_logo) + if footer_url_from_site_config: + return _absolute_url_staticfile(is_secure, footer_url_from_site_config) + + if brand_footer_logo_url: + return brand_footer_logo_url + + log.info( + "Failed to find footer logo at '%s', using '%s' instead", + footer_url_from_site_config, + default_local_path, + ) + + # And we'll use the default logo path of "images/logo.png" instead. + # There is a core asset that corresponds to this logo, so this should + # always succeed. + return staticfiles_storage.url(default_local_path) def _absolute_url(is_secure, url_path): @@ -564,27 +579,72 @@ def get_base_url(is_secure): def get_logo_url(is_secure=True): """ - Return the url for the branded logo image to be used + Return the url for the branded logo image to be used. + + Preference of the logo should be, + Look for site configuration override and return absolute url + Absolute url of brand Logo if defined in django settings + Relative default local image path + Arguments: is_secure (bool): If true, use HTTPS as the protocol. """ + brand_logo_url = settings.LOGO_URL + default_local_path = 'images/logo.png' + logo_url_from_site_config = configuration_helpers.get_value('logo_image_url') + university = configuration_helpers.get_value('university') - # if the configuration has an overide value for the logo_image_url - # let's use that - image_url = configuration_helpers.get_value('logo_image_url') - if image_url: - return _absolute_url_staticfile( - is_secure=is_secure, - name=image_url, + if app_logs_enabled(): + log.info( + ("[Branding][get_logo_url]: site_config:%s, university:%s, " + "brand_url:%s, default:%s"), + logo_url_from_site_config, + university, + brand_logo_url, + default_local_path ) - # otherwise, use the legacy means to configure this - university = configuration_helpers.get_value('university') + if logo_url_from_site_config: + return _absolute_url_staticfile(is_secure=is_secure, name=logo_url_from_site_config) if university: return staticfiles_storage.url('images/{uni}-on-edx-logo.png'.format(uni=university)) - else: - return staticfiles_storage.url('images/logo.png') + + if brand_logo_url: + return brand_logo_url + + return staticfiles_storage.url(default_local_path) + + +def get_favicon_url(): + """ + Return the url for the branded favicon image to be used. + + Preference of the icon should be, + Look for site configuration override + Brand favicon url is defined in settings + Default local image path + """ + brand_favicon_url = settings.FAVICON_URL + default_local_path = getattr(settings, 'FAVICON_PATH', 'images/favicon.ico') + favicon_url_from_site_config = configuration_helpers.get_value('favicon_path') + + if app_logs_enabled(): + log.info( + ("[Branding][get_favicon_url]: site_config:%s, brand_url:%s " + "default:%s"), + favicon_url_from_site_config, + brand_favicon_url, + default_local_path, + ) + + if favicon_url_from_site_config: + return staticfiles_storage.url(favicon_url_from_site_config) + + if brand_favicon_url: + return brand_favicon_url + + return staticfiles_storage.url(default_local_path) def get_tos_and_honor_code_url(): diff --git a/lms/djangoapps/branding/toggles.py b/lms/djangoapps/branding/toggles.py new file mode 100644 index 0000000000..b025af216a --- /dev/null +++ b/lms/djangoapps/branding/toggles.py @@ -0,0 +1,32 @@ +""" +Waffle flags for Branding app. +""" +from edx_toggles.toggles import WaffleFlag, WaffleFlagNamespace + + +WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='branding') +# Waffle flag for testing purpose only. When setting the flag in prod, +# make sure to have the following settings. Use "dwft_branding.enable_branding_logs=1" +# in the browser query to enable the flag. +# .. toggle_name: branding.enable_branding_logs +# .. toggle_everyone: unknown +# .. toggle_testing: True +# .. toggle_implementation: WaffleFlag +# .. toggle_default: False +# .. toggle_description: Supports testing for re-branding work. +# .. toggle_category: branding +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2020-11-22 +# .. toggle_target_removal_date: 2021-01-01 +# .. toggle_warnings: n/a +# .. toggle_tickets: TNL-7695 +# .. toggle_status: supported +ENABLE_BRANDING_LOGS = WaffleFlag( + waffle_namespace=WAFFLE_FLAG_NAMESPACE, + flag_name='enable_branding_logs', +) + + +def app_logs_enabled(): + """Check if app logging is enabled. """ + return ENABLE_BRANDING_LOGS.is_enabled() diff --git a/lms/envs/common.py b/lms/envs/common.py index 2dcc99d26f..7dfd0fd638 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -4148,3 +4148,10 @@ MAX_BLOCKS_PER_CONTENT_LIBRARY = 1000 # COUNTRIES_FIRST = ['SA', 'BH', 'QA'] will display these countries on top of the list # https://github.com/SmileyChris/django-countries#show-certain-countries-first COUNTRIES_FIRST = [] + +################# Settings for brand logos. ################# +LOGO_URL = None +LOGO_URL_PNG = None +LOGO_TRADEMARK_URL = None +FAVICON_URL = None +DEFAULT_EMAIL_LOGO_URL = 'https://edx-cdn.org/v3/default/logo.png' diff --git a/lms/envs/production.py b/lms/envs/production.py index 578fac7bc2..2cb1d13608 100644 --- a/lms/envs/production.py +++ b/lms/envs/production.py @@ -975,3 +975,9 @@ COMPLETION_VIDEO_COMPLETE_PERCENTAGE = ENV_TOKENS.get('COMPLETION_VIDEO_COMPLETE COMPLETION_VIDEO_COMPLETE_PERCENTAGE) COMPLETION_VIDEO_COMPLETE_PERCENTAGE = ENV_TOKENS.get('COMPLETION_BY_VIEWING_DELAY_MS', COMPLETION_BY_VIEWING_DELAY_MS) + +################# Settings for brand logos. ################# +LOGO_URL = ENV_TOKENS.get('LOGO_URL', LOGO_URL) +LOGO_URL_PNG = ENV_TOKENS.get('LOGO_URL_PNG', LOGO_URL_PNG) +LOGO_TRADEMARK_URL = ENV_TOKENS.get('LOGO_TRADEMARK_URL', LOGO_TRADEMARK_URL) +FAVICON_URL = ENV_TOKENS.get('FAVICON_URL', FAVICON_URL) diff --git a/lms/static/sass/shared/_footer-edx.scss b/lms/static/sass/shared/_footer-edx.scss index e1ade87c0b..286ccfe4bd 100644 --- a/lms/static/sass/shared/_footer-edx.scss +++ b/lms/static/sass/shared/_footer-edx.scss @@ -159,6 +159,10 @@ footer#footer-edx-v3 { display: inline-flex; @include margin-left(5px); + + img { + height: $header-logo-height*1.05; + } } // To match the Pattern Library diff --git a/lms/static/sass/shared/_footer.scss b/lms/static/sass/shared/_footer.scss index 18ef6d6a7d..23b315e685 100644 --- a/lms/static/sass/shared/_footer.scss +++ b/lms/static/sass/shared/_footer.scss @@ -124,6 +124,10 @@ a { display: inline-block; + img { + height: $header-logo-height*1.05; + } + &:hover { border-bottom: 0; } diff --git a/lms/templates/main.html b/lms/templates/main.html index 14b02024c3..741a4092fd 100644 --- a/lms/templates/main.html +++ b/lms/templates/main.html @@ -82,8 +82,10 @@ from common.djangoapps.pipeline_mako import render_require_js_path_overrides % endif + <% favicon_url = branding_api.get_favicon_url() %> + + - <%static:css group='style-vendor'/> % if '/' in self.attr.main_css: diff --git a/openedx/core/djangoapps/ace_common/template_context.py b/openedx/core/djangoapps/ace_common/template_context.py index b569798a95..a1339e209f 100644 --- a/openedx/core/djangoapps/ace_common/template_context.py +++ b/openedx/core/djangoapps/ace_common/template_context.py @@ -16,6 +16,7 @@ def get_base_template_context(site): """ # When on LMS and a dashboard is available, use that as the dashboard url. # Otherwise, use the home url instead. + default_logo_url = getattr(settings, 'DEFAULT_EMAIL_LOGO_URL') try: dashboard_url = reverse('dashboard') except NoReverseMatch: @@ -37,4 +38,5 @@ def get_base_template_context(site): 'CONTACT_MAILING_ADDRESS', site=site, site_config_name='contact_mailing_address'), 'social_media_urls': get_config_value_from_site_or_settings('SOCIAL_MEDIA_FOOTER_URLS', site=site), 'mobile_store_urls': get_config_value_from_site_or_settings('MOBILE_STORE_URLS', site=site), + 'logo_url': getattr(settings, 'LOGO_URL_PNG', default_logo_url), } diff --git a/openedx/core/djangoapps/ace_common/templates/ace_common/edx_ace/common/base_body.html b/openedx/core/djangoapps/ace_common/templates/ace_common/edx_ace/common/base_body.html index 7130554dcf..ab792a7d3b 100644 --- a/openedx/core/djangoapps/ace_common/templates/ace_common/edx_ace/common/base_body.html +++ b/openedx/core/djangoapps/ace_common/templates/ace_common/edx_ace/common/base_body.html @@ -62,7 +62,7 @@ {% filter force_escape %}{% blocktrans %}Go to {{ platform_name }} Home Page{% endblocktrans %}{% endfilter %} diff --git a/openedx/core/djangoapps/schedules/tests/test_resolvers.py b/openedx/core/djangoapps/schedules/tests/test_resolvers.py index 822200cd08..d01d2f3e95 100644 --- a/openedx/core/djangoapps/schedules/tests/test_resolvers.py +++ b/openedx/core/djangoapps/schedules/tests/test_resolvers.py @@ -124,6 +124,7 @@ class TestCourseUpdateResolver(SchedulesResolverTestMixin, ModuleStoreTestCase): ) @override_settings(CONTACT_MAILING_ADDRESS='123 Sesame Street') + @override_settings(LOGO_URL_PNG='https://www.logo.png') @override_waffle_flag(COURSE_UPDATE_WAFFLE_FLAG, True) def test_schedule_context(self): resolver = self.create_resolver() @@ -137,6 +138,7 @@ class TestCourseUpdateResolver(SchedulesResolverTestMixin, ModuleStoreTestCase): 'dashboard_url': '/dashboard', 'homepage_url': '/', 'mobile_store_urls': {}, + 'logo_url': 'https://www.logo.png', 'platform_name': '\xe9dX', 'show_upsell': False, 'social_media_urls': {}, @@ -211,12 +213,14 @@ class TestCourseNextSectionUpdateResolver(SchedulesResolverTestMixin, ModuleStor ) @override_settings(CONTACT_MAILING_ADDRESS='123 Sesame Street') + @override_settings(LOGO_URL_PNG='https://www.logo.png') @override_waffle_flag(COURSE_UPDATE_WAFFLE_FLAG, True) def test_schedule_context(self): resolver = self.create_resolver() # using this to make sure the select_related stays intact with self.assertNumQueries(17): - schedules = list(resolver.get_schedules()) + sc = resolver.get_schedules() + schedules = list(sc) expected_context = { 'contact_email': 'info@example.com', @@ -227,6 +231,7 @@ class TestCourseNextSectionUpdateResolver(SchedulesResolverTestMixin, ModuleStor 'dashboard_url': '/dashboard', 'homepage_url': '/', 'mobile_store_urls': {}, + 'logo_url': 'https://www.logo.png', 'platform_name': '\xe9dX', 'show_upsell': False, 'social_media_urls': {}, diff --git a/openedx/core/djangoapps/site_configuration/templatetags/configuration.py b/openedx/core/djangoapps/site_configuration/templatetags/configuration.py index e142a7a385..f053fe8a4b 100644 --- a/openedx/core/djangoapps/site_configuration/templatetags/configuration.py +++ b/openedx/core/djangoapps/site_configuration/templatetags/configuration.py @@ -7,8 +7,8 @@ based on the current site. from django import template from django.conf import settings from django.templatetags.static import static -from django.contrib.staticfiles.storage import staticfiles_storage +from lms.djangoapps.branding.api import get_favicon_url from openedx.core.djangoapps.theming import helpers as theming_helpers from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangolib.markup import HTML @@ -35,12 +35,12 @@ def platform_name(): @register.simple_tag(name="favicon_path") -def favicon_path(default=getattr(settings, 'FAVICON_PATH', 'images/favicon.ico')): +def favicon_path(): """ Django template tag that outputs the configured favicon: {% favicon_path %} """ - return staticfiles_storage.url(configuration_helpers.get_value('favicon_path', default)) + return get_favicon_url() @register.simple_tag(name="microsite_css_overrides_file")