684 lines
29 KiB
Ruby
684 lines
29 KiB
Ruby
#
|
|
# Copyright (C) 2011 - present 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 ContextModulesController < ApplicationController
|
|
include Api::V1::ContextModule
|
|
include WebZipExportHelper
|
|
|
|
before_action :require_context
|
|
add_crumb(proc { t('#crumbs.modules', "Modules") }) { |c| c.send :named_context_url, c.instance_variable_get("@context"), :context_context_modules_url }
|
|
before_action { |c| c.active_tab = "modules" }
|
|
|
|
module ModuleIndexHelper
|
|
include ContextModulesHelper
|
|
|
|
def load_module_file_details
|
|
attachment_tags = @context.module_items_visible_to(@current_user).where(content_type: 'Attachment').preload(:content => :folder)
|
|
attachment_tags.inject({}) do |items, file_tag|
|
|
items[file_tag.id] = {
|
|
id: file_tag.id,
|
|
content_id: file_tag.content_id,
|
|
content_details: content_details(file_tag, @current_user, :for_admin => true)
|
|
}
|
|
items
|
|
end
|
|
end
|
|
|
|
def modules_cache_key
|
|
@modules_cache_key ||= begin
|
|
visible_assignments = @current_user.try(:assignment_and_quiz_visibilities, @context)
|
|
cache_key_items = [@context.cache_key, @can_edit, @is_student, @can_view_unpublished, 'all_context_modules_draft_10', collection_cache_key(@modules), Time.zone, Digest::MD5.hexdigest(visible_assignments.to_s)]
|
|
cache_key = cache_key_items.join('/')
|
|
cache_key = add_menu_tools_to_cache_key(cache_key)
|
|
cache_key = add_mastery_paths_to_cache_key(cache_key, @context, @modules, @current_user)
|
|
end
|
|
end
|
|
|
|
def load_modules
|
|
@modules = @context.modules_visible_to(@current_user)
|
|
@modules.each(&:check_for_stale_cache_after_unlocking!)
|
|
@collapsed_modules = ContextModuleProgression.for_user(@current_user).for_modules(@modules).pluck(:context_module_id, :collapsed).select{|cm_id, collapsed| !!collapsed }.map(&:first)
|
|
|
|
@can_edit = can_do(@context, @current_user, :manage_content)
|
|
@is_student = @context.grants_right?(@current_user, session, :participate_as_student)
|
|
@can_view_unpublished = @context.grants_right?(@current_user, session, :read_as_admin)
|
|
|
|
modules_cache_key
|
|
|
|
@is_cyoe_on = @current_user && ConditionalRelease::Service.enabled_in_context?(@context)
|
|
if allow_web_export_download?
|
|
@allow_web_export_download = true
|
|
@last_web_export = @context.web_zip_exports.visible_to(@current_user).order('epub_exports.created_at').last
|
|
end
|
|
|
|
@menu_tools = {}
|
|
placements = [:assignment_menu, :discussion_topic_menu, :file_menu, :module_menu, :quiz_menu, :wiki_page_menu]
|
|
tools = ContextExternalTool.all_tools_for(@context, placements: placements,
|
|
:root_account => @domain_root_account, :current_user => @current_user).to_a
|
|
placements.select { |p| @menu_tools[p] = tools.select{|t| t.has_placement? p} }
|
|
|
|
module_file_details = load_module_file_details if @context.grants_right?(@current_user, session, :manage_content)
|
|
js_env :course_id => @context.id,
|
|
:CONTEXT_URL_ROOT => polymorphic_path([@context]),
|
|
:DUPLICATE_ENABLED => @domain_root_account.feature_enabled?(:duplicate_modules),
|
|
:FILES_CONTEXTS => [{asset_string: @context.asset_string}],
|
|
:MODULE_FILE_DETAILS => module_file_details,
|
|
:MODULE_FILE_PERMISSIONS => {
|
|
usage_rights_required: @context.feature_enabled?(:usage_rights_required),
|
|
manage_files: @context.grants_right?(@current_user, session, :manage_files)
|
|
}
|
|
|
|
if master_courses?
|
|
is_master_course = MasterCourses::MasterTemplate.is_master_course?(@context)
|
|
is_child_course = MasterCourses::ChildSubscription.is_child_course?(@context)
|
|
if is_master_course || is_child_course
|
|
js_env(:MASTER_COURSE_SETTINGS => {
|
|
:IS_MASTER_COURSE => is_master_course,
|
|
:IS_CHILD_COURSE => is_child_course,
|
|
:MASTER_COURSE_DATA_URL => context_url(@context, :context_context_modules_master_course_info_url)
|
|
})
|
|
end
|
|
end
|
|
|
|
conditional_release_js_env(includes: :active_rules)
|
|
end
|
|
end
|
|
include ModuleIndexHelper
|
|
|
|
def index
|
|
if authorized_action(@context, @current_user, :read)
|
|
log_asset_access([ "modules", @context ], "modules", "other")
|
|
load_modules
|
|
|
|
set_tutorial_js_env
|
|
|
|
if @is_student && tab_enabled?(@context.class::TAB_MODULES)
|
|
@modules.each{|m| m.evaluate_for(@current_user) }
|
|
session[:module_progressions_initialized] = true
|
|
end
|
|
end
|
|
end
|
|
|
|
def choose_mastery_path
|
|
if authorized_action(@context, @current_user, :participate_as_student)
|
|
id = params[:id]
|
|
item = @context.context_module_tags.not_deleted.find(params[:id])
|
|
|
|
if item.present? && item.published? && item.context_module.published?
|
|
rules = ConditionalRelease::Service.rules_for(@context, @current_user, item, session)
|
|
rule = conditional_release_rule_for_module_item(item, conditional_release_rules: rules)
|
|
|
|
# locked assignments always have 0 sets, so this check makes it not return 404 if locked
|
|
# but instead progress forward and return a warning message if is locked later on
|
|
if rule.present? && (rule[:locked] || !rule[:selected_set_id] || rule[:assignment_sets].length > 1)
|
|
if !rule[:locked]
|
|
options = rule[:assignment_sets].map { |set|
|
|
option = {
|
|
setId: set[:id]
|
|
}
|
|
|
|
option[:assignments] = set[:assignments].map { |a|
|
|
assg = assignment_json(a[:model], @current_user, session)
|
|
assg[:assignmentId] = a[:assignment_id]
|
|
assg
|
|
}
|
|
|
|
option
|
|
}
|
|
|
|
js_env({
|
|
CHOOSE_MASTERY_PATH_DATA: {
|
|
options: options,
|
|
selectedOption: rule[:selected_set_id],
|
|
courseId: @context.id,
|
|
moduleId: item.context_module.id,
|
|
itemId: id
|
|
}
|
|
})
|
|
|
|
css_bundle :choose_mastery_path
|
|
js_bundle :choose_mastery_path
|
|
|
|
@page_title = join_title(t('Choose Assignment Set'), @context.name)
|
|
|
|
return render :html => '', :layout => true
|
|
else
|
|
flash[:warning] = t('Module Item is locked.')
|
|
return redirect_to named_context_url(@context, :context_context_modules_url)
|
|
end
|
|
end
|
|
end
|
|
return render status: 404, template: 'shared/errors/404_message'
|
|
end
|
|
end
|
|
|
|
def item_redirect
|
|
if authorized_action(@context, @current_user, :read)
|
|
@tag = @context.context_module_tags.not_deleted.find(params[:id])
|
|
|
|
if !(@tag.unpublished? || @tag.context_module.unpublished?) || authorized_action(@tag.context_module, @current_user, :view_unpublished_items)
|
|
reevaluate_modules_if_locked(@tag)
|
|
@progression = @tag.context_module.evaluate_for(@current_user) if @tag.context_module
|
|
@progression.uncollapse! if @progression && @progression.collapsed?
|
|
content_tag_redirect(@context, @tag, :context_context_modules_url, :modules)
|
|
end
|
|
end
|
|
end
|
|
|
|
def item_redirect_mastery_paths
|
|
@tag = @context.context_module_tags.not_deleted.find(params[:id])
|
|
|
|
type_controllers = {
|
|
assignment: 'assignments',
|
|
quiz: 'quizzes/quizzes',
|
|
discussion_topic: 'discussion_topics'
|
|
}
|
|
|
|
if @tag
|
|
if authorized_action(@tag.content, @current_user, :update)
|
|
controller = type_controllers[@tag.content_type_class.to_sym]
|
|
|
|
if controller.present?
|
|
redirect_to url_for(
|
|
controller: controller,
|
|
action: 'edit',
|
|
id: @tag.content_id,
|
|
anchor: 'mastery-paths-editor',
|
|
return_to: params[:return_to]
|
|
)
|
|
else
|
|
render status: 404, template: 'shared/errors/404_message'
|
|
end
|
|
end
|
|
else
|
|
render status: 404, template: 'shared/errors/404_message'
|
|
end
|
|
end
|
|
|
|
def module_redirect
|
|
if authorized_action(@context, @current_user, :read)
|
|
@module = @context.context_modules.not_deleted.find(params[:context_module_id])
|
|
@tags = @module.content_tags_visible_to(@current_user)
|
|
if params[:last]
|
|
@tags.pop while @tags.last && @tags.last.content_type == 'ContextModuleSubHeader'
|
|
else
|
|
@tags.shift while @tags.first && @tags.first.content_type == 'ContextModuleSubHeader'
|
|
end
|
|
@tag = params[:last] ? @tags.last : @tags.first
|
|
if !@tag
|
|
flash[:notice] = t 'module_empty', %{There are no items in the module "%{module}"}, :module => @module.name
|
|
redirect_to named_context_url(@context, :context_context_modules_url, :anchor => "module_#{@module.id}")
|
|
return
|
|
end
|
|
|
|
reevaluate_modules_if_locked(@tag)
|
|
@progression = @tag.context_module.evaluate_for(@current_user) if @tag && @tag.context_module
|
|
@progression.uncollapse! if @progression && @progression.collapsed?
|
|
content_tag_redirect(@context, @tag, :context_context_modules_url)
|
|
end
|
|
end
|
|
|
|
def reevaluate_modules_if_locked(tag)
|
|
# if the object is locked for this user, reevaluate all the modules and clear the cache so it will be checked again when loaded
|
|
if tag.content && tag.content.respond_to?(:locked_for?)
|
|
locked = tag.content.locked_for?(@current_user, :context => @context)
|
|
if locked
|
|
@context.context_modules.active.each { |m| m.evaluate_for(@current_user) }
|
|
if tag.content.respond_to?(:clear_locked_cache)
|
|
tag.content.clear_locked_cache(@current_user)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def create
|
|
if authorized_action(@context.context_modules.temp_record, @current_user, :create)
|
|
@module = @context.context_modules.build
|
|
@module.workflow_state = 'unpublished'
|
|
@module.attributes = context_module_params
|
|
respond_to do |format|
|
|
if @module.save
|
|
format.html { redirect_to named_context_url(@context, :context_context_modules_url) }
|
|
format.json { render :json => @module.as_json(:include => :content_tags, :methods => :workflow_state, :permissions => {:user => @current_user, :session => session}) }
|
|
else
|
|
format.html
|
|
format.json { render :json => @module.errors, :status => :bad_request }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def reorder
|
|
if authorized_action(@context.context_modules.temp_record, @current_user, :update)
|
|
m = @context.context_modules.not_deleted.first
|
|
|
|
m.update_order(params[:order].split(","))
|
|
# Need to invalidate the ordering cache used by context_module.rb
|
|
@context.touch
|
|
|
|
# I'd like to get rid of this saving every module, but we have to
|
|
# update the list of prerequisites since a reorder can cause
|
|
# prerequisites to no longer be valid
|
|
@modules = @context.context_modules.not_deleted.to_a
|
|
@modules.each do |m|
|
|
m.updated_at = Time.now
|
|
m.save_without_touching_context
|
|
end
|
|
@context.touch
|
|
|
|
# # Background this, not essential that it happen right away
|
|
# ContextModule.send_later(:update_tag_order, @context)
|
|
render :json => @modules.map{ |m| m.as_json(include: :content_tags, methods: :workflow_state) }
|
|
end
|
|
end
|
|
|
|
def content_tag_assignment_data
|
|
if authorized_action(@context, @current_user, :read)
|
|
info = {}
|
|
now = Time.now.utc.iso8601
|
|
|
|
all_tags = @context.module_items_visible_to(@current_user)
|
|
user_is_admin = @context.grants_right?(@current_user, session, :read_as_admin)
|
|
|
|
preload_assignments_and_quizzes(all_tags, user_is_admin)
|
|
|
|
all_tags.each do |tag|
|
|
info[tag.id] = if tag.can_have_assignment? && tag.assignment
|
|
tag.assignment.context_module_tag_info(@current_user, @context, user_is_admin: user_is_admin)
|
|
elsif tag.content_type_quiz?
|
|
tag.content.context_module_tag_info(@current_user, @context, user_is_admin: user_is_admin)
|
|
else
|
|
{:points_possible => nil, :due_date => nil}
|
|
end
|
|
end
|
|
render :json => info
|
|
end
|
|
end
|
|
|
|
def content_tag_master_course_data
|
|
return not_found unless master_courses?
|
|
if authorized_action(@context, @current_user, :read_as_admin)
|
|
info = {}
|
|
is_child_course = MasterCourses::ChildSubscription.is_child_course?(@context)
|
|
is_master_course = MasterCourses::MasterTemplate.is_master_course?(@context)
|
|
|
|
if is_child_course || is_master_course
|
|
tag_scope = @context.module_items_visible_to(@current_user).where(:content_type => %w{Assignment Attachment DiscussionTopic Quizzes::Quiz WikiPage})
|
|
tag_scope = tag_scope.where(:id => params[:tag_id]) if params[:tag_id]
|
|
tag_ids = tag_scope.pluck(:id)
|
|
restriction_info = {}
|
|
if tag_ids.any?
|
|
restriction_info = is_child_course ?
|
|
MasterCourses::MasterContentTag.fetch_module_item_restrictions_for_child(tag_ids) :
|
|
MasterCourses::MasterContentTag.fetch_module_item_restrictions_for_master(tag_ids)
|
|
end
|
|
info[:tag_restrictions] = restriction_info
|
|
end
|
|
render :json => info
|
|
end
|
|
end
|
|
|
|
def prerequisites_needing_finishing_for(mod, progression, before_tag=nil)
|
|
tags = mod.content_tags_visible_to(@current_user)
|
|
pres = []
|
|
tags.each do |tag|
|
|
if req = (mod.completion_requirements || []).detect{|r| r[:id] == tag.id }
|
|
progression.requirements_met ||= []
|
|
if !progression.requirements_met.any?{|r| r[:id] == req[:id] && r[:type] == req[:type] }
|
|
if !before_tag || tag.position <= before_tag.position
|
|
pre = {
|
|
:url => named_context_url(@context, :context_context_modules_item_redirect_url, tag.id),
|
|
:id => tag.id,
|
|
:context_module_id => mod.id,
|
|
:title => tag.title
|
|
}
|
|
pre[:requirement] = req
|
|
pre[:requirement_description] = ContextModule.requirement_description(req)
|
|
pre[:available] = !progression.locked? && (!mod.require_sequential_progress || tag.position <= progression.current_position)
|
|
pres << pre
|
|
end
|
|
end
|
|
end
|
|
end
|
|
pres
|
|
end
|
|
protected :prerequisites_needing_finishing_for
|
|
|
|
def content_tag_prerequisites_needing_finishing
|
|
type, id = ActiveRecord::Base.parse_asset_string params[:code]
|
|
raise ActiveRecord::RecordNotFound if id == 0
|
|
if type == 'ContentTag'
|
|
@tag = @context.context_module_tags.active.where(id: id).first
|
|
else
|
|
@tag = @context.context_module_tags.active.where(context_module_id: params[:context_module_id], content_id: id, content_type: type).first
|
|
end
|
|
@module = @context.context_modules.active.find(params[:context_module_id])
|
|
@progression = @module.evaluate_for(@current_user)
|
|
@progression.current_position ||= 0 if @progression
|
|
res = {};
|
|
if !@progression
|
|
elsif @progression.locked?
|
|
res[:locked] = true
|
|
res[:modules] = []
|
|
previous_modules = @context.context_modules.active.where('position<?', @module.position).order(:position).to_a
|
|
previous_modules.reverse!
|
|
valid_previous_modules = []
|
|
prereq_ids = @module.prerequisites.select{|p| p[:type] == 'context_module' }.map{|p| p[:id] }
|
|
previous_modules.each do |mod|
|
|
if prereq_ids.include?(mod.id)
|
|
valid_previous_modules << mod
|
|
prereq_ids += mod.prerequisites.select{|p| p[:type] == 'context_module' }.map{|p| p[:id] }
|
|
end
|
|
end
|
|
valid_previous_modules.reverse!
|
|
valid_previous_modules.each do |mod|
|
|
prog = mod.evaluate_for(@current_user)
|
|
res[:modules] << {
|
|
:id => mod.id,
|
|
:name => mod.name,
|
|
:prerequisites => prerequisites_needing_finishing_for(mod, prog),
|
|
:locked => prog.locked?
|
|
} unless prog.completed?
|
|
end
|
|
elsif @module.require_sequential_progress && @progression.current_position && @tag && @tag.position && @progression.current_position < @tag.position
|
|
res[:locked] = true
|
|
pres = prerequisites_needing_finishing_for(@module, @progression, @tag)
|
|
res[:modules] = [{
|
|
:id => @module.id,
|
|
:name => @module.name,
|
|
:prerequisites => pres,
|
|
:locked => false
|
|
}]
|
|
else
|
|
res[:locked] = false
|
|
end
|
|
render :json => res
|
|
end
|
|
|
|
def toggle_collapse
|
|
if authorized_action(@context, @current_user, :read)
|
|
@module = @context.modules_visible_to(@current_user).find(params[:context_module_id])
|
|
@progression = @module.evaluate_for(@current_user) #context_module_progressions.find_by_user_id(@current_user)
|
|
@progression ||= ContextModuleProgression.new
|
|
if params[:collapse] == '1'
|
|
@progression.collapsed = true
|
|
elsif params[:collapse]
|
|
@progression.uncollapse!
|
|
else
|
|
@progression.collapsed = !@progression.collapsed
|
|
end
|
|
@progression.save unless @progression.new_record?
|
|
respond_to do |format|
|
|
format.html { redirect_to named_context_url(@context, :context_context_modules_url) }
|
|
format.json { render :json => (@progression.collapsed ? @progression : @module.content_tags_visible_to(@current_user) )}
|
|
end
|
|
end
|
|
end
|
|
|
|
def show
|
|
@module = @context.context_modules.not_deleted.find(params[:id])
|
|
if authorized_action @module, @current_user, :read
|
|
respond_to do |format|
|
|
format.html { redirect_to named_context_url(@context, :context_context_modules_url, :anchor => "module_#{params[:id]}") }
|
|
format.json { render :json => @module.content_tags_visible_to(@current_user) }
|
|
end
|
|
end
|
|
end
|
|
|
|
def reorder_items
|
|
@module = @context.context_modules.not_deleted.find(params[:context_module_id])
|
|
if authorized_action(@module, @current_user, :update)
|
|
order = params[:order].split(",").map{|id| id.to_i}
|
|
tags = @context.context_module_tags.not_deleted.where(id: order)
|
|
affected_module_ids = (tags.map(&:context_module_id) + [@module.id]).uniq.compact
|
|
affected_items = []
|
|
items = order.map{|id| tags.detect{|t| t.id == id.to_i } }.compact.uniq
|
|
items.each_with_index do |item, idx|
|
|
item.position = idx + 1
|
|
item.context_module_id = @module.id
|
|
if item.changed?
|
|
item.skip_touch = true
|
|
item.save
|
|
affected_items << item
|
|
end
|
|
end
|
|
ContentTag.touch_context_modules(affected_module_ids)
|
|
ContentTag.update_could_be_locked(affected_items)
|
|
@context.touch
|
|
@module.reload
|
|
render :json => @module.as_json(:include => :content_tags, :methods => :workflow_state, :permissions => {:user => @current_user, :session => session})
|
|
end
|
|
end
|
|
|
|
|
|
def item_details
|
|
if authorized_action(@context, @current_user, :read)
|
|
# namespaced models are separated by : in the url
|
|
code = params[:id].gsub(":", "/").split("_")
|
|
id = code.pop.to_i
|
|
type = code.join("_").classify
|
|
@modules = @context.modules_visible_to(@current_user)
|
|
@tags = @context.context_module_tags.active.sort_by{|t| t.position ||= 999}
|
|
result = {}
|
|
possible_tags = @tags.find_all {|t| t.content_type == type && t.content_id == id }
|
|
if possible_tags.size > 1
|
|
# if there's more than one tag for the item, but the caller didn't
|
|
# specify which one they want, we don't want to return any information.
|
|
# this way the module item prev/next links won't appear with misleading navigation info.
|
|
if params[:module_item_id]
|
|
result[:current_item] = possible_tags.detect { |t| t.id == params[:module_item_id].to_i }
|
|
end
|
|
else
|
|
result[:current_item] = possible_tags.first
|
|
if !result[:current_item]
|
|
obj = @context.find_asset(params[:id], [:attachment, :discussion_topic, :assignment, :quiz, :wiki_page, :content_tag])
|
|
if obj.is_a?(ContentTag)
|
|
result[:current_item] = @tags.detect{|t| t.id == obj.id }
|
|
elsif obj.is_a?(DiscussionTopic) && obj.assignment_id
|
|
result[:current_item] = @tags.detect{|t| t.content_type == 'Assignment' && t.content_id == obj.assignment_id }
|
|
elsif obj.is_a?(Quizzes::Quiz) && obj.assignment_id
|
|
result[:current_item] = @tags.detect{|t| t.content_type == 'Assignment' && t.content_id == obj.assignment_id }
|
|
end
|
|
end
|
|
end
|
|
result[:current_item].evaluate_for(@current_user) rescue nil
|
|
if result[:current_item] && result[:current_item].position
|
|
result[:previous_item] = @tags.reverse.detect{|t| t.id != result[:current_item].id && t.context_module_id == result[:current_item].context_module_id && t.position && t.position <= result[:current_item].position && t.content_type != "ContextModuleSubHeader" }
|
|
result[:next_item] = @tags.detect{|t| t.id != result[:current_item].id && t.context_module_id == result[:current_item].context_module_id && t.position && t.position >= result[:current_item].position && t.content_type != "ContextModuleSubHeader" }
|
|
current_module = @modules.detect{|m| m.id == result[:current_item].context_module_id}
|
|
if current_module
|
|
result[:previous_module] = @modules.reverse.detect{|m| (m.position || 0) < (current_module.position || 0) }
|
|
result[:next_module] = @modules.detect{|m| (m.position || 0) > (current_module.position || 0) }
|
|
end
|
|
end
|
|
render :json => result
|
|
end
|
|
end
|
|
|
|
include ContextModulesHelper
|
|
def add_item
|
|
@module = @context.context_modules.not_deleted.find(params[:context_module_id])
|
|
if authorized_action(@module, @current_user, :update)
|
|
@tag = @module.add_item(params[:item])
|
|
unless @tag.valid?
|
|
return render :json => @tag.errors, :status => :bad_request
|
|
end
|
|
json = @tag.as_json
|
|
json['content_tag'].merge!(
|
|
publishable: module_item_publishable?(@tag),
|
|
published: @tag.published?,
|
|
publishable_id: module_item_publishable_id(@tag),
|
|
unpublishable: module_item_unpublishable?(@tag),
|
|
graded: @tag.graded?,
|
|
content_details: content_details(@tag, @current_user),
|
|
assignment_id: @tag.assignment.try(:id),
|
|
is_cyoe_able: cyoe_able?(@tag),
|
|
is_duplicate_able: @tag.duplicate_able?,
|
|
)
|
|
render json: json
|
|
end
|
|
end
|
|
|
|
def remove_item
|
|
@tag = @context.context_module_tags.not_deleted.find(params[:id])
|
|
if authorized_action(@tag.context_module, @current_user, :update)
|
|
@module = @tag.context_module
|
|
@tag.destroy
|
|
render :json => @tag
|
|
end
|
|
end
|
|
|
|
def update_item
|
|
@tag = @context.context_module_tags.not_deleted.find(params[:id])
|
|
if authorized_action(@tag.context_module, @current_user, :update)
|
|
@tag.title = params[:content_tag][:title] if params[:content_tag] && params[:content_tag][:title]
|
|
@tag.url = params[:content_tag][:url] if %w(ExternalUrl ContextExternalTool).include?(@tag.content_type) && params[:content_tag] && params[:content_tag][:url]
|
|
@tag.indent = params[:content_tag][:indent] if params[:content_tag] && params[:content_tag][:indent]
|
|
@tag.new_tab = params[:content_tag][:new_tab] if params[:content_tag] && params[:content_tag][:new_tab]
|
|
|
|
unless @tag.save
|
|
return render :json => @tag.errors, :status => :bad_request
|
|
end
|
|
|
|
@tag.update_asset_name!(@current_user) if params[:content_tag][:title]
|
|
render :json => @tag
|
|
end
|
|
end
|
|
|
|
def progressions
|
|
if authorized_action(@context, @current_user, :read)
|
|
if request.format == :json
|
|
if @context.grants_right?(@current_user, session, :view_all_grades)
|
|
if params[:user_id] && @user = @context.students.find(params[:user_id])
|
|
@progressions = @context.context_modules.active.map{|m| m.evaluate_for(@user) }
|
|
else
|
|
if @context.large_roster
|
|
@progressions = []
|
|
else
|
|
context_module_ids = @context.context_modules.active.pluck(:id)
|
|
@progressions = ContextModuleProgression.where(:context_module_id => context_module_ids).each{|p| p.evaluate }
|
|
end
|
|
end
|
|
elsif @context.grants_right?(@current_user, session, :participate_as_student)
|
|
@progressions = @context.context_modules.active.order(:id).map{|m| m.evaluate_for(@current_user) }
|
|
else
|
|
# module progressions don't apply, but unlock_at still does
|
|
@progressions = @context.context_modules.active.order(:id).map do |m|
|
|
{ :context_module_progression =>
|
|
{ :context_module_id => m.id,
|
|
:workflow_state => (m.to_be_unlocked ? 'locked' : 'unlocked'),
|
|
:requirements_met => [],
|
|
:incomplete_requirements => [] } }
|
|
end
|
|
end
|
|
render :json => @progressions
|
|
elsif !@context.grants_right?(@current_user, session, :view_all_grades)
|
|
@restrict_student_list = true
|
|
student_ids = @context.observer_enrollments.for_user(@current_user).map(&:associated_user_id)
|
|
student_ids << @current_user.id if @context.user_is_student?(@current_user)
|
|
students = UserSearch.scope_for(@context, @current_user, {:enrollment_type => 'student'}).where(:id => student_ids)
|
|
@visible_students = students.map { |u| user_json(u, @current_user, session) }
|
|
end
|
|
end
|
|
end
|
|
|
|
def update
|
|
@module = @context.context_modules.not_deleted.find(params[:id])
|
|
if authorized_action(@module, @current_user, :update)
|
|
if params[:publish]
|
|
@module.publish
|
|
@module.publish_items!
|
|
elsif params[:unpublish]
|
|
@module.unpublish
|
|
end
|
|
if @module.update_attributes(context_module_params)
|
|
json = @module.as_json(:include => :content_tags, :methods => :workflow_state, :permissions => {:user => @current_user, :session => session})
|
|
json['context_module']['relock_warning'] = true if @module.relock_warning?
|
|
render :json => json
|
|
else
|
|
render :json => @module.errors, :status => :bad_request
|
|
end
|
|
end
|
|
end
|
|
|
|
def destroy
|
|
@module = @context.context_modules.not_deleted.find(params[:id])
|
|
if authorized_action(@module, @current_user, :delete)
|
|
@module.destroy
|
|
respond_to do |format|
|
|
format.html { redirect_to named_context_url(@context, :context_context_modules_url) }
|
|
format.json { render :json => @module.as_json(:methods => :workflow_state) }
|
|
end
|
|
end
|
|
end
|
|
|
|
private
|
|
def preload_assignments_and_quizzes(tags, user_is_admin)
|
|
assignment_tags = tags.select{|ct| ct.can_have_assignment?}
|
|
return unless assignment_tags.any?
|
|
ActiveRecord::Associations::Preloader.new.preload(assignment_tags, :content)
|
|
|
|
content_with_assignments = assignment_tags.
|
|
select{|ct| ct.content_type != "Assignment" && ct.content.assignment_id}.map(&:content)
|
|
ActiveRecord::Associations::Preloader.new.preload(content_with_assignments, :assignment) if content_with_assignments.any?
|
|
|
|
if user_is_admin && should_preload_override_data?
|
|
assignments = assignment_tags.map(&:assignment).compact
|
|
plain_quizzes = assignment_tags.select{|ct| ct.content.is_a?(Quizzes::Quiz) && !ct.content.assignment}.map(&:content)
|
|
|
|
preload_has_too_many_overrides(assignments, :assignment_id)
|
|
preload_has_too_many_overrides(plain_quizzes, :quiz_id)
|
|
overrideables = (assignments + plain_quizzes).select{|o| !o.has_too_many_overrides}
|
|
|
|
if overrideables.any?
|
|
ActiveRecord::Associations::Preloader.new.preload(overrideables, :assignment_overrides)
|
|
overrideables.each { |o| o.has_no_overrides = true if o.assignment_overrides.size == 0 }
|
|
end
|
|
end
|
|
end
|
|
|
|
def should_preload_override_data?
|
|
key = ['preloaded_module_override_data', @context.global_asset_string, @current_user].cache_key
|
|
# if the user has been touched we should preload all of the overrides because it's almost certain we'll need them all
|
|
if Rails.cache.read(key)
|
|
false
|
|
else
|
|
Rails.cache.write(key, true)
|
|
true
|
|
end
|
|
end
|
|
|
|
def preload_has_too_many_overrides(assignments_or_quizzes, override_column)
|
|
# find the assignments/quizzes with too many active overrides and mark them as such
|
|
if assignments_or_quizzes.any?
|
|
ids = AssignmentOverride.active.where(override_column => assignments_or_quizzes).
|
|
group(override_column).having("COUNT(*) > ?", Setting.get('assignment_all_dates_too_many_threshold', '25').to_i).
|
|
active.pluck(override_column)
|
|
|
|
if ids.any?
|
|
assignments_or_quizzes.each{|o| o.has_too_many_overrides = true if ids.include?(o.id) }
|
|
end
|
|
end
|
|
end
|
|
|
|
def context_module_params
|
|
params.require(:context_module).permit(:name, :unlock_at, :require_sequential_progress, :publish_final_grade, :requirement_count,
|
|
:completion_requirements => strong_anything, :prerequisites => strong_anything)
|
|
end
|
|
end
|