self enrollment refactor to facilitate CN integration
fixes #CNVS-1119, potentially supersedes https://gerrit.instructure.com/14501 with a little work. simpler flow that is more consistent with FFT signup. whether you click the "join course" button (popup) or go to the join url, the workflow is the same: 1. if you are authenticated, you just click the enroll button. 2. if you are not authenticated, you can either: 1. enter your (canvas/ldap) credentials and submit to join the course. 2. register and join the course (single form). you will then be dropped on the course dashboard in the pre_registered state just like a /register signup (you have to follow the link in your email to set a password). note that if open registration is turned off, option 2.2 is not available. other items of interest: * fix CSRF vulnerabilities where you can enroll authenticated users in open courses, or un-enroll them if you know their enrollment's UUID * move to shorter course-id-less route (w/ join code) * reuse UserController#create * handy openAsDialog behavior and embedded view mode * better json support in PseudonymSessionsController#create * extract markdown helper from mt * show "you need to confirm your email" popup when you land on the course page the first time (already showed on dashboard) test plan: 1. test the authenticated/unauthenticated scenarios above, for both the popup and join pages 2. regression test of /registration forms Change-Id: I0d8351695356d437bdbba72cb66c23ed268b0d1a Reviewed-on: https://gerrit.instructure.com/15902 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Joe Tanner <joe@instructure.com> QA-Review: Jon Jensen <jon@instructure.com>
This commit is contained in:
parent
8ff2592a3e
commit
f74ebd096b
|
@ -0,0 +1,29 @@
|
|||
define [
|
||||
'jquery'
|
||||
'compiled/fn/preventDefault'
|
||||
'jqueryui/dialog',
|
||||
], ($, preventDefault) ->
|
||||
|
||||
$.fn.openAsDialog = (options = {}) ->
|
||||
@click preventDefault (e) ->
|
||||
$link = $(e.target)
|
||||
|
||||
options.width ?= 550
|
||||
options.height ?= 450
|
||||
options.title ?= $link.attr('title')
|
||||
options.resizable ?= false
|
||||
|
||||
$dialog = $("<div>")
|
||||
$iframe = $('<iframe>', style: "position:absolute;top:0;left:0;border:none", src: $link.attr('href') + '?embedded=1')
|
||||
$dialog.append $iframe
|
||||
|
||||
$dialog.on "dialogopen", ->
|
||||
$container = $dialog.closest('.ui-dialog-content')
|
||||
$iframe.height $container.outerHeight()
|
||||
$iframe.width $container.outerWidth()
|
||||
$dialog.dialog options
|
||||
|
||||
$ ->
|
||||
$('a[data-open-as-dialog]').openAsDialog()
|
||||
|
||||
$
|
|
@ -31,6 +31,7 @@ require [
|
|||
'jqueryui/effects/drop'
|
||||
'jqueryui/progressbar'
|
||||
'jqueryui/tabs'
|
||||
'compiled/registration/incompleteRegistrationWarning'
|
||||
|
||||
# random modules required by the js_blocks, put them all in here
|
||||
# so RequireJS doesn't try to load them before common is loaded
|
||||
|
|
|
@ -3,11 +3,7 @@ require [
|
|||
'Backbone',
|
||||
'jquery',
|
||||
'i18n!dashboard'
|
||||
'compiled/registration/incompleteRegistrationWarning'
|
||||
], (_, {View}, $, I18n, incompleteRegistrationWarning) ->
|
||||
|
||||
if ENV.INCOMPLETE_REGISTRATION
|
||||
incompleteRegistrationWarning(ENV.USER_EMAIL)
|
||||
], (_, {View}, $, I18n) ->
|
||||
|
||||
class DashboardView extends View
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
require [
|
||||
'jquery'
|
||||
'compiled/views/registration/SelfEnrollmentForm'
|
||||
], ($, SelfEnrollmentForm) ->
|
||||
|
||||
new SelfEnrollmentForm el: '#enroll_form'
|
|
@ -12,6 +12,7 @@ define [
|
|||
invalid: I18n.t("errors.invalid", "May only contain letters, numbers, or the following: %{characters}", {characters: ". + - _ @ ="})
|
||||
taken: I18n.t("errors.taken", "Email already in use")
|
||||
bad_credentials: I18n.t("errors.bad_credentials", "Invalid username or password")
|
||||
not_email: I18n.t("errors.not_email", "Not a valid email address")
|
||||
password:
|
||||
too_short: I18n.t("errors.too_short", "Must be at least %{min} characters", {min: 6})
|
||||
confirmation: I18n.t("errors.mismatch", "Doesn't match")
|
||||
|
|
|
@ -16,6 +16,7 @@ define [
|
|||
self_enrollment_code:
|
||||
blank: I18n.t("errors.required", "Required")
|
||||
invalid: I18n.t("errors.invalid_code", "Invalid code")
|
||||
already_enrolled: I18n.t("errors.already_enrolled", "You are already enrolled in this course")
|
||||
terms_of_use:
|
||||
accepted: I18n.t("errors.terms", "You must agree to the terms")
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ define [
|
|||
'i18n!registration'
|
||||
'jst/registration/incompleteRegistrationWarning'
|
||||
], ($, I18n, template) ->
|
||||
(email) ->
|
||||
$(template(email: email)).
|
||||
if ENV.INCOMPLETE_REGISTRATION
|
||||
$(template(email: ENV.USER_EMAIL)).
|
||||
appendTo($('body')).
|
||||
dialog
|
||||
title: I18n.t('welcome_to_canvas', 'Welcome to Canvas!')
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
define [
|
||||
'compiled/models/User'
|
||||
'compiled/models/Pseudonym'
|
||||
'compiled/object/flatten'
|
||||
], (User, Pseudonym, flatten) ->
|
||||
|
||||
# normalize errors we get from POST /user (user creation API)
|
||||
registrationErrors = (errors) ->
|
||||
errors = flatten
|
||||
user: User::normalizeErrors(errors.user)
|
||||
pseudonym: Pseudonym::normalizeErrors(errors.pseudonym)
|
||||
observee: Pseudonym::normalizeErrors(errors.observee)
|
||||
, arrays: false
|
||||
if errors['user[birthdate]']
|
||||
errors['user[birthdate(1i)]'] = errors['user[birthdate]']
|
||||
delete errors['user[birthdate]']
|
||||
errors
|
|
@ -2,16 +2,14 @@ define [
|
|||
'underscore'
|
||||
'i18n!registration'
|
||||
'compiled/fn/preventDefault'
|
||||
'compiled/models/User'
|
||||
'compiled/models/Pseudonym'
|
||||
'compiled/registration/registrationErrors'
|
||||
'jst/registration/teacherDialog'
|
||||
'jst/registration/studentDialog'
|
||||
'jst/registration/studentHigherEdDialog'
|
||||
'jst/registration/parentDialog'
|
||||
'compiled/object/flatten'
|
||||
'jquery.instructure_forms'
|
||||
'jquery.instructure_date_and_time'
|
||||
], (_, I18n, preventDefault, User, Pseudonym, teacherDialog, studentDialog, studentHigherEdDialog, parentDialog, flatten) ->
|
||||
], (_, I18n, preventDefault, registrationErrors, teacherDialog, studentDialog, studentHigherEdDialog, parentDialog) ->
|
||||
|
||||
$nodes = {}
|
||||
templates = {teacherDialog, studentDialog, studentHigherEdDialog, parentDialog}
|
||||
|
@ -36,27 +34,22 @@ define [
|
|||
$form.disableWhileLoading(promise)
|
||||
success: (data) =>
|
||||
# they should now be authenticated (either registered or pre_registered)
|
||||
window.location = "/?login_success=1®istration_success=1"
|
||||
if data.course
|
||||
window.location = "/courses/#{data.course.course.id}?registration_success=1"
|
||||
else
|
||||
window.location = "/?registration_success=1"
|
||||
formErrors: false
|
||||
error: (errors) ->
|
||||
promise.reject()
|
||||
if _.any(errors.user.birthdate ? [], (e) -> e.type is 'too_young')
|
||||
$node.find('.registration-dialog').html I18n.t('too_young_error', 'You must be at least %{min_years} years of age to use Canvas without a course join code.', min_years: ENV.USER.MIN_AGE)
|
||||
$node.find('.registration-dialog').text I18n.t('too_young_error_join_code', 'You must be at least %{min_years} years of age to use Canvas without a course join code.', min_years: ENV.USER.MIN_AGE)
|
||||
$node.dialog buttons: [
|
||||
text: I18n.t('ok', "OK")
|
||||
click: -> $node.dialog('close')
|
||||
class: 'btn-primary'
|
||||
]
|
||||
return
|
||||
errors = flatten
|
||||
user: User::normalizeErrors(errors.user)
|
||||
pseudonym: Pseudonym::normalizeErrors(errors.pseudonym)
|
||||
observee: Pseudonym::normalizeErrors(errors.observee)
|
||||
, arrays: false
|
||||
if errors['user[birthdate]']
|
||||
errors['user[birthdate(1)]'] = errors['user[birthdate]']
|
||||
delete errors['user[birthdate]']
|
||||
$form.formErrors errors
|
||||
$form.formErrors registrationErrors(errors)
|
||||
|
||||
$node.dialog
|
||||
resizable: false
|
||||
|
|
|
@ -34,6 +34,8 @@ define [
|
|||
dateSelect = (name, options, htmlOptions = _.clone(options)) ->
|
||||
validOptions = ['type', 'startYear', 'endYear', 'includeBlank', 'order']
|
||||
delete htmlOptions[opt] for opt in validOptions
|
||||
htmlOptions['class'] ?= ''
|
||||
htmlOptions['class'] += ' date-select'
|
||||
|
||||
year = (new Date()).getFullYear()
|
||||
position = {year: 1, month: 2, day: 3}
|
||||
|
@ -53,7 +55,7 @@ define [
|
|||
$result = $('<span>')
|
||||
for i in [0...options.order.length]
|
||||
type = options.order[i]
|
||||
tName = name.replace(/(\]?)$/, "(" + position[type] + ")$1")
|
||||
tName = name.replace(/(\]?)$/, "(" + position[type] + "i)$1")
|
||||
$result.append(
|
||||
builders[type](
|
||||
options,
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
#
|
||||
# Copyright (C) 2012 Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
define [
|
||||
'jquery'
|
||||
'underscore'
|
||||
'Backbone'
|
||||
'i18n!registration'
|
||||
'compiled/registration/registrationErrors'
|
||||
'jquery.instructure_forms'
|
||||
'jquery.ajaxJSON'
|
||||
], ($, _, Backbone, I18n, registrationErrors) ->
|
||||
|
||||
class SelfEnrollmentForm extends Backbone.View
|
||||
events:
|
||||
'change input[name=user_type]': 'changeType'
|
||||
'click #logout_link': 'logOutAndRefresh'
|
||||
|
||||
initialize: ->
|
||||
@enrollAction = @$el.attr('action')
|
||||
@userType = @$el.find('input[type=hidden][name=user_type]').val()
|
||||
@$el.formSubmit
|
||||
beforeSubmit: @beforeSubmit
|
||||
onSubmit: @onSubmit
|
||||
success: @enrollSuccess
|
||||
error: @enrollError
|
||||
formErrors: false
|
||||
|
||||
changeType: (e) =>
|
||||
@userType = $(e.target).val()
|
||||
@$el.find('.user_info').hide()
|
||||
@$el.find("##{@userType}_user_info").show()
|
||||
@$el.find("#submit_button").css(visibility: 'visible')
|
||||
|
||||
beforeSubmit: =>
|
||||
return false unless @userType
|
||||
unless @promise?
|
||||
@promise = $.Deferred()
|
||||
@$el.disableWhileLoading(@promise)
|
||||
|
||||
switch @userType
|
||||
when 'new'
|
||||
# create user and self-enroll in course(s)
|
||||
@$el.attr('action', '/users')
|
||||
when 'existing'
|
||||
@logIn =>
|
||||
# yay, now enroll the user
|
||||
@userType = 'authenticated'
|
||||
@enrollErrorOnce = (errors) =>
|
||||
if @hasError(errors.user?.self_enrollment_code, 'already_enrolled')
|
||||
# we don't reload the form, so we want a subsequent login
|
||||
# or signup attempt to work
|
||||
@userType = 'existing'
|
||||
@logOut()
|
||||
@$el.submit()
|
||||
return false
|
||||
when 'authenticated'
|
||||
@$el.attr('action', @enrollAction)
|
||||
|
||||
onSubmit: (deferred) ->
|
||||
$.when(deferred).done => @enrollErrorOnce = null
|
||||
|
||||
error: (errors) =>
|
||||
@promise.reject()
|
||||
# move the "already enrolled" error to the username, since that's visible
|
||||
if errors['user[self_enrollment_code]']
|
||||
errors['pseudonym[unique_id]'] ?= []
|
||||
errors['pseudonym[unique_id]'].push errors['user[self_enrollment_code]'][0]
|
||||
delete errors['user[self_enrollment_code]']
|
||||
@$el.formErrors errors
|
||||
@promise = null
|
||||
|
||||
enrollError: (errors) =>
|
||||
@enrollErrorOnce?(errors)
|
||||
if @hasError(errors.user?.birthdate, 'too_young')
|
||||
errors = []
|
||||
@$el.text I18n.t('too_young_error', 'You must be at least %{min_years} years of age to enroll in this course.', min_years: ENV.USER.MIN_AGE)
|
||||
@error registrationErrors(errors)
|
||||
|
||||
enrollSuccess: (data) =>
|
||||
# they should now be authenticated (either registered or pre_registered)
|
||||
q = window.location.search
|
||||
q = (if q then "#{q}&" else "?")
|
||||
q += "enrolled=1"
|
||||
q += '&just_created=1' if @userType is 'new'
|
||||
window.location.search = q
|
||||
|
||||
logIn: (successCb) ->
|
||||
data = pseudonym_session:
|
||||
unique_id: @$el.find('#student_email').val()
|
||||
password: @$el.find('#student_password').val()
|
||||
|
||||
$.ajaxJSON '/login', 'POST', data, successCb, (errors, xhr) =>
|
||||
baseErrors = errors.errors.base
|
||||
error = baseErrors[baseErrors.length - 1].message
|
||||
@error 'pseudonym[password]': error
|
||||
|
||||
logOut: (refresh = false) =>
|
||||
$.ajaxJSON '/logout', 'POST', {}, ->
|
||||
location.reload true if refresh
|
||||
|
||||
logOutAndRefresh: (e) =>
|
||||
e.preventDefault()
|
||||
@logOut(true)
|
||||
|
||||
hasError: (errors, type) ->
|
||||
return false unless errors
|
||||
return true for e in errors when e.type is type
|
||||
false
|
|
@ -46,7 +46,7 @@ class ApplicationController < ActionController::Base
|
|||
after_filter :set_user_id_header
|
||||
before_filter :fix_xhr_requests
|
||||
before_filter :init_body_classes
|
||||
before_filter :set_response_headers
|
||||
after_filter :set_response_headers
|
||||
|
||||
add_crumb(proc { %Q{<i title="#{I18n.t('links.dashboard', "My Dashboard")}" class="icon-home standalone-icon"></i>}.html_safe }, :root_path, :class => "home")
|
||||
|
||||
|
@ -153,7 +153,7 @@ class ApplicationController < ActionController::Base
|
|||
# we can't block frames on the files domain, since files domain requests
|
||||
# are typically embedded in an iframe in canvas, but the hostname is
|
||||
# different
|
||||
if !files_domain? && Setting.get_cached('block_html_frames', 'true') == 'true'
|
||||
if !files_domain? && Setting.get_cached('block_html_frames', 'true') == 'true' && !@embeddable
|
||||
headers['X-Frame-Options'] = 'SAMEORIGIN'
|
||||
end
|
||||
true
|
||||
|
@ -1177,6 +1177,12 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
end
|
||||
|
||||
def check_incomplete_registration
|
||||
if @current_user
|
||||
js_env :INCOMPLETE_REGISTRATION => params[:registration_success] && @current_user.pre_registered?, :USER_EMAIL => @current_user.email
|
||||
end
|
||||
end
|
||||
|
||||
def page_views_enabled?
|
||||
PageView.page_views_enabled?
|
||||
end
|
||||
|
@ -1237,7 +1243,21 @@ class ApplicationController < ActionController::Base
|
|||
(params[:format].to_s != 'json' || in_app?)
|
||||
end
|
||||
|
||||
def reset_session
|
||||
# when doing login/logout via ajax, we need to have the new csrf token
|
||||
# for subsequent requests.
|
||||
@resend_csrf_token_if_json = true
|
||||
super
|
||||
end
|
||||
|
||||
def set_layout_options
|
||||
@embedded_view = params[:embedded]
|
||||
@headers = false if params[:no_headers] || @embedded_view
|
||||
(@body_classes ||= []) << 'embedded' if @embedded_view
|
||||
end
|
||||
|
||||
def render(options = nil, extra_options = {}, &block)
|
||||
set_layout_options
|
||||
if options && options.key?(:json)
|
||||
json = options.delete(:json)
|
||||
json = ActiveSupport::JSON.encode(json) unless json.is_a?(String)
|
||||
|
@ -1247,6 +1267,10 @@ class ApplicationController < ActionController::Base
|
|||
json = "while(1);#{json}"
|
||||
end
|
||||
|
||||
if @resend_csrf_token_if_json
|
||||
response.headers['X-CSRF-Token'] = form_authenticity_token
|
||||
end
|
||||
|
||||
# fix for some browsers not properly handling json responses to multipart
|
||||
# file upload forms and s3 upload success redirects -- we'll respond with text instead.
|
||||
if options[:as_text] || json_as_text?
|
||||
|
|
|
@ -778,46 +778,21 @@ class CoursesController < ApplicationController
|
|||
|
||||
def self_unenrollment
|
||||
get_context
|
||||
unless @context_enrollment && params[:self_unenrollment] && params[:self_unenrollment] == @context_enrollment.uuid && @context_enrollment.self_enrolled?
|
||||
redirect_to course_url(@context)
|
||||
return
|
||||
if @context_enrollment && params[:self_unenrollment] && params[:self_unenrollment] == @context_enrollment.uuid && @context_enrollment.self_enrolled?
|
||||
@context_enrollment.conclude
|
||||
render :json => ""
|
||||
else
|
||||
render :json => "", :status => :bad_request
|
||||
end
|
||||
@context_enrollment.complete
|
||||
redirect_to course_url(@context)
|
||||
end
|
||||
|
||||
# DEPRECATED
|
||||
def self_enrollment
|
||||
get_context
|
||||
unless @context.self_enrollment && params[:self_enrollment] && @context.self_enrollment_codes.include?(params[:self_enrollment])
|
||||
return redirect_to course_url(@context)
|
||||
end
|
||||
unless @current_user || @context.root_account.open_registration?
|
||||
store_location
|
||||
flash[:notice] = t('notices.login_required', "Please log in to join this course.")
|
||||
return redirect_to login_url
|
||||
end
|
||||
if @current_user
|
||||
@enrollment = @context.self_enroll_student(@current_user)
|
||||
flash[:notice] = t('notices.enrolled', "You are now enrolled in this course.")
|
||||
return redirect_to course_url(@context)
|
||||
end
|
||||
if params[:email]
|
||||
begin
|
||||
address = TMail::Address::parse(params[:email])
|
||||
rescue
|
||||
flash[:error] = t('errors.invalid_email', "Invalid e-mail address, please try again.")
|
||||
render :action => 'open_enrollment'
|
||||
return
|
||||
end
|
||||
user = User.new(:name => address.name || address.address)
|
||||
user.communication_channels.build(:path => address.address)
|
||||
user.workflow_state = 'creation_pending'
|
||||
user.save!
|
||||
@enrollment = @context.enroll_student(user)
|
||||
@enrollment.update_attribute(:self_enrolled, true)
|
||||
return render :action => 'open_enrollment_confirmed'
|
||||
end
|
||||
render :action => 'open_enrollment'
|
||||
redirect_to enroll_url(@context.self_enrollment_code)
|
||||
end
|
||||
|
||||
def check_pending_teacher
|
||||
|
@ -900,6 +875,8 @@ class CoursesController < ApplicationController
|
|||
|
||||
@context_enrollment ||= @pending_enrollment
|
||||
if is_authorized_action?(@context, @current_user, :read)
|
||||
check_incomplete_registration
|
||||
|
||||
if @current_user && @context.grants_right?(@current_user, session, :manage_grades)
|
||||
@assignments_needing_publishing = @context.assignments.active.need_publishing || []
|
||||
end
|
||||
|
|
|
@ -163,35 +163,25 @@ class PseudonymSessionsController < ApplicationController
|
|||
end
|
||||
|
||||
if pseudonym == :too_many_attempts || @pseudonym_session.too_many_attempts?
|
||||
flash[:error] = t 'errors.max_attempts', "Too many failed login attempts. Please try again later or contact your system administrator."
|
||||
redirect_to login_url
|
||||
unsuccessful_login t('errors.max_attempts', "Too many failed login attempts. Please try again later or contact your system administrator."), true
|
||||
return
|
||||
end
|
||||
|
||||
@pseudonym = @pseudonym_session && @pseudonym_session.record
|
||||
# If the user's account has been deleted, feel free to share that information
|
||||
if @pseudonym && (!@pseudonym.user || @pseudonym.user.unavailable?)
|
||||
flash[:error] = t 'errors.user_deleted', "That user account has been deleted. Please contact your system administrator to have your account re-activated."
|
||||
redirect_to login_url
|
||||
unsuccessful_login t('errors.user_deleted', "That user account has been deleted. Please contact your system administrator to have your account re-activated."), true
|
||||
return
|
||||
end
|
||||
|
||||
# Call for some cleanups that should be run when a user logs in
|
||||
@user = @pseudonym.login_assertions_for_user if found
|
||||
|
||||
# If the user is registered and logged in, redirect them to their dashboard page
|
||||
if found
|
||||
# Call for some cleanups that should be run when a user logs in
|
||||
@user = @pseudonym.login_assertions_for_user
|
||||
successful_login(@user, @pseudonym)
|
||||
# Otherwise re-render the login page to show the error
|
||||
else
|
||||
respond_to do |format|
|
||||
flash[:error] = t 'errors.invalid_credentials', "Incorrect username and/or password"
|
||||
@errored = true
|
||||
@pre_registered = @user if @user && !@user.registered?
|
||||
@headers = false
|
||||
format.html { maybe_render_mobile_login :bad_request }
|
||||
format.json { render :json => @pseudonym_session.errors.to_json, :status => :bad_request }
|
||||
end
|
||||
unsuccessful_login t('errors.invalid_credentials', "Incorrect username and/or password")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -553,6 +543,26 @@ class PseudonymSessionsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def unsuccessful_login(message, refresh = false)
|
||||
respond_to do |format|
|
||||
flash[:error] = message
|
||||
format.html do
|
||||
if refresh
|
||||
redirect_to login_url
|
||||
else
|
||||
@errored = true
|
||||
@pre_registered = @user if @user && !@user.registered?
|
||||
@headers = false
|
||||
maybe_render_mobile_login :bad_request
|
||||
end
|
||||
end
|
||||
format.json do
|
||||
@pseudonym_session.errors.add('base', message)
|
||||
render :json => @pseudonym_session.errors.to_json, :status => :bad_request
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
OAUTH2_OOB_URI = 'urn:ietf:wg:oauth:2.0:oob'
|
||||
|
||||
def oauth2_auth
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
#
|
||||
# Copyright (C) 2012 Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
class SelfEnrollmentsController < ApplicationController
|
||||
before_filter :infer_signup_info, :only => [:new, :create]
|
||||
before_filter :require_user, :only => :create
|
||||
|
||||
include Api::V1::Course
|
||||
|
||||
def new
|
||||
js_env :USER => {:MIN_AGE => @course.self_enrollment_min_age || User.self_enrollment_min_age}
|
||||
end
|
||||
|
||||
def create
|
||||
@current_user.validation_root_account = @domain_root_account
|
||||
@current_user.require_self_enrollment_code = true
|
||||
@current_user.self_enrollment_code = params[:self_enrollment_code]
|
||||
if @current_user.save
|
||||
render :json => course_json(@current_user.self_enrollment_course, @current_user, session, [], nil)
|
||||
else
|
||||
render :json => {:user => @current_user.errors.as_json[:errors]}, :status => :bad_request
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def infer_signup_info
|
||||
@embeddable = true
|
||||
@course = @domain_root_account.self_enrollment_course_for(params[:self_enrollment_code])
|
||||
|
||||
# TODO: have a join code field in new.html.erb if none is provided in the url
|
||||
raise ActiveRecord::RecordNotFound unless @course
|
||||
end
|
||||
end
|
|
@ -284,10 +284,7 @@ class UsersController < ApplicationController
|
|||
end
|
||||
|
||||
def user_dashboard
|
||||
if @current_user && @current_user.registered? && params[:registration_success]
|
||||
# user just joined with a course code
|
||||
return redirect_to @current_user.courses.first if @current_user.courses.size == 1
|
||||
end
|
||||
check_incomplete_registration
|
||||
get_context
|
||||
|
||||
# dont show crubms on dashboard because it does not make sense to have a breadcrumb
|
||||
|
@ -305,9 +302,6 @@ class UsersController < ApplicationController
|
|||
@announcements = AccountNotification.for_user_and_account(@current_user, @domain_root_account)
|
||||
@pending_invitations = @current_user.cached_current_enrollments(:include_enrollment_uuid => session[:enrollment_uuid]).select { |e| e.invited? }
|
||||
@stream_items = @current_user.try(:cached_recent_stream_items) || []
|
||||
|
||||
incomplete_registration = @current_user && @current_user.pre_registered? && params[:registration_success]
|
||||
js_env({:INCOMPLETE_REGISTRATION => incomplete_registration, :USER_EMAIL => @current_user.email})
|
||||
end
|
||||
|
||||
def toggle_dashboard
|
||||
|
@ -649,6 +643,14 @@ class UsersController < ApplicationController
|
|||
|
||||
def new
|
||||
return redirect_to(root_url) if @current_user
|
||||
unless @context == Account.default && @context.no_enrollments_can_create_courses?
|
||||
# TODO: generic/brandable page, so we can up it up to non-default accounts
|
||||
# also more control so we can conditionally enable features (e.g. if
|
||||
# no_enrollments_can_create_courses==false, but open reg is on, students
|
||||
# should still be able to sign up with join codes, etc. ... we should just
|
||||
# not have the teacher button/form)
|
||||
return redirect_to(root_url)
|
||||
end
|
||||
render :layout => 'bare'
|
||||
end
|
||||
|
||||
|
@ -678,7 +680,10 @@ class UsersController < ApplicationController
|
|||
|
||||
manage_user_logins = @context.grants_right?(@current_user, session, :manage_user_logins)
|
||||
self_enrollment = params[:self_enrollment].present?
|
||||
allow_password = self_enrollment || manage_user_logins
|
||||
allow_non_email_pseudonyms = manage_user_logins || self_enrollment && params[:pseudonym_type] == 'username'
|
||||
@domain_root_account.email_pseudonyms = !allow_non_email_pseudonyms
|
||||
require_password = self_enrollment && allow_non_email_pseudonyms
|
||||
allow_password = require_password || manage_user_logins
|
||||
|
||||
notify = params[:pseudonym].delete(:send_confirmation) == '1'
|
||||
notify = :self_registration unless manage_user_logins
|
||||
|
@ -695,7 +700,7 @@ class UsersController < ApplicationController
|
|||
end
|
||||
@user.name ||= params[:pseudonym][:unique_id]
|
||||
unless @user.registered?
|
||||
@user.workflow_state = if self_enrollment
|
||||
@user.workflow_state = if require_password
|
||||
# no email confirmation required (self_enrollment_code and password
|
||||
# validations will ensure everything is legit)
|
||||
'registered'
|
||||
|
@ -727,7 +732,7 @@ class UsersController < ApplicationController
|
|||
end
|
||||
|
||||
@pseudonym ||= @user.pseudonyms.build(:account => @context)
|
||||
@pseudonym.require_password = self_enrollment
|
||||
@pseudonym.require_password = require_password
|
||||
# pre-populate the reverse association
|
||||
@pseudonym.user = @user
|
||||
# don't require password_confirmation on api calls
|
||||
|
@ -754,7 +759,11 @@ class UsersController < ApplicationController
|
|||
# unless the user is registered/pre_registered (if the latter, he still
|
||||
# needs to confirm his email and set a password, otherwise he can't get
|
||||
# back in once his session expires)
|
||||
@pseudonym.send(:skip_session_maintenance=, true) unless @user.registered? || @user.pre_registered? # automagically logged in
|
||||
if @user.registered? || @user.pre_registered? # automagically logged in
|
||||
PseudonymSession.new(@pseudonym).save unless @pseudonym.new_record?
|
||||
else
|
||||
@pseudonym.send(:skip_session_maintenance=, true)
|
||||
end
|
||||
@user.save!
|
||||
message_sent = false
|
||||
if notify == :self_registration
|
||||
|
@ -762,7 +771,7 @@ class UsersController < ApplicationController
|
|||
message_sent = true
|
||||
@pseudonym.send_confirmation!
|
||||
end
|
||||
@user.new_teacher_registration((params[:user] || {}).merge({:remote_ip => request.remote_ip}))
|
||||
@user.new_registration((params[:user] || {}).merge({:remote_ip => request.remote_ip}))
|
||||
elsif notify && !@user.registered?
|
||||
message_sent = true
|
||||
@pseudonym.send_registration_notification!
|
||||
|
@ -770,7 +779,7 @@ class UsersController < ApplicationController
|
|||
@cc.send_merge_notification! if @cc.merge_candidates.length != 0
|
||||
end
|
||||
|
||||
data = { :user => @user, :pseudonym => @pseudonym, :channel => @cc, :observee => @observee, :message_sent => message_sent }
|
||||
data = { :user => @user, :pseudonym => @pseudonym, :channel => @cc, :observee => @observee, :message_sent => message_sent, :course => @user.self_enrollment_course }
|
||||
if api_request?
|
||||
render(:json => user_json(@user, @current_user, session, %w{locale}))
|
||||
else
|
||||
|
@ -1133,7 +1142,7 @@ class UsersController < ApplicationController
|
|||
get_context
|
||||
@context = @domain_root_account || Account.default unless @context.is_a?(Account)
|
||||
@context = @context.root_account
|
||||
if !@context.grants_right?(@current_user, session, :manage_user_logins) && (!@context.open_registration? || !@context.no_enrollments_can_create_courses? || @context != Account.default)
|
||||
unless @context.grants_right?(@current_user, session, :manage_user_logins) || @context.open_registration?
|
||||
flash[:error] = t('no_open_registration', "Open registration has not been enabled for this account")
|
||||
respond_to do |format|
|
||||
format.html { redirect_to root_url }
|
||||
|
|
|
@ -43,6 +43,23 @@ module DashboardHelper
|
|||
@current_user.cached_current_enrollments(:include_enrollment_uuid => session[:enrollment_uuid]).empty?
|
||||
end
|
||||
|
||||
def welcome_message
|
||||
if @current_user.cached_current_enrollments(:include_future => true).present?
|
||||
t('#users.welcome.unpublished_courses_message', <<-BODY)
|
||||
You've enrolled in one or more courses that have not started yet. Once
|
||||
those courses are available, you will see information about them here
|
||||
and in the top navigation. In the meantime, feel free to sign up for
|
||||
more courses or set up your profile.
|
||||
BODY
|
||||
else
|
||||
t('#users.welcome.no_courses_message', <<-BODY)
|
||||
You don't have any courses, so this page won't be very exciting for now.
|
||||
Once you've created or signed up for courses, you'll start to see
|
||||
conversations from all of your classes.
|
||||
BODY
|
||||
end
|
||||
end
|
||||
|
||||
def activity_category_links(category, items)
|
||||
max_contexts = 4
|
||||
contexts = items.map{ |i| [i.context.name, i.context.linked_to] }.uniq
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
#
|
||||
# Copyright (C) 2012 Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
module SelfEnrollmentsHelper
|
||||
def registration_summary
|
||||
# allow plugins to display additional content
|
||||
if @registration_summary
|
||||
markdown(@registration_summary, :never) rescue nil
|
||||
end
|
||||
end
|
||||
end
|
|
@ -377,6 +377,12 @@ class Account < ActiveRecord::Base
|
|||
@cached_courses_name_like[[query, opts]] ||= self.fast_course_base(opts) {|q| q.name_like(query)}
|
||||
end
|
||||
|
||||
def self_enrollment_course_for(code)
|
||||
all_courses.
|
||||
where(:self_enrollment => true, :self_enrollment_code => code).
|
||||
first
|
||||
end
|
||||
|
||||
def file_namespace
|
||||
Shard.default.activate { "account_#{self.root_account.id}" }
|
||||
end
|
||||
|
@ -684,9 +690,8 @@ class Account < ActiveRecord::Base
|
|||
name
|
||||
end
|
||||
|
||||
def email_pseudonyms
|
||||
false
|
||||
end
|
||||
# can be set/overridden by plugin to enforce email pseudonyms
|
||||
attr_accessor :email_pseudonyms
|
||||
|
||||
def password_authentication?
|
||||
!!(!self.account_authorization_config || self.account_authorization_config.password_authentication?)
|
||||
|
|
|
@ -550,10 +550,12 @@ class Course < ActiveRecord::Base
|
|||
end
|
||||
memoize :user_is_instructor?
|
||||
|
||||
def user_is_student?(user)
|
||||
def user_is_student?(user, opts = {})
|
||||
return unless user
|
||||
Rails.cache.fetch([self, user, "course_user_is_student"].cache_key) do
|
||||
user.cached_current_enrollments.any? { |e| e.course_id == self.id && e.participating_student? }
|
||||
Rails.cache.fetch([self, user, "course_user_is_student", opts[:include_future]].cache_key) do
|
||||
user.cached_current_enrollments(:include_future => opts[:include_future]).any? { |e|
|
||||
e.course_id == self.id && (opts[:include_future] ? e.student? : e.participating_student?)
|
||||
}
|
||||
end
|
||||
end
|
||||
memoize :user_is_student?
|
||||
|
@ -742,6 +744,10 @@ class Course < ActiveRecord::Base
|
|||
code
|
||||
end
|
||||
|
||||
# can be overridden via plugin
|
||||
def self_enrollment_min_age
|
||||
end
|
||||
|
||||
def long_self_enrollment_code
|
||||
Digest::MD5.hexdigest("#{uuid}_for_#{id}")
|
||||
end
|
||||
|
@ -1511,7 +1517,7 @@ class Course < ActiveRecord::Base
|
|||
def self_enroll_student(user, opts = {})
|
||||
enrollment = enroll_student(user, opts.merge(:no_notify => true))
|
||||
enrollment.self_enrolled = true
|
||||
enrollment.accept
|
||||
enrollment.accept(:force)
|
||||
unless opts[:skip_pseudonym]
|
||||
new_pseudonym = user.find_or_initialize_pseudonym_for_account(root_account)
|
||||
new_pseudonym.save if new_pseudonym && new_pseudonym.changed?
|
||||
|
|
|
@ -496,13 +496,13 @@ class Enrollment < ActiveRecord::Base
|
|||
res
|
||||
end
|
||||
|
||||
def accept
|
||||
return false unless invited?
|
||||
def accept(force = false)
|
||||
return false unless force || invited?
|
||||
ids = nil
|
||||
ids = self.user.dashboard_messages.find_all_by_context_id_and_context_type(self.id, 'Enrollment', :select => "id").map(&:id) if self.user
|
||||
Message.delete_all({:id => ids}) if ids && !ids.empty?
|
||||
update_attribute(:workflow_state, 'active')
|
||||
user.touch
|
||||
touch_user
|
||||
end
|
||||
|
||||
workflow do
|
||||
|
|
|
@ -192,7 +192,7 @@ class Pseudonym < ActiveRecord::Base
|
|||
def validate_unique_id
|
||||
if (!self.account || self.account.email_pseudonyms) && !self.deleted?
|
||||
unless self.unique_id.match(/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i)
|
||||
self.errors.add(:unique_id, t('errors.invalid_email_address', "\"%{email}\" is not a valid email address", :email => self.unique_id))
|
||||
self.errors.add(:unique_id, "not_email")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
|
|
@ -76,6 +76,8 @@ class User < ActiveRecord::Base
|
|||
has_many :invited_enrollments, :class_name => 'Enrollment', :include => [:course, :course_section], :conditions => enrollment_conditions(:invited), :order => 'enrollments.created_at'
|
||||
has_many :current_and_invited_enrollments, :class_name => 'Enrollment', :include => [:course], :order => 'enrollments.created_at',
|
||||
:conditions => enrollment_conditions(:current_and_invited)
|
||||
has_many :current_and_future_enrollments, :class_name => 'Enrollment', :include => [:course], :order => 'enrollments.created_at',
|
||||
:conditions => enrollment_conditions(:current_and_invited, false)
|
||||
has_many :not_ended_enrollments, :class_name => 'Enrollment', :conditions => "enrollments.workflow_state NOT IN ('rejected', 'completed', 'deleted')", :order => 'enrollments.created_at'
|
||||
has_many :concluded_enrollments, :class_name => 'Enrollment', :include => [:course, :course_section], :conditions => enrollment_conditions(:completed), :order => 'enrollments.created_at'
|
||||
has_many :observer_enrollments
|
||||
|
@ -288,8 +290,11 @@ class User < ActiveRecord::Base
|
|||
if value.blank?
|
||||
record.errors.add(attr, "blank")
|
||||
elsif record.validation_root_account
|
||||
record.self_enrollment_course = record.validation_root_account.all_courses.find_by_self_enrollment_code(value)
|
||||
record.errors.add(attr, "invalid") unless record.self_enrollment_course
|
||||
course = record.validation_root_account.self_enrollment_course_for(value)
|
||||
record.self_enrollment_course = course
|
||||
record.errors.add(attr, "invalid") unless course
|
||||
record.errors.add(attr, "already_enrolled") if course && course.user_is_student?(record, :include_future => true)
|
||||
record.errors.add(:birthdate, "too_young") if course && course.self_enrollment_min_age && record.birthdate && record.birthdate > course.self_enrollment_min_age.years.ago
|
||||
else
|
||||
record.errors.add(attr, "account_required")
|
||||
end
|
||||
|
@ -457,9 +462,12 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
# These two methods can be overridden by a plugin if you want to have an approval process for new teachers
|
||||
# These methods can be overridden by a plugin if you want to have an approval
|
||||
# process or implement additional tracking for new users
|
||||
def registration_approval_required?; false; end
|
||||
def new_teacher_registration(form_params = {}); end
|
||||
def new_registration(form_params = {}); end
|
||||
# DEPRECATED, override new_registration instead
|
||||
def new_teacher_registration(form_params = {}); new_registration(form_params); end
|
||||
|
||||
set_broadcast_policy do |p|
|
||||
p.dispatch :new_teacher_registration
|
||||
|
@ -1737,8 +1745,8 @@ class User < ActiveRecord::Base
|
|||
# this method takes an optional {:include_enrollment_uuid => uuid} so that you can pass it the session[:enrollment_uuid] and it will include it.
|
||||
def cached_current_enrollments(opts={})
|
||||
self.shard.activate do
|
||||
res = Rails.cache.fetch([self, 'current_enrollments', opts[:include_enrollment_uuid] ].cache_key) do
|
||||
res = self.current_and_invited_enrollments.with_each_shard
|
||||
res = Rails.cache.fetch([self, 'current_enrollments', opts[:include_enrollment_uuid], opts[:include_future] ].cache_key) do
|
||||
res = (opts[:include_future] ? current_and_future_enrollments : current_and_invited_enrollments).with_each_shard
|
||||
if opts[:include_enrollment_uuid] && pending_enrollment = Enrollment.find_by_uuid_and_workflow_state(opts[:include_enrollment_uuid], "invited")
|
||||
res << pending_enrollment
|
||||
res.uniq!
|
||||
|
|
|
@ -19,6 +19,28 @@ body
|
|||
box-shadow: none
|
||||
#content
|
||||
padding-top: 0
|
||||
|
||||
&.embedded
|
||||
#wrapper-container, #wrapper, #main
|
||||
height: 100%
|
||||
#main
|
||||
min-height: 0
|
||||
#content
|
||||
padding: 1em
|
||||
.embedded-footer
|
||||
position: absolute
|
||||
bottom: 0
|
||||
width: 100%
|
||||
padding: 14px 0 15px
|
||||
margin: 0 -1em !important
|
||||
background-color: #EFEFEF
|
||||
border-top: 1px solid #DDD
|
||||
box-shadow: inset 0 1px 0 white
|
||||
text-align: right
|
||||
.controls
|
||||
margin-left: 15px
|
||||
margin-right: 15px
|
||||
background: #fff
|
||||
|
||||
// so we don't get the non-interactionable content
|
||||
.scripts-not-loaded
|
||||
|
|
|
@ -134,6 +134,11 @@ input[type="url"], input[type="search"], input[type="tel"], input[type="color"],
|
|||
width: 220px; // default input width + 10px of padding that doesn't get applied
|
||||
border: 1px solid #bbb;
|
||||
}
|
||||
select.date-select {
|
||||
width: auto;
|
||||
float: left;
|
||||
margin: 0 3px 0 0;
|
||||
}
|
||||
|
||||
// Make multiple select elements height not fixed
|
||||
select[multiple], select[size] {
|
||||
|
|
|
@ -17,9 +17,6 @@ a {
|
|||
.registration-dialog .spinner {
|
||||
width: 100px;
|
||||
}
|
||||
.select-birthdate {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.help-block-small {
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
<%
|
||||
jammit_css :login
|
||||
@headers = false
|
||||
@body_classes << "modal"
|
||||
content_for :page_title, t('titles.invitation', 'Invitation to %{course}', :course => @context.name)
|
||||
%>
|
||||
|
||||
<% content_for :page_header do %>
|
||||
<h1><%= t('headings.invitation', %{Course Invitation}) %></h1>
|
||||
<% end %>
|
||||
|
||||
<div id="modal-box">
|
||||
<h2><%= t('headings.accept_invitation', %{Accept Enrollment Invitation}) %></h2>
|
||||
<p>
|
||||
<%= t 'details', %{You've been invited to participate in this course, %{course}.
|
||||
To continue the enrollment process, please provide us with your current
|
||||
email address.}, :course => content_tag('b', @context.name) %>
|
||||
</p>
|
||||
<% form_tag course_self_enrollment_path(@context, @context.self_enrollment_code), :class => 'bootstrap-form', :style => 'overflow:hidden' do %>
|
||||
<div style="float:left;padding-top:8px">
|
||||
<label for="enrollment_email" style="display:inline"><%= before_label('email', %{Email Address}) %></label>
|
||||
<input type="text" name="email" id="enrollment_email"/>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"><%= t('buttons.enroll', %{Enroll in Course}) %></button>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
|
@ -1,17 +0,0 @@
|
|||
<%
|
||||
jammit_css :login
|
||||
@headers = false
|
||||
@body_classes << "modal"
|
||||
content_for :page_title, t('titles.invitation', 'Invitation to %{course}', :course => @context.name)
|
||||
%>
|
||||
|
||||
<% @show_left_side = false %>
|
||||
|
||||
<% content_for :page_header do %>
|
||||
<h1><%= t('headings.invitation', %{Course Invitation}) %></h1>
|
||||
<% end %>
|
||||
|
||||
<div id="modal-box">
|
||||
<h2><%= t('headings.received', %{Invitation Received!}) %></h2>
|
||||
<%= t 'details', %{You should get an email at %{email} in the next few minutes. That email will have a link that you can use to finish the registration process. Once you've set up your account you'll be able to access content from %{course}. Welcome to Canvas!}, :email => content_tag('b', params[:email]), :course => content_tag('b', @context.name) %>
|
||||
</div>
|
|
@ -19,15 +19,7 @@
|
|||
<% end %>
|
||||
<% if @context.available? && @context.self_enrollment && @context.open_enrollment && (!@context_enrollment || !@context_enrollment.active?) && !session["role_course_#{@context.id}"] %>
|
||||
<div class="rs-margin-lr rs-margin-top">
|
||||
<a href="#" class="btn button-sidebar-wide self_enrollment_link"><i class="icon-user-add"></i> <%= t('links.join_course', %{Join this Course}) %></a>
|
||||
</div>
|
||||
<div id="self_enrollment_dialog" style="display: none;">
|
||||
<h2><%= image_tag "group.png", :style => "vertical-align: middle;" %> <%= t('headings.joining_a_course', %{Joining a Course}) %></h2>
|
||||
<%= t('details.joining_a_course', %{Once you join a course you will see it show up in your course list. You'll be able to participate in graded portions of the course and communicate directly with the teachers and other students. You'll also see conversations and events from this course show up in your stream and as notifications.}) %>
|
||||
<div class="button-container">
|
||||
<a href="<%= course_self_enrollment_path(@context, @context.self_enrollment_code) %>" class="btn action"><i class="icon-user-add"></i> <span><%= t('links.join_course', %{Join this Course}) %></span></a>
|
||||
<a href="#" class="btn button-secondary cancel_button"><%= t('#buttons.cancel', %{Cancel}) %></a>
|
||||
</div>
|
||||
<a href="<%= enroll_url(@context.self_enrollment_code) %>" title="<%= t('links.join_course', %{Join this Course}) %>" class="btn button-sidebar-wide self_enrollment_link" data-open-as-dialog><i class="icon-user-add"></i> <%= t('links.join_course', %{Join this Course}) %></a>
|
||||
</div>
|
||||
<% elsif @context_enrollment && @context_enrollment.self_enrolled && @context_enrollment.active? && (!session["role_course_#{@context.id}"]) %>
|
||||
<div class="rs-margin-lr rs-margin-top">
|
||||
|
@ -37,8 +29,8 @@
|
|||
<h2><i class="icon-warning"></i> <%= t('headings.confirm_unenroll', %{Confirm Unenrollment}) %></h2>
|
||||
<%= t('details.confirm_unenroll', %{Are you sure you want to unenroll in this course? You will no longer be able to see the course roster or communicate directly with the teachers, and you will no longer see course events in your stream and as notifications.}) %>
|
||||
<div class="button-container">
|
||||
<a href="<%= course_self_unenrollment_path(@context, @context_enrollment.uuid) %>" class="btn action"><i class="icon-end"></i> <span><%= t('links.drop_course', %{Drop this Course}) %></span></a>
|
||||
<a href="#" class="btn button-secondary cancel_button"><%= t('#buttons.cancel', %{Cancel}) %></a>
|
||||
<a href="<%= course_self_unenrollment_path(@context, @context_enrollment.uuid) %>" class="btn btn-primary action"><i class="icon-end"></i> <span><%= t('links.drop_course', %{Drop this Course}) %></span></a>
|
||||
<a href="#" class="btn dialog_closer"><%= t('#buttons.cancel', %{Cancel}) %></a>
|
||||
</div>
|
||||
</div>
|
||||
<% elsif temp_type = session["role_course_#{@context.id}"] %>
|
||||
|
@ -101,34 +93,23 @@ require([
|
|||
'jqueryui/dialog',
|
||||
'compiled/jquery/fixDialogButtons' /* fix dialog formatting */,
|
||||
'jquery.loadingImg' /* loadingImg, loadingImage */,
|
||||
'vendor/jquery.scrollTo' /* /\.scrollTo/ */
|
||||
'vendor/jquery.scrollTo' /* /\.scrollTo/ */,
|
||||
'compiled/behaviors/openAsDialog'
|
||||
], function($) {
|
||||
|
||||
$(document).ready(function() {
|
||||
var $selfUnenrollmentDialog = $("#self_unenrollment_dialog");
|
||||
$(".self_unenrollment_link").click(function(event) {
|
||||
$("#self_unenrollment_dialog").dialog({
|
||||
title: <%= raw t('titles.drop_course', "Drop this Course").to_json %>
|
||||
$selfUnenrollmentDialog.dialog({
|
||||
title: <%= raw t('titles.drop_course', "Drop this Course").to_json %>,
|
||||
}).fixDialogButtons();
|
||||
});
|
||||
$("#self_unenrollment_dialog .action").click(function() {
|
||||
$("#self_unenrollment_dialog a.btn").attr('disabled', true);
|
||||
$(this).find("span").text(<%= raw t('dropping_course', "Dropping Course...").to_json %>);
|
||||
});
|
||||
$("#self_unenrollment_dialog .cancel_button").click(function() {
|
||||
$("#self_enrollment_dialog").dialog('close');
|
||||
});
|
||||
$(".self_enrollment_link").click(function(event) {
|
||||
$("#self_enrollment_dialog").dialog({
|
||||
title: <%= raw t('titles.join_course', "Join this Course").to_json %>
|
||||
$selfUnenrollmentDialog.on('click', '.action', function() {
|
||||
$selfUnenrollmentDialog.disableWhileLoading($.Deferred());
|
||||
$.ajaxJSON($(this).attr('href'), 'POST', {}, function() {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
$("#self_enrollment_dialog .action").click(function() {
|
||||
$("#self_enrollment_dialog a.btn").attr('disabled', true);
|
||||
$(this).find("span").text(<%= raw t('joining_course', "Joining Course...").to_json %>);
|
||||
});
|
||||
$("#self_enrollment_dialog .cancel_button").click(function() {
|
||||
$("#self_enrollment_dialog").dialog('close');
|
||||
});
|
||||
$(".re_send_confirmation_link").click(function(event) {
|
||||
event.preventDefault();
|
||||
var $link = $(this);
|
||||
|
|
|
@ -50,8 +50,9 @@
|
|||
<div class="controls">
|
||||
<input type="hidden" name="user[initial_enrollment_type]" value="student">
|
||||
<input type="hidden" name="self_enrollment" value="1">
|
||||
<input type="hidden" name="pseudonym_type" value="username">
|
||||
<button class="btn btn-primary" type="submit">{{#t "buttons.start_learning"}}Start Learning{{/t}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<div class="form-horizontal bootstrap-form" id="enroll_form">
|
||||
<p><%= mt :already_enrolled, "You are already enrolled in **%{course}**.", :course => @course.name %></p>
|
||||
|
||||
|
||||
<% if params[:login_warning] %>
|
||||
<p><%= t :switch_users, "You are currently signed in as *%{user}*. **Sign in as another user**.", :user => @current_user.name, :wrapper => {'*' => '<b>\1</b>', '**' => link_to('\1', "/logout", :id => 'logout_link')} %></p>
|
||||
<% end %>
|
||||
</div>
|
|
@ -0,0 +1,22 @@
|
|||
<form action="<%= url_for(request.query_parameters) %>" method="post" id="enroll_form" class="form-horizontal bootstrap-form">
|
||||
<%= registration_summary || content_tag(:p, mt(:getting_started, "You are enrolling in **%{course}**.", :course => @course.name)) %>
|
||||
<p><%= mt :log_in, "Please enter your email and password:" %></p>
|
||||
<input type="hidden" name="user_type" value="existing">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="student_email"><%= t "labels.email", "Email" %></label>
|
||||
<div class="controls">
|
||||
<input type="text" id="student_email" name="pseudonym[unique_id]">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="student_password"><%= t "labels.password", "Password" %></label>
|
||||
<div class="controls">
|
||||
<input type="password" id="student_password" name="pseudonym[password]">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group embedded-footer">
|
||||
<div class="controls">
|
||||
<button class="btn btn-primary" type="submit"><%= t "buttons.enroll_in_course", "Enroll in Course" %></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
|
@ -0,0 +1,61 @@
|
|||
<form action="<%= url_for(request.query_parameters) %>" method="post" id="enroll_form" class="form-horizontal bootstrap-form">
|
||||
<%= registration_summary || content_tag(:p, mt(:getting_started, "You are enrolling in **%{course}**.", :course => @course.name)) %>
|
||||
<p><%= t(:enter_email, "Please enter your email address:") %></p>
|
||||
<input type="hidden" name="user[initial_enrollment_type]" value="student">
|
||||
<input type="hidden" name="self_enrollment" value="1">
|
||||
<input type="hidden" name="self_enrollment_mode" value="email">
|
||||
<input type="hidden" name="user[self_enrollment_code]" value="<%= params[:self_enrollment_code] %>">
|
||||
<div class="control-group" id="email_info">
|
||||
<label class="control-label" for="student_email"><%= t "labels.email", "Email" %></label>
|
||||
<div class="controls">
|
||||
<input type="text" id="student_email" name="pseudonym[unique_id]">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" id="user_type">
|
||||
<div class="controls">
|
||||
<label class="radio">
|
||||
<input type="radio" name="user_type" value="new" id="user_type_new">
|
||||
<%= t "new_user", "I am a new user" %>
|
||||
</label>
|
||||
<label class="radio">
|
||||
<input type="radio" name="user_type" value="existing" id="user_type_existing">
|
||||
<%= t "existing_user", "I already have a %{institution_name} login", :institution_name => @domain_root_account.short_name %>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user_info" id="existing_user_info" style="<%= hidden %>">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="student_password"><%= t "labels.password", "Password" %></label>
|
||||
<div class="controls">
|
||||
<input type="password" id="student_password" name="pseudonym[password]">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user_info" id="new_user_info" style="<%= hidden %>">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="student_name"><%= t "labels.name", "Full Name" %></label>
|
||||
<div class="controls">
|
||||
<input type="text" id="student_name" name="user[name]">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="student_birthdate"><%= t "labels.birthdate", "Birth Date" %></label>
|
||||
<div class="controls">
|
||||
<%= date_select 'user', 'birthdate', {:start_year => Time.now.year - 1, :end_year => Time.now.year - 125, :include_blank => true}, :class => 'date-select', :id => 'student_birthdate' %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="user[terms_of_use]" value="1">
|
||||
<%= t "agree_to_terms", "You agree to the *terms of use*.", :wrapper => link_to('\1', "http://www.instructure.com/terms-of-use", :target => "_new") %>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group embedded-footer">
|
||||
<div class="controls">
|
||||
<button class="btn btn-primary" style="visibility: hidden" id="submit_button" type="submit"><%= t "buttons.enroll_in_course", "Enroll in Course" %></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
|
@ -0,0 +1,12 @@
|
|||
<form action="<%= url_for(request.query_parameters) %>" method="post" id="enroll_form" class="form-horizontal bootstrap-form">
|
||||
<input type="hidden" name="user_type" value="authenticated">
|
||||
<%= registration_summary || content_tag(:p, mt(:getting_started, "You are enrolling in **%{course}**", :course => @course.name)) %>
|
||||
<% if params[:login_warning] %>
|
||||
<p><%= t :switch_users, "You are currently signed in as *%{user}*. **Sign in as another user**.", :user => @current_user.name, :wrapper => {'*' => '<b>\1</b>', '**' => link_to('\1', "/logout", :id => 'logout_link')} %></p>
|
||||
<% end %>
|
||||
<div class="control-group embedded-footer">
|
||||
<div class="controls">
|
||||
<button class="btn btn-primary" type="submit"><%= t "buttons.enroll_in_course", "Enroll in Course" %></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
|
@ -0,0 +1,22 @@
|
|||
<div class="form-horizontal bootstrap-form" id="enroll_form">
|
||||
<p><%= mt :already_enrolled, "You have successfully enrolled in **%{course}**.", :course => @course.name %></p>
|
||||
|
||||
<% if @course.available? %>
|
||||
<%= registration_summary %>
|
||||
<div class="control-group embedded-footer">
|
||||
<div class="controls">
|
||||
<%= @extra_actions %>
|
||||
<a class="btn" href="<%= dashboard_url(:registration_success => params[:just_created]) %>" target="_top"><%= t "buttons.go_to_dashboard", "Go to your Dashboard" %></a>
|
||||
<a class="btn btn-primary" href="<%= course_url(@course, :registration_success => params[:just_created]) %>" target="_top"><%= t "buttons.go_to_course", "Go to the Course" %></a>
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
<%= registration_summary || content_tag(:p, t(:not_available_yet, "We'll send you an email shortly before the course begins.")) %>
|
||||
<div class="control-group embedded-footer">
|
||||
<div class="controls">
|
||||
<%= @extra_actions %>
|
||||
<a class="btn btn-primary" href="<%= dashboard_url(:registration_success => params[:just_created]) %>" target="_top"><%= t "buttons.go_to_dashboard", "Go to your Dashboard" %></a>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
|
@ -0,0 +1,34 @@
|
|||
<%
|
||||
content_for :page_title, t('titles.course_enrollment', 'Enroll in %{course}', :course => @course.name)
|
||||
|
||||
js_bundle :self_enrollment
|
||||
|
||||
if !@current_user && !@embedded_view
|
||||
jammit_css :login
|
||||
@headers = false
|
||||
@body_classes << "modal"
|
||||
end
|
||||
%>
|
||||
|
||||
<div id="modal-box">
|
||||
<% if !@embedded_view %>
|
||||
<h2><%= t :course_enrollment, "Course Enrollment" %></h2>
|
||||
<% end %>
|
||||
<% if @current_user %>
|
||||
<% if @course.user_is_student?(@current_user, :include_future => true) %>
|
||||
<% if params[:enrolled] %>
|
||||
<%= render :partial => 'successfully_enrolled' %>
|
||||
<% else %>
|
||||
<%= render :partial => 'already_enrolled' %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= render :partial => 'confirm_enrollments' %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<% if @domain_root_account.open_registration? %>
|
||||
<%= render :partial => 'authenticate_or_register' %>
|
||||
<% else %>
|
||||
<%= render :partial => 'authenticate' %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
|
@ -11,12 +11,7 @@
|
|||
<%= t('welcome_to_happiness', 'Welcome to Canvas!') %>
|
||||
</div>
|
||||
<div class="message">
|
||||
<p><%= t 'no_courses_message', <<-BODY
|
||||
You don't have any courses, so this page won't be very exciting for now.
|
||||
Once you've created or signed up for courses, you'll start to see
|
||||
conversations from all of your classes.
|
||||
BODY
|
||||
%></p>
|
||||
<p><%= welcome_message %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -146,8 +146,8 @@ ActionController::Routing::Routes.draw do |map|
|
|||
# and the application_helper method :context_url to make retrieving
|
||||
# these contexts, and also generating context-specific urls, easier.
|
||||
map.resources :courses do |course|
|
||||
course.self_enrollment 'self_enrollment/:self_enrollment', :controller => 'courses', :action => 'self_enrollment'
|
||||
course.self_unenrollment 'self_unenrollment/:self_unenrollment', :controller => 'courses', :action => 'self_unenrollment'
|
||||
course.self_enrollment 'self_enrollment/:self_enrollment', :controller => 'courses', :action => 'self_enrollment', :conditions => {:method => :get}
|
||||
course.self_unenrollment 'self_unenrollment/:self_unenrollment', :controller => 'courses', :action => 'self_unenrollment', :conditions => {:method => :post}
|
||||
course.restore 'restore', :controller => 'courses', :action => 'restore'
|
||||
course.backup 'backup', :controller => 'courses', :action => 'backup'
|
||||
course.unconclude 'unconclude', :controller => 'courses', :action => 'unconclude'
|
||||
|
@ -503,6 +503,8 @@ ActionController::Routing::Routes.draw do |map|
|
|||
map.register "register", :controller => "users", :action => "new"
|
||||
map.register_from_website "register_from_website", :controller => "users", :action => "new"
|
||||
map.registered "registered", :controller => "users", :action => "registered"
|
||||
map.enroll 'enroll/:self_enrollment_code', :controller => 'self_enrollments', :action => 'new', :conditions => {:method => :get}
|
||||
map.enroll_frd 'enroll/:self_enrollment_code', :controller => 'self_enrollments', :action => 'create', :conditions => {:method => :post}
|
||||
map.services 'services', :controller => 'users', :action => 'services'
|
||||
map.bookmark_search 'search/bookmarks', :controller => 'users', :action => 'bookmark_search'
|
||||
map.search_rubrics 'search/rubrics', :controller => "search", :action => "rubrics"
|
||||
|
|
|
@ -420,8 +420,12 @@ def self.date_component(start_date, style=:normal)
|
|||
end
|
||||
end
|
||||
translated = t(*args)
|
||||
translated = ERB::Util.h(translated) unless translated.html_safe?
|
||||
result = RDiscount.new(translated).to_html.strip
|
||||
markdown(translated, inlinify)
|
||||
end
|
||||
|
||||
def markdown(string, inlinify = :auto)
|
||||
string = ERB::Util.h(string) unless string.html_safe?
|
||||
result = RDiscount.new(string).to_html.strip
|
||||
# Strip wrapping <p></p> if inlinify == :auto && they completely wrap the result && there are not multiple <p>'s
|
||||
result.gsub!(/<\/?p>/, '') if inlinify == :auto && result =~ /\A<p>.*<\/p>\z/m && !(result =~ /.*<p>.*<p>.*/m)
|
||||
result.html_safe.strip
|
||||
|
|
|
@ -69,7 +69,8 @@ define([
|
|||
url: url,
|
||||
dataType: "json",
|
||||
type: submit_type,
|
||||
success: function(data) {
|
||||
success: function(data, textStatus, xhr) {
|
||||
updateCSRFToken(xhr);
|
||||
data = data || {};
|
||||
var page_view_id = null;
|
||||
if(xhr && xhr.getResponseHeader && (page_view_id = xhr.getResponseHeader("X-Canvas-Page-View-Id"))) {
|
||||
|
@ -88,9 +89,12 @@ define([
|
|||
success(data, xhr);
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
error: function(xhr) {
|
||||
updateCSRFToken(xhr);
|
||||
ajaxError.apply(this, arguments);
|
||||
},
|
||||
complete: function(xhr) {
|
||||
},
|
||||
data: data
|
||||
};
|
||||
if(options && options.timeout) {
|
||||
|
@ -121,6 +125,17 @@ define([
|
|||
return null;
|
||||
};
|
||||
|
||||
function updateCSRFToken(xhr) {
|
||||
// in case the server has generated a new one, e.g. session reset on
|
||||
// login actions
|
||||
var token = xhr.getResponseHeader('X-CSRF-Token');
|
||||
if (token) {
|
||||
ENV.AUTHENTICITY_TOKEN = token;
|
||||
// TODO: stop using me
|
||||
$("#ajax_authenticity_token").text(token);
|
||||
}
|
||||
}
|
||||
|
||||
// Defines a default error for all ajax requests. Will always be called
|
||||
// in the development environment, and as a last-ditch error catching
|
||||
// otherwise. See "ajax_errors.js"
|
||||
|
|
|
@ -685,59 +685,21 @@ describe CoursesController do
|
|||
Account.default.update_attribute(:settings, :self_enrollment => 'any', :open_registration => true)
|
||||
end
|
||||
|
||||
it "should enroll the currently logged in user" do
|
||||
it "should redirect to the new self enrollment form" do
|
||||
course(:active_all => true)
|
||||
@course.update_attribute(:self_enrollment, true)
|
||||
user
|
||||
user_session(@user, @pseudonym)
|
||||
|
||||
get 'self_enrollment', :course_id => @course.id, :self_enrollment => @course.self_enrollment_code.dup
|
||||
response.should redirect_to(course_url(@course))
|
||||
flash[:notice].should_not be_empty
|
||||
@user.enrollments.length.should == 1
|
||||
@enrollment = @user.enrollments.first
|
||||
@enrollment.course.should == @course
|
||||
@enrollment.workflow_state.should == 'active'
|
||||
@enrollment.should be_self_enrolled
|
||||
get 'self_enrollment', :course_id => @course.id, :self_enrollment => @course.self_enrollment_code
|
||||
response.should redirect_to(enroll_url(@course.self_enrollment_code))
|
||||
end
|
||||
|
||||
it "should enroll the currently logged in user using the long code" do
|
||||
it "should redirect to the new self enrollment form if using a long code" do
|
||||
course(:active_all => true)
|
||||
@course.update_attribute(:self_enrollment, true)
|
||||
user
|
||||
user_session(@user, @pseudonym)
|
||||
|
||||
get 'self_enrollment', :course_id => @course.id, :self_enrollment => @course.long_self_enrollment_code.dup
|
||||
response.should redirect_to(course_url(@course))
|
||||
flash[:notice].should_not be_empty
|
||||
@user.enrollments.length.should == 1
|
||||
@enrollment = @user.enrollments.first
|
||||
@enrollment.course.should == @course
|
||||
@enrollment.workflow_state.should == 'active'
|
||||
@enrollment.should be_self_enrolled
|
||||
get 'self_enrollment', :course_id => @course.id, :self_enrollment => @course.long_self_enrollment_code
|
||||
response.should redirect_to(enroll_url(@course.self_enrollment_code))
|
||||
end
|
||||
|
||||
it "should create a compatible pseudonym" do
|
||||
@account2 = Account.create!
|
||||
course(:active_all => true, :account => @account2)
|
||||
@course.update_attribute(:self_enrollment, true)
|
||||
user_with_pseudonym(:active_all => 1, :username => 'jt@instructure.com')
|
||||
user_session(@user, @pseudonym)
|
||||
@new_pseudonym = Pseudonym.new(:account => @account2, :unique_id => 'jt@instructure.com', :user => @user)
|
||||
User.any_instance.stubs(:find_or_initialize_pseudonym_for_account).with(@account2).once.returns(@new_pseudonym)
|
||||
|
||||
get 'self_enrollment', :course_id => @course.id, :self_enrollment => @course.self_enrollment_code.dup
|
||||
response.should redirect_to(course_url(@course))
|
||||
flash[:notice].should_not be_empty
|
||||
@user.enrollments.length.should == 1
|
||||
@enrollment = @user.enrollments.first
|
||||
@enrollment.course.should == @course
|
||||
@enrollment.workflow_state.should == 'active'
|
||||
@enrollment.should be_self_enrolled
|
||||
@user.reload.pseudonyms.length.should == 2
|
||||
end
|
||||
|
||||
it "should not enroll for incorrect code" do
|
||||
it "should return to the course page for an incorrect code" do
|
||||
course(:active_all => true)
|
||||
@course.update_attribute(:self_enrollment, true)
|
||||
user
|
||||
|
@ -748,59 +710,24 @@ describe CoursesController do
|
|||
@user.enrollments.length.should == 0
|
||||
end
|
||||
|
||||
it "should not enroll if self_enrollment is disabled" do
|
||||
it "should return to the course page if self_enrollment is disabled" do
|
||||
course(:active_all => true)
|
||||
user
|
||||
user_session(@user)
|
||||
|
||||
get 'self_enrollment', :course_id => @course.id, :self_enrollment => @course.long_self_enrollment_code.dup
|
||||
get 'self_enrollment', :course_id => @course.id, :self_enrollment => @course.long_self_enrollment_code
|
||||
response.should redirect_to(course_url(@course))
|
||||
@user.enrollments.length.should == 0
|
||||
end
|
||||
|
||||
it "should redirect to login without open registration" do
|
||||
Account.default.update_attribute(:settings, :open_registration => false)
|
||||
course(:active_all => true)
|
||||
@course.update_attribute(:self_enrollment, true)
|
||||
|
||||
get 'self_enrollment', :course_id => @course.id, :self_enrollment => @course.self_enrollment_code.dup
|
||||
response.should redirect_to(login_url)
|
||||
end
|
||||
|
||||
it "should render for non-logged-in user" do
|
||||
course(:active_all => true)
|
||||
@course.update_attribute(:self_enrollment, true)
|
||||
|
||||
get 'self_enrollment', :course_id => @course.id, :self_enrollment => @course.self_enrollment_code.dup
|
||||
response.should be_success
|
||||
response.should render_template('open_enrollment')
|
||||
end
|
||||
|
||||
it "should create a creation_pending user" do
|
||||
course(:active_all => true)
|
||||
@course.update_attribute(:self_enrollment, true)
|
||||
|
||||
post 'self_enrollment', :course_id => @course.id, :self_enrollment => @course.self_enrollment_code.dup, :email => 'bracken@instructure.com'
|
||||
response.should be_success
|
||||
response.should render_template('open_enrollment_confirmed')
|
||||
@course.student_enrollments.length.should == 1
|
||||
@enrollment = @course.student_enrollments.first
|
||||
@enrollment.should be_self_enrolled
|
||||
@enrollment.should be_invited
|
||||
@enrollment.user.should be_creation_pending
|
||||
@enrollment.user.email_channel.path.should == 'bracken@instructure.com'
|
||||
@enrollment.user.email_channel.should be_unconfirmed
|
||||
@enrollment.user.pseudonyms.should be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET 'self_unenrollment'" do
|
||||
describe "POST 'self_unenrollment'" do
|
||||
it "should unenroll" do
|
||||
course_with_student_logged_in(:active_all => true)
|
||||
@enrollment.update_attribute(:self_enrolled, true)
|
||||
|
||||
get 'self_unenrollment', :course_id => @course.id, :self_unenrollment => @enrollment.uuid
|
||||
response.should redirect_to(course_url(@course))
|
||||
post 'self_unenrollment', :course_id => @course.id, :self_unenrollment => @enrollment.uuid
|
||||
response.should be_success
|
||||
@enrollment.reload
|
||||
@enrollment.should be_completed
|
||||
end
|
||||
|
@ -809,8 +736,8 @@ describe CoursesController do
|
|||
course_with_student_logged_in(:active_all => true)
|
||||
@enrollment.update_attribute(:self_enrolled, true)
|
||||
|
||||
get 'self_unenrollment', :course_id => @course.id, :self_unenrollment => 'abc'
|
||||
response.should redirect_to(course_url(@course))
|
||||
post 'self_unenrollment', :course_id => @course.id, :self_unenrollment => 'abc'
|
||||
response.status.should =~ /400 Bad Request/
|
||||
@enrollment.reload
|
||||
@enrollment.should be_active
|
||||
end
|
||||
|
@ -818,8 +745,8 @@ describe CoursesController do
|
|||
it "should not unenroll a non-self-enrollment" do
|
||||
course_with_student_logged_in(:active_all => true)
|
||||
|
||||
get 'self_unenrollment', :course_id => @course.id, :self_unenrollment => @enrollment.uuid
|
||||
response.should redirect_to(course_url(@course))
|
||||
post 'self_unenrollment', :course_id => @course.id, :self_unenrollment => @enrollment.uuid
|
||||
response.status.should =~ /400 Bad Request/
|
||||
@enrollment.reload
|
||||
@enrollment.should be_active
|
||||
end
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
#
|
||||
# Copyright (C) 2012 Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
||||
|
||||
describe SelfEnrollmentsController do
|
||||
describe "GET 'new'" do
|
||||
before do
|
||||
course(:active_all => true)
|
||||
@course.update_attribute(:self_enrollment, true)
|
||||
end
|
||||
|
||||
it "should render if the course is open for enrollment" do
|
||||
get 'new', :self_enrollment_code => @course.self_enrollment_code
|
||||
response.should be_success
|
||||
end
|
||||
|
||||
it "should not render for an incorrect code" do
|
||||
lambda {
|
||||
get 'new', :self_enrollment_code => 'abc'
|
||||
}.should raise_exception(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
it "should not render if self_enrollment is disabled" do
|
||||
code = @course.self_enrollment_code
|
||||
@course.update_attribute(:self_enrollment, false)
|
||||
|
||||
lambda {
|
||||
get 'new', :self_enrollment_code => code
|
||||
}.should raise_exception(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST 'create'" do
|
||||
before do
|
||||
Account.default.update_attribute(:settings, :self_enrollment => 'any', :open_registration => true)
|
||||
course(:active_all => true)
|
||||
@course.update_attribute(:self_enrollment, true)
|
||||
end
|
||||
|
||||
it "should enroll the currently logged in user" do
|
||||
user
|
||||
user_session(@user, @pseudonym)
|
||||
|
||||
post 'create', :self_enrollment_code => @course.self_enrollment_code
|
||||
response.should be_success
|
||||
@user.enrollments.length.should == 1
|
||||
@enrollment = @user.enrollments.first
|
||||
@enrollment.course.should == @course
|
||||
@enrollment.workflow_state.should == 'active'
|
||||
@enrollment.should be_self_enrolled
|
||||
end
|
||||
|
||||
it "should not enroll an unauthenticated user" do
|
||||
post 'create', :self_enrollment_code => @course.self_enrollment_code
|
||||
response.should redirect_to(login_url)
|
||||
end
|
||||
|
||||
it "should not enroll for an incorrect code" do
|
||||
user
|
||||
user_session(@user)
|
||||
|
||||
lambda {
|
||||
post 'create', :self_enrollment_code => 'abc'
|
||||
}.should raise_exception(ActiveRecord::RecordNotFound)
|
||||
@user.enrollments.length.should == 0
|
||||
end
|
||||
|
||||
it "should not enroll if self_enrollment is disabled" do
|
||||
code = @course.self_enrollment_code
|
||||
@course.update_attribute(:self_enrollment, false)
|
||||
user
|
||||
user_session(@user)
|
||||
|
||||
lambda {
|
||||
post 'create', :self_enrollment_code => code
|
||||
}.should raise_exception(ActiveRecord::RecordNotFound)
|
||||
@user.enrollments.length.should == 0
|
||||
end
|
||||
end
|
||||
end
|
|
@ -287,6 +287,20 @@ describe UsersController do
|
|||
response.should be_success
|
||||
end
|
||||
|
||||
it "should require email pseudonyms by default" do
|
||||
post 'create', :pseudonym => { :unique_id => 'jacob' }, :user => { :name => 'Jacob Fugal', :terms_of_use => '1' }
|
||||
response.status.should =~ /400 Bad Request/
|
||||
json = JSON.parse(response.body)
|
||||
json["errors"]["pseudonym"]["unique_id"].should be_present
|
||||
end
|
||||
|
||||
it "should require email pseudonyms if not self enrolling" do
|
||||
post 'create', :pseudonym => { :unique_id => 'jacob' }, :user => { :name => 'Jacob Fugal', :terms_of_use => '1' }, :pseudonym_type => 'username'
|
||||
response.status.should =~ /400 Bad Request/
|
||||
json = JSON.parse(response.body)
|
||||
json["errors"]["pseudonym"]["unique_id"].should be_present
|
||||
end
|
||||
|
||||
it "should validate the self enrollment code" do
|
||||
post 'create', :pseudonym => { :unique_id => 'jacob@instructure.com', :password => 'asdfasdf', :password_confirmation => 'asdfasdf' }, :user => { :name => 'Jacob Fugal', :terms_of_use => '1', :birthdate => 20.years.ago.strftime('%Y-%m-%d'), :self_enrollment_code => 'omg ... not valid', :initial_enrollment_type => 'student' }, :self_enrollment => '1'
|
||||
response.status.should =~ /400 Bad Request/
|
||||
|
@ -302,11 +316,22 @@ describe UsersController do
|
|||
u.pseudonym.should be_password_auto_generated
|
||||
end
|
||||
|
||||
it "should require a password if self enrolling" do
|
||||
it "should ignore the password if self enrolling with an email pseudonym" do
|
||||
course(:active_all => true)
|
||||
@course.update_attribute(:self_enrollment, true)
|
||||
|
||||
post 'create', :pseudonym => { :unique_id => 'jacob@instructure.com' }, :user => { :name => 'Jacob Fugal', :terms_of_use => '1', :birthdate => 20.years.ago.strftime('%Y-%m-%d'), :self_enrollment_code => @course.self_enrollment_code, :initial_enrollment_type => 'student' }, :self_enrollment => '1'
|
||||
post 'create', :pseudonym => { :unique_id => 'jacob@instructure.com', :password => 'asdfasdf', :password_confirmation => 'asdfasdf' }, :user => { :name => 'Jacob Fugal', :terms_of_use => '1', :birthdate => 20.years.ago.strftime('%Y-%m-%d'), :self_enrollment_code => @course.self_enrollment_code, :initial_enrollment_type => 'student' }, :pseudonym_type => 'email', :self_enrollment => '1'
|
||||
response.should be_success
|
||||
u = User.find_by_name 'Jacob Fugal'
|
||||
u.should be_pre_registered
|
||||
u.pseudonym.should be_password_auto_generated
|
||||
end
|
||||
|
||||
it "should require a password if self enrolling with a non-email pseudonym" do
|
||||
course(:active_all => true)
|
||||
@course.update_attribute(:self_enrollment, true)
|
||||
|
||||
post 'create', :pseudonym => { :unique_id => 'jacob' }, :user => { :name => 'Jacob Fugal', :terms_of_use => '1', :birthdate => 20.years.ago.strftime('%Y-%m-%d'), :self_enrollment_code => @course.self_enrollment_code, :initial_enrollment_type => 'student' }, :pseudonym_type => 'username', :self_enrollment => '1'
|
||||
response.status.should =~ /400 Bad Request/
|
||||
json = JSON.parse(response.body)
|
||||
json["errors"]["pseudonym"]["password"].should be_present
|
||||
|
@ -317,7 +342,7 @@ describe UsersController do
|
|||
course(:active_all => true)
|
||||
@course.update_attribute(:self_enrollment, true)
|
||||
|
||||
post 'create', :pseudonym => { :unique_id => 'jacob@instructure.com', :password => 'asdfasdf', :password_confirmation => 'asdfasdf' }, :user => { :name => 'Jacob Fugal', :terms_of_use => '1', :birthdate => 20.years.ago.strftime('%Y-%m-%d'), :self_enrollment_code => @course.self_enrollment_code, :initial_enrollment_type => 'student' }, :self_enrollment => '1'
|
||||
post 'create', :pseudonym => { :unique_id => 'jacob', :password => 'asdfasdf', :password_confirmation => 'asdfasdf' }, :user => { :name => 'Jacob Fugal', :terms_of_use => '1', :birthdate => 20.years.ago.strftime('%Y-%m-%d'), :self_enrollment_code => @course.self_enrollment_code, :initial_enrollment_type => 'student' }, :pseudonym_type => 'username', :self_enrollment => '1'
|
||||
response.should be_success
|
||||
u = User.find_by_name 'Jacob Fugal'
|
||||
@course.students.should include(u)
|
||||
|
@ -361,6 +386,20 @@ describe UsersController do
|
|||
p.user.should be_pre_registered
|
||||
end
|
||||
|
||||
it "should create users with non-email pseudonyms" do
|
||||
account = Account.create!
|
||||
user_with_pseudonym(:account => account)
|
||||
account.add_user(@user)
|
||||
user_session(@user, @pseudonym)
|
||||
post 'create', :format => 'json', :account_id => account.id, :pseudonym => { :unique_id => 'jacob', :sis_user_id => 'testsisid' }, :user => { :name => 'Jacob Fugal' }
|
||||
response.should be_success
|
||||
p = Pseudonym.find_by_unique_id('jacob')
|
||||
p.account_id.should == account.id
|
||||
p.should be_active
|
||||
p.sis_user_id.should == 'testsisid'
|
||||
p.user.should be_pre_registered
|
||||
end
|
||||
|
||||
it "should not allow an admin to set the sis id when creating a user if they don't have privileges to manage sis" do
|
||||
account = Account.create!
|
||||
admin = account_admin_user_with_role_changes(:account => account, :role_changes => {'manage_sis' => false})
|
||||
|
|
|
@ -37,7 +37,7 @@ describe "site-wide" do
|
|||
end
|
||||
|
||||
it "should set the x-ua-compatible http header" do
|
||||
get "/"
|
||||
get "/login"
|
||||
response['x-ua-compatible'].should == "IE=edge,chrome=1"
|
||||
end
|
||||
|
||||
|
@ -60,8 +60,10 @@ describe "site-wide" do
|
|||
end
|
||||
|
||||
it "should not set x-frame-options when on a files domain" do
|
||||
user_session user(:active_all => true)
|
||||
attachment_model(:context => @user)
|
||||
FilesController.any_instance.expects(:files_domain?).returns(true)
|
||||
get "http://files-test.host/files/1/download"
|
||||
get "http://files-test.host/files/#{@attachment.id}/download"
|
||||
response['x-frame-options'].should be_nil
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
require File.expand_path(File.dirname(__FILE__) + '/common')
|
||||
|
||||
describe "self enrollment" do
|
||||
it_should_behave_like "in-process server selenium tests"
|
||||
|
||||
shared_examples_for "open registration" do
|
||||
before do
|
||||
Account.default.update_attribute(:settings, :self_enrollment => 'any', :open_registration => true)
|
||||
course(:active_all => active_course)
|
||||
@course.update_attribute(:self_enrollment, true)
|
||||
end
|
||||
|
||||
it "should register a new user" do
|
||||
get "/enroll/#{@course.self_enrollment_code}"
|
||||
f("#student_email").send_keys('new@example.com')
|
||||
f('#user_type_new').click
|
||||
f("#student_name").send_keys('new guy')
|
||||
f("#enroll_form select[name='user[birthdate(1i)]'] option[value='#{Time.now.year - 20}']").click
|
||||
f("#enroll_form select[name='user[birthdate(2i)]'] option[value='1']").click
|
||||
f("#enroll_form select[name='user[birthdate(3i)]'] option[value='1']").click
|
||||
f('#enroll_form input[name="user[terms_of_use]"]').click
|
||||
expect_new_page_load {
|
||||
submit_form("#enroll_form")
|
||||
}
|
||||
f('.btn-primary').text.should eql primary_action
|
||||
get "/"
|
||||
assert_valid_dashboard
|
||||
end
|
||||
|
||||
it "should authenticate and register an existing user" do
|
||||
user_with_pseudonym(:active_all => true, :username => "existing@example.com", :password => "asdfasdf")
|
||||
get "/enroll/#{@course.self_enrollment_code}"
|
||||
f("#student_email").send_keys("existing@example.com")
|
||||
f('#user_type_existing').click
|
||||
f("#student_password").send_keys("asdfasdf")
|
||||
expect_new_page_load {
|
||||
submit_form("#enroll_form")
|
||||
}
|
||||
f('.btn-primary').text.should eql primary_action
|
||||
get "/"
|
||||
assert_valid_dashboard
|
||||
end
|
||||
|
||||
it "should register an authenticated user" do
|
||||
user_logged_in
|
||||
get "/enroll/#{@course.self_enrollment_code}"
|
||||
# no option to log in/register, since already authenticated
|
||||
f("input[name='pseudonym[unique_id]']").should be_nil
|
||||
expect_new_page_load {
|
||||
submit_form("#enroll_form")
|
||||
}
|
||||
f('.btn-primary').text.should eql primary_action
|
||||
get "/"
|
||||
assert_valid_dashboard
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for "closed registration" do
|
||||
before do
|
||||
course(:active_all => active_course)
|
||||
@course.update_attribute(:self_enrollment, true)
|
||||
end
|
||||
|
||||
it "should not register a new user" do
|
||||
get "/enroll/#{@course.self_enrollment_code}"
|
||||
f("input[type=radio][name=user_type]").should be_nil
|
||||
f("input[name='user[name]']").should be_nil
|
||||
end
|
||||
|
||||
it "should authenticate and register an existing user" do
|
||||
user_with_pseudonym(:active_all => true, :username => "existing@example.com", :password => "asdfasdf")
|
||||
get "/enroll/#{@course.self_enrollment_code}"
|
||||
f("#student_email").send_keys("existing@example.com")
|
||||
f("#student_password").send_keys("asdfasdf")
|
||||
expect_new_page_load {
|
||||
submit_form("#enroll_form")
|
||||
}
|
||||
f('.btn-primary').text.should eql primary_action
|
||||
get "/"
|
||||
assert_valid_dashboard
|
||||
end
|
||||
|
||||
it "should register an authenticated user" do
|
||||
user_logged_in
|
||||
get "/enroll/#{@course.self_enrollment_code}"
|
||||
# no option to log in/register, since already authenticated
|
||||
f("input[name='pseudonym[unique_id]']").should be_nil
|
||||
expect_new_page_load {
|
||||
submit_form("#enroll_form")
|
||||
}
|
||||
f('.btn-primary').text.should eql primary_action
|
||||
get "/"
|
||||
assert_valid_dashboard
|
||||
end
|
||||
end
|
||||
|
||||
context "in a published course" do
|
||||
let(:active_course){ true }
|
||||
let(:primary_action){ "Go to the Course" }
|
||||
let(:assert_valid_dashboard) {
|
||||
f('#courses_menu_item').should include_text("Courses")
|
||||
}
|
||||
|
||||
context "with open registration" do
|
||||
it_should_behave_like "open registration"
|
||||
end
|
||||
context "without open registration" do
|
||||
it_should_behave_like "closed registration"
|
||||
end
|
||||
end
|
||||
|
||||
context "in an unpublished course" do
|
||||
let(:active_course){ false }
|
||||
let(:primary_action){ "Go to your Dashboard" }
|
||||
let(:assert_valid_dashboard) {
|
||||
f('#courses_menu_item').should include_text("Home")
|
||||
f('#dashboard').should include_text("You've enrolled in one or more courses that have not started yet")
|
||||
}
|
||||
context "with open registration" do
|
||||
it_should_behave_like "open registration"
|
||||
end
|
||||
context "without open registration" do
|
||||
it_should_behave_like "closed registration"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -202,9 +202,9 @@ describe "users" do
|
|||
form = fj('.ui-dialog:visible form')
|
||||
f('#student_join_code').send_keys(@course.self_enrollment_code)
|
||||
f('#student_name').send_keys('student!')
|
||||
form.find_element(:css, "select[name='user[birthdate(1)]'] option[value='#{Time.now.year - 20}']").click
|
||||
form.find_element(:css, "select[name='user[birthdate(2)]'] option[value='1']").click
|
||||
form.find_element(:css, "select[name='user[birthdate(3)]'] option[value='1']").click
|
||||
form.find_element(:css, "select[name='user[birthdate(1i)]'] option[value='#{Time.now.year - 20}']").click
|
||||
form.find_element(:css, "select[name='user[birthdate(2i)]'] option[value='1']").click
|
||||
form.find_element(:css, "select[name='user[birthdate(3i)]'] option[value='1']").click
|
||||
f('#student_username').send_keys('student')
|
||||
f('#student_password').send_keys('asdfasdf')
|
||||
f('#student_password_confirmation').send_keys('asdfasdf')
|
||||
|
@ -225,9 +225,9 @@ describe "users" do
|
|||
form = fj('.ui-dialog:visible form')
|
||||
f('#student_higher_ed_name').send_keys('student!')
|
||||
f('#student_higher_ed_email').send_keys('student@example.com')
|
||||
form.find_element(:css, "select[name='user[birthdate(1)]'] option[value='#{Time.now.year - 20}']").click
|
||||
form.find_element(:css, "select[name='user[birthdate(2)]'] option[value='1']").click
|
||||
form.find_element(:css, "select[name='user[birthdate(3)]'] option[value='1']").click
|
||||
form.find_element(:css, "select[name='user[birthdate(1i)]'] option[value='#{Time.now.year - 20}']").click
|
||||
form.find_element(:css, "select[name='user[birthdate(2i)]'] option[value='1']").click
|
||||
form.find_element(:css, "select[name='user[birthdate(3i)]'] option[value='1']").click
|
||||
form.find_element(:css, 'input[name="user[terms_of_use]"]').click
|
||||
|
||||
expect_new_page_load { form.submit }
|
||||
|
|
Loading…
Reference in New Issue