canvas-lms/app/controllers/context_modules_controller.rb

347 lines
15 KiB
Ruby

#
# Copyright (C) 2011 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
before_filter :require_context
add_crumb("Modules") { |c| c.send :named_context_url, c.instance_variable_get("@context"), :context_context_modules_url }
before_filter { |c| c.active_tab = "modules" }
def index
@modules = ContextModule.fast_cached_for_context(@context)
@collapsed_modules = ContextModuleProgression.for_user(@current_user).for_modules(@modules).scoped(:select => ['context_module_id, collapsed']).select{|p| p.collapsed? }.map(&:context_module_id)
if @context.grants_right?(@current_user, session, :participate_as_student)
return unless tab_enabled?(@context.class::TAB_MODULES)
@modules = @context.context_modules.active.include_tags_and_progressions
@modules.each{|m| m.evaluate_for(@current_user) }
session[:module_progressions_initialized] = true
end
if authorized_action(@context, @current_user, :read)
end
end
def item_redirect
if authorized_action(@context, @current_user, :read)
@tag = @context.context_module_tags.active.include_progressions.find(params[:id])
@progression = @tag.context_module.evaluate_for(@current_user) if @tag.context_module
@progression.uncollapse! if @progression && @progression.collapsed != false
content_tag_redirect(@context, @tag, :context_context_modules_url)
end
end
def module_redirect
if authorized_action(@context, @current_user, :read)
@module = @context.context_modules.active.find(params[:context_module_id])
@tags = @module.content_tags.active.include_progressions
@tag = params[:last] ? @tags.last : @tags.first
if !@tag
flash[:notice] = "There are no items in the module \"#{@module.name}\""
redirect_to named_context_url(@context, :context_context_modules_url, :anchor => "module_#{@module.id}")
return
end
@progression = @tag.context_module.evaluate_for(@current_user) if @tag && @tag.context_module
@progression.uncollapse! if @progression && @progression.collapsed != false
content_tag_redirect(@context, @tag, :context_context_modules_url)
end
end
def create
if authorized_action(@context.context_modules.new, @current_user, :create)
@module = @context.context_modules.build
@module.attributes = params[:context_module]
respond_to do |format|
if @module.save
format.html { redirect_to named_context_url(@context, :context_context_modules_url) }
format.json { render :json => @module.to_json(:include => :content_tags, :permissions => {:user => @current_user, :session => session}) }
else
format.html
format.json { render :json => @module.errors.to_json, :status => :bad_request }
end
end
end
end
def reorder
if authorized_action(@context.context_modules.new, @current_user, :update)
m = @context.context_modules.active.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.active
@modules.each{|m| m.save_without_touching_context }
@context.touch
# # Background this, not essential that it happen right away
# ContextModule.send_later(:update_tag_order, @context)
respond_to do |format|
format.json { render :json => @modules.to_json }
end
end
end
def content_tag_assignment_data
if authorized_action(@context, @current_user, :read)
result = Rails.cache.fetch([ @context, "content_tag_assignment_info_all" ].cache_key) do
info = {}
@context.context_module_tags.active.map do |tag|
info[tag.id] = {
:due_date => (tag.assignment.due_at.utc.iso8601 rescue tag.content.due_at.utc.iso8601 rescue nil),
:points_possible => (tag.assignment.points_possible rescue nil)
}
end
info.to_json
end
render :json => result
end
end
def prerequisites_needing_finishing_for(mod, progression, before_tag=nil)
tags = mod.content_tags.active #.find(:all, :conditions => ['position <= ?', progression.current_position], :order => :position)
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
code = params[:code].split("_")
id = code.pop
type = code.join("_").classify
@tag = @context.context_module_tags.active.find_by_context_module_id_and_content_id_and_content_type(params[:context_module_id], id, type)
@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.find(:all, :conditions => ['position < ?', @module.position], :order => :position)
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.to_json
end
def toggle_collapse
if authorized_action(@context, @current_user, :read)
@module = @context.context_modules.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
respond_to do |format|
format.html { redirect_to named_context_url(@context, :context_context_modules_url) }
format.json { render :json => (@progression.collapsed ? @progression.to_json : @module.content_tags.active.to_json) }
end
end
end
def show
@module = @context.context_modules.find(params[:id])
respond_to do |format|
format.html { redirect_to named_context_url(@context, :context_context_modules_url) }
format.json { render :json => (@module.content_tags.active.to_json) }
end
end
def reorder_items
@module = @context.context_modules.find(params[:context_module_id])
if authorized_action(@module, @current_user, :update)
order = params[:order].split(",")
tags = @context.context_module_tags.active.find_all_by_id(order).compact
affected_module_ids = tags.map(&:context_module_id).uniq.compact
items = order.map{|id| tags.detect{|t| t.id == id.to_i } }.compact.uniq
items.each_index do |idx|
item = items[idx]
item.update_attributes(:position => idx, :context_module_id => @module.id)
end
ContextModule.update_all({:updated_at => Time.now}, {:id => affected_module_ids})
@context.touch
@module.reload
respond_to do |format|
format.json { render :json => @module.to_json(:include => :content_tags, :permissions => {:user => @current_user, :session => session}) }
end
end
end
def item_details
if authorized_action(@context, @current_user, :read)
code = params[:id].split("_")
id = code.pop.to_i
type = code.join("_").classify
@modules = @context.context_modules.active
@tags = @context.context_module_tags.active.sort_by{|t| t.position ||= 999}
result = {}
result[:current_item] = @tags.detect{|t| t.content_type == type && t.content_id == id }
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?(Quiz) && obj.assignment_id
result[:current_item] = @tags.detect{|t| t.content_type == 'Assignment' && t.content_id == obj.assignment_id }
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.to_json
end
end
def add_item
@module = @context.context_modules.find(params[:context_module_id])
if authorized_action(@module, @current_user, :update)
@tag = @module.add_item(params[:item]) #@item)
@module.touch
render :json => @tag.to_json
end
end
def remove_item
@tag = @context.context_module_tags.find(params[:id])
if authorized_action(@tag.context_module, @current_user, :update)
@module = @tag.context_module
@tag.destroy
@module.touch
render :json => @tag.to_json
end
end
def update_item
@tag = @context.context_module_tags.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 @tag.content_type == 'ExternalUrl' && params[:content_tag] && params[:content_tag][:url]
@tag.indent = params[:content_tag][:indent] if params[:content_tag] && params[:content_tag][:indent]
@tag.save
@tag.update_asset_name! if params[:content_tag][:title]
render :json => @tag.to_json
end
end
def progressions
if authorized_action(@context, @current_user, :read)
if @context.context_modules.new.grants_right?(@current_user, session, :update)
if params[:user_id] && @user = @context.students.find(params[:user_id])
@progressions = @context.context_modules.map{|m| m.evaluate_for(@user, true, true) }
else
context_module_ids = @context.context_modules.scoped(:select => "id").map &:id
@progressions = ContextModuleProgression.scoped(:conditions => {:context_module_id => context_module_ids})
end
render :json => @progressions.to_json
else
@progressions = @context.context_modules.map{|m| m.evaluate_for(@current_user, true) }
render :json => @progressions.to_json
end
end
end
def update
@module = @context.context_modules.find(params[:id])
if authorized_action(@module, @current_user, :update)
respond_to do |format|
if @module.update_attributes(params[:context_module])
format.json { render :json => @module.to_json(:include => :content_tags, :permissions => {:user => @current_user, :session => session}) }
else
format.json { render :json => @module.errors.to_json, :status => :bad_request }
end
end
end
end
def destroy
@module = @context.context_modules.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.to_json }
end
end
end
end