diff --git a/cms/envs/common.py b/cms/envs/common.py
index 0aca432654..e150374cef 100644
--- a/cms/envs/common.py
+++ b/cms/envs/common.py
@@ -317,3 +317,7 @@ INSTALLED_APPS = (
'staticfiles',
'static_replace',
)
+
+################# EDX MARKETING SITE ##################################
+
+EDXMKTG_COOKIE_NAME = 'edxloggedin'
diff --git a/common/djangoapps/mitxmako/shortcuts.py b/common/djangoapps/mitxmako/shortcuts.py
index ebeb0fc180..0766564027 100644
--- a/common/djangoapps/mitxmako/shortcuts.py
+++ b/common/djangoapps/mitxmako/shortcuts.py
@@ -14,9 +14,57 @@
from django.template import Context
from django.http import HttpResponse
+import logging
from . import middleware
from django.conf import settings
+from django.core.urlresolvers import reverse
+log = logging.getLogger(__name__)
+
+
+def marketing_link(name):
+ """Returns the correct URL for a link to the marketing site
+ depending on if the marketing site is enabled
+
+ Since the marketing site is enabled by a setting, we have two
+ possible URLs for certain links. This function is to decides
+ which URL should be provided.
+ """
+
+ # link_map maps URLs from the marketing site to the old equivalent on
+ # the Django site
+ link_map = settings.MKTG_URL_LINK_MAP
+ if settings.MITX_FEATURES.get('ENABLE_MKTG_SITE') and name in settings.MKTG_URLS:
+ # special case for when we only want the root marketing URL
+ if name == 'ROOT':
+ return settings.MKTG_URLS.get('ROOT')
+ return settings.MKTG_URLS.get('ROOT') + settings.MKTG_URLS.get(name)
+ # only link to the old pages when the marketing site isn't on
+ elif not settings.MITX_FEATURES.get('ENABLE_MKTG_SITE') and name in link_map:
+ return reverse(link_map[name])
+ else:
+ log.warning("Cannot find corresponding link for name: {name}".format(name=name))
+ return '#'
+
+
+def marketing_link_context_processor(request):
+ """
+ A django context processor to give templates access to marketing URLs
+
+ Returns a dict whose keys are the marketing link names usable with the
+ marketing_link method (e.g. 'ROOT', 'CONTACT', etc.) prefixed with
+ 'MKTG_URL_' and whose values are the corresponding URLs as computed by the
+ marketing_link method.
+ """
+ return dict(
+ [
+ ("MKTG_URL_" + k, marketing_link(k))
+ for k in (
+ settings.MKTG_URL_LINK_MAP.viewkeys() |
+ settings.MKTG_URLS.viewkeys()
+ )
+ ]
+ )
def render_to_string(template_name, dictionary, context=None, namespace='main'):
@@ -27,6 +75,7 @@ def render_to_string(template_name, dictionary, context=None, namespace='main'):
context_dictionary = {}
context_instance['settings'] = settings
context_instance['MITX_ROOT_URL'] = settings.MITX_ROOT_URL
+ context_instance['marketing_link'] = marketing_link
# In various testing contexts, there might not be a current request context.
if middleware.requestcontext is not None:
diff --git a/common/djangoapps/mitxmako/template.py b/common/djangoapps/mitxmako/template.py
index 6ef8058c7c..5becfbf1df 100644
--- a/common/djangoapps/mitxmako/template.py
+++ b/common/djangoapps/mitxmako/template.py
@@ -14,6 +14,7 @@
from django.conf import settings
from mako.template import Template as MakoTemplate
+from mitxmako.shortcuts import marketing_link
from mitxmako import middleware
@@ -37,7 +38,6 @@ class Template(MakoTemplate):
kwargs.update(overrides)
super(Template, self).__init__(*args, **kwargs)
-
def render(self, context_instance):
"""
This takes a render call with a context (from Django) and translates
@@ -55,5 +55,6 @@ class Template(MakoTemplate):
context_dictionary['settings'] = settings
context_dictionary['MITX_ROOT_URL'] = settings.MITX_ROOT_URL
context_dictionary['django_context'] = context_instance
+ context_dictionary['marketing_link'] = marketing_link
return super(Template, self).render_unicode(**context_dictionary)
diff --git a/common/djangoapps/mitxmako/tests.py b/common/djangoapps/mitxmako/tests.py
new file mode 100644
index 0000000000..21866eb9b5
--- /dev/null
+++ b/common/djangoapps/mitxmako/tests.py
@@ -0,0 +1,27 @@
+from django.test import TestCase
+from django.test.utils import override_settings
+from django.core.urlresolvers import reverse
+from django.conf import settings
+from mitxmako.shortcuts import marketing_link
+from mock import patch
+
+
+class ShortcutsTests(TestCase):
+ """
+ Test the mitxmako shortcuts file
+ """
+
+ @override_settings(MKTG_URLS={'ROOT': 'dummy-root', 'ABOUT': '/about-us'})
+ @override_settings(MKTG_URL_LINK_MAP={'ABOUT': 'login'})
+ def test_marketing_link(self):
+ # test marketing site on
+ with patch.dict('django.conf.settings.MITX_FEATURES', {'ENABLE_MKTG_SITE': True}):
+ expected_link = 'dummy-root/about-us'
+ link = marketing_link('ABOUT')
+ self.assertEquals(link, expected_link)
+ # test marketing site off
+ with patch.dict('django.conf.settings.MITX_FEATURES', {'ENABLE_MKTG_SITE': False}):
+ # we are using login because it is common across both cms and lms
+ expected_link = reverse('login')
+ link = marketing_link('ABOUT')
+ self.assertEquals(link, expected_link)
diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py
index 53d1c72cc4..e8a70d6089 100644
--- a/common/djangoapps/student/views.py
+++ b/common/djangoapps/student/views.py
@@ -7,7 +7,7 @@ import string
import sys
import urllib
import uuid
-
+import time
from django.conf import settings
from django.contrib.auth import logout, authenticate, login
@@ -20,9 +20,10 @@ from django.core.mail import send_mail
from django.core.urlresolvers import reverse
from django.core.validators import validate_email, validate_slug, ValidationError
from django.db import IntegrityError
-from django.http import HttpResponse, HttpResponseRedirect, Http404
+from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseNotAllowed, HttpResponseRedirect, Http404
from django.shortcuts import redirect
from django_future.csrf import ensure_csrf_cookie, csrf_exempt
+from django.utils.http import cookie_date
from mitxmako.shortcuts import render_to_response, render_to_string
from bs4 import BeautifulSoup
@@ -212,6 +213,36 @@ def _cert_info(user, course, cert_status):
return d
+@ensure_csrf_cookie
+def signin_user(request):
+ """
+ This view will display the non-modal login form
+ """
+ if request.user.is_authenticated():
+ return redirect(reverse('dashboard'))
+
+ context = {
+ 'course_id': request.GET.get('course_id'),
+ 'enrollment_action': request.GET.get('enrollment_action')
+ }
+ return render_to_response('login.html', context)
+
+
+@ensure_csrf_cookie
+def register_user(request):
+ """
+ This view will display the non-modal registration form
+ """
+ if request.user.is_authenticated():
+ return redirect(reverse('dashboard'))
+
+ context = {
+ 'course_id': request.GET.get('course_id'),
+ 'enrollment_action': request.GET.get('enrollment_action')
+ }
+ return render_to_response('register.html', context)
+
+
@login_required
@ensure_csrf_cookie
def dashboard(request):
@@ -250,7 +281,7 @@ def dashboard(request):
exam_registrations = {course.id: exam_registration_info(request.user, course) for course in courses}
# Get the 3 most recent news
- top_news = _get_news(top=3)
+ top_news = _get_news(top=3) if not settings.MITX_FEATURES.get('ENABLE_MKTG_SITE', False) else None
context = {'courses': courses,
'message': message,
@@ -275,35 +306,47 @@ def try_change_enrollment(request):
"""
if 'enrollment_action' in request.POST:
try:
- enrollment_output = change_enrollment(request)
+ enrollment_response = change_enrollment(request)
# There isn't really a way to display the results to the user, so we just log it
# We expect the enrollment to be a success, and will show up on the dashboard anyway
- log.info("Attempted to automatically enroll after login. Results: {0}".format(enrollment_output))
+ log.info(
+ "Attempted to automatically enroll after login. Response code: {0}; response body: {1}".format(
+ enrollment_response.status_code,
+ enrollment_response.content
+ )
+ )
except Exception, e:
log.exception("Exception automatically enrolling after login: {0}".format(str(e)))
-@login_required
-def change_enrollment_view(request):
- """Delegate to change_enrollment to actually do the work."""
- return HttpResponse(json.dumps(change_enrollment(request)))
-
-
-
def change_enrollment(request):
+ """
+ Modify the enrollment status for the logged-in user.
+
+ The request parameter must be a POST request (other methods return 405)
+ that specifies course_id and enrollment_action parameters. If course_id or
+ enrollment_action is not specified, if course_id is not valid, if
+ enrollment_action is something other than "enroll" or "unenroll", if
+ enrollment_action is "enroll" and enrollment is closed for the course, or
+ if enrollment_action is "unenroll" and the user is not enrolled in the
+ course, a 400 error will be returned. If the user is not logged in, 403
+ will be returned; it is important that only this case return 403 so the
+ front end can redirect the user to a registration or login page when this
+ happens. This function should only be called from an AJAX request or
+ as a post-login/registration helper, so the error messages in the responses
+ should never actually be user-visible.
+ """
if request.method != "POST":
- raise Http404
+ return HttpResponseNotAllowed(["POST"])
user = request.user
if not user.is_authenticated():
- raise Http404
+ return HttpResponseForbidden()
- action = request.POST.get("enrollment_action", "")
-
- course_id = request.POST.get("course_id", None)
+ action = request.POST.get("enrollment_action")
+ course_id = request.POST.get("course_id")
if course_id is None:
- return HttpResponse(json.dumps({'success': False,
- 'error': 'There was an error receiving the course id.'}))
+ return HttpResponseBadRequest("Course id not specified")
if action == "enroll":
# Make sure the course exists
@@ -313,12 +356,10 @@ def change_enrollment(request):
except ItemNotFoundError:
log.warning("User {0} tried to enroll in non-existent course {1}"
.format(user.username, course_id))
- return {'success': False, 'error': 'The course requested does not exist.'}
+ return HttpResponseBadRequest("Course id is invalid")
if not has_access(user, course, 'enroll'):
- return {'success': False,
- 'error': 'enrollment in {} not allowed at this time'
- .format(course.display_name_with_default)}
+ return HttpResponseBadRequest("Enrollment is closed")
org, course_num, run = course_id.split("/")
statsd.increment("common.student.enrollment",
@@ -332,7 +373,7 @@ def change_enrollment(request):
# If we've already created this enrollment in a separate transaction,
# then just continue
pass
- return {'success': True}
+ return HttpResponse()
elif action == "unenroll":
try:
@@ -345,21 +386,17 @@ def change_enrollment(request):
"course:{0}".format(course_num),
"run:{0}".format(run)])
- return {'success': True}
+ return HttpResponse()
except CourseEnrollment.DoesNotExist:
- return {'success': False, 'error': 'You are not enrolled for this course.'}
+ return HttpResponseBadRequest("You are not enrolled in this course")
else:
- return {'success': False, 'error': 'Invalid enrollment_action.'}
-
- return {'success': False, 'error': 'We weren\'t able to unenroll you. Please try again.'}
+ return HttpResponseBadRequest("Enrollment action is invalid")
@ensure_csrf_cookie
def accounts_login(request, error=""):
-
- return render_to_response('accounts_login.html', {'error': error})
-
+ return render_to_response('login.html', {'error': error})
# Need different levels of logging
@@ -403,8 +440,29 @@ def login_user(request, error=""):
try_change_enrollment(request)
statsd.increment("common.student.successful_login")
+ response = HttpResponse(json.dumps({'success': True}))
- return HttpResponse(json.dumps({'success': True}))
+ # set the login cookie for the edx marketing site
+ # we want this cookie to be accessed via javascript
+ # so httponly is set to None
+
+ if request.session.get_expire_at_browser_close():
+ max_age = None
+ expires = None
+ else:
+ max_age = request.session.get_expiry_age()
+ expires_time = time.time() + max_age
+ expires = cookie_date(expires_time)
+
+
+ response.set_cookie(settings.EDXMKTG_COOKIE_NAME,
+ 'true', max_age=max_age,
+ expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
+ path='/',
+ secure=None,
+ httponly=None)
+
+ return response
log.warning(u"Login failed - Account not active for user {0}, resending activation".format(username))
@@ -418,9 +476,18 @@ def login_user(request, error=""):
@ensure_csrf_cookie
def logout_user(request):
- ''' HTTP request to log out the user. Redirects to marketing page'''
+ '''
+ HTTP request to log out the user. Redirects to marketing page.
+ Deletes both the CSRF and sessionid cookies so the marketing
+ site can determine the logged in state of the user
+ '''
+
logout(request)
- return redirect('/')
+ response = redirect('/')
+ response.delete_cookie(settings.EDXMKTG_COOKIE_NAME,
+ path='/',
+ domain=settings.SESSION_COOKIE_DOMAIN)
+ return response
@login_required
@@ -615,7 +682,31 @@ def create_account(request, post_override=None):
statsd.increment("common.student.account_created")
js = {'success': True}
- return HttpResponse(json.dumps(js), mimetype="application/json")
+ HttpResponse(json.dumps(js), mimetype="application/json")
+
+ response = HttpResponse(json.dumps({'success': True}))
+
+ # set the login cookie for the edx marketing site
+ # we want this cookie to be accessed via javascript
+ # so httponly is set to None
+
+ if request.session.get_expire_at_browser_close():
+ max_age = None
+ expires = None
+ else:
+ max_age = request.session.get_expiry_age()
+ expires_time = time.time() + max_age
+ expires = cookie_date(expires_time)
+
+
+ response.set_cookie(settings.EDXMKTG_COOKIE_NAME,
+ 'true', max_age=max_age,
+ expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
+ path='/',
+ secure=None,
+ httponly=None)
+ return response
+
def exam_registration_info(user, course):
@@ -701,7 +792,6 @@ def create_exam_registration(request, post_override=None):
for fieldname in TestCenterUser.user_provided_fields():
if fieldname in post_vars:
demographic_data[fieldname] = (post_vars[fieldname]).strip()
-
try:
testcenter_user = TestCenterUser.objects.get(user=user)
needs_updating = testcenter_user.needs_update(demographic_data)
diff --git a/common/djangoapps/terrain/steps.py b/common/djangoapps/terrain/steps.py
index fdab514177..d2be2eeea8 100644
--- a/common/djangoapps/terrain/steps.py
+++ b/common/djangoapps/terrain/steps.py
@@ -122,6 +122,13 @@ def should_see_a_link_called(step, text):
assert len(world.browser.find_link_by_text(text)) > 0
+@step(r'should see (?:the|a) link with the id "([^"]*)" called "([^"]*)"$')
+def should_have_link_with_id_and_text(step, link_id, text):
+ link = world.browser.find_by_id(link_id)
+ assert len(link) > 0
+ assert_equals(link.text, text)
+
+
@step(r'should see "(.*)" (?:somewhere|anywhere) in (?:the|this) page')
def should_see_in_the_page(step, text):
assert_in(text, world.css_text('body'))
@@ -144,3 +151,8 @@ def i_am_an_edx_user(step):
@step(u'User "([^"]*)" is an edX user$')
def registered_edx_user(step, uname):
world.create_user(uname)
+
+
+@step(u'All dialogs should be closed$')
+def dialogs_are_closed(step):
+ assert world.dialogs_closed()
diff --git a/common/djangoapps/terrain/ui_helpers.py b/common/djangoapps/terrain/ui_helpers.py
index 1a85904135..40b839ae24 100644
--- a/common/djangoapps/terrain/ui_helpers.py
+++ b/common/djangoapps/terrain/ui_helpers.py
@@ -1,7 +1,7 @@
#pylint: disable=C0111
#pylint: disable=W0621
-from lettuce import world, step
+from lettuce import world
import time
from urllib import quote_plus
from selenium.common.exceptions import WebDriverException
@@ -104,6 +104,17 @@ def css_visible(css_selector):
return world.browser.find_by_css(css_selector).visible
+@world.absorb
+def dialogs_closed():
+ def are_dialogs_closed(driver):
+ '''
+ Return True when no modal dialogs are visible
+ '''
+ return not css_visible('.modal')
+ wait_for(are_dialogs_closed)
+ return not css_visible('.modal')
+
+
@world.absorb
def save_the_html(path='/tmp'):
u = world.browser.url
@@ -111,4 +122,4 @@ def save_the_html(path='/tmp'):
filename = '%s.html' % quote_plus(u)
f = open('%s/%s' % (path, filename), 'w')
f.write(html)
- f.close
+ f.close()
diff --git a/lms/djangoapps/branding/views.py b/lms/djangoapps/branding/views.py
index 9fe912e947..dd57e8d4d4 100644
--- a/lms/djangoapps/branding/views.py
+++ b/lms/djangoapps/branding/views.py
@@ -6,6 +6,7 @@ from django_future.csrf import ensure_csrf_cookie
import student.views
import branding
import courseware.views
+from mitxmako.shortcuts import marketing_link
from util.cache import cache_if_anonymous
@@ -22,6 +23,8 @@ def index(request):
if settings.MITX_FEATURES.get('AUTH_USE_MIT_CERTIFICATES'):
from external_auth.views import ssl_login
return ssl_login(request)
+ if settings.MITX_FEATURES.get('ENABLE_MKTG_SITE'):
+ return redirect(settings.MKTG_URLS.get('ROOT'))
university = branding.get_university(request.META.get('HTTP_HOST'))
if university is None:
@@ -34,9 +37,12 @@ def index(request):
@cache_if_anonymous
def courses(request):
"""
- Render the "find courses" page. If subdomain branding is on, this is the
- university profile page, otherwise it's the edX courseware.views.courses page
+ Render the "find courses" page. If the marketing site is enabled, redirect
+ to that. Otherwise, if subdomain branding is on, this is the university
+ profile page. Otherwise, it's the edX courseware.views.courses page
"""
+ if settings.MITX_FEATURES.get('ENABLE_MKTG_SITE', False):
+ return redirect(marketing_link('COURSES'), permanent=True)
university = branding.get_university(request.META.get('HTTP_HOST'))
if university is None:
diff --git a/lms/djangoapps/courseware/features/homepage.feature b/lms/djangoapps/courseware/features/homepage.feature
index c0c1c32f02..2c354acd49 100644
--- a/lms/djangoapps/courseware/features/homepage.feature
+++ b/lms/djangoapps/courseware/features/homepage.feature
@@ -5,29 +5,24 @@ Feature: Homepage for web users
Scenario: User can see the "Login" button
Given I visit the homepage
- Then I should see a link called "Log In"
+ Then I should see a link called "Log in"
- Scenario: User can see the "Sign up" button
+ Scenario: User can see the "Register Now" button
Given I visit the homepage
- Then I should see a link called "Sign Up"
+ Then I should see a link called "Register Now"
Scenario Outline: User can see main parts of the page
Given I visit the homepage
- Then I should see a link called ""
- When I click the link with the text ""
- Then I should see that the path is ""
+ Then I should see a link with the id "" called ""
Examples:
- | Link | Path |
- | Find Courses | /courses |
- | About | /about |
- | Jobs | /jobs |
- | Contact | /contact |
+ | id | Link |
+ | about | About |
+ | jobs | Jobs |
+ | faq | FAQ |
+ | contact | Contact|
+ | press | Press |
- Scenario: User can visit the blog
- Given I visit the homepage
- When I click the link with the text "Blog"
- Then I should see that the url is "http://blog.edx.org/"
# TODO: test according to domain or policy
Scenario: User can see the partner institutions
diff --git a/lms/djangoapps/courseware/features/login.feature b/lms/djangoapps/courseware/features/login.feature
index 23317b4876..a1b788a7b2 100644
--- a/lms/djangoapps/courseware/features/login.feature
+++ b/lms/djangoapps/courseware/features/login.feature
@@ -7,7 +7,7 @@ Feature: Login in as a registered user
Given I am an edX user
And I am an unactivated user
And I visit the homepage
- When I click the link with the text "Log In"
+ When I click the link with the text "Log in"
And I submit my credentials on the login form
Then I should see the login error message "This account has not been activated"
@@ -15,7 +15,7 @@ Feature: Login in as a registered user
Given I am an edX user
And I am an activated user
And I visit the homepage
- When I click the link with the text "Log In"
+ When I click the link with the text "Log in"
And I submit my credentials on the login form
Then I should be on the dashboard page
@@ -23,5 +23,5 @@ Feature: Login in as a registered user
Given I am logged in
When I click the dropdown arrow
And I click the link with the text "Log Out"
- Then I should see a link with the text "Log In"
+ Then I should see a link with the text "Log in"
And I should see that the path is "/"
diff --git a/lms/djangoapps/courseware/features/login.py b/lms/djangoapps/courseware/features/login.py
index bc90ea301c..857b70fa5d 100644
--- a/lms/djangoapps/courseware/features/login.py
+++ b/lms/djangoapps/courseware/features/login.py
@@ -19,13 +19,13 @@ def i_am_an_activated_user(step):
def i_submit_my_credentials_on_the_login_form(step):
fill_in_the_login_form('email', 'robot@edx.org')
fill_in_the_login_form('password', 'test')
- login_form = world.browser.find_by_css('form#login_form')
- login_form.find_by_value('Access My Courses').click()
+ login_form = world.browser.find_by_css('form#login-form')
+ login_form.find_by_name('submit').click()
@step(u'I should see the login error message "([^"]*)"$')
def i_should_see_the_login_error_message(step, msg):
- login_error_div = world.browser.find_by_css('form#login_form #login_error')
+ login_error_div = world.browser.find_by_css('.submission-error.is-shown')
assert (msg in login_error_div.text)
@@ -49,6 +49,6 @@ def user_is_an_activated_user(uname):
def fill_in_the_login_form(field, value):
- login_form = world.browser.find_by_css('form#login_form')
+ login_form = world.browser.find_by_css('form#login-form')
form_field = login_form.find_by_name(field)
form_field.fill(value)
diff --git a/lms/djangoapps/courseware/features/registration.feature b/lms/djangoapps/courseware/features/registration.feature
index 5933f860bb..43b04a5ad0 100644
--- a/lms/djangoapps/courseware/features/registration.feature
+++ b/lms/djangoapps/courseware/features/registration.feature
@@ -15,4 +15,6 @@ Feature: Register for a course
And I visit the dashboard
When I click the link with the text "Unregister"
And I press the "Unregister" button in the Unenroll dialog
- Then I should see "Looks like you haven't registered for any courses yet." somewhere in the page
+ Then All dialogs should be closed
+ And I should be on the dashboard page
+ And I should see "Looks like you haven't registered for any courses yet." somewhere in the page
diff --git a/lms/djangoapps/courseware/features/signup.feature b/lms/djangoapps/courseware/features/signup.feature
index b28a6819a1..cfc8b6e924 100644
--- a/lms/djangoapps/courseware/features/signup.feature
+++ b/lms/djangoapps/courseware/features/signup.feature
@@ -5,12 +5,12 @@ Feature: Sign in
Scenario: Sign up from the homepage
Given I visit the homepage
- When I click the link with the text "Sign Up"
+ When I click the link with the text "Register Now"
And I fill in "email" on the registration form with "robot2@edx.org"
And I fill in "password" on the registration form with "test"
And I fill in "username" on the registration form with "robot2"
And I fill in "name" on the registration form with "Robot Two"
And I check the checkbox named "terms_of_service"
And I check the checkbox named "honor_code"
- And I press the "Create My Account" button on the registration form
+ And I submit the registration form
Then I should see "THANKS FOR REGISTERING!" in the dashboard banner
diff --git a/lms/djangoapps/courseware/features/signup.py b/lms/djangoapps/courseware/features/signup.py
index 5ba385ef54..3dc34d5af8 100644
--- a/lms/djangoapps/courseware/features/signup.py
+++ b/lms/djangoapps/courseware/features/signup.py
@@ -3,17 +3,18 @@
from lettuce import world, step
+
@step('I fill in "([^"]*)" on the registration form with "([^"]*)"$')
def when_i_fill_in_field_on_the_registration_form_with_value(step, field, value):
- register_form = world.browser.find_by_css('form#register_form')
+ register_form = world.browser.find_by_css('form#register-form')
form_field = register_form.find_by_name(field)
form_field.fill(value)
-@step('I press the "([^"]*)" button on the registration form$')
-def i_press_the_button_on_the_registration_form(step, button):
- register_form = world.browser.find_by_css('form#register_form')
- register_form.find_by_value(button).click()
+@step('I submit the registration form$')
+def i_press_the_button_on_the_registration_form(step):
+ register_form = world.browser.find_by_css('form#register-form')
+ register_form.find_by_name('submit').click()
@step('I check the checkbox named "([^"]*)"$')
diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py
index a189160a48..ec3e55b1b8 100644
--- a/lms/djangoapps/courseware/tests/tests.py
+++ b/lms/djangoapps/courseware/tests/tests.py
@@ -220,25 +220,20 @@ class LoginEnrollmentTestCase(TestCase):
# Now make sure that the user is now actually activated
self.assertTrue(get_user(email).is_active)
- def _enroll(self, course):
- """Post to the enrollment view, and return the parsed json response"""
+ def try_enroll(self, course):
+ """Try to enroll. Return bool success instead of asserting it."""
resp = self.client.post('/change_enrollment', {
'enrollment_action': 'enroll',
'course_id': course.id,
})
- return parse_json(resp)
-
- def try_enroll(self, course):
- """Try to enroll. Return bool success instead of asserting it."""
- data = self._enroll(course)
- print ('Enrollment in %s result: %s'
- % (course.location.url(), str(data)))
- return data['success']
+ print ('Enrollment in %s result status code: %s'
+ % (course.location.url(), str(resp.status_code)))
+ return resp.status_code == 200
def enroll(self, course):
"""Enroll the currently logged-in user, and check that it worked."""
- data = self._enroll(course)
- self.assertTrue(data['success'])
+ result = self.try_enroll(course)
+ self.assertTrue(result)
def unenroll(self, course):
"""Unenroll the currently logged-in user, and check that it worked."""
@@ -246,8 +241,7 @@ class LoginEnrollmentTestCase(TestCase):
'enrollment_action': 'unenroll',
'course_id': course.id,
})
- data = parse_json(resp)
- self.assertTrue(data['success'])
+ self.assertTrue(resp.status_code == 200)
def check_for_get_code(self, code, url):
"""
diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py
index 385b27e7f1..9c5a665754 100644
--- a/lms/djangoapps/courseware/views.py
+++ b/lms/djangoapps/courseware/views.py
@@ -515,6 +515,9 @@ def registered_for_course(course, user):
@ensure_csrf_cookie
@cache_if_anonymous
def course_about(request, course_id):
+ if settings.MITX_FEATURES.get('ENABLE_MKTG_SITE', False):
+ raise Http404
+
course = get_course_with_access(request.user, course_id, 'see_exists')
registered = registered_for_course(course, request.user)
@@ -531,6 +534,37 @@ def course_about(request, course_id):
'registered': registered,
'course_target': course_target,
'show_courseware_link': show_courseware_link})
+@ensure_csrf_cookie
+@cache_if_anonymous
+def mktg_course_about(request, course_id):
+
+ try:
+ course = get_course_with_access(request.user, course_id, 'see_exists')
+ except (ValueError, Http404) as e:
+ # if a course does not exist yet, display a coming
+ # soon button
+ return render_to_response('courseware/mktg_coming_soon.html',
+ {'course_id': course_id})
+
+ registered = registered_for_course(course, request.user)
+
+ if has_access(request.user, course, 'load'):
+ course_target = reverse('info', args=[course.id])
+ else:
+ course_target = reverse('about_course', args=[course.id])
+
+ allow_registration = has_access(request.user, course, 'enroll')
+
+ show_courseware_link = (has_access(request.user, course, 'load') or
+ settings.MITX_FEATURES.get('ENABLE_LMS_MIGRATION'))
+
+ return render_to_response('courseware/mktg_course_about.html',
+ {'course': course,
+ 'registered': registered,
+ 'allow_registration': allow_registration,
+ 'course_target': course_target,
+ 'show_courseware_link': show_courseware_link})
+
@ensure_csrf_cookie
diff --git a/lms/envs/aws.py b/lms/envs/aws.py
index d60215bf7b..9e3518ddc7 100644
--- a/lms/envs/aws.py
+++ b/lms/envs/aws.py
@@ -133,6 +133,7 @@ COMMENTS_SERVICE_KEY = ENV_TOKENS.get("COMMENTS_SERVICE_KEY", '')
CERT_QUEUE = ENV_TOKENS.get("CERT_QUEUE", 'test-pull')
ZENDESK_URL = ENV_TOKENS.get("ZENDESK_URL")
FEEDBACK_SUBMISSION_EMAIL = ENV_TOKENS.get("FEEDBACK_SUBMISSION_EMAIL")
+MKTG_URLS = ENV_TOKENS.get('MKTG_URLS', MKTG_URLS)
for name, value in ENV_TOKENS.get("CODE_JAIL", {}).items():
oldvalue = CODE_JAIL.get(name)
diff --git a/lms/envs/common.py b/lms/envs/common.py
index cc03915fb2..a198f010c6 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -36,6 +36,7 @@ DISCUSSION_SETTINGS = {
'MAX_COMMENT_DEPTH': 2,
}
+
# Features
MITX_FEATURES = {
'SAMPLE': False,
@@ -182,6 +183,9 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'django.contrib.messages.context_processors.messages',
'sekizai.context_processors.sekizai',
'course_wiki.course_nav.context_processor',
+
+ # Hack to get required link URLs to password reset templates
+ 'mitxmako.shortcuts.marketing_link_context_processor',
)
STUDENT_FILEUPLOAD_MAX_SIZE = 4 * 1000 * 1000 # 4 MB
@@ -691,3 +695,17 @@ INSTALLED_APPS = (
# Student notes
'notes',
)
+
+######################### MARKETING SITE ###############################
+EDXMKTG_COOKIE_NAME = 'edxloggedin'
+MKTG_URLS = {}
+MKTG_URL_LINK_MAP = {
+ 'ABOUT': 'about_edx',
+ 'CONTACT': 'contact',
+ 'FAQ': 'help_edx',
+ 'COURSES': 'courses',
+ 'ROOT': 'root',
+ 'TOS': 'tos',
+ 'HONOR': 'honor',
+ 'PRIVACY': 'privacy_edx',
+}
diff --git a/lms/static/images/bg-banner-example.png b/lms/static/images/bg-banner-example.png
new file mode 100644
index 0000000000..a52ffb6ef2
Binary files /dev/null and b/lms/static/images/bg-banner-example.png differ
diff --git a/lms/static/images/bg-banner-login.png b/lms/static/images/bg-banner-login.png
new file mode 100644
index 0000000000..4bbba21628
Binary files /dev/null and b/lms/static/images/bg-banner-login.png differ
diff --git a/lms/static/images/bg-banner-register.png b/lms/static/images/bg-banner-register.png
new file mode 100644
index 0000000000..f1fe626f05
Binary files /dev/null and b/lms/static/images/bg-banner-register.png differ
diff --git a/lms/static/images/bg-footer-divider.jpg b/lms/static/images/bg-footer-divider.jpg
new file mode 100644
index 0000000000..9d5e6fa6cd
Binary files /dev/null and b/lms/static/images/bg-footer-divider.jpg differ
diff --git a/lms/static/images/header-logo.png b/lms/static/images/header-logo.png
index f1d2357e6b..df8cb13233 100644
Binary files a/lms/static/images/header-logo.png and b/lms/static/images/header-logo.png differ
diff --git a/lms/static/images/social/ico-social-facebook.png b/lms/static/images/social/ico-social-facebook.png
new file mode 100644
index 0000000000..3588e7f29a
Binary files /dev/null and b/lms/static/images/social/ico-social-facebook.png differ
diff --git a/lms/static/images/social/ico-social-google.png b/lms/static/images/social/ico-social-google.png
new file mode 100644
index 0000000000..f5c39640df
Binary files /dev/null and b/lms/static/images/social/ico-social-google.png differ
diff --git a/lms/static/images/social/ico-social-meetup.png b/lms/static/images/social/ico-social-meetup.png
new file mode 100644
index 0000000000..52a7f447d7
Binary files /dev/null and b/lms/static/images/social/ico-social-meetup.png differ
diff --git a/lms/static/images/social/ico-social-twitter.png b/lms/static/images/social/ico-social-twitter.png
new file mode 100644
index 0000000000..c812e7dd5c
Binary files /dev/null and b/lms/static/images/social/ico-social-twitter.png differ
diff --git a/lms/static/images/social/ico-social-youtube.png b/lms/static/images/social/ico-social-youtube.png
new file mode 100644
index 0000000000..65f167f742
Binary files /dev/null and b/lms/static/images/social/ico-social-youtube.png differ
diff --git a/lms/static/sass/_shame.scss b/lms/static/sass/_shame.scss
new file mode 100644
index 0000000000..d3cc0b9a80
--- /dev/null
+++ b/lms/static/sass/_shame.scss
@@ -0,0 +1,100 @@
+// edX LMS - shame
+// shame file - used for any bad-form/orphaned scss that knowingly violate edX FED architecture/standards (see - http://csswizardry.com/2013/04/shame-css/)
+// ====================
+
+// marketing site - registration iframe band-aid (poor form enough to isolate out)
+.view-partial-mktgregister {
+ background: transparent;
+
+ // dimensions needed for course about page on marketing site
+ .wrapper-view {
+ overflow: hidden;
+ }
+
+ // button elements - not a better place to put these, sadly
+ .btn {
+ @include box-sizing('border-box');
+ display: block;
+ padding: $baseline/2;
+ text-transform: lowercase;
+ color: $white;
+ letter-spacing: 0.1rem;
+ cursor: pointer;
+ text-align: center;
+ border: none !important;
+ text-decoration: none;
+ text-shadow: none;
+ letter-spacing: 0.1rem;
+ font-size: 17px;
+ font-weight: 300;
+ box-shadow: 0 !important;
+
+ strong {
+ font-weight: 400;
+ text-transform: none;
+ }
+ }
+
+ .btn-primary {
+ @extend .btn;
+ @include linear-gradient($m-blue-s1 5%, $m-blue-d1 95%);
+
+ // no hover state conventions to follow from marketing :/
+ &:hover, &:active {
+
+ }
+ }
+
+ .btn-secondary {
+ @extend .btn;
+ @include linear-gradient($m-gray 5%, $m-gray-d1 95%);
+
+ // no hover state conventions to follow from marketing :/
+ &:hover, &:active {
+
+ }
+ }
+
+ .btn-tertiary {
+ @extend .btn;
+ background: $m-blue-l1;
+ color: $m-blue;
+
+ // no hover state conventions to follow from marketing :/
+ &:hover, &:active {
+
+ }
+ }
+
+ // nav list
+ .list-actions {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+
+ .item {
+ margin: 0;
+ }
+ }
+
+ .action {
+
+ // register or access courseware
+ &.action-register, &.access-courseware {
+ @extend .btn-primary;
+ }
+
+ // already registered but course not started or registration is closed
+ &.is-registered, &.registration-closed {
+ @extend .btn-secondary;
+ pointer-events: none !important;
+ }
+
+ // coming soon
+ &.coming-soon {
+ @extend .btn-tertiary;
+ pointer-events: none !important;
+ outline: none;
+ }
+ }
+}
diff --git a/lms/static/sass/application.scss b/lms/static/sass/application.scss
index 519118af84..6a1ef8743e 100644
--- a/lms/static/sass/application.scss
+++ b/lms/static/sass/application.scss
@@ -19,6 +19,7 @@
@import 'multicourse/home';
@import 'multicourse/dashboard';
+@import 'multicourse/account';
@import 'multicourse/testcenter-register';
@import 'multicourse/courses';
@import 'multicourse/course_about';
@@ -33,3 +34,5 @@
@import 'discussion';
@import 'news';
+
+@import 'shame';
diff --git a/lms/static/sass/base/_base.scss b/lms/static/sass/base/_base.scss
index d2d4a0564f..e62dd12541 100644
--- a/lms/static/sass/base/_base.scss
+++ b/lms/static/sass/base/_base.scss
@@ -1,4 +1,8 @@
-html, body {
+// html {
+// overflow-y: scroll;
+// }
+
+body {
background: rgb(250,250,250);
font-family: $sans-serif;
font-size: 1em;
@@ -81,9 +85,10 @@ a:link, a:visited {
}
.content-wrapper {
- background: rgb(255,255,255);
- margin: 0 auto 0;
width: flex-grid(12);
+ margin: 0 auto;
+ padding-bottom: ($baseline*2);
+ background: rgb(255,255,255);
}
.container {
@@ -92,6 +97,7 @@ a:link, a:visited {
padding: 0px 30px;
max-width: grid-width(12);
min-width: 760px;
+ width: flex-grid(12);
}
span.edx {
@@ -202,6 +208,10 @@ mark {
}
}
+.sr {
+ @include text-sr();
+}
+
.help-tab {
@include transform(rotate(-90deg));
@include transform-origin(0 0);
diff --git a/lms/static/sass/base/_mixins.scss b/lms/static/sass/base/_mixins.scss
index 58a92d1ee6..97703e8f0f 100644
--- a/lms/static/sass/base/_mixins.scss
+++ b/lms/static/sass/base/_mixins.scss
@@ -7,10 +7,23 @@
@return $body-line-height * $amount;
}
-@mixin hide-text(){
- text-indent: -9999px;
+// image-replacement hidden text
+@mixin text-hide() {
+ text-indent: 100%;
+ white-space: nowrap;
overflow: hidden;
- display: block;
+}
+
+// hidden elems - screenreaders
+@mixin text-sr() {
+ border: 0;
+ clip: rect(0 0 0 0);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ width: 1px;
}
@mixin vertically-and-horizontally-centered ( $height, $width ) {
@@ -22,3 +35,10 @@
position: absolute;
top: 150px;
}
+
+// sunsetted, but still used mixins
+@mixin hide-text(){
+ text-indent: -9999px;
+ overflow: hidden;
+ display: block;
+}
diff --git a/lms/static/sass/base/_variables.scss b/lms/static/sass/base/_variables.scss
index 4d27798649..ddbd930323 100644
--- a/lms/static/sass/base/_variables.scss
+++ b/lms/static/sass/base/_variables.scss
@@ -1,3 +1,5 @@
+$baseline: 20px;
+
$gw-column: 80px;
$gw-gutter: 20px;
@@ -8,27 +10,48 @@ $fg-max-width: 1400px;
$fg-min-width: 810px;
$sans-serif: 'Open Sans', $verdana;
+$monospace: Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', monospace;
$body-font-family: $sans-serif;
$serif: $georgia;
-$monospace: Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', monospace;
-
-$body-font-size: em(14);
-$body-line-height: golden-ratio(.875em, 1);
-$base-font-color: rgb(60,60,60);
-$baseFontColor: rgb(60,60,60);
-$base-font-color: rgb(60,60,60);
-$lighter-base-font-color: rgb(100,100,100);
+$white: rgb(255,255,255);
+$black: rgb(0,0,0);
$blue: rgb(29,157,217);
$pink: rgb(182,37,104);
$yellow: rgb(255, 252, 221);
+$red: rgb(178, 6, 16);
$error-red: rgb(253, 87, 87);
-$border-color: #C8C8C8;
-$sidebar-color: #f6f6f6;
-$outer-border-color: #aaa;
+$light-gray: rgb(221, 221, 221);
+$dark-gray: rgb(51, 51, 51);
+$border-color: rgb(200, 200, 200);
+$sidebar-color: rgb(246, 246, 246);
+$outer-border-color: rgb(170, 170, 170);
// old variables
$light-gray: #ddd;
$dark-gray: #333;
+
+// edx.org-related
+$m-gray-l1: rgb(203,203,203);
+$m-gray-l2: rgb(246,246,246);
+$m-gray: rgb(153,153,153);
+$m-gray-d1: rgb(102,102,102);
+$m-gray-d2: rgb(51,51,51);
+$m-gray-a1: rgb(80,80,80);
+$m-blue: rgb(85, 151, 221);
+$m-blue-l1: rgb(230,245,252);
+$m-blue-d1: shade($m-blue,15%);
+$m-blue-s1: saturate($m-blue,15%);
+$m-pink: rgb(204,51,102);
+
+$m-base-font-size: em(15);
+
+
+$base-font-color: rgb(60,60,60);
+$baseFontColor: rgb(60,60,60);
+$lighter-base-font-color: rgb(100,100,100);
$text-color: $dark-gray;
+$body-font-family: $sans-serif;
+$body-font-size: em(14);
+$body-line-height: golden-ratio(.875em, 1);
diff --git a/lms/static/sass/course/layout/_courseware_header.scss b/lms/static/sass/course/layout/_courseware_header.scss
index b5c93f8e14..e27a6e99d8 100644
--- a/lms/static/sass/course/layout/_courseware_header.scss
+++ b/lms/static/sass/course/layout/_courseware_header.scss
@@ -61,10 +61,10 @@ nav.course-material {
}
header.global.slim {
- border-bottom: 1px solid $outer-border-color;
@include box-shadow(0 1px 2px rgba(0, 0, 0, .1));
height: 50px;
- @include linear-gradient(top, #fff, #eee);
+ border-bottom: 1px solid $outer-border-color;
+ background: $white;
.guest .secondary {
margin-right: 0;
@@ -111,7 +111,7 @@ header.global.slim {
margin-right: 20px;
padding-right: 20px;
- &::before {
+ &:before {
@extend .faded-vertical-divider;
content: "";
display: block;
@@ -122,7 +122,7 @@ header.global.slim {
width: 1px;
}
- &::after {
+ &:after {
@extend .faded-vertical-divider-light;
content: "";
display: block;
@@ -134,7 +134,7 @@ header.global.slim {
}
}
- .find-courses-button {
+ .nav-global {
display: none;
}
diff --git a/lms/static/sass/multicourse/_account.scss b/lms/static/sass/multicourse/_account.scss
new file mode 100644
index 0000000000..eab8cbe66b
--- /dev/null
+++ b/lms/static/sass/multicourse/_account.scss
@@ -0,0 +1,620 @@
+// plus on button
+// border radius on inputs
+
+// Account-Centric (Login/Register)
+// =====
+
+// page-level
+.view-register, .view-login, .view-passwordreset {
+ background: $white;
+
+
+
+ // edx.org - marketing typography
+ .heading-1, .heading-2, .heading-3, .heading-4, .heading-5, .body-text-emphasized, .body-text, .button-primary, .button-secondary {
+ display: block;
+ font-family: $sans-serif;
+ line-height: lh(1);
+ }
+
+ .heading-2 {
+ font-size: 25px;
+ margin: 0 0 $baseline 0;
+ font-weight: 300;
+ text-transform: uppercase;
+ color: $m-blue;
+ }
+
+ .heading-3 {
+ font-size: 21px;
+ margin: 0 0 $baseline 0;
+ font-weight: 300;
+ color: $m-gray-d2;
+ }
+
+ .heading-4 {
+ font-size: 14px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0 !important;
+ color: $m-blue-s1;
+ }
+
+ .heading-5 {
+ }
+
+ // specific examples - body
+ .body-text-emphasized {
+ font-size: 18px;
+ margin: 0 0 $baseline 0;
+ font-weight: 300;
+ color: $m-gray-a1;
+ font-family: 'Open Sans', sans-serif;
+ line-height: lh(1.1);
+ }
+
+ .body-text {
+ font-size: 15px;
+ margin: 0 0 $baseline 0;
+ color: $m-gray-a1;
+ line-height: lh(1);
+ }
+
+ // specific examples - buttons
+ .button-primary {
+ @include border-radius(0);
+ @include linear-gradient($m-blue-s1 5%, $m-blue-d1 95%);
+ display: inline-block;
+ padding: $baseline/2 $baseline*2.5;
+ text-transform: lowercase;
+ color: $white;
+ letter-spacing: 0.1rem;
+ font-weight: 500;
+ cursor: pointer;
+ text-align: center;
+ border: none !important;
+ text-shadow: none;
+ letter-spacing: 0;
+ font-size: 16px;
+ box-shadow: none !important;
+ }
+
+ .button-secondary {
+ @include linear-gradient($m-gray 5%, $m-gray-d1 95%);
+ display: inline-block;
+ padding: $baseline/2 $baseline*2.5;
+ text-transform: lowercase;
+ color: $white;
+ letter-spacing: 0.1rem;
+ font-weight: 600;
+ cursor: pointer;
+ text-align: center;
+ border: none !important;
+ text-shadow: none;
+ letter-spacing: 0;
+ font-size: 16px;
+ box-shadow: 0 !important;
+ }
+
+ // layout
+ .content-wrapper {
+ background: $m-gray-l2;
+ padding-bottom: 0;
+ }
+
+ .container, .introduction {
+ @include box-sizing(border-box);
+ @include clearfix;
+ margin: 0 auto;
+ width: 960px;
+ background: $white;
+ }
+
+ .container {
+ padding: $baseline $baseline ($baseline*2) $baseline;
+ }
+
+ .introduction {
+ padding: ($baseline*2) $baseline 0 $baseline;
+
+ header h1 {
+ @extend .heading-2;
+ margin-bottom: $baseline;
+ padding-bottom: $baseline;
+ text-align: left;
+ }
+ }
+}
+
+// shared
+.login, .register, .passwordreset, #forgot-password-modal #password-reset {
+
+ // reset - horrible, but necessary
+ p, ol, ul, h1, h2, h3, h4, h5, h6, label, input, textarea {
+ @extend .body-text;
+ }
+
+ h1, h2, h3, h4, h5, h6 {
+ letter-spacing: 0;
+ }
+
+ a {
+ @include transition(color 0.15s ease-in-out, border 0.15s ease-in-out);
+
+ &:link, &:visited, &:hover, &:active {
+ color: $m-blue;
+ text-decoration: none !important;
+ font-family: $sans-serif;
+ }
+
+ &:hover, &:active {
+ border-bottom: 1px dotted $m-blue-s1;
+ color: $m-blue-s1;
+ }
+ }
+
+ strong {
+ font-weight: 600;
+ }
+
+ // basic layout
+ .content, aside {
+ @include box-sizing(border-box);
+ margin: $baseline 0 0 0;
+ }
+
+ .content {
+ margin-right: ($baseline*2);
+ width: 600px;
+ float: left;
+ }
+
+ aside {
+ width: 280px;
+ float: left;
+
+ p, ol, ul {
+ font-size: 14px !important;
+ }
+ }
+
+ // content
+ .content {
+ }
+
+ // aside
+ aside {
+
+ .cta {
+ margin: 0 0 ($baseline*2) 0;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+
+ h3 {
+ @extend .heading-4;
+ margin: 0 0 ($baseline/4) 0;
+ }
+ }
+ }
+
+ // forms
+ form {
+
+ .instructions {
+ @extend .body-text-emphasized;
+ margin-bottom: $baseline;
+ }
+
+ fieldset {
+ margin: 0;
+ padding-top: 0;
+ padding-bottom: $baseline;
+ }
+
+ .list-input {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ }
+
+ // field groups
+ .field-group {
+ @include clearfix();
+ margin: 0 0 $baseline 0;
+
+ .field {
+ display: block;
+ float: left;
+ border-bottom: none;
+ margin: 0 ($baseline*1.5) 0 0;
+ padding-bottom: 0;
+
+ input, textarea {
+ width: 100%;
+ font-weight: 600;
+ }
+ }
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ // individual fields
+ .field {
+ margin: 0 0 $baseline 0;
+
+ // elements
+ label, input, textarea {
+ @include border-radius(0);
+ display: block;
+ height: auto;
+ font-family: $sans-serif;
+ font-style: normal;
+ font-weight: 500;
+ color: $m-gray-d2;
+ }
+
+ label {
+ @include transition(color 0.15s ease-in-out);
+ margin: 0 0 ($baseline/4) 0;
+ color: tint($black, 20%);
+ }
+
+ .tip {
+ @include transition(color 0.15s ease-in-out);
+ display: block;
+ margin-top: ($baseline/4);
+ color: tint($m-gray, 50%);
+ font-size: em(13);
+ }
+
+ input, textarea {
+ width: 100%;
+ margin: 0;
+ padding: ($baseline/2) ($baseline*.75);
+
+ &.long {
+ width: 100%;
+ }
+
+ &.short {
+ width: 25%;
+ }
+ }
+
+ textarea.long {
+ height: ($baseline*5);
+ }
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+
+ // types - password
+
+ // types - select
+ &.select {
+
+ select {
+ width: 100%;
+ }
+ }
+
+ // types - checkboxes/radio buttons
+ &.checkbox {
+
+ input[type="checkbox"] {
+ display: inline-block;
+ width: auto;
+ margin-right: ($baseline/4);
+ }
+
+ label {
+ display: inline-block;
+ }
+ }
+
+ // states - all
+ &.disabled, &.submitted {
+ color: rgba(0,0,0,.25);
+
+ label {
+ cursor: text;
+
+ &:after {
+ margin-left: ($baseline/4);
+ }
+ }
+
+ textarea, input {
+ background: $white;
+ color: rgba(0,0,0,.25);
+ }
+ }
+
+ // states - focused
+ &.is-focused {
+
+ label {
+ color: $m-blue-s1;
+ }
+
+ .tip {
+ color: $m-blue-s1;
+ }
+ }
+
+ // states - disabled
+ &.disabled {
+ label:after {
+ color: rgba(0,0,0,.35);
+ content: "(Disabled Currently)";
+ }
+ }
+
+ &.error {
+
+ label {
+ color: $red;
+ }
+
+ input, textarea {
+ border-color: tint($red,50%);
+ }
+ }
+
+ &.required {
+
+ label {
+ font-weight: 600;
+
+ a {
+ font-weight: 600 !important;
+ }
+ }
+
+ label:after {
+ margin-left: ($baseline/4);
+ content: "*";
+ }
+ }
+ }
+ }
+
+ // forms - actions
+ .form-actions {
+ @include clearfix();
+
+ button[type="submit"] {
+ @extend .button-primary;
+
+ &:disabled, &.is-disabled {
+ opacity: 0.3;
+ cursor: default !important;
+ }
+ }
+
+ .action-primary {
+ float: left;
+ width: flex-grid(8,8);
+ margin-right: flex-gutter(0);
+ }
+
+ .action-secondary {
+ display: block;
+ float: right;
+ width: flex-grid(3,8);
+ margin: $baseline $baseline 0 0;
+ font-size: em(14);
+ text-align: right;
+ }
+
+ &.error {
+
+ }
+ }
+
+ // forms - messages/status
+ .status {
+ @include box-sizing(border-box);
+ margin: 0 0 $baseline 0;
+ border-bottom: 3px solid shade($yellow, 10%);
+ padding: $baseline $baseline;
+ background: tint($yellow,20%);
+
+ .message-title {
+ @extend .heading-4;
+ margin: 0 0 ($baseline/4) 0;
+ font-size: em(14);
+ font-weight: 600;
+ color: $m-gray-d2 !important;
+ }
+
+ .message-copy {
+ @extend .body-text;
+ margin: 0 !important;
+ padding: 0;
+ list-style: none;
+
+ li {
+ margin: 0 0 ($baseline/4) 0;
+ }
+ }
+ }
+
+ .submission-error, .system-error {
+ @include box-shadow(inset 0 -1px 2px 0 tint($red, 85%));
+ border-bottom: 3px solid shade($red, 10%);
+ background: tint($red,95%);
+
+ .message-title {
+ color: shade($red, 10%) !important;
+ }
+
+ .message-copy {
+
+ }
+ }
+
+ // misc
+ .orn-plus {
+ color: $white;
+ padding: 0 $baseline/4;
+ }
+
+ #register-form, #login-form, #passwordreset-form {
+
+ .status.message {
+ display: none;
+
+ &.is-shown {
+ display: block;
+ }
+ }
+ }
+}
+
+// =====
+
+// login
+.view-login {
+
+ header.global .nav-courseware .cta-login {
+ display: none;
+ }
+
+ .introduction {
+ padding: 0;
+
+ header {
+ height: 120px;
+ border-bottom: 1px solid $m-gray;
+ background: transparent url("../images/bg-banner-login.png") 0 0 no-repeat;
+ }
+ }
+}
+
+// register
+.view-register {
+
+ .introduction {
+ padding: 0;
+
+ header {
+ height: 120px;
+ border-bottom: 1px solid $m-gray;
+ background: transparent url("../images/bg-banner-register.png") 0 0 no-repeat;
+ }
+ }
+}
+
+// password reset
+.view-passwordreset {
+ background: $m-gray-l2;
+
+ header.global {
+
+ h1 {
+ float: none;
+ }
+ }
+
+ .introduction {
+ width: auto;
+ padding: 0;
+
+ header h1 {
+ margin: 0;
+ }
+ }
+
+ .content {
+ margin-top: 0;
+ }
+}
+
+// modal password reset form
+#forgot-password-modal {
+ @include border-radius(2px);
+
+
+ .inner-wrapper {
+ @include border-radius(2px);
+ background: $white;
+ padding-bottom: 0 !important;
+ }
+
+ #password-reset {
+ padding: $baseline;
+
+ header {
+ margin: 0;
+ padding: 0;
+
+ &:before {
+ background-image: none;
+ }
+
+ h2 {
+ @extend .heading-2;
+ text-align: left;
+ }
+ }
+
+ .message {
+ margin: $baseline 0 0 0;
+ }
+
+ fieldset {
+ margin-bottom: ($baseline/2);
+ padding: 0;
+ }
+
+ .instructions p {
+ margin-bottom: ($baseline/4);
+ }
+
+ form {
+ @include border-radius(0);
+ @include box-shadow(none);
+ margin: 0;
+ border: none;
+ padding: 0;
+
+ .field {
+
+ &.text, &.email, &.textarea {
+
+ input {
+ background: #fafafa;
+ margin-bottom: 0;
+ }
+ }
+ }
+
+ .form-actions {
+ padding: 0 !important;
+
+ .action-primary {
+ float: none;
+ display: block !important;
+ width: 100%;
+ }
+ }
+ }
+ }
+
+ .modal-form-error {
+ @extend .body-text;
+ @include box-shadow(inset 0 -1px 2px 0 tint($red, 85%));
+ @include box-sizing(border-box);
+ margin: $baseline 0 ($baseline/2) 0 !important;
+ padding: $baseline;
+ border: none;
+ border-bottom: 3px solid shade($red, 10%);
+ background: tint($red,95%);
+ }
+}
diff --git a/lms/static/sass/multicourse/_course_about.scss b/lms/static/sass/multicourse/_course_about.scss
index 0982577f42..195760721e 100644
--- a/lms/static/sass/multicourse/_course_about.scss
+++ b/lms/static/sass/multicourse/_course_about.scss
@@ -154,6 +154,15 @@
@include transition();
width: flex-grid(5, 8);
}
+
+ #register_error {
+ background: $error-red;
+ border: 1px solid rgb(202, 17, 17);
+ color: rgb(143, 14, 14);
+ display: none;
+ padding: 12px;
+ margin-top: 5px;
+ }
}
}
diff --git a/lms/static/sass/multicourse/_dashboard.scss b/lms/static/sass/multicourse/_dashboard.scss
index 4555a426d3..cc54b9b242 100644
--- a/lms/static/sass/multicourse/_dashboard.scss
+++ b/lms/static/sass/multicourse/_dashboard.scss
@@ -1,6 +1,6 @@
.dashboard {
@include clearfix;
- padding: 60px 0px 120px;
+ padding: 60px 0 0 0;
.dashboard-banner {
background: $yellow;
@@ -327,7 +327,7 @@
color: $lighter-base-font-color;
}
- h3 a {
+ h3 a, h3 span {
display: block;
margin-bottom: 10px;
font-family: $sans-serif;
diff --git a/lms/static/sass/multicourse/_password_reset.scss b/lms/static/sass/multicourse/_password_reset.scss
index a2365e3e3e..9f145351d1 100644
--- a/lms/static/sass/multicourse/_password_reset.scss
+++ b/lms/static/sass/multicourse/_password_reset.scss
@@ -73,6 +73,7 @@
input[type="email"],
input[type="text"],
input[type="password"] {
+ border: 1px solid red !important;
background: rgb(255,255,255);
display: block;
height: 45px;
diff --git a/lms/static/sass/multicourse/_testcenter-register.scss b/lms/static/sass/multicourse/_testcenter-register.scss
index 6d85fc167f..01405d7fc1 100644
--- a/lms/static/sass/multicourse/_testcenter-register.scss
+++ b/lms/static/sass/multicourse/_testcenter-register.scss
@@ -1,10 +1,5 @@
-// ==========
-
-$baseline: 20px;
-$yellow: rgb(255, 235, 169);
-$red: rgb(178, 6, 16);
-
-// ==========
+// Pearson VUE Test Center Registration
+// =====
.testcenter-register {
@include clearfix;
diff --git a/lms/static/sass/shared/_footer.scss b/lms/static/sass/shared/_footer.scss
index a418b887ad..d891ff408b 100644
--- a/lms/static/sass/shared/_footer.scss
+++ b/lms/static/sass/shared/_footer.scss
@@ -1,179 +1,162 @@
-footer {
- background: transparent;
- border-top: 1px solid rgb(200,200,200);
- @include box-shadow(inset 0 1px 3px 0 rgba(0,0,0, 0.1));
- margin: 0 auto;
- width: flex-grid(12);
+.wrapper-footer {
+ @include box-shadow(0 -1px 5px 0 rgba(0,0,0, 0.1));
+ border-top: 1px solid tint($m-gray,50%);
+ padding: 25px ($baseline/2) ($baseline*1.5) ($baseline/2);
+ background: $white;
- &.fixed-bottom {
- bottom: 0px;
- max-width: 100%;
- position: absolute;
- }
-
- nav {
- max-width: 1200px;
- margin: 0 auto;
- padding: 30px 10px 0;
+ footer {
+ @include clearfix();
max-width: grid-width(12);
min-width: 760px;
+ width: flex-grid(12);
+ margin: 0 auto;
- .top {
- border-bottom: 1px solid rgb(200,200,200);
- @include clearfix;
- padding-bottom: 30px;
- width: flex-grid(12);
- text-align: center;
+ p, ol, ul {
+ font-family: $sans-serif;
+ }
- ol {
- float: right;
+ a {
+ @include transition(color 0.15s ease-in-out, border 0.15s ease-in-out);
+
+ &:link, &:visited, &:hover, &:active {
+ border-bottom: none;
+ color: $m-blue;
+ text-decoration: none !important;
+ font-family: $sans-serif;
+ }
+
+ &:hover, &:active {
+ border-bottom: 1px dotted $m-blue-s1;
+ color: $m-blue-s1;
+ }
+ }
+
+ // colophon
+ .colophon {
+ margin-right: flex-gutter(2);
+ width: flex-grid(6,12);
+ float: left;
+
+ .nav-colophon {
+ @include clearfix();
+ margin: ($baseline/4) 0 ($baseline*1.5) 0;
li {
- @include inline-block;
- list-style: none;
- padding: 0px 15px;
- position: relative;
- vertical-align: middle;
-
- &::after {
- @extend .faded-vertical-divider;
- content: "";
- display: block;
- height: 30px;
- right: 0px;
- position: absolute;
- top: -5px;
- width: 1px;
- }
-
- a:link, a:visited {
- color: $lighter-base-font-color;
- padding: 6px 0px;
- }
- }
- }
-
- .primary {
- @include clearfix;
- float: left;
-
- a.logo {
- @include background-image(url('/static/images/logo.png'));
- background-position: 0 -24px;
- background-repeat: no-repeat;
- @include inline-block;
- height: 22px;
- margin-right: 15px;
- margin-top: 2px;
- padding-right: 15px;
- position: relative;
- width: 47px;
- vertical-align: middle;
- @include transition(none);
-
- &:hover {
- background-position: 0 0;
- }
-
- &::after {
- @extend .faded-vertical-divider;
- content: "";
- display: block;
- height: 30px;
- right: 0px;
- position: absolute;
- top: -3px;
- width: 1px;
- }
- }
-
- a {
- color: $lighter-base-font-color;
- @include inline-block;
- margin-right: 20px;
- padding-top: 2px;
- vertical-align: middle;
-
- &:hover {
- color: $base-font-color;
- text-decoration: none;
- }
- }
- }
-
- .social {
- float: right;
-
- &.social {
- border: none;
- margin: 0 0 0 5px;
- padding: 0;
+ float: left;
+ margin-right: ($baseline*0.75);
a {
- @include inline-block;
- opacity: 0.3;
- @include transition(all, 0.1s, linear);
+ color: tint($black, 20%);
- &:hover {
- opacity: 1;
+ &:hover, &:active {
+ color: $m-blue-s1;
}
}
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+ }
+
+ .colophon-about {
+ @include clearfix();
+
+ img {
+ width: 68px;
+ height: 34px;
+ margin-right: 0;
+ float: left;
+ }
+
+ p {
+ float: left;
+ width: 460px;
+ margin-left: $baseline;
+ padding-left: $baseline;
+ font-size: em(13);
+ background: transparent url(/static/images/bg-footer-divider.jpg) 0 0 no-repeat;
}
}
}
- .bottom {
- @include clearfix;
- opacity: 0.8;
- padding: 10px 0px 30px;
- @include transition(all, 0.15s, linear);
- width: flex-grid(12);
+ // references
+ .references {
+ margin: -10px 0 0 0;
+ width: flex-grid(4,12);
+ float: right;
- &:hover {
- opacity: 1;
+ .nav-social {
+ margin: 0;
+ text-align: right;
+
+ li {
+ margin-right: ($baseline/10);
+ display: inline-block;
+
+ &:last-child {
+ margin-right: 0;
+ }
+
+ a {
+ display: block;
+
+ &:hover, &:active {
+ border: none;
+ }
+ }
+
+ img {
+ display: block;
+ }
+ }
}
.copyright {
- float: left;
-
- p {
- color: $lighter-base-font-color;
- font-style: italic;
- @include inline-block;
- margin: 0 auto;
- padding-top: 1px;
- text-align: center;
- vertical-align: middle;
-
- a {
- color: $lighter-base-font-color;
- font-style: italic;
- margin-left: 5px;
-
- &:hover {
- color: $blue;
- }
- }
- }
+ margin: -2px 0 8px 0;
+ font-size: em(11);
+ color: tint($m-gray,50%);
+ text-align: right;
}
- .secondary {
- float: right;
- text-align: left;
+ .nav-legal {
+ @include clearfix();
+ text-align: right;
- a {
- color: $lighter-base-font-color;
- font-family: $serif;
- font-style: italic;
- line-height: 1.6em;
- margin-left: 20px;
- text-transform: lowercase;
+ li {
+ display: inline-block;
+ font-size: em(11);
- &:hover {
- color: $blue;
+ a {
+ display: block;
+ }
+ }
+
+ .nav-legal-01 a {
+
+ &:after {
+ margin-left: 5px;
+ content: "-";
}
}
}
}
+
}
}
+
+// marketing site design syncing
+.view-register, .view-login {
+
+ .wrapper-footer footer {
+ width: 960px;
+
+ .colophon-about img {
+ margin-top: ($baseline*1.5);
+ }
+
+ .colophon-about p {
+ width: 360px;
+ }
+ }
+}
\ No newline at end of file
diff --git a/lms/static/sass/shared/_header.scss b/lms/static/sass/shared/_header.scss
index 688ffbf57e..5eb453448c 100644
--- a/lms/static/sass/shared/_header.scss
+++ b/lms/static/sass/shared/_header.scss
@@ -1,8 +1,8 @@
header.global {
- border-bottom: 1px solid rgb(190,190,190);
+ border-bottom: 1px solid $m-gray;
@include box-shadow(0 1px 5px 0 rgba(0,0,0, 0.1));
- @include background-image(linear-gradient(-90deg, rgba(255,255,255, 1), rgba(230,230,230, 0.9)));
- height: 68px;
+ background: $white;
+ height: 76px;
position: relative;
width: 100%;
z-index: 10;
@@ -11,40 +11,17 @@ header.global {
@include clearfix;
height: 40px;
margin: 0 auto;
- max-width: 1200px;
- padding: 14px 10px 0px;
+ padding: 18px 10px 0px;
max-width: grid-width(12);
min-width: 760px;
+ width: flex-grid(12);
}
h1.logo {
float: left;
- margin: 0px 15px 0px 0px;
- padding-right: 20px;
+ margin: -2px 39px 0px 0px;
position: relative;
- &::before {
- @extend .faded-vertical-divider;
- content: "";
- display: block;
- height: 50px;
- position: absolute;
- right: 1px;
- top: -8px;
- width: 1px;
- }
-
- &::after {
- @extend .faded-vertical-divider-light;
- content: "";
- display: block;
- height: 50px;
- position: absolute;
- right: 0px;
- top: -12px;
- width: 1px;
- }
-
a {
display: block;
}
@@ -251,4 +228,104 @@ header.global {
}
}
}
+
+ .nav-global {
+ margin-top: ($baseline/2);
+ list-style: none;
+
+ li {
+ display: inline-block;
+ margin: 0 $baseline+1 0 0;
+ font-size: em(14);
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0 !important;
+
+ &:last-child {
+ margin-right: 0;
+ }
+
+ a {
+ display:block;
+ padding: ($baseline/4);
+ color: $m-gray-d1;
+ font-weight: 600;
+
+ &:hover, &:active {
+ text-decoration: none;
+ color: $m-blue-s1;
+ }
+ }
+
+ &.active {
+
+ a {
+ text-decoration: none;
+ color: $m-blue-s1;
+ }
+ }
+ }
+
+ // logged in
+ &.authenticated {
+
+ }
+ }
+
+ .nav-courseware {
+ float: right;
+ margin-top: ($baseline/4);
+ list-style: none;
+
+ li {
+ display: inline-block;
+
+ a {
+ @include border-radius(0);
+ @include linear-gradient($m-blue-s1 5%, $m-blue-d1 95%);
+ display: inline-block;
+ padding: $baseline/2 $baseline*2.5;
+ text-transform: lowercase;
+ color: $white;
+ letter-spacing: 0.1rem;
+ font-weight: 300;
+ cursor: pointer;
+ text-align: center;
+ border: none !important;
+ text-shadow: none;
+ letter-spacing: 0.1rem;
+ font-size: 14px;
+ box-shadow: none !important;
+
+ &:hover {
+ text-decoration: none;
+ }
+ }
+ }
+
+ // logged in
+ &.authenticated {
+
+ }
+ }
}
+
+// marketing site design syncing
+.view-register, .view-login {
+
+ header.global nav {
+ width: 960px;
+ }
+}
+
+// page-based nav states
+.view-howitworks .nav-global-01,
+.view-courses .nav-global-02,
+.view-schools .nav-global-03,
+.view-register .nav-global-04 {
+
+ a {
+ text-decoration: none;
+ color: $m-blue-s1 !important;
+ }
+}
\ No newline at end of file
diff --git a/lms/templates/accounts_login.html b/lms/templates/accounts_login.html
deleted file mode 100644
index db9cca2b22..0000000000
--- a/lms/templates/accounts_login.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<%! from django.core.urlresolvers import reverse %>
-<%inherit file="main.html" />
-<%namespace name='static' file='static_content.html'/>
-
-<%block name="headextra">
-
-%block>
-
-
-
-
diff --git a/lms/templates/courseware/course_about.html b/lms/templates/courseware/course_about.html
index dc1dc17532..941bf61698 100644
--- a/lms/templates/courseware/course_about.html
+++ b/lms/templates/courseware/course_about.html
@@ -13,42 +13,26 @@
<%block name="js_extra">
- % if not registered:
- %if user.is_authenticated():
- ## If the user is authenticated, clicking the enroll button just submits a form
-
- %else:
- ## If the user is not authenticated, clicking the enroll button pops up the register
- ## field. We also slip in the registration fields into the login/register fields so
- ## the user is automatically registered after logging in / registering
-
- %endif
- %endif
+ $('#class_enroll_form').on('ajax:complete', function(event, xhr) {
+ if(xhr.status == 200) {
+ location.href = "${reverse('dashboard')}";
+ } else if (xhr.status == 403) {
+ location.href = "${reverse('register_user')}?course_id=${course.id}&enrollment_action=enroll";
+ } else {
+ $('#register_error').html(
+ (xhr.responseText ? xhr.responseText : 'An error occurred. Please try again later.')
+ ).css("display", "block");
+ }
+ });
+ })(this)
+
%block>
@@ -66,8 +50,7 @@
- %if user.is_authenticated():
- %if registered:
+ %if user.is_authenticated() and registered:
%if show_courseware_link:
%endif
@@ -76,16 +59,9 @@
View Courseware
%endif
- %else:
-
Register for ${course.number}
-
- %endif
%else:
-
Log In
-% endif
- to enroll.'>Register for ${course.number}
+
Register for ${course.number}
+
%endif
@@ -204,5 +180,4 @@
%endif
-
<%include file="../video_modal.html" />
diff --git a/lms/templates/courseware/mktg_coming_soon.html b/lms/templates/courseware/mktg_coming_soon.html
new file mode 100644
index 0000000000..c100c1cb5d
--- /dev/null
+++ b/lms/templates/courseware/mktg_coming_soon.html
@@ -0,0 +1,30 @@
+<%!
+ from django.core.urlresolvers import reverse
+ from courseware.courses import course_image_url, get_course_about_section
+ from courseware.access import has_access
+%>
+<%namespace name='static' file='../static_content.html'/>
+
+<%inherit file="../mktg_iframe.html" />
+
+<%block name="title">About ${course_id}%block>
+
+<%block name="bodyclass">view-partial-mktgregister%block>
+
+
+<%block name="headextra">
+ <%include file="../google_analytics.html" />
+%block>
+
+<%block name="content">
+
+
+
+
+
+
+%block>
diff --git a/lms/templates/courseware/mktg_course_about.html b/lms/templates/courseware/mktg_course_about.html
new file mode 100644
index 0000000000..dc667c850c
--- /dev/null
+++ b/lms/templates/courseware/mktg_course_about.html
@@ -0,0 +1,75 @@
+<%!
+ from django.core.urlresolvers import reverse
+ from courseware.courses import course_image_url, get_course_about_section
+ from courseware.access import has_access
+%>
+<%namespace name='static' file='../static_content.html'/>
+
+<%inherit file="../mktg_iframe.html" />
+
+<%block name="title">About ${course.number}%block>
+
+<%block name="bodyclass">view-partial-mktgregister%block>
+
+
+<%block name="headextra">
+ <%include file="../google_analytics.html" />
+%block>
+
+<%block name="js_extra">
+
+%block>
+
+<%block name="content">
+
+
+
+ -
+ %if user.is_authenticated() and registered:
+ %if show_courseware_link:
+ Access Courseware
+ %else:
+
You Are Registered
+ %endif
+ %elif allow_registration:
+ Register for ${course.number}
+ %else:
+ Registration Is Closed
+ %endif
+
+
+
+%if not registered:
+
+%endif
+%block>
diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html
index d23609801f..75c0cafabd 100644
--- a/lms/templates/dashboard.html
+++ b/lms/templates/dashboard.html
@@ -59,14 +59,16 @@
$("#unenroll_course_number").text( $(event.target).data("course-number") );
});
- $(document).delegate('#unenroll_form', 'ajax:success', function(data, json, xhr) {
- if(json.success) {
- location.href="${reverse('dashboard')}";
+ $('#unenroll_form').on('ajax:complete', function(event, xhr) {
+ if(xhr.status == 200) {
+ location.href = "${reverse('dashboard')}";
+ } else if (xhr.status == 403) {
+ location.href = "${reverse('signin_user')}?course_id=" +
+ $("#unenroll_course_id").val() + "&enrollment_action=unenroll";
} else {
- if($('#unenroll_error').length == 0) {
- $('#unenroll_form').prepend('');
- }
- $('#unenroll_error').text(json.error).stop().css("display", "block");
+ $('#unenroll_error').html(
+ xhr.responseText ? xhr.responseText : "An error occurred. Please try again later."
+ ).stop().css("display", "block");
}
});
@@ -192,17 +194,20 @@
<%
- if has_access(user, course, 'load'):
course_target = reverse('info', args=[course.id])
- else:
- course_target = reverse('about_course', args=[course.id])
%>
-
-
-
+ % if course.id in show_courseware_links_for:
+
+
+
+ % else:
+
+
})
+
+ % endif
@@ -216,7 +221,13 @@
% endif
${get_course_about_section(course, 'university')}
-
+
+ % if course.id in show_courseware_links_for:
+ ${course.number} ${course.display_name_with_default}
+ % else:
+ ${course.number} ${course.display_name_with_default}
+ % endif
+
<%
@@ -326,7 +337,9 @@
% else:
% endif
@@ -355,6 +368,8 @@
+
+