api.py 13 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
"""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 _
20
from django.contrib.staticfiles.storage import staticfiles_storage
21 22 23

from edxmako.shortcuts import marketing_link
from branding.models import BrandingApiConfig
24
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
25 26 27


log = logging.getLogger("edx.footer")
28
EMPTY_URL = '#'
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58


def is_enabled():
    """Check whether the branding API is enabled. """
    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",
59 60
                "icon-class": "fa-facebook-square",
                "action": "Sign up on Facebook!"
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
            },
            ...
        ],
        "navigation_links": [
            {
                "name": "about",
                "title": "About",
                "url": "http://www.example.com/about.html"
            },
            ...
        ],
        "mobile_links": [
            {
                "name": "apple",
                "title": "Apple",
76
                "url": "http://store.apple.com/example_app",
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
                "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

    """
    return _(
115 116
        # Translators: 'EdX', 'edX', and 'Open edX' are trademarks of 'edX Inc.'.
        # Please do not translate any of these trademarks and company names.
117 118 119
        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."
120
    ).format(org_name=configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME))
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147


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

    """
148
    platform_name = configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME)
149
    links = []
150

151
    for social_name in settings.SOCIAL_MEDIA_FOOTER_NAMES:
152
        display = settings.SOCIAL_MEDIA_FOOTER_DISPLAY.get(social_name, {})
153 154 155
        links.append(
            {
                "name": social_name,
156
                "title": unicode(display.get("title", "")),
157
                "url": settings.SOCIAL_MEDIA_FOOTER_URLS.get(social_name, "#"),
158 159
                "icon-class": display.get("icon", ""),
                "action": unicode(display.get("action", "")).format(platform_name=platform_name),
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
            }
        )
    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")),
Peter Fogg committed
177
            ("help-center", settings.SUPPORT_SITE_LINK, _("Help Center")),
178
            ("contact", marketing_link("CONTACT"), _("Contact")),
Peter Fogg committed
179
            ("careers", marketing_link("CAREERS"), _("Careers")),
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
            ("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

    """
225
    platform_name = configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME)
226

227 228 229 230 231
    mobile_links = []
    if settings.FEATURES.get('ENABLE_FOOTER_MOBILE_APP_LINKS'):
        mobile_links = [
            {
                "name": "apple",
232 233 234
                "title": _(
                    "Download the {platform_name} mobile app from the Apple App Store"
                ).format(platform_name=platform_name),
235
                "url": settings.MOBILE_STORE_URLS.get('apple', '#'),
236
                "image": _absolute_url_staticfile(is_secure, 'images/app/app_store_badge_135x40.svg'),
237 238 239
            },
            {
                "name": "google",
240 241 242
                "title": _(
                    "Download the {platform_name} mobile app from Google Play"
                ).format(platform_name=platform_name),
243
                "url": settings.MOBILE_STORE_URLS.get('google', '#'),
244
                "image": _absolute_url_staticfile(is_secure, 'images/app/google_play_badge_45.png'),
245 246 247 248 249 250 251 252 253 254 255 256 257 258
            }
        ]
    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
    """
259 260
    logo_name = configuration_helpers.get_value('FOOTER_ORGANIZATION_IMAGE', settings.FOOTER_ORGANIZATION_IMAGE)
    # `logo_name` is looked up from the configuration,
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
    # which falls back on the Django settings, which loads it from
    # `lms.env.json`, which is created and managed by Ansible. Because of
    # this runaround, we lose a lot of the flexibility that Django's
    # staticfiles system provides, and we end up having to hardcode the path
    # to the footer logo rather than use the comprehensive theming system.
    # 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(
            "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)
288 289 290 291 292 293 294 295 296 297 298 299 300


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

    """
301
    site_name = configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME)
302 303 304 305 306
    parts = ("https" if is_secure else "http", site_name, url_path, '', '', '')
    return urlparse.urlunparse(parts)


def _absolute_url_staticfile(is_secure, name):
307
    """Construct an absolute URL to a static resource on the site.
308 309 310 311 312 313 314 315 316 317

    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)
318 319 320 321 322 323 324 325 326

    # In production, the static files URL will be an absolute
    # URL pointing to a CDN.  If this happens, we can just
    # return the URL.
    if urlparse.urlparse(url_path).netloc:
        return url_path

    # For local development, the returned URL will be relative,
    # so we need to make it absolute.
327
    return _absolute_url(is_secure, url_path)
328 329


330
def get_configuration_url(name):
331
    """
332 333
    Look up and return the value for given url name in configuration.
    URLs are saved in "urls" dictionary inside configuration.
334

335
    Return 'EMPTY_URL' if given url name is not defined in configuration urls.
336
    """
337
    urls = configuration_helpers.get_value("urls", default={})
338 339 340 341 342 343 344
    return urls.get(name) or EMPTY_URL


def get_url(name):
    """
    Lookup and return page url, lookup is performed in the following order

345
    1. get url, If configuration URL override exists, return it
346 347 348 349
    2. Otherwise return the marketing URL.

    :return: string containing page url.
    """
350 351 352 353
    # If a configuration URL override exists, return it.  Otherwise return the marketing URL.
    configuration_url = get_configuration_url(name)
    if configuration_url != EMPTY_URL:
        return configuration_url
354 355 356 357 358 359 360 361 362

    # get marketing link, if marketing is disabled then platform url will be used instead.
    url = marketing_link(name)

    return url or EMPTY_URL


def get_base_url(is_secure):
    """
363
    Return Base URL for site.
364 365 366 367 368 369
    Arguments:
        is_secure (bool): If true, use HTTPS as the protocol.
    """
    return _absolute_url(is_secure=is_secure, url_path="")


370
def get_logo_url(is_secure=True):
371 372
    """
    Return the url for the branded logo image to be used
373 374
    Arguments:
        is_secure (bool): If true, use HTTPS as the protocol.
375 376
    """

377
    # if the configuration has an overide value for the logo_image_url
378
    # let's use that
379
    image_url = configuration_helpers.get_value('logo_image_url')
380
    if image_url:
381 382 383
        return _absolute_url_staticfile(
            is_secure=is_secure,
            name=image_url,
384 385 386
        )

    # otherwise, use the legacy means to configure this
387
    university = configuration_helpers.get_value('university')
388

389
    if university:
390 391 392 393 394
        return staticfiles_storage.url('images/{uni}-on-edx-logo.png'.format(uni=university))
    else:
        return staticfiles_storage.url('images/logo.png')


395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413
def get_tos_and_honor_code_url():
    """
    Lookup and return terms of services page url
    """
    return get_url("TOS_AND_HONOR")


def get_privacy_url():
    """
    Lookup and return privacy policies page url
    """
    return get_url("PRIVACY")


def get_about_url():
    """
    Lookup and return About page url
    """
    return get_url("ABOUT")