diff --git a/.gitignore b/.gitignore index e1e15e6102..6769176b86 100644 --- a/.gitignore +++ b/.gitignore @@ -78,14 +78,13 @@ lms/static/sass/lms-main.scss lms/static/sass/lms-main-rtl.scss lms/static/sass/lms-course.scss lms/static/sass/lms-course-rtl.scss -lms/static/sass/lms-footer-edx.scss -lms/static/sass/lms-footer-edx-rtl.scss +lms/static/sass/lms-footer.scss +lms/static/sass/lms-footer-rtl.scss cms/static/css/ cms/static/sass/*.css cms/static/sass/*.css.map - ### Logging artifacts log/ logs diff --git a/common/static/js/spec_helpers/rwd_header_footer.js b/common/static/js/spec_helpers/rwd_header_footer.js deleted file mode 100644 index 8f737a6bbf..0000000000 --- a/common/static/js/spec_helpers/rwd_header_footer.js +++ /dev/null @@ -1,99 +0,0 @@ -/** - * Adds rwd classes and click handlers. - */ - -(function($) { - 'use strict'; - - var rwd = (function() { - - var _fn = { - header: 'header.global-new', - - footer: '.edx-footer-new', - - resultsUrl: 'course-search', - - init: function() { - _fn.$header = $( _fn.header ); - _fn.$footer = $( _fn.footer ); - _fn.$nav = _fn.$header.find('nav'); - _fn.$globalNav = _fn.$nav.find('.nav-global'); - - _fn.add.elements(); - _fn.add.classes(); - _fn.eventHandlers.init(); - }, - - add: { - classes: function() { - // Add any RWD-specific classes - _fn.$header.addClass('rwd'); - _fn.$footer.addClass('rwd'); - }, - - elements: function() { - _fn.add.burger(); - _fn.add.registerLink(); - }, - - burger: function() { - _fn.$nav.prepend([ - '', - '', - '' - ].join('')); - }, - - registerLink: function() { - var $register = _fn.$nav.find('.cta-register'), - $li = {}, - $a = {}, - count = 0; - - // Add if register link is shown - if ( $register.length > 0 ) { - count = _fn.$globalNav.find('li').length + 1; - - // Create new li - $li = $('
  • '); - $li.addClass('desktop-hide nav-global-0' + count); - - // Clone register link and remove classes - $a = $register.clone(); - $a.removeClass(); - - // append to DOM - $a.appendTo( $li ); - _fn.$globalNav.append( $li ); - } - } - }, - - eventHandlers: { - init: function() { - _fn.eventHandlers.click(); - }, - - click: function() { - // Toggle menu - _fn.$nav.on( 'click', '.mobile-menu-button', _fn.toggleMenu ); - } - }, - - toggleMenu: function( event ) { - event.preventDefault(); - - _fn.$globalNav.toggleClass('show'); - } - }; - - return { - init: _fn.init - }; - })(); - - setTimeout( function() { - rwd.init(); - }, 100); -})(jQuery); diff --git a/common/static/js/utils/rwd_header_footer.js b/common/static/js/utils/rwd_header.js similarity index 93% rename from common/static/js/utils/rwd_header_footer.js rename to common/static/js/utils/rwd_header.js index 83ba652c8b..616f176268 100644 --- a/common/static/js/utils/rwd_header_footer.js +++ b/common/static/js/utils/rwd_header.js @@ -10,6 +10,7 @@ var _fn = { header: 'header.global-new', + // TODO (ECOM-1339): Remove this once the V3 footer is enabled permanently footer: '.edx-footer-new', resultsUrl: 'course-search', @@ -29,7 +30,7 @@ classes: function() { // Add any RWD-specific classes _fn.$header.addClass('rwd'); - _fn.$footer.addClass('rwd'); + _fn.$footer.addClass('rwd'); // TODO (ECOM-1339): remove once the V3 footer is enabled permanently }, elements: function() { diff --git a/conf/locale/eo/LC_MESSAGES/django.mo b/conf/locale/eo/LC_MESSAGES/django.mo index 338bdec0f2..0b34bcb3f0 100644 Binary files a/conf/locale/eo/LC_MESSAGES/django.mo and b/conf/locale/eo/LC_MESSAGES/django.mo differ diff --git a/conf/locale/eo/LC_MESSAGES/django.po b/conf/locale/eo/LC_MESSAGES/django.po index 6963d0a28c..9e5407259f 100644 --- a/conf/locale/eo/LC_MESSAGES/django.po +++ b/conf/locale/eo/LC_MESSAGES/django.po @@ -37,8 +37,8 @@ msgid "" msgstr "" "Project-Id-Version: 0.1a\n" "Report-Msgid-Bugs-To: openedx-translation@googlegroups.com\n" -"POT-Creation-Date: 2015-05-26 15:42+0000\n" -"PO-Revision-Date: 2015-05-26 15:42:32.205037\n" +"POT-Creation-Date: 2015-05-26 18:33+0000\n" +"PO-Revision-Date: 2015-05-26 18:33:10.093000\n" "Last-Translator: \n" "Language-Team: openedx-translation \n" "MIME-Version: 1.0\n" @@ -179,7 +179,7 @@ msgstr "" "Ýöü'ré énrölléd äs än hönör çödé stüdént Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, " "¢σηѕє¢тєтυя#" -#: common/djangoapps/course_modes/models.py +#: common/djangoapps/course_modes/models.py lms/djangoapps/branding/api.py #: openedx/core/djangoapps/user_api/views.py #: lms/templates/static_templates/honor.html msgid "Honor Code" @@ -3184,9 +3184,9 @@ msgstr "" msgid "Both" msgstr "Böth Ⱡ'σяєм ι#" -#: common/lib/xmodule/xmodule/course_module.py -#: lms/templates/footer-edx-v2.html lms/templates/footer-edx-v3.html -#: lms/templates/footer.html lms/templates/static_templates/about.html +#: common/lib/xmodule/xmodule/course_module.py lms/djangoapps/branding/api.py +#: lms/templates/footer-edx-v2.html lms/templates/footer.html +#: lms/templates/static_templates/about.html msgid "About" msgstr "Àßöüt Ⱡ'σяєм ιρѕ#" @@ -5141,6 +5141,80 @@ msgstr "Séärçh Ⱡ'σяєм ιρѕυ#" msgid "Copyright" msgstr "Çöpýrïght Ⱡ'σяєм ιρѕυм ∂σł#" +#: lms/djangoapps/branding/api.py +msgid "" +"© {org_name}. All rights reserved except where noted. EdX, Open edX and " +"the edX and Open EdX logos are registered trademarks or trademarks of edX " +"Inc." +msgstr "" +"© {org_name}. Àll rïghts résérvéd éxçépt whéré nötéd. ÉdX, Öpén édX änd " +"thé édX änd Öpén ÉdX lögös äré régïstéréd trädémärks ör trädémärks öf édX " +"Ìnç. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α∂ιριѕι¢ιηg єłιт, ѕє∂ ∂σ " +"єιυѕмσ∂ тємρσя ιη¢ι∂ι∂υηт υт łαвσяє єт ∂σłσяє мαgηα αłιqυα. υт єηιм α∂ мιηιм" +" νєηιαм, qυιѕ ησѕтяυ∂ єχєя¢ιтαтιση υłłαм¢σ łαвσяιѕ ηιѕι υт αłιqυιρ єχ єα " +"¢σммσ∂σ ¢σηѕєqυαт. ∂υιѕ αυтє ιяυяє ∂σłσя ιη яєρяєнєη∂єяιт ιη νσłυρтαтє νєłιт" +" єѕѕє ¢ιłłυм ∂σłσяє єυ ƒυgιαт ηυłłα ραяιαтυя. єχ¢єρтєυя ѕιηт σ¢¢αє¢αт " +"¢υρι∂αтαт ηση ρяσι∂єηт, ѕυηт ιη ¢υłρα qυι σƒƒι¢ια ∂єѕєяυηт мσłłιт αηιм ι∂#" + +#. #-#-#-#-# django-partial.po (0.1a) #-#-#-#-# +#. Translators: 'Open edX' is a brand, please keep this untranslated. +#. See http://openedx.org for more information. +#: lms/djangoapps/branding/api.py cms/templates/widgets/footer.html +#: lms/templates/footer-edx-v2.html +msgid "Powered by Open edX" +msgstr "Pöwéréd ßý Öpén édX Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт,#" + +#: lms/djangoapps/branding/api.py lms/templates/static_templates/blog.html +msgid "Blog" +msgstr "Blög Ⱡ'σяєм ι#" + +#: lms/djangoapps/branding/api.py lms/templates/footer-edx-v2.html +msgid "News" +msgstr "Néws Ⱡ'σяєм ι#" + +#: lms/djangoapps/branding/api.py +msgid "FAQs" +msgstr "FÀQs Ⱡ'σяєм ι#" + +#: lms/djangoapps/branding/api.py lms/templates/footer-edx-v2.html +#: lms/templates/static_templates/contact.html +msgid "Contact" +msgstr "Çöntäçt Ⱡ'σяєм ιρѕυм #" + +#: lms/djangoapps/branding/api.py lms/templates/static_templates/jobs.html +msgid "Jobs" +msgstr "Jößs Ⱡ'σяєм ι#" + +#: lms/djangoapps/branding/api.py lms/templates/static_templates/donate.html +msgid "Donate" +msgstr "Dönäté Ⱡ'σяєм ιρѕυ#" + +#: lms/djangoapps/branding/api.py +msgid "Sitemap" +msgstr "Sïtémäp Ⱡ'σяєм ιρѕυм #" + +#. #-#-#-#-# django-partial.po (0.1a) #-#-#-#-# +#. Translators: This is a legal document users must agree to +#. in order to register a new account. +#: lms/djangoapps/branding/api.py openedx/core/djangoapps/user_api/views.py +#: cms/templates/widgets/footer.html lms/templates/static_templates/tos.html +msgid "Terms of Service" +msgstr "Térms öf Sérvïçé Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αм#" + +#: lms/djangoapps/branding/api.py +msgid "Terms of Service & Honor Code" +msgstr "Térms öf Sérvïçé & Hönör Çödé Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢#" + +#: lms/djangoapps/branding/api.py lms/djangoapps/certificates/views.py +#: cms/templates/widgets/footer.html lms/templates/footer-edx-v2.html +#: lms/templates/static_templates/privacy.html +msgid "Privacy Policy" +msgstr "Prïväçý Pölïçý Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт#" + +#: lms/djangoapps/branding/api.py +msgid "Accessibility Policy" +msgstr "Àççéssïßïlïtý Pölïçý Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, #" + #: lms/djangoapps/ccx/views.py msgid "You must be a CCX Coach to access this view." msgstr "" @@ -5363,13 +5437,7 @@ msgstr "Wörk ät {platform_name} Ⱡ'σяєм ιρѕυм ∂σłσя #" msgid "Contact {platform_name}" msgstr "Çöntäçt {platform_name} Ⱡ'σяєм ιρѕυм ∂σłσя #" -#: lms/djangoapps/certificates/views.py cms/templates/widgets/footer.html -#: lms/templates/footer-edx-v2.html lms/templates/footer-edx-v3.html -#: lms/templates/footer.html lms/templates/static_templates/privacy.html -msgid "Privacy Policy" -msgstr "Prïväçý Pölïçý Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт#" - -#: lms/djangoapps/certificates/views.py lms/templates/footer-edx-v3.html +#: lms/djangoapps/certificates/views.py msgid "Terms of Service & Honor Code" msgstr "" "Térms öf Sérvïçé & Hönör Çödé Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢т#" @@ -8427,42 +8495,26 @@ msgstr "Çöüld nöt süßmït phötös Ⱡ'σяєм ιρѕυм ∂σłσя ѕ #. #-#-#-#-# django-partial.po (0.1a) #-#-#-#-# #. Translators: This is the website name of www.facebook.com. Please #. translate this the way that Facebook advertises in your language. -#. #-#-#-#-# mako.po (0.1a) #-#-#-#-# -#. Translators: This is the website name of www.facebook.com. Please -#. translate this the way that Facebook advertises in your language. -#: lms/envs/common.py lms/templates/footer-edx-v3.html -#: lms/templates/dashboard/_dashboard_course_listing.html +#: lms/envs/common.py lms/templates/dashboard/_dashboard_course_listing.html msgid "Facebook" msgstr "Fäçéßöök Ⱡ'σяєм ιρѕυм ∂#" #. #-#-#-#-# django-partial.po (0.1a) #-#-#-#-# #. Translators: This is the website name of www.twitter.com. Please #. translate this the way that Twitter advertises in your language. -#. #-#-#-#-# mako.po (0.1a) #-#-#-#-# -#. Translators: This is the website name of www.twitter.com. Please -#. translate this the way that Twitter advertises in your language. -#: lms/envs/common.py lms/templates/footer-edx-v3.html -#: lms/templates/dashboard/_dashboard_course_listing.html +#: lms/envs/common.py lms/templates/dashboard/_dashboard_course_listing.html msgid "Twitter" msgstr "Twïttér Ⱡ'σяєм ιρѕυм #" -#. #-#-#-#-# django-partial.po (0.1a) #-#-#-#-# #. Translators: This is the website name of www.linkedin.com. Please #. translate this the way that LinkedIn advertises in your language. -#. #-#-#-#-# mako.po (0.1a) #-#-#-#-# -#. Translators: This is the website name of www.linked.com. Please -#. translate this the way that LinkedIn advertises in your language. -#: lms/envs/common.py lms/templates/footer-edx-v3.html +#: lms/envs/common.py msgid "LinkedIn" msgstr "LïnkédÌn Ⱡ'σяєм ιρѕυм ∂#" -#. #-#-#-#-# django-partial.po (0.1a) #-#-#-#-# #. Translators: This is the website name of plus.google.com. Please #. translate this the way that Google+ advertises in your language. -#. #-#-#-#-# mako.po (0.1a) #-#-#-#-# -#. Translators: This is the website name of plus.google.com. Please -#. translate this the way that Google+ advertises in your language. -#: lms/envs/common.py lms/templates/footer-edx-v3.html +#: lms/envs/common.py msgid "Google+" msgstr "Gööglé+ Ⱡ'σяєм ιρѕυм #" @@ -8472,13 +8524,9 @@ msgstr "Gööglé+ Ⱡ'σяєм ιρѕυм #" msgid "Tumblr" msgstr "Tümßlr Ⱡ'σяєм ιρѕυ#" -#. #-#-#-#-# django-partial.po (0.1a) #-#-#-#-# #. Translators: This is the website name of www.meetup.com. Please #. translate this the way that MeetUp advertises in your language. -#. #-#-#-#-# mako.po (0.1a) #-#-#-#-# -#. Translators: This is the website name of www.meetup.com. Please -#. translate this the way that Meetup advertises in your language. -#: lms/envs/common.py lms/templates/footer-edx-v3.html +#: lms/envs/common.py msgid "Meetup" msgstr "Méétüp Ⱡ'σяєм ιρѕυ#" @@ -9397,14 +9445,6 @@ msgstr "" "Ýöü müst ägréé tö thé {platform_name} {terms_of_service}. Ⱡ'σяєм ιρѕυм ∂σłσя" " ѕιт αмєт, ¢σηѕє¢т#" -#. #-#-#-#-# django-partial.po (0.1a) #-#-#-#-# -#. Translators: This is a legal document users must agree to -#. in order to register a new account. -#: openedx/core/djangoapps/user_api/views.py cms/templates/widgets/footer.html -#: lms/templates/footer.html lms/templates/static_templates/tos.html -msgid "Terms of Service" -msgstr "Térms öf Sérvïçé Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αм#" - #: openedx/core/djangoapps/user_api/accounts/api.py msgid "The '{field_name}' field cannot be edited." msgstr "" @@ -10081,13 +10121,6 @@ msgstr "" "Vïsït ýöür {link_start}däshßöärd{link_end} tö séé ýöür çöürsés. Ⱡ'σяєм ιρѕυм" " ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α#" -#. Translators: 'Open edX' is a brand, please keep this untranslated. See -#. http://openedx.org for more information. -#: cms/templates/widgets/footer.html lms/templates/footer-edx-v2.html -#: lms/templates/footer-edx-v3.html lms/templates/footer.html -msgid "Powered by Open edX" -msgstr "Pöwéréd ßý Öpén édX Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт,#" - #: cms/templates/widgets/header.html lms/templates/courseware/courseware.html msgid "Course Navigation" msgstr "Çöürsé Nävïgätïön Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмє#" @@ -10499,7 +10532,7 @@ msgstr "" #. Translators: 'EdX', 'edX', and 'Open edX' are trademarks of 'edX Inc.'. #. Please do not translate any of these trademarks and company names. -#: lms/templates/footer-edx-v2.html lms/templates/footer.html +#: lms/templates/footer-edx-v2.html msgid "" "EdX, Open edX, and the edX and Open edX logos are registered trademarks or " "trademarks of {link_start}edX Inc.{link_end}" @@ -10521,18 +10554,7 @@ msgstr "(Révïséd {date}) Ⱡ'σяєм ιρѕυм ∂σłσя ѕι#" msgid "10/22/2014" msgstr "10/22/2014 Ⱡ'σяєм ιρѕυм ∂σłσ#" -#: lms/templates/footer-edx-v2.html lms/templates/footer-edx-v3.html -#: lms/templates/footer.html -msgid "News" -msgstr "Néws Ⱡ'σяєм ι#" - -#: lms/templates/footer-edx-v2.html lms/templates/footer-edx-v3.html -#: lms/templates/footer.html lms/templates/static_templates/contact.html -msgid "Contact" -msgstr "Çöntäçt Ⱡ'σяєм ιρѕυм #" - -#: lms/templates/footer-edx-v2.html lms/templates/footer.html -#: lms/templates/static_templates/faq.html +#: lms/templates/footer-edx-v2.html lms/templates/static_templates/faq.html msgid "FAQ" msgstr "FÀQ Ⱡ'σяєм#" @@ -10556,11 +10578,11 @@ msgstr "Föllöw Ûs Ⱡ'σяєм ιρѕυм ∂σł#" msgid "Mobile Apps" msgstr "Mößïlé Àpps Ⱡ'σяєм ιρѕυм ∂σłσя #" -#: lms/templates/footer-edx-v2.html lms/templates/footer-edx-v3.html +#: lms/templates/footer-edx-v2.html msgid "Apple app on Apple Store" msgstr "Àpplé äpp ön Àpplé Störé Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢ση#" -#: lms/templates/footer-edx-v2.html lms/templates/footer-edx-v3.html +#: lms/templates/footer-edx-v2.html msgid "Android app on Google Play" msgstr "Àndröïd äpp ön Gööglé Pläý Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕ#" @@ -10568,43 +10590,10 @@ msgstr "Àndröïd äpp ön Gööglé Pläý Ⱡ'σяєм ιρѕυм ∂σłσя msgid "Page Footer" msgstr "Pägé Föötér Ⱡ'σяєм ιρѕυм ∂σłσя #" -#: lms/templates/footer-edx-v3.html lms/templates/static_templates/blog.html -msgid "Blog" -msgstr "Blög Ⱡ'σяєм ι#" - -#: lms/templates/footer-edx-v3.html -msgid "FAQs" -msgstr "FÀQs Ⱡ'σяєм ι#" - -#: lms/templates/footer-edx-v3.html lms/templates/footer.html -#: lms/templates/static_templates/jobs.html -msgid "Jobs" -msgstr "Jößs Ⱡ'σяєм ι#" - -#: lms/templates/footer-edx-v3.html lms/templates/static_templates/donate.html -msgid "Donate" -msgstr "Dönäté Ⱡ'σяєм ιρѕυ#" - -#: lms/templates/footer-edx-v3.html -msgid "Sitemap" -msgstr "Sïtémäp Ⱡ'σяєм ιρѕυм #" - #: lms/templates/footer-edx-v3.html lms/templates/footer.html msgid "Legal" msgstr "Légäl Ⱡ'σяєм ιρѕ#" -#: lms/templates/footer-edx-v3.html -msgid "Website Accessibility Policy" -msgstr "Wéßsïté Àççéssïßïlïtý Pölïçý Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢#" - -#: lms/templates/footer.html -msgid "" -"{tos_link_start}Terms of Service{tos_link_end} and {honor_link_start}Honor " -"Code{honor_link_end}" -msgstr "" -"{tos_link_start}Térms öf Sérvïçé{tos_link_end} änd {honor_link_start}Hönör " -"Çödé{honor_link_end} Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя #" - #: lms/templates/forgot_password_modal.html msgid "Password Reset" msgstr "Pässwörd Rését Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт#" diff --git a/lms/djangoapps/branding/admin.py b/lms/djangoapps/branding/admin.py index d10b2a9c61..4450174e16 100644 --- a/lms/djangoapps/branding/admin.py +++ b/lms/djangoapps/branding/admin.py @@ -1,9 +1,8 @@ -''' -Django admin pages for Video Branding Configuration. -''' +"""Django admin pages for branding configuration. """ from django.contrib import admin from config_models.admin import ConfigurationModelAdmin -from .models import BrandingInfoConfig +from .models import BrandingInfoConfig, BrandingApiConfig admin.site.register(BrandingInfoConfig, ConfigurationModelAdmin) +admin.site.register(BrandingApiConfig, ConfigurationModelAdmin) diff --git a/lms/djangoapps/branding/api.py b/lms/djangoapps/branding/api.py new file mode 100644 index 0000000000..119f3aa06b --- /dev/null +++ b/lms/djangoapps/branding/api.py @@ -0,0 +1,291 @@ +"""EdX Branding API + +Provides a way to retrieve "branded" parts of the site, +such as the site footer. + +This information exposed to: +1) Templates in the LMS. +2) Consumers of the branding API. + +This ensures that branded UI elements such as the footer +are consistent across the LMS and other sites (such as +the marketing site and blog). + +""" +import logging +import urlparse + +from django.conf import settings +from django.utils.translation import ugettext as _ +from staticfiles.storage import staticfiles_storage + +from microsite_configuration import microsite +from edxmako.shortcuts import marketing_link +from branding.models import BrandingApiConfig + + +log = logging.getLogger("edx.footer") + + +def is_enabled(): + """Check whether the branding API is enabled. """ + # TODO (ECOM-1339): Remove this comment + # Currently, the branding API configuration controls two things: + # 1) whether we're using the new version of the footer + # 2) whether we're exposing footer information through the API. + # + # Once we've enabled the new footer, the feature flag will control + # only (2), but not (1). + return BrandingApiConfig.current().enabled + + +def get_footer(is_secure=True): + """Retrieve information used to render the footer. + + This will handle both the OpenEdX and EdX.org versions + of the footer. All user-facing text is internationalized. + + Currently, this does NOT support theming. + + Keyword Arguments: + is_secure (bool): If True, use https:// in URLs. + + Returns: dict + + Example: + >>> get_footer() + { + "copyright": "(c) 2015 EdX Inc", + "logo_image": "http://www.example.com/logo.png", + "social_links": [ + { + "name": "facebook", + "title": "Facebook", + "url": "http://www.facebook.com/example", + "icon-class": "fa-facebook-square" + }, + ... + ], + "navigation_links": [ + { + "name": "about", + "title": "About", + "url": "http://www.example.com/about.html" + }, + ... + ], + "mobile_links": [ + { + "name": "apple", + "title": "Apple", + "url": "http://store.apple.com/example_app" + "image": "http://example.com/static/apple_logo.png" + }, + ... + ], + "legal_links": [ + { + "url": "http://example.com/terms-of-service.html", + "name": "terms_of_service", + "title': "Terms of Service" + }, + # ... + ], + "openedx_link": { + "url": "http://open.edx.org", + "title": "Powered by Open edX", + "image": "http://example.com/openedx.png" + } + } + + """ + return { + "copyright": _footer_copyright(), + "logo_image": _footer_logo_img(is_secure), + "social_links": _footer_social_links(), + "navigation_links": _footer_navigation_links(), + "mobile_links": _footer_mobile_links(is_secure), + "legal_links": _footer_legal_links(), + "openedx_link": _footer_openedx_link(), + } + + +def _footer_copyright(): + """Return the copyright to display in the footer. + + Returns: unicode + + """ + org_name = ( + "edX Inc" if settings.FEATURES.get('IS_EDX_DOMAIN', False) + else microsite.get_value('PLATFORM_NAME', settings.PLATFORM_NAME) + ) + + # Translators: 'EdX', 'edX', and 'Open edX' are trademarks of 'edX Inc.'. + # Please do not translate any of these trademarks and company names. + return _( + u"\u00A9 {org_name}. All rights reserved except where noted. " + u"EdX, Open edX and the edX and Open EdX logos are registered trademarks " + u"or trademarks of edX Inc." + ).format(org_name=org_name) + + +def _footer_openedx_link(): + """Return the image link for "powered by OpenEdX". + + Args: + is_secure (bool): Whether the request is using TLS. + + Returns: dict + + """ + # Translators: 'Open edX' is a brand, please keep this untranslated. + # See http://openedx.org for more information. + title = _("Powered by Open edX") + return { + "url": settings.FOOTER_OPENEDX_URL, + "title": title, + "image": settings.FOOTER_OPENEDX_LOGO_IMAGE, + } + + +def _footer_social_links(): + """Return the social media links to display in the footer. + + Returns: list + + """ + links = [] + for social_name in settings.SOCIAL_MEDIA_FOOTER_NAMES: + links.append( + { + "name": social_name, + "title": unicode(settings.SOCIAL_MEDIA_FOOTER_DISPLAY.get(social_name, {}).get("title", "")), + "url": settings.SOCIAL_MEDIA_FOOTER_URLS.get(social_name, "#"), + "icon-class": settings.SOCIAL_MEDIA_FOOTER_DISPLAY.get(social_name, {}).get("icon", ""), + } + ) + return links + + +def _footer_navigation_links(): + """Return the navigation links to display in the footer. """ + return [ + { + "name": link_name, + "title": link_title, + "url": link_url, + } + for link_name, link_url, link_title in [ + ("about", marketing_link("ABOUT"), _("About")), + ("blog", marketing_link("BLOG"), _("Blog")), + ("news", marketing_link("NEWS"), _("News")), + ("faq", marketing_link("FAQ"), _("FAQs")), + ("contact", marketing_link("CONTACT"), _("Contact")), + ("jobs", marketing_link("JOBS"), _("Jobs")), + ("donate", marketing_link("DONATE"), _("Donate")), + ("sitemap", marketing_link("SITE_MAP"), _("Sitemap")), + ] + if link_url and link_url != "#" + ] + + +def _footer_legal_links(): + """Return the legal footer links (e.g. terms of service). """ + + links = [ + ("terms_of_service_and_honor_code", marketing_link("TOS_AND_HONOR"), _("Terms of Service & Honor Code")), + ("privacy_policy", marketing_link("PRIVACY"), _("Privacy Policy")), + ("accessibility_policy", marketing_link("ACCESSIBILITY"), _("Accessibility Policy")), + ] + + # Backwards compatibility: If a combined "terms of service and honor code" + # link isn't provided, add separate TOS and honor code links. + tos_and_honor_link = marketing_link("TOS_AND_HONOR") + if not (tos_and_honor_link and tos_and_honor_link != "#"): + links.extend([ + ("terms_of_service", marketing_link("TOS"), _("Terms of Service")), + ("honor_code", marketing_link("HONOR"), _("Honor Code")), + ]) + + return [ + { + "name": link_name, + "title": link_title, + "url": link_url, + } + for link_name, link_url, link_title in links + if link_url and link_url != "#" + ] + + +def _footer_mobile_links(is_secure): + """Return the mobile app store links. + + Args: + is_secure (bool): Whether the request is using TLS. + + Returns: list + + """ + mobile_links = [] + if settings.FEATURES.get('ENABLE_FOOTER_MOBILE_APP_LINKS'): + mobile_links = [ + { + "name": "apple", + "title": "Apple", + "url": settings.MOBILE_STORE_URLS.get('apple', '#'), + "image": _absolute_url_staticfile(is_secure, 'images/app/app_store_badge_135x40.svg') + }, + { + "name": "google", + "title": "Google", + "url": settings.MOBILE_STORE_URLS.get('google', '#'), + "image": _absolute_url_staticfile(is_secure, 'images/app/google_play_badge_45.png') + } + ] + return mobile_links + + +def _footer_logo_img(is_secure): + """Return the logo used for footer about link + + Args: + is_secure (bool): Whether the request is using TLS. + + Returns: + Absolute url to logo + """ + logo_name = microsite.get_value('FOOTER_ORGANIZATION_IMAGE', settings.FOOTER_ORGANIZATION_IMAGE) + return _absolute_url_staticfile(is_secure, logo_name) + + +def _absolute_url(is_secure, url_path): + """Construct an absolute URL back to the site. + + Arguments: + is_secure (bool): If true, use HTTPS as the protocol. + url_path (unicode): The path of the URL. + + Returns: + unicode + + """ + site_name = microsite.get_value('SITE_NAME', settings.SITE_NAME) + parts = ("https" if is_secure else "http", site_name, url_path, '', '', '') + return urlparse.urlunparse(parts) + + +def _absolute_url_staticfile(is_secure, name): + """Construct an absolute URL back to a static resource on the site. + + Arguments: + is_secure (bool): If true, use HTTPS as the protocol. + name (unicode): The name of the static resource to retrieve. + + Returns: + unicode + + """ + url_path = staticfiles_storage.url(name) + return _absolute_url(is_secure, url_path) diff --git a/lms/djangoapps/branding/api_urls.py b/lms/djangoapps/branding/api_urls.py new file mode 100644 index 0000000000..d7ba50c012 --- /dev/null +++ b/lms/djangoapps/branding/api_urls.py @@ -0,0 +1,15 @@ +""" +Branding API endpoint urls. +""" + +from django.conf.urls import patterns, url + +urlpatterns = patterns( + "", + + url( + r"^footer$", + "branding.views.footer", + name="branding_footer", + ), +) diff --git a/lms/djangoapps/branding/context_processors.py b/lms/djangoapps/branding/context_processors.py new file mode 100644 index 0000000000..3aa688381d --- /dev/null +++ b/lms/djangoapps/branding/context_processors.py @@ -0,0 +1,10 @@ +"""Context processors for Django templates. """ +from branding import api as branding_api + + +# TODO (ECOM-1339): Remove this module once we permanently enable the V3 footer. +def branding_context_processor(request): # pylint: disable=unused-argument + """Add the feature flag to Django template context. """ + return { + "ENABLE_BRANDING_API": branding_api.is_enabled() + } diff --git a/lms/djangoapps/branding/migrations/0002_auto__add_brandingapiconfig.py b/lms/djangoapps/branding/migrations/0002_auto__add_brandingapiconfig.py new file mode 100644 index 0000000000..ffa45652a6 --- /dev/null +++ b/lms/djangoapps/branding/migrations/0002_auto__add_brandingapiconfig.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'BrandingApiConfig' + db.create_table('branding_brandingapiconfig', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('change_date', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('changed_by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, on_delete=models.PROTECT)), + ('enabled', self.gf('django.db.models.fields.BooleanField')(default=False)), + )) + db.send_create_signal('branding', ['BrandingApiConfig']) + + + def backwards(self, orm): + # Deleting model 'BrandingApiConfig' + db.delete_table('branding_brandingapiconfig') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'branding.brandingapiconfig': { + 'Meta': {'object_name': 'BrandingApiConfig'}, + 'change_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.PROTECT'}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'branding.brandinginfoconfig': { + 'Meta': {'object_name': 'BrandingInfoConfig'}, + 'change_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.PROTECT'}), + 'configuration': ('django.db.models.fields.TextField', [], {}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['branding'] diff --git a/lms/djangoapps/branding/models.py b/lms/djangoapps/branding/models.py index 7ee3d21e99..67a51d4f26 100644 --- a/lms/djangoapps/branding/models.py +++ b/lms/djangoapps/branding/models.py @@ -44,3 +44,14 @@ class BrandingInfoConfig(ConfigurationModel): """ info = cls.current() return json.loads(info.configuration) if info.enabled else {} + + +class BrandingApiConfig(ConfigurationModel): + """Configure Branding api's + + Enable or disable api's functionality. + When this flag is disabled, the api will return 404. + + When the flag is enabled, the api will returns the valid reponse. + """ + pass diff --git a/lms/djangoapps/branding/tests/test_views.py b/lms/djangoapps/branding/tests/test_views.py new file mode 100644 index 0000000000..b11ab62518 --- /dev/null +++ b/lms/djangoapps/branding/tests/test_views.py @@ -0,0 +1,214 @@ +# encoding: utf-8 +"""Tests of Branding API views. """ +import contextlib +import json +import urllib +from django.test import TestCase +from django.core.urlresolvers import reverse +from django.conf import settings + +import mock +import ddt +from config_models.models import cache +from branding.models import BrandingApiConfig + + +@ddt.ddt +class TestFooter(TestCase): + """Test API end-point for retrieving the footer. """ + + def setUp(self): + """Clear the configuration cache. """ + super(TestFooter, self).setUp() + cache.clear() + + @ddt.data("*/*", "text/html", "application/json") + def test_feature_flag(self, accepts): + self._set_feature_flag(False) + resp = self._get_footer(accepts=accepts) + self.assertEqual(resp.status_code, 404) + + @ddt.data( + # Open source version + (False, "application/json", "application/json; charset=utf-8", "Open edX"), + (False, "text/html", "text/html; charset=utf-8", "lms-footer.css"), + (False, "text/html", "text/html; charset=utf-8", "Open edX"), + + # EdX.org version + (True, "application/json", "application/json; charset=utf-8", "edX Inc"), + (True, "text/html", "text/html; charset=utf-8", "lms-footer-edx.css"), + (True, "text/html", "text/html; charset=utf-8", "edX Inc"), + ) + @ddt.unpack + def test_footer_content_types(self, is_edx_domain, accepts, content_type, content): + self._set_feature_flag(True) + with self._set_is_edx_domain(is_edx_domain): + resp = self._get_footer(accepts=accepts) + + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp["Content-Type"], content_type) + self.assertIn(content, resp.content) + + @mock.patch.dict(settings.FEATURES, {'ENABLE_FOOTER_MOBILE_APP_LINKS': True}) + @ddt.data(True, False) + def test_footer_json(self, is_edx_domain): + self._set_feature_flag(True) + with self._set_is_edx_domain(is_edx_domain): + resp = self._get_footer() + + self.assertEqual(resp.status_code, 200) + json_data = json.loads(resp.content) + self.assertTrue(isinstance(json_data, dict)) + + # Logo + self.assertIn("logo_image", json_data) + + # Links + self.assertIn("navigation_links", json_data) + for link in json_data["navigation_links"]: + self.assertIn("name", link) + self.assertIn("title", link) + self.assertIn("url", link) + + # Social links + self.assertIn("social_links", json_data) + for link in json_data["social_links"]: + self.assertIn("name", link) + self.assertIn("title", link) + self.assertIn("url", link) + self.assertIn("icon-class", link) + + # Mobile links + self.assertIn("mobile_links", json_data) + for link in json_data["mobile_links"]: + self.assertIn("name", link) + self.assertIn("title", link) + self.assertIn("url", link) + self.assertIn("image", link) + + # Legal links + self.assertIn("legal_links", json_data) + for link in json_data["legal_links"]: + self.assertIn("name", link) + self.assertIn("title", link) + self.assertIn("url", link) + + # OpenEdX + self.assertIn("openedx_link", json_data) + self.assertIn("url", json_data["openedx_link"]) + self.assertIn("title", json_data["openedx_link"]) + self.assertIn("image", json_data["openedx_link"]) + + # Copyright + self.assertIn("copyright", json_data) + + @ddt.data( + ("en", "registered trademarks"), + ("eo", u"régïstéréd trädémärks"), # Dummy language string + ("unknown", "registered trademarks"), # default to English + ) + @ddt.unpack + def test_language_override_translation(self, language, expected_copyright): + self._set_feature_flag(True) + + # Load the footer with the specified language + resp = self._get_footer(params={'language': language}) + self.assertEqual(resp.status_code, 200) + json_data = json.loads(resp.content) + + # Verify that the translation occurred + self.assertIn(expected_copyright, json_data['copyright']) + + @ddt.data( + # OpenEdX + (False, "en", "lms-footer.css"), + (False, "ar", "lms-footer-rtl.css"), + + # EdX.org + (True, "en", "lms-footer-edx.css"), + (True, "ar", "lms-footer-edx-rtl.css"), + ) + @ddt.unpack + def test_language_rtl(self, is_edx_domain, language, static_path): + self._set_feature_flag(True) + + with self._set_is_edx_domain(is_edx_domain): + resp = self._get_footer(accepts="text/html", params={'language': language}) + + self.assertEqual(resp.status_code, 200) + self.assertIn(static_path, resp.content) + + @ddt.data( + # OpenEdX + (False, True), + (False, False), + + # EdX.org + (True, True), + (True, False), + ) + @ddt.unpack + def test_show_openedx_logo(self, is_edx_domain, show_logo): + self._set_feature_flag(True) + + with self._set_is_edx_domain(is_edx_domain): + params = {'show-openedx-logo': 1} if show_logo else {} + resp = self._get_footer(accepts="text/html", params=params) + + self.assertEqual(resp.status_code, 200) + + if show_logo: + self.assertIn(settings.FOOTER_OPENEDX_URL, resp.content) + else: + self.assertNotIn(settings.FOOTER_OPENEDX_URL, resp.content) + + @ddt.data( + # OpenEdX + (False, False), + (False, True), + + # EdX.org + (True, False), + (True, True), + ) + @ddt.unpack + def test_include_dependencies(self, is_edx_domain, include_dependencies): + self._set_feature_flag(True) + with self._set_is_edx_domain(is_edx_domain): + params = {'include-dependencies': 1} if include_dependencies else {} + resp = self._get_footer(accepts="text/html", params=params) + + self.assertEqual(resp.status_code, 200) + + if include_dependencies: + self.assertIn("vendor", resp.content) + else: + self.assertNotIn("vendor", resp.content) + + def test_no_supported_accept_type(self): + self._set_feature_flag(True) + resp = self._get_footer(accepts="application/x-shockwave-flash") + self.assertEqual(resp.status_code, 406) + + def _set_feature_flag(self, enabled): + """Enable or disable the feature flag for the branding API end-points. """ + config = BrandingApiConfig(enabled=enabled) + config.save() + + def _get_footer(self, accepts="application/json", params=None): + """Retrieve the footer. """ + url = reverse("branding_footer") + + if params is not None: + url = u"{url}?{params}".format( + url=url, + params=urllib.urlencode(params) + ) + + return self.client.get(url, HTTP_ACCEPT=accepts) + + @contextlib.contextmanager + def _set_is_edx_domain(self, is_edx_domain): + """Configure whether this an EdX-controlled domain. """ + with mock.patch.dict(settings.FEATURES, {'IS_EDX_DOMAIN': is_edx_domain}): + yield diff --git a/lms/djangoapps/branding/views.py b/lms/djangoapps/branding/views.py index b79df70f55..8e950dfb91 100644 --- a/lms/djangoapps/branding/views.py +++ b/lms/djangoapps/branding/views.py @@ -1,17 +1,29 @@ +"""Views for the branding app. """ +import logging +import urllib + from django.conf import settings from django.core.urlresolvers import reverse -from django.http import Http404 +from django.core.cache import cache +from django.views.decorators.cache import cache_control +from django.http import HttpResponse, Http404 +from django.utils import translation from django.shortcuts import redirect from django_future.csrf import ensure_csrf_cookie +from staticfiles.storage import staticfiles_storage +from edxmako.shortcuts import render_to_response import student.views from student.models import CourseEnrollment - import courseware.views - from microsite_configuration import microsite from edxmako.shortcuts import marketing_link from util.cache import cache_if_anonymous +from util.json_request import JsonResponse +import branding.api as branding_api + + +log = logging.getLogger(__name__) def get_course_enrollments(user): @@ -102,3 +114,198 @@ def courses(request): # we do not expect this case to be reached in cases where # marketing is enabled or the courses are not browsable return courseware.views.courses(request) + + +def _footer_static_url(request, name): + """Construct an absolute URL to a static asset. """ + return request.build_absolute_uri(staticfiles_storage.url(name)) + + +def _footer_css_urls(request, package_name): + """Construct absolute URLs to CSS assets in a package. """ + # We need this to work both in local development and in production. + # Unfortunately, in local development we don't run the full asset pipeline, + # so fully processed output files may not exist. + # For this reason, we use the *css package* name(s), rather than the static file name + # to identify the CSS file name(s) to include in the footer. + # We then construct an absolute URI so that external sites (such as the marketing site) + # can locate the assets. + package = settings.PIPELINE_CSS.get(package_name, {}) + paths = [package['output_filename']] if not settings.DEBUG else package['source_filenames'] + return [ + _footer_static_url(request, path) + for path in paths + ] + + +def _render_footer_html(request, show_openedx_logo, include_dependencies): + """Render the footer as HTML. + + Arguments: + show_openedx_logo (bool): If True, include the OpenEdX logo in the rendered HTML. + include_dependencies (bool): If True, include JavaScript and CSS dependencies. + + Returns: unicode + + """ + bidi = 'rtl' if translation.get_language_bidi() else 'ltr' + version = 'edx' if settings.FEATURES.get('IS_EDX_DOMAIN') else 'openedx' + css_name = settings.FOOTER_CSS[version][bidi] + + context = { + 'hide_openedx_link': not show_openedx_logo, + 'footer_js_url': _footer_static_url(request, 'js/footer-edx.js'), + 'footer_css_urls': _footer_css_urls(request, css_name), + 'bidi': bidi, + 'include_dependencies': include_dependencies, + } + + return ( + render_to_response("footer-edx-v3.html", context) + if settings.FEATURES.get("IS_EDX_DOMAIN", False) + else render_to_response("footer.html", context) + ) + + +@cache_control(must_revalidate=True, max_age=settings.FOOTER_BROWSER_CACHE_MAX_AGE) +def footer(request): + """Retrieve the branded footer. + + This end-point provides information about the site footer, + allowing for consistent display of the footer across other sites + (for example, on the marketing site and blog). + + It can be used in one of two ways: + 1) A client renders the footer from a JSON description. + 2) A browser loads an HTML representation of the footer + and injects it into the DOM. The HTML includes + CSS and JavaScript links. + + In case (2), we assume that the following dependencies + are included on the page: + a) JQuery (same version as used in edx-platform) + b) font-awesome (same version as used in edx-platform) + c) Open Sans web fonts + + Example: Retrieving the footer as JSON + + GET /api/branding/v1/footer + Accepts: application/json + + { + "navigation_links": [ + { + "url": "http://example.com/about", + "name": "about", + "title": "About" + }, + # ... + ], + "social_links": [ + { + "url": "http://example.com/social", + "name": "facebook", + "icon-class": "fa-facebook-square", + "title": "Facebook" + }, + # ... + ], + "mobile_links": [ + { + "url": "http://example.com/android", + "name": "google", + "image": "http://example.com/google.png", + "title": "Google" + }, + # ... + ], + "legal_links": [ + { + "url": "http://example.com/terms-of-service.html", + "name": "terms_of_service", + "title': "Terms of Service" + }, + # ... + ], + "openedx_link": { + "url": "http://open.edx.org", + "title": "Powered by Open edX", + "image": "http://example.com/openedx.png" + }, + "logo_image": "http://example.com/static/images/default-theme/logo.png", + "copyright": "EdX, Open edX, and the edX and Open edX logos are \ + registered trademarks or trademarks of edX Inc." + } + + + Example: Retrieving the footer as HTML + + GET /api/branding/v1/footer + Accepts: text/html + + + Example: Including the footer with the "Powered by OpenEdX" logo + + GET /api/branding/v1/footer?show-openedx-logo=1 + Accepts: text/html + + + Example: Retrieving the footer in a particular language + + GET /api/branding/v1/footer?language=en + Accepts: text/html + + Example: Retrieving the footer with all JS and CSS dependencies (for testing) + + GET /api/branding/v1/footer?include-dependencies=1 + Accepts: text/html + + """ + if not branding_api.is_enabled(): + raise Http404 + + # Use the content type to decide what representation to serve + accepts = request.META.get('HTTP_ACCEPT', '*/*') + + # Show the OpenEdX logo in the footer + show_openedx_logo = bool(request.GET.get('show-openedx-logo', False)) + + # Include JS and CSS dependencies + # This is useful for testing the end-point directly. + include_dependencies = bool(request.GET.get('include-dependencies', False)) + + # Override the language if necessary + language = request.GET.get('language', translation.get_language()) + + # Render the footer information based on the extension + if 'text/html' in accepts or '*/*' in accepts: + cache_key = u"branding.footer.{params}.html".format( + params=urllib.urlencode({ + 'language': language, + 'show_openedx_logo': show_openedx_logo, + 'include_dependencies': include_dependencies, + }) + ) + content = cache.get(cache_key) + if content is None: + with translation.override(language): + content = _render_footer_html(request, show_openedx_logo, include_dependencies) + cache.set(cache_key, content, settings.FOOTER_CACHE_TIMEOUT) + return HttpResponse(content, status=200, content_type="text/html; charset=utf-8") + + elif 'application/json' in accepts: + cache_key = u"branding.footer.{params}.json".format( + params=urllib.urlencode({ + 'language': language, + 'is_secure': request.is_secure(), + }) + ) + footer_dict = cache.get(cache_key) + if footer_dict is None: + with translation.override(language): + footer_dict = branding_api.get_footer(is_secure=request.is_secure()) + cache.set(cache_key, footer_dict, settings.FOOTER_CACHE_TIMEOUT) + return JsonResponse(footer_dict, 200, content_type="application/json; charset=utf-8") + + else: + return HttpResponse(status=406) diff --git a/lms/djangoapps/courseware/features/homepage.feature b/lms/djangoapps/courseware/features/homepage.feature index 49b3cf3e65..2cf3173713 100644 --- a/lms/djangoapps/courseware/features/homepage.feature +++ b/lms/djangoapps/courseware/features/homepage.feature @@ -18,6 +18,6 @@ Feature: LMS.Homepage for web users | id | Link | | about | About | | jobs | Jobs | - | faq | FAQ | + | faq | FAQs | | contact | Contact| | news | News | diff --git a/lms/djangoapps/courseware/tests/test_about.py b/lms/djangoapps/courseware/tests/test_about.py index 6d19bccc4a..0c75863408 100644 --- a/lms/djangoapps/courseware/tests/test_about.py +++ b/lms/djangoapps/courseware/tests/test_about.py @@ -306,7 +306,7 @@ class AboutWithInvitationOnly(ModuleStoreTestCase): url = reverse('about_course', args=[self.course.id.to_deprecated_string()]) resp = self.client.get(url) self.assertEqual(resp.status_code, 200) - self.assertIn(u"Register for {}".format(self.course.id.course), resp.content) + self.assertIn(u"Register for {}".format(self.course.id.course), resp.content.decode('utf-8')) # Check that registration button is present self.assertIn(REG_STR, resp.content) @@ -336,7 +336,7 @@ class AboutTestCaseShibCourse(LoginEnrollmentTestCase, ModuleStoreTestCase): resp = self.client.get(url) self.assertEqual(resp.status_code, 200) self.assertIn("OOGIE BLOOGIE", resp.content) - self.assertIn(u"Register for {}".format(self.course.id.course), resp.content) + self.assertIn(u"Register for {}".format(self.course.id.course), resp.content.decode('utf-8')) self.assertIn(SHIB_ERROR_STR, resp.content) self.assertIn(REG_STR, resp.content) @@ -348,7 +348,7 @@ class AboutTestCaseShibCourse(LoginEnrollmentTestCase, ModuleStoreTestCase): resp = self.client.get(url) self.assertEqual(resp.status_code, 200) self.assertIn("OOGIE BLOOGIE", resp.content) - self.assertIn(u"Register for {}".format(self.course.id.course), resp.content) + self.assertIn(u"Register for {}".format(self.course.id.course), resp.content.decode('utf-8')) self.assertIn(SHIB_ERROR_STR, resp.content) self.assertIn(REG_STR, resp.content) diff --git a/lms/djangoapps/courseware/tests/test_footer.py b/lms/djangoapps/courseware/tests/test_footer.py index edd873de54..1300f562f4 100644 --- a/lms/djangoapps/courseware/tests/test_footer.py +++ b/lms/djangoapps/courseware/tests/test_footer.py @@ -14,6 +14,17 @@ from django.test.utils import override_settings @attr('shard_1') class TestFooter(TestCase): + SOCIAL_MEDIA_NAMES = [ + "facebook", + "google_plus", + "twitter", + "linkedin", + "tumblr", + "meetup", + "reddit", + "youtube", + ] + SOCIAL_MEDIA_URLS = { "facebook": "http://www.facebook.com/", "google_plus": "https://plus.google.com/", @@ -51,7 +62,10 @@ class TestFooter(TestCase): self.assertContains(resp, 'wrapper-footer') @patch.dict(settings.FEATURES, {'IS_EDX_DOMAIN': True}) - @override_settings(SOCIAL_MEDIA_FOOTER_URLS=SOCIAL_MEDIA_URLS) + @override_settings( + SOCIAL_MEDIA_FOOTER_NAMES=SOCIAL_MEDIA_NAMES, + SOCIAL_MEDIA_FOOTER_URLS=SOCIAL_MEDIA_URLS + ) def test_edx_footer_social_links(self): resp = self.client.get('/') for name, url in self.SOCIAL_MEDIA_URLS.iteritems(): diff --git a/lms/envs/aws.py b/lms/envs/aws.py index af77744869..94d72aa61e 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -309,6 +309,13 @@ if FEATURES.get('AUTH_USE_CAS'): # Example: {'CN': 'http://api.xuetangx.com/edx/video?s3_url='} VIDEO_CDN_URL = ENV_TOKENS.get('VIDEO_CDN_URL', {}) +# Branded footer +FOOTER_OPENEDX_URL = ENV_TOKENS.get('FOOTER_OPENEDX_URL', FOOTER_OPENEDX_URL) +FOOTER_OPENEDX_LOGO_IMAGE = ENV_TOKENS.get('FOOTER_OPENEDX_LOGO_IMAGE', FOOTER_OPENEDX_LOGO_IMAGE) +FOOTER_ORGANIZATION_IMAGE = ENV_TOKENS.get('FOOTER_ORGANIZATION_IMAGE', FOOTER_ORGANIZATION_IMAGE) +FOOTER_CACHE_TIMEOUT = ENV_TOKENS.get('FOOTER_CACHE_TIMEOUT', FOOTER_CACHE_TIMEOUT) +FOOTER_BROWSER_CACHE_MAX_AGE = ENV_TOKENS.get('FOOTER_BROWSER_CACHE_MAX_AGE', FOOTER_BROWSER_CACHE_MAX_AGE) + ############# CORS headers for cross-domain requests ################# if FEATURES.get('ENABLE_CORS_HEADERS') or FEATURES.get('ENABLE_CROSS_DOMAIN_CSRF_COOKIE'): diff --git a/lms/envs/common.py b/lms/envs/common.py index ecb8c94bcb..bbd17ef5aa 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -342,9 +342,6 @@ FEATURES = { # Show the mobile app links in the footer 'ENABLE_FOOTER_MOBILE_APP_LINKS': False, - # Use version 3 of the footer (added May 2015) - 'ENABLE_FOOTER_V3': False, - # Let students save and manage their annotations 'ENABLE_EDXNOTES': False, @@ -507,6 +504,11 @@ TEMPLATE_CONTEXT_PROCESSORS = ( # Allows the open edX footer to be leveraged in Django Templates. 'edxmako.shortcuts.open_source_footer_context_processor', + # TODO (ECOM-1339): Remove once the V3 version of the footer is enabled permanently + # This allows us to pass the appropriate feature flag to the main Django template + # that contains the footer. + 'branding.context_processors.branding_context_processor', + # Shoppingcart processor (detects if request.user has a cart) 'shoppingcart.context_processor.user_has_cart_context_processor', @@ -1039,6 +1041,48 @@ PARENTAL_CONSENT_AGE_LIMIT = 13 ################################# Jasmine ################################## JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee' + +######################### Branded Footer ################################### +# Constants for the footer used on the site and shared with other sites +# (such as marketing and the blog) via the branding API. + +# URL for OpenEdX displayed in the footer +FOOTER_OPENEDX_URL = "http://open.edx.org" + +# URL for the OpenEdX logo image +# We use logo images served from files.edx.org so we can (roughly) track +# how many OpenEdX installations are running. +# Site operators can choose from these logo options: +# * https://files.edx.org/openedx-logos/edx-openedx-logo-tag.png +# * https://files.edx.org/openedx-logos/edx-openedx-logo-tag-light.png" +# * https://files.edx.org/openedx-logos/edx-openedx-logo-tag-dark.png +FOOTER_OPENEDX_LOGO_IMAGE = "https://files.edx.org/openedx-logos/edx-openedx-logo-tag.png" + +# This is just a placeholder image. +# Site operators can customize this with their organization's image. +FOOTER_ORGANIZATION_IMAGE = "images/default-theme/logo.png" + +# These are referred to both by the Django asset pipeline +# AND by the branding footer API, which needs to decide which +# version of the CSS to serve. +FOOTER_CSS = { + "openedx": { + "ltr": "style-lms-footer", + "rtl": "style-lms-footer-rtl", + }, + "edx": { + "ltr": "style-lms-footer-edx", + "rtl": "style-lms-footer-edx-rtl", + }, +} + +# Cache expiration for the version of the footer served +# by the branding API. +FOOTER_CACHE_TIMEOUT = 30 * 60 + +# Max age cache control header for the footer (controls browser caching). +FOOTER_BROWSER_CACHE_MAX_AGE = 5 * 60 + ################################# Deprecation warnings ##################### # Ignore deprecation warnings (so we don't clutter Jenkins builds/production) @@ -1182,8 +1226,7 @@ dashboard_js = ( ['js/search/dashboard/main.js'] ) discussion_js = sorted(rooted_glob(COMMON_ROOT / 'static', 'coffee/src/discussion/**/*.js')) -rwd_header_footer_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'js/common_helpers/rwd_header_footer.js')) -footer_edx_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'js/footer-edx.js')) +rwd_header_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'js/utils/rwd_header.js')) staff_grading_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/staff_grading/**/*.js')) open_ended_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/open_ended/**/*.js')) notes_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/notes/**/*.js')) @@ -1196,7 +1239,7 @@ instructor_dash_js = ( # These are not courseware, so they do not need many of the courseware-specific # JavaScript modules. student_account_js = [ - 'js/utils/rwd_header_footer.js', + 'js/utils/rwd_header.js', 'js/utils/edx.utils.validate.js', 'js/form.ext.js', 'js/my_courses_dropdown.js', @@ -1342,17 +1385,29 @@ PIPELINE_CSS = { ], 'output_filename': 'css/lms-style-xmodule-annotations.css', }, - 'style-edx-footer': { + FOOTER_CSS['openedx']['ltr']: { 'source_filenames': [ - 'sass/footer-v3.css', + 'sass/lms-footer.css', ], - 'output_filename': 'css/lms-footer-edx.css', + 'output_filename': 'css/lms-footer.css', }, - 'style-edx-footer-rtl': { + FOOTER_CSS['openedx']['rtl']: { 'source_filenames': [ - 'sass/footer-v3-rtl.css', + 'sass/lms-footer-rtl.css', ], - 'output_filename': 'css/lms-footer-edx-rtl.css', + 'output_filename': 'css/lms-footer-rtl.css' + }, + FOOTER_CSS['edx']['ltr']: { + 'source_filenames': [ + 'sass/lms-footer-edx.css', + ], + 'output_filename': 'css/lms-footer-edx.css' + }, + FOOTER_CSS['edx']['rtl']: { + 'source_filenames': [ + 'sass/lms-footer-edx-rtl.css', + ], + 'output_filename': 'css/lms-footer-edx-rtl.css' }, } @@ -1423,9 +1478,9 @@ PIPELINE_JS = { 'source_filenames': dashboard_js, 'output_filename': 'js/dashboard.js' }, - 'rwd_header_footer': { - 'source_filenames': rwd_header_footer_js, - 'output_filename': 'js/rwd_header_footer.js' + 'rwd_header': { + 'source_filenames': rwd_header_js, + 'output_filename': 'js/rwd_header.js' }, 'student_account': { 'source_filenames': student_account_js, @@ -1448,8 +1503,8 @@ PIPELINE_JS = { 'output_filename': 'js/ccx.js' }, 'footer_edx': { - 'source_filenames': footer_edx_js, - 'output_filename': 'js/footer_edx.js' + 'source_filenames': ['js/footer-edx.js'], + 'output_filename': 'js/footer-edx.js', } } @@ -1492,6 +1547,7 @@ PIPELINE_UGLIFYJS_BINARY = 'node_modules/.bin/uglifyjs' # Setting that will only affect the edX version of django-pipeline until our changes are merged upstream PIPELINE_COMPILE_INPLACE = True + ################################# CELERY ###################################### # Message configuration @@ -1797,25 +1853,14 @@ MKTG_URL_LINK_MAP = { ################# Social Media Footer Links ####################### # The names list controls the order of social media # links in the footer. -if FEATURES.get('ENABLE_FOOTER_V3'): - SOCIAL_MEDIA_FOOTER_NAMES = [ - "facebook", - "twitter", - "linkedin", - "weibo", - "vk", - ] -else: - SOCIAL_MEDIA_FOOTER_NAMES = [ - "facebook", - "twitter", - "linkedin", - "google_plus", - "tumblr", - "meetup", - "reddit", - "youtube", - ] +SOCIAL_MEDIA_FOOTER_NAMES = [ + "facebook", + "twitter", + "youtube", + "linkedin", + "google_plus", + "reddit", +] # The footer URLs dictionary maps social footer names # to URLs defined in configuration. @@ -1852,7 +1897,7 @@ SOCIAL_MEDIA_FOOTER_DISPLAY = { # Translators: This is the website name of www.tumblr.com. Please # translate this the way that Tumblr advertises in your language. "title": _("Tumblr"), - "icon": "fa-tumblr-square" + "icon": "fa-tumblr" }, "meetup": { # Translators: This is the website name of www.meetup.com. Please @@ -1864,7 +1909,7 @@ SOCIAL_MEDIA_FOOTER_DISPLAY = { # Translators: This is the website name of www.reddit.com. Please # translate this the way that Reddit advertises in your language. "title": _("Reddit"), - "icon": "fa-reddit-square" + "icon": "fa-reddit" }, "vk": { # Translators: This is the website name of https://vk.com. Please @@ -1882,7 +1927,7 @@ SOCIAL_MEDIA_FOOTER_DISPLAY = { # Translators: This is the website name of www.youtube.com. Please # translate this the way that YouTube advertises in your language. "title": _("Youtube"), - "icon": "fa-youtube-square" + "icon": "fa-youtube" } } diff --git a/lms/static/js/footer-edx.js b/lms/static/js/footer-edx.js index 0d589f5885..0b388d1aa6 100644 --- a/lms/static/js/footer-edx.js +++ b/lms/static/js/footer-edx.js @@ -7,18 +7,6 @@ var edx = edx || {}; var _fn = { el: '#footer-edx-v3', - init: function() { - _fn.$el = _fn.$el || $( _fn.el ); - - /** - * Only continue if the expected element - * to add footer to is in the DOM - */ - if ( _fn.$el.length > -1 ) { - _fn.footer.get(); - } - }, - analytics: { init: function() { _fn.$el = _fn.$el || $( _fn.el ); @@ -27,7 +15,7 @@ var edx = edx || {}; * Only continue if the expected element * to add footer to is in the DOM */ - if ( _fn.$el.length > -1 ) { + if ( _fn.$el.length ) { _fn.analytics.eventListener(); } }, @@ -50,31 +38,12 @@ var edx = edx || {}; } } }, - - footer: { - get: function() { - $.ajax({ - url: 'https://courses.edx.org/api/v1/branding/footer', - type: 'GET', - dataType: 'html', - success: function( data ) { - _fn.footer.render( data ); - } - }); - }, - - render: function( html ) { - _fn.$el.html( html ); - } - } }; return { - load: _fn.init, analytics: _fn.analytics.init }; })(); - // Initialize the analytics events edx.footer.analytics(); })(jQuery); diff --git a/lms/static/sass/_build-footer-edx.scss b/lms/static/sass/_build-footer-edx.scss new file mode 100644 index 0000000000..09b539b5c5 --- /dev/null +++ b/lms/static/sass/_build-footer-edx.scss @@ -0,0 +1,19 @@ +// ---------------------------------------- +// LMS edx.org Footer: Shared Build Compile + +// base - utilities +@import 'base/variables'; +@import 'base/mixins'; + +footer#footer-edx-v3 { + @import 'base/extends'; + + // base - starter + @import 'base/base'; +} + +// base - elements +@import 'elements/typography'; + +// shared - platform +@import 'shared/footer-edx'; diff --git a/lms/static/sass/_build-lms.scss b/lms/static/sass/_build-lms.scss index 3b05db8196..c5f95fe8bf 100644 --- a/lms/static/sass/_build-lms.scss +++ b/lms/static/sass/_build-lms.scss @@ -21,7 +21,7 @@ @import 'shared/fields'; @import 'shared/forms'; @import 'shared/footer'; -@import 'shared/footer-edx'; // Replaces most of the footer partial. Will update footer to remove edx specific styles once feature flag removed. +@import 'shared/footer-edx'; @import 'shared/header'; @import 'shared/course_object'; @import 'shared/course_filter'; diff --git a/lms/static/sass/base/_variables-ltr.scss b/lms/static/sass/base/_variables-ltr.scss new file mode 100644 index 0000000000..4e999af162 --- /dev/null +++ b/lms/static/sass/base/_variables-ltr.scss @@ -0,0 +1,7 @@ +// Variables for LMS (left-to-right) +// ================================== + +// Neat +// ================================== +// Sets the default layout direction of the grid. +$default-layout-direction: LTR !global; diff --git a/lms/static/sass/base/_variables-rtl.scss b/lms/static/sass/base/_variables-rtl.scss new file mode 100644 index 0000000000..b49e44b1f0 --- /dev/null +++ b/lms/static/sass/base/_variables-rtl.scss @@ -0,0 +1,7 @@ +// Variables for LMS (right-to-left) +// ================================== + +// Neat +// ================================== +// Sets the default layout direction of the grid. +$default-layout-direction: RTL !global; diff --git a/lms/static/sass/lms-footer-edx-rtl.scss b/lms/static/sass/lms-footer-edx-rtl.scss new file mode 100644 index 0000000000..e189fe9e6d --- /dev/null +++ b/lms/static/sass/lms-footer-edx-rtl.scss @@ -0,0 +1,10 @@ +// Footer for edx.org (right-to-left) +// ================================== + +// libs and resets *do not edit* +@import 'bourbon/bourbon'; // lib - bourbon +@import 'vendor/bi-app/bi-app-rtl'; // set the layout for right to left languages +@import 'base/variables-rtl'; + +// Import shared build for the edx.org footer +@import 'build-footer-edx' diff --git a/lms/static/sass/lms-footer-edx.scss b/lms/static/sass/lms-footer-edx.scss new file mode 100644 index 0000000000..afa3d26592 --- /dev/null +++ b/lms/static/sass/lms-footer-edx.scss @@ -0,0 +1,10 @@ +// Footer for edx.org (left-to-right) +// ================================== + +// libs and resets *do not edit* +@import 'bourbon/bourbon'; // lib - bourbon +@import 'vendor/bi-app/bi-app-ltr'; // set the layout for left to right languages +@import 'base/variables-ltr'; + +// Import shared build for the edx.org footer +@import 'build-footer-edx' diff --git a/lms/static/sass/lms-footer-edx-rtl.scss.mako b/lms/static/sass/lms-footer-rtl.scss.mako similarity index 60% rename from lms/static/sass/lms-footer-edx-rtl.scss.mako rename to lms/static/sass/lms-footer-rtl.scss.mako index 7c6f4466c7..cc91fab379 100644 --- a/lms/static/sass/lms-footer-edx-rtl.scss.mako +++ b/lms/static/sass/lms-footer-rtl.scss.mako @@ -1,14 +1,9 @@ -## Note: This Sass infrastructure is repeated in application-extend1 and application-extend2, but needed in order to address an IE9 rule limit within CSS - http://blogs.msdn.com/b/ieinternals/archive/2011/05/14/10164546.aspx - -// lms - css application architecture -// ==================== +// Footer for OpenEdX (right-to-left) +// ================================== // libs and resets *do not edit* @import 'bourbon/bourbon'; // lib - bourbon -@import 'vendor/bi-app/bi-app-rtl'; // set the layout for left to right languages - -// BASE *default edX offerings* -// ==================== +@import 'vendor/bi-app/bi-app-rtl'; // set the layout for right to left languages // base - utilities @import 'base/variables'; @@ -27,19 +22,14 @@ @import '${env.get('THEME_NAME')}'; % endif -// base - assets -@import 'base/font_face'; - -footer#footer-edx-v3 { +footer#footer-openedx { + @import 'base/reset'; @import 'base/extends'; - - // base - starter @import 'base/base'; - } // base - elements @import 'elements/typography'; // shared - platform -@import 'shared/footer-edx'; +@import 'shared/footer'; diff --git a/lms/static/sass/lms-footer-edx.scss.mako b/lms/static/sass/lms-footer.scss.mako similarity index 66% rename from lms/static/sass/lms-footer-edx.scss.mako rename to lms/static/sass/lms-footer.scss.mako index ceabdbc5de..0b4aff0381 100644 --- a/lms/static/sass/lms-footer-edx.scss.mako +++ b/lms/static/sass/lms-footer.scss.mako @@ -1,15 +1,10 @@ -## Note: This Sass infrastructure is repeated in application-extend1 and application-extend2, but needed in order to address an IE9 rule limit within CSS - http://blogs.msdn.com/b/ieinternals/archive/2011/05/14/10164546.aspx - -// lms - css application architecture -// ==================== +// Footer for OpenEdX (left-to-right) +// ================================== // libs and resets *do not edit* @import 'bourbon/bourbon'; // lib - bourbon @import 'vendor/bi-app/bi-app-ltr'; // set the layout for left to right languages -// BASE *default edX offerings* -// ==================== - // base - utilities @import 'base/variables'; @import 'base/mixins'; @@ -27,19 +22,14 @@ @import '${env.get('THEME_NAME')}'; % endif -// base - assets -@import 'base/font_face'; - -footer#footer-edx-v3 { +footer#footer-openedx { + @import 'base/reset'; @import 'base/extends'; - - // base - starter @import 'base/base'; - } // base - elements @import 'elements/typography'; // shared - platform -@import 'shared/footer-edx'; +@import 'shared/footer'; diff --git a/lms/static/sass/lms-main-rtl.scss.mako b/lms/static/sass/lms-main-rtl.scss.mako index 1bac12f885..dcb06ff91a 100644 --- a/lms/static/sass/lms-main-rtl.scss.mako +++ b/lms/static/sass/lms-main-rtl.scss.mako @@ -4,6 +4,7 @@ // libs and resets *do not edit* @import 'bourbon/bourbon'; // lib - bourbon @import 'vendor/bi-app/bi-app-rtl'; // set the layout for right to left languages +@import 'base/variables-rtl'; // BASE *default edX offerings* // ==================== diff --git a/lms/static/sass/lms-main.scss.mako b/lms/static/sass/lms-main.scss.mako index 3a1a71f6e5..83d7be23d3 100644 --- a/lms/static/sass/lms-main.scss.mako +++ b/lms/static/sass/lms-main.scss.mako @@ -4,6 +4,7 @@ // libs and resets *do not edit* @import 'bourbon/bourbon'; // lib - bourbon @import 'vendor/bi-app/bi-app-ltr'; // set the layout for left to right languages +@import 'base/variables-ltr'; // BASE *default edX offerings* // ==================== diff --git a/lms/static/sass/shared/_footer-edx.scss b/lms/static/sass/shared/_footer-edx.scss index fc2babf3be..b82484c91b 100644 --- a/lms/static/sass/shared/_footer-edx.scss +++ b/lms/static/sass/shared/_footer-edx.scss @@ -80,7 +80,7 @@ footer#footer-edx-v3 { margin-bottom: 30px; } - .sm-link { + a.sm-link { @include float(left); @include margin(0, 0, 10px, 12px); @include font-size(28); diff --git a/lms/static/sass/shared/_footer.scss b/lms/static/sass/shared/_footer.scss index d38397475c..8240d7ce7b 100644 --- a/lms/static/sass/shared/_footer.scss +++ b/lms/static/sass/shared/_footer.scss @@ -10,7 +10,7 @@ background: $footer-bg; clear: both; - footer { + footer#footer-openedx { @include clearfix(); @include box-sizing(border-box); max-width: grid-width(12); @@ -286,6 +286,8 @@ $edx-footer-bg-color: rgb(252,252,252); } } +// TODO (ECOM-1339): Remove the "new" (v2) footer once the v3 footer +// is permanently enabled. .edx-footer-new { background: $edx-footer-bg-color; diff --git a/lms/templates/commerce/checkout_receipt.html b/lms/templates/commerce/checkout_receipt.html index 2989e84597..448fd6faf7 100644 --- a/lms/templates/commerce/checkout_receipt.html +++ b/lms/templates/commerce/checkout_receipt.html @@ -17,7 +17,7 @@ ${_("Receipt")} <%block name="js_extra"> -<%static:js group='rwd_header_footer'/> +<%static:js group='rwd_header'/> diff --git a/lms/templates/footer-edx-v2.html b/lms/templates/footer-edx-v2.html index 5072e66700..042c06b849 100644 --- a/lms/templates/footer-edx-v2.html +++ b/lms/templates/footer-edx-v2.html @@ -1,4 +1,5 @@ ## mako +## TODO (ECOM-1339): Delete this template once the V3 footer is enabled <%! from django.core.urlresolvers import reverse %> <%! from django.utils.translation import ugettext as _ %> <%namespace name='static' file='static_content.html'/> @@ -50,7 +51,7 @@ {% if IS_REQUEST_IN_MICROSITE %} {# For now we don't support overriden Django templates in microsites. Leave footer blank for now which is better than saying Edx.#} - {% elif IS_EDX_DOMAIN and not ENABLE_FOOTER_V3 %} - {% include "footer-edx-v2.html" %} - {% elif IS_EDX_DOMAIN and ENABLE_FOOTER_V3 %} - {% include "footer-edx-v3.html" %} + {% elif IS_EDX_DOMAIN %} + {# TODO (ECOM-1339): Remove this check once we switch to the v3 footer permanently. #} + {% if ENABLE_BRANDING_API %} + {% include "footer-edx-v3.html" %} + {% else %} + {% include "footer-edx-v2.html" %} + {% endif %} {% else %} {% include "footer.html" %} {% endif %} diff --git a/lms/templates/verify_student/_verification_header.html b/lms/templates/verify_student/_verification_header.html index 519b14ae8d..db71f588ab 100644 --- a/lms/templates/verify_student/_verification_header.html +++ b/lms/templates/verify_student/_verification_header.html @@ -27,5 +27,5 @@ <%block name="js_extra"> - <%static:js group='rwd_header_footer'/> + <%static:js group='rwd_header'/> diff --git a/lms/templates/verify_student/incourse_reverify.html b/lms/templates/verify_student/incourse_reverify.html index 6a24eb072f..723e8b7363 100644 --- a/lms/templates/verify_student/incourse_reverify.html +++ b/lms/templates/verify_student/incourse_reverify.html @@ -20,7 +20,7 @@ from verify_student.views import PayAndVerifyView % endfor <%block name="js_extra"> - <%static:js group='rwd_header_footer'/> + <%static:js group='rwd_header'/> diff --git a/lms/templates/verify_student/pay_and_verify.html b/lms/templates/verify_student/pay_and_verify.html index 259f646cc3..ac99eb2a53 100644 --- a/lms/templates/verify_student/pay_and_verify.html +++ b/lms/templates/verify_student/pay_and_verify.html @@ -34,7 +34,7 @@ from verify_student.views import PayAndVerifyView % endfor <%block name="js_extra"> - <%static:js group='rwd_header_footer'/> + <%static:js group='rwd_header'/> diff --git a/lms/urls.py b/lms/urls.py index 4e026d30cc..37e4246cbd 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -427,6 +427,8 @@ if settings.COURSEWARE_ENABLED: # Student Notes url(r'^courses/{}/edxnotes'.format(settings.COURSE_ID_PATTERN), include('edxnotes.urls'), name="edxnotes_endpoints"), + + url(r'^api/branding/v1/', include('branding.api_urls')), ) # allow course staff to change to student view of courseware