723 lines
30 KiB
Ruby
723 lines
30 KiB
Ruby
#
|
|
# 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/>.
|
|
#
|
|
|
|
# @API Discussion Topics
|
|
#
|
|
# A discussion topic object looks like:
|
|
#
|
|
# !!!javascript
|
|
# {
|
|
# // The ID of this topic.
|
|
# "id":1,
|
|
#
|
|
# // The topic title.
|
|
# "title":"Topic 1",
|
|
#
|
|
# // The HTML content of the message body.
|
|
# "message":"<p>content here</p>",
|
|
#
|
|
# // The URL to the discussion topic in canvas.
|
|
# "html_url": "https://<canvas>/courses/1/discussion_topics/2",
|
|
#
|
|
# // The datetime the topic was posted. If it is null it hasn't been
|
|
# // posted yet. (see delayed_post_at)
|
|
# "posted_at":"2037-07-21T13:29:31Z",
|
|
#
|
|
# // The datetime for when the last reply was in the topic.
|
|
# "last_reply_at":"2037-07-28T19:38:31Z",
|
|
#
|
|
# // If true then a user may not respond to other replies until that user
|
|
# // has made an initial reply. Defaults to false.
|
|
# "require_initial_post":false,
|
|
#
|
|
# // Whether or not posts in this topic are visible to the user.
|
|
# "user_can_see_posts":true,
|
|
#
|
|
# // The count of entries in the topic.
|
|
# "discussion_subentry_count":0,
|
|
#
|
|
# // The read_state of the topic for the current user, "read" or "unread".
|
|
# "read_state":"read",
|
|
#
|
|
# // The count of unread entries of this topic for the current user.
|
|
# "unread_count":0,
|
|
#
|
|
# // Whether or not the current user is subscribed to this topic.
|
|
# "subscribed":true,
|
|
#
|
|
# // (Optional) Why the user cannot subscribe to this topic. Only one reason
|
|
# // will be returned even if multiple apply. Can be one of:
|
|
# // 'initial_post_required': The user must post a reply first
|
|
# // 'not_in_group_set': The user is not in the group set for this graded group discussion
|
|
# // 'not_in_group': The user is not in this topic's group
|
|
# // 'topic_is_announcement': This topic is an announcement
|
|
# "subscription_hold":"not_in_group_set",
|
|
#
|
|
# // The unique identifier of the assignment if the topic is for grading, otherwise null.
|
|
# "assignment_id":null,
|
|
#
|
|
# // The datetime to publish the topic (if not right away).
|
|
# "delayed_post_at":null,
|
|
#
|
|
# // Whether this discussion topic is published (true) or draft state (false)
|
|
# "published":true,
|
|
#
|
|
# // The datetime to lock the topic (if ever).
|
|
# "lock_at":null,
|
|
#
|
|
# // whether or not this is locked for students to see.
|
|
# "locked":false,
|
|
#
|
|
# // whether or not the discussion has been "pinned" by an instructor
|
|
# "pinned":false,
|
|
#
|
|
# // Whether or not this is locked for the user.
|
|
# "locked_for_user":true,
|
|
#
|
|
# // (Optional) Information for the user about the lock. Present when locked_for_user is true.
|
|
# "lock_info": {
|
|
# // Asset string for the object causing the lock
|
|
# "asset_string":"discussion_topic_1",
|
|
#
|
|
# // (Optional) Time at which this was/will be unlocked.
|
|
# "unlock_at":"2013-01-01T00:00:00-06:00",
|
|
#
|
|
# // (Optional) Time at which this was/will be locked.
|
|
# "lock_at":"2013-02-01T00:00:00-06:00",
|
|
#
|
|
# // (Optional) Context module causing the lock.
|
|
# "context_module":{ ... }
|
|
# },
|
|
#
|
|
# // (Optional) An explanation of why this is locked for the user. Present when locked_for_user is true.
|
|
# "lock_explanation":"This discussion is locked until September 1 at 12:00am",
|
|
#
|
|
# // The username of the topic creator.
|
|
# "user_name":"User Name",
|
|
#
|
|
# // An array of topic_ids for the group discussions the user is a part of.
|
|
# "topic_children":[5, 7, 10],
|
|
#
|
|
# // If the topic is for grading and a group assignment this will
|
|
# // point to the original topic in the course.
|
|
# "root_topic_id":null,
|
|
#
|
|
# // If the topic is a podcast topic this is the feed url for the current user.
|
|
# "podcast_url":"/feeds/topics/1/enrollment_1XAcepje4u228rt4mi7Z1oFbRpn3RAkTzuXIGOPe.rss",
|
|
#
|
|
# // The type of discussion. Values are 'side_comment', for discussions
|
|
# // that only allow one level of nested comments, and 'threaded' for
|
|
# // fully threaded discussions.
|
|
# "discussion_type":"side_comment",
|
|
#
|
|
# // Array of file attachments.
|
|
# "attachments":[
|
|
# {
|
|
# "content-type":"unknown/unknown",
|
|
# "url":"http://www.example.com/courses/1/files/1/download",
|
|
# "filename":"content.txt",
|
|
# "display_name":"content.txt"
|
|
# }
|
|
# ],
|
|
#
|
|
# // The current user's permissions on this topic.
|
|
# "permissions":
|
|
# {
|
|
# // If true, the calling user can attach files to this discussion's entries.
|
|
# "attach": true
|
|
# }
|
|
# }
|
|
class DiscussionTopicsController < ApplicationController
|
|
before_filter :require_context, :except => :public_feed
|
|
|
|
include Api::V1::DiscussionTopics
|
|
include Api::V1::Assignment
|
|
include Api::V1::AssignmentOverride
|
|
include KalturaHelper
|
|
|
|
# @API List discussion topics
|
|
#
|
|
# Returns the paginated list of discussion topics for this course or group.
|
|
#
|
|
# @argument order_by [String, "position"|"recent_activity"]
|
|
# Determines the order of the discussion topic list. Defaults to "position".
|
|
#
|
|
# @argument scope [Optional, String, "locked"|"unlocked"]
|
|
# Only return discussion topics in the given state. Defaults to including
|
|
# locked and unlocked topics. Filtering is done after pagination, so pages
|
|
# may be smaller than requested if topics are filtered
|
|
#
|
|
# @argument only_announcements [Optional, Boolean]
|
|
# Return announcements instead of discussion topics. Defaults to false
|
|
#
|
|
# @argument search_term [Optional, String]
|
|
# The partial title of the discussion topics to match and return.
|
|
#
|
|
# @example_request
|
|
# curl https://<canvas>/api/v1/courses/<course_id>/discussion_topics \
|
|
# -H 'Authorization: Bearer <token>'
|
|
def index
|
|
return unless authorized_action(@context.discussion_topics.new, @current_user, :read)
|
|
return child_topic if is_child_topic?
|
|
|
|
log_asset_access("topics:#{@context.asset_string}", 'topics', 'other')
|
|
|
|
scope = if params[:only_announcements]
|
|
@context.active_announcements
|
|
else
|
|
@context.active_discussion_topics.only_discussion_topics
|
|
end
|
|
|
|
scope = params[:order_by] == 'recent_activity' ? scope.by_last_reply_at : scope.by_position
|
|
|
|
scope = DiscussionTopic.search_by_attribute(scope, :title, params[:search_term])
|
|
|
|
@topics = Api.paginate(scope, self, topic_pagination_url)
|
|
@topics.reject! { |t| t.locked? || t.locked_for?(@current_user) } if params[:scope] == 'unlocked'
|
|
@topics.select! { |t| t.locked? || t.locked_for?(@current_user) } if params[:scope] == 'locked'
|
|
@topics.each { |topic| topic.current_user = @current_user }
|
|
|
|
respond_to do |format|
|
|
format.html do
|
|
@active_tab = 'discussions'
|
|
add_crumb(t('#crumbs.discussions', 'Discussions'),
|
|
named_context_url(@context, :context_discussion_topics_url))
|
|
|
|
locked_topics, open_topics = @topics.partition do |topic|
|
|
topic.locked? || topic.locked_for?(@current_user)
|
|
end
|
|
hash = {USER_SETTINGS_URL: api_v1_user_settings_url(@current_user),
|
|
openTopics: open_topics,
|
|
lockedTopics: locked_topics,
|
|
newTopicURL: named_context_url(@context, :new_context_discussion_topic_url),
|
|
permissions: {
|
|
create: @context.discussion_topics.new.grants_right?(@current_user, session, :create),
|
|
moderate: user_can_moderate,
|
|
change_settings: user_can_edit_course_settings?
|
|
}}
|
|
append_sis_data(hash)
|
|
|
|
js_env(hash)
|
|
|
|
if user_can_edit_course_settings?
|
|
js_env(SETTINGS_URL: named_context_url(@context, :api_v1_context_settings_url))
|
|
end
|
|
end
|
|
|
|
format.json do
|
|
render json: discussion_topics_api_json(@topics, @context, @current_user, session)
|
|
end
|
|
end
|
|
end
|
|
|
|
def is_child_topic?
|
|
root_topic_id = params[:root_discussion_topic_id]
|
|
|
|
root_topic_id && @context.respond_to?(:context) &&
|
|
@context.context && @context.context.discussion_topics.find(root_topic_id)
|
|
end
|
|
|
|
def new
|
|
@topic = @context.send(params[:is_announcement] ? :announcements : :discussion_topics).new
|
|
add_discussion_or_announcement_crumb
|
|
add_crumb t :create_new_crumb, "Create new"
|
|
edit
|
|
end
|
|
|
|
def edit
|
|
@topic ||= @context.all_discussion_topics.find(params[:id])
|
|
if authorized_action(@topic, @current_user, (@topic.new_record? ? :create : :update))
|
|
hash = {
|
|
:URL_ROOT => named_context_url(@context, :api_v1_context_discussion_topics_url),
|
|
:PERMISSIONS => {
|
|
:CAN_CREATE_ASSIGNMENT => @context.respond_to?(:assignments) && @context.assignments.new.grants_right?(@current_user, session, :create),
|
|
:CAN_ATTACH => @topic.grants_right?(@current_user, session, :attach),
|
|
:CAN_MODERATE => user_can_moderate
|
|
}
|
|
}
|
|
|
|
unless @topic.new_record?
|
|
add_discussion_or_announcement_crumb
|
|
add_crumb(@topic.title, named_context_url(@context, :context_discussion_topic_url, @topic.id))
|
|
add_crumb t :edit_crumb, "Edit"
|
|
hash[:ATTRIBUTES] = discussion_topic_api_json(@topic, @context, @current_user, session)
|
|
end
|
|
(hash[:ATTRIBUTES] ||= {})[:is_announcement] = @topic.is_announcement
|
|
handle_assignment_edit_params(hash[:ATTRIBUTES])
|
|
|
|
if @topic.assignment.present?
|
|
hash[:ATTRIBUTES][:assignment][:assignment_overrides] =
|
|
(assignment_overrides_json(@topic.assignment.overrides_visible_to(@current_user)))
|
|
end
|
|
|
|
categories = @context.respond_to?(:group_categories) ? @context.group_categories : []
|
|
sections = @context.respond_to?(:course_sections) ? @context.course_sections.active : []
|
|
js_hash = {:DISCUSSION_TOPIC => hash,
|
|
:SECTION_LIST => sections.map { |section| { :id => section.id, :name => section.name } },
|
|
:GROUP_CATEGORIES => categories.
|
|
reject { |category| category.student_organized? }.
|
|
map { |category| { :id => category.id, :name => category.name } },
|
|
:CONTEXT_ID => @context.id,
|
|
:CONTEXT_ACTION_SOURCE => :discussion_topic}
|
|
append_sis_data(js_hash)
|
|
js_env(js_hash)
|
|
render :action => "edit"
|
|
end
|
|
end
|
|
|
|
def show
|
|
parent_id = params[:parent_id]
|
|
@topic = @context.all_discussion_topics.find(params[:id])
|
|
@presenter = DiscussionTopicPresenter.new(@topic, @current_user)
|
|
@assignment = if @topic.for_assignment?
|
|
AssignmentOverrideApplicator.assignment_overridden_for(@topic.assignment, @current_user)
|
|
else
|
|
nil
|
|
end
|
|
@context.assert_assignment_group rescue nil
|
|
add_discussion_or_announcement_crumb
|
|
add_crumb(@topic.title, named_context_url(@context, :context_discussion_topic_url, @topic.id))
|
|
if @topic.deleted?
|
|
flash[:notice] = t :deleted_topic_notice, "That topic has been deleted"
|
|
redirect_to named_context_url(@context, :context_discussion_topics_url)
|
|
return
|
|
end
|
|
|
|
if authorized_action(@topic, @current_user, :read)
|
|
@headers = !params[:headless]
|
|
@locked = @topic.locked_for?(@current_user, :check_policies => true, :deep_check_if_needed => true) || @topic.locked?
|
|
@topic.change_read_state('read', @current_user)
|
|
if @topic.for_group_assignment?
|
|
@groups = @topic.assignment.group_category.groups.active.select{ |g| g.grants_right?(@current_user, session, :read) }
|
|
topics = @topic.child_topics.to_a
|
|
topics = topics.select{|t| @groups.include?(t.context) } unless @topic.grants_right?(@current_user, session, :update)
|
|
@group_topics = @groups.map do |group|
|
|
{:group => group, :topic => topics.find{|t| t.context == group} }
|
|
end
|
|
end
|
|
|
|
@initial_post_required = @topic.initial_post_required?(@current_user, @context_enrollment, session)
|
|
|
|
@padless = true
|
|
|
|
log_asset_access(@topic, 'topics', 'topics')
|
|
respond_to do |format|
|
|
if @topic.deleted?
|
|
flash[:notice] = t :deleted_topic_notice, "That topic has been deleted"
|
|
format.html { redirect_to named_context_url(@context, :discussion_topics_url) }
|
|
elsif topics && topics.length == 1 && !@topic.grants_right?(@current_user, session, :update)
|
|
format.html { redirect_to named_context_url(topics[0].context, :context_discussion_topics_url, :root_discussion_topic_id => @topic.id) }
|
|
else
|
|
format.html do
|
|
|
|
@context_module_tag = ContextModuleItem.find_tag_with_preferred([@topic, @topic.root_topic, @topic.assignment], params[:module_item_id])
|
|
@sequence_asset = @context_module_tag.try(:content)
|
|
env_hash = {
|
|
:APP_URL => named_context_url(@context, :context_discussion_topic_url, @topic),
|
|
:TOPIC => {
|
|
:ID => @topic.id,
|
|
:IS_SUBSCRIBED => @topic.subscribed?(@current_user),
|
|
},
|
|
:PERMISSIONS => {
|
|
:CAN_REPLY => @locked ? false : !(@topic.for_group_assignment? || @topic.locked?), # Can reply
|
|
:CAN_ATTACH => @locked ? false : @topic.grants_right?(@current_user, session, :attach), # Can attach files on replies
|
|
:CAN_MANAGE_OWN => @context.user_can_manage_own_discussion_posts?(@current_user), # Can moderate their own topics
|
|
:MODERATE => user_can_moderate # Can moderate any topic
|
|
},
|
|
:ROOT_URL => named_context_url(@context, :api_v1_context_discussion_topic_view_url, @topic),
|
|
:ENTRY_ROOT_URL => named_context_url(@context, :api_v1_context_discussion_topic_entry_list_url, @topic),
|
|
:REPLY_URL => named_context_url(@context, :api_v1_context_discussion_add_reply_url, @topic, ':entry_id'),
|
|
:ROOT_REPLY_URL => named_context_url(@context, :api_v1_context_discussion_add_entry_url, @topic),
|
|
:DELETE_URL => named_context_url(@context, :api_v1_context_discussion_delete_reply_url, @topic, ':id'),
|
|
:UPDATE_URL => named_context_url(@context, :api_v1_context_discussion_update_reply_url, @topic, ':id'),
|
|
:MARK_READ_URL => named_context_url(@context, :api_v1_context_discussion_topic_discussion_entry_mark_read_url, @topic, ':id'),
|
|
:MARK_UNREAD_URL => named_context_url(@context, :api_v1_context_discussion_topic_discussion_entry_mark_unread_url, @topic, ':id'),
|
|
:MARK_ALL_READ_URL => named_context_url(@context, :api_v1_context_discussion_topic_mark_all_read_url, @topic),
|
|
:MARK_ALL_UNREAD_URL => named_context_url(@context, :api_v1_context_discussion_topic_mark_all_unread_url, @topic),
|
|
:MANUAL_MARK_AS_READ => @current_user.try(:manual_mark_as_read?),
|
|
:CAN_SUBSCRIBE => !@topic.subscription_hold(@current_user, @context_enrollment, session),
|
|
:CURRENT_USER => user_display_json(@current_user),
|
|
:INITIAL_POST_REQUIRED => @initial_post_required,
|
|
:THREADED => @topic.threaded?
|
|
}
|
|
if @topic.for_assignment? &&
|
|
@topic.assignment.grants_right?(@current_user, session, :grade) && @presenter.allows_speed_grader?
|
|
env_hash[:SPEEDGRADER_URL_TEMPLATE] = named_context_url(@topic.assignment.context,
|
|
:speed_grader_context_gradebook_url,
|
|
:assignment_id => @topic.assignment.id,
|
|
:anchor => {:student_id => ":student_id"}.to_json)
|
|
end
|
|
|
|
js_hash = {:DISCUSSION => env_hash}
|
|
js_hash[:CONTEXT_ACTION_SOURCE] = :discussion_topic
|
|
append_sis_data(js_hash)
|
|
js_env(js_hash)
|
|
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
# @API Create a new discussion topic
|
|
#
|
|
# Create an new discussion topic for the course or group.
|
|
#
|
|
# @argument title [String]
|
|
# @argument message [String]
|
|
# @argument discussion_type [String]
|
|
#
|
|
# @argument published [Optional, Boolean]
|
|
# Whether this topic is published (true) or draft state (false). Only
|
|
# teachers and TAs have the ability to create draft state topics.
|
|
#
|
|
# @argument delayed_post_at [Optional, DateTime]
|
|
# If a timestamp is given, the topic will not be published until that time.
|
|
#
|
|
# @argument lock_at [Optional, DateTime]
|
|
# If a timestamp is given, the topic will be scheduled to lock at the
|
|
# provided timestamp. If the timestamp is in the past, the topic will be
|
|
# locked.
|
|
#
|
|
# @argument podcast_enabled [Boolean]
|
|
# If true, the topic will have an associated podcast feed.
|
|
#
|
|
# @argument podcast_has_student_posts [Boolean]
|
|
# If true, the podcast will include posts from students as well. Implies
|
|
# podcast_enabled.
|
|
#
|
|
# @argument require_initial_post [Boolean]
|
|
# If true then a user may not respond to other replies until that user has
|
|
# made an initial reply. Defaults to false.
|
|
#
|
|
# @argument assignment [Assignment]
|
|
# To create an assignment discussion, pass the assignment parameters as a
|
|
# sub-object. See the {api:AssignmentsApiController#create Create an Assignment API}
|
|
# for the available parameters. The name parameter will be ignored, as it's
|
|
# taken from the discussion title. If you want to make a discussion that was
|
|
# an assignment NOT an assignment, pass set_assignment = false as part of
|
|
# the assignment object
|
|
#
|
|
# @argument is_announcement [Boolean]
|
|
# If true, this topic is an announcement. It will appear in the
|
|
# announcement's section rather than the discussions section. This requires
|
|
# announcment-posting permissions.
|
|
#
|
|
# @argument position_after [String]
|
|
# By default, discussions are sorted chronologically by creation date, you
|
|
# can pass the id of another topic to have this one show up after the other
|
|
# when they are listed.
|
|
#
|
|
# @example_request
|
|
# curl https://<canvas>/api/v1/courses/<course_id>/discussion_topics \
|
|
# -F title='my topic' \
|
|
# -F message='initial message' \
|
|
# -F podcast_enabled=1 \
|
|
# -H 'Authorization: Bearer <token>'
|
|
#
|
|
# @example_request
|
|
# curl https://<canvas>/api/v1/courses/<course_id>/discussion_topics \
|
|
# -F title='my assignment topic' \
|
|
# -F message='initial message' \
|
|
# -F assignment[points_possible]=15 \
|
|
# -H 'Authorization: Bearer <token>'
|
|
#
|
|
def create
|
|
process_discussion_topic(!!:is_new)
|
|
end
|
|
|
|
# @API Update a topic
|
|
#
|
|
# Accepts the same parameters as create
|
|
#
|
|
# @example_request
|
|
# curl https://<canvas>/api/v1/courses/<course_id>/discussion_topics/<topic_id> \
|
|
# -F title='This will be positioned after Topic #1234' \
|
|
# -F position_after=1234 \
|
|
# -H 'Authorization: Bearer <token>'
|
|
#
|
|
def update
|
|
process_discussion_topic(!:is_new)
|
|
end
|
|
|
|
# @API Delete a topic
|
|
#
|
|
# Deletes the discussion topic. This will also delete the assignment, if it's
|
|
# an assignment discussion.
|
|
#
|
|
# @example_request
|
|
# curl -X DELETE https://<canvas>/api/v1/courses/<course_id>/discussion_topics/<topic_id> \
|
|
# -H 'Authorization: Bearer <token>'
|
|
def destroy
|
|
@topic = @context.all_discussion_topics.find(params[:id] || params[:topic_id])
|
|
if authorized_action(@topic, @current_user, :delete)
|
|
@topic.destroy
|
|
respond_to do |format|
|
|
format.html {
|
|
flash[:notice] = t :topic_deleted_notice, "%{topic_title} deleted successfully", :topic_title => @topic.title
|
|
redirect_to named_context_url(@context, :context_discussion_topics_url)
|
|
}
|
|
format.json { render :json => @topic.to_json(:include => {:user => {:only => :name} } ), :status => :ok }
|
|
end
|
|
end
|
|
end
|
|
|
|
def public_feed
|
|
return unless get_feed_context
|
|
feed = Atom::Feed.new do |f|
|
|
f.title = t :discussion_feed_title, "%{title} Discussion Feed", :title => @context.name
|
|
f.links << Atom::Link.new(:href => polymorphic_url([@context, :discussion_topics]), :rel => 'self')
|
|
f.updated = Time.now
|
|
f.id = polymorphic_url([@context, :discussion_topics])
|
|
end
|
|
@entries = []
|
|
@entries.concat @context.discussion_topics.reject{|a| a.locked_for?(@current_user, :check_policies => true) }
|
|
@entries.concat @context.discussion_entries.active
|
|
@entries = @entries.sort_by{|e| e.updated_at}
|
|
@entries.each do |entry|
|
|
feed.entries << entry.to_atom
|
|
end
|
|
respond_to do |format|
|
|
format.atom { render :text => feed.to_xml }
|
|
end
|
|
end
|
|
|
|
def public_topic_feed
|
|
end
|
|
|
|
protected
|
|
|
|
def add_discussion_or_announcement_crumb
|
|
if @topic.is_a? Announcement
|
|
@active_tab = "announcements"
|
|
add_crumb t('#crumbs.announcements', "Announcements"), named_context_url(@context, :context_announcements_url)
|
|
else
|
|
@active_tab = "discussions"
|
|
add_crumb t('#crumbs.discussions', "Discussions"), named_context_url(@context, :context_discussion_topics_url)
|
|
end
|
|
end
|
|
|
|
def user_can_moderate
|
|
@user_can_moderate = @context.grants_right?(@current_user, session, :moderate_forum) if @user_can_moderate.nil?
|
|
@user_can_moderate
|
|
end
|
|
|
|
API_ALLOWED_TOPIC_FIELDS = %w(title message discussion_type delayed_post_at lock_at podcast_enabled
|
|
podcast_has_student_posts require_initial_post is_announcement pinned)
|
|
def process_discussion_topic(is_new = false)
|
|
@errors = {}
|
|
discussion_topic_hash = params.slice(*API_ALLOWED_TOPIC_FIELDS)
|
|
model_type = value_to_boolean(discussion_topic_hash.delete(:is_announcement)) && @context.announcements.new.grants_right?(@current_user, session, :create) ? :announcements : :discussion_topics
|
|
if is_new
|
|
@topic = @context.send(model_type).build
|
|
else
|
|
@topic = @context.send(model_type).active.find(params[:id] || params[:topic_id])
|
|
end
|
|
|
|
return unless authorized_action(@topic, @current_user, (is_new ? :create : :update))
|
|
|
|
process_podcast_parameters(discussion_topic_hash)
|
|
|
|
@topic.send(is_new ? :user= : :editor=, @current_user)
|
|
@topic.current_user = @current_user
|
|
@topic.content_being_saved_by(@current_user)
|
|
|
|
if discussion_topic_hash.has_key?(:message)
|
|
discussion_topic_hash[:message] = process_incoming_html_content(discussion_topic_hash[:message])
|
|
end
|
|
|
|
unless process_future_date_parameters(discussion_topic_hash)
|
|
process_lock_parameters(discussion_topic_hash)
|
|
process_published_parameters(discussion_topic_hash)
|
|
end
|
|
|
|
if @errors.present?
|
|
render :json => {errors: @errors}, :status => :bad_request
|
|
elsif @topic.update_attributes(discussion_topic_hash)
|
|
log_asset_access(@topic, 'topics', 'topics', 'participate')
|
|
generate_new_page_view
|
|
|
|
apply_positioning_parameters
|
|
apply_attachment_parameters
|
|
apply_assignment_parameters
|
|
|
|
render :json => discussion_topic_api_json(@topic, @context, @current_user, session)
|
|
else
|
|
render :json => @topic.errors.to_json, :status => :bad_request
|
|
end
|
|
end
|
|
|
|
def process_podcast_parameters(discussion_topic_hash)
|
|
discussion_topic_hash[:podcast_enabled] = true if value_to_boolean(discussion_topic_hash[:podcast_has_student_posts])
|
|
|
|
unless user_can_moderate
|
|
discussion_topic_hash.delete :podcast_enabled
|
|
discussion_topic_hash.delete :podcast_has_student_posts
|
|
end
|
|
end
|
|
|
|
# Internal: detetermines if the delayed_post_at or lock_at dates were changed
|
|
# and applies changes to the topic if the were.
|
|
#
|
|
# Returns true if dates were changed and the topic was updated, false otherwise.
|
|
def process_future_date_parameters(discussion_topic_hash)
|
|
# Set the delayed_post_at and lock_at if provided. This will be used to determine if the values have changed
|
|
# in order to know if we should rely on this data to update the workflow state
|
|
@topic.delayed_post_at = discussion_topic_hash[:delayed_post_at] if params.has_key? :delayed_post_at
|
|
@topic.lock_at = discussion_topic_hash[:lock_at] if params.has_key? :lock_at
|
|
|
|
if @topic.delayed_post_at_changed? || @topic.lock_at_changed?
|
|
@topic.workflow_state = @topic.should_not_post_yet ? 'post_delayed' : 'active'
|
|
if @topic.should_lock_yet
|
|
@topic.lock(without_save: true)
|
|
else
|
|
@topic.unlock(without_save: true)
|
|
end
|
|
true
|
|
else
|
|
false
|
|
end
|
|
end
|
|
|
|
def process_lock_parameters(discussion_topic_hash)
|
|
# Handle locking/unlocking (overrides workflow state if provided). It appears that the locked param as a hash
|
|
# is from old code and is not being used. Verification requested.
|
|
if params.has_key?(:locked) && !params[:locked].is_a?(Hash)
|
|
should_lock = value_to_boolean(params[:locked])
|
|
if should_lock != @topic.locked?
|
|
if should_lock
|
|
@topic.lock(without_save: true)
|
|
else
|
|
discussion_topic_hash[:lock_at] = nil
|
|
@topic.unlock(without_save: true)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def process_published_parameters(discussion_topic_hash)
|
|
if params.has_key?(:published)
|
|
should_publish = value_to_boolean(params[:published])
|
|
if should_publish != @topic.published?
|
|
if should_publish
|
|
@topic.workflow_state = 'active'
|
|
elsif @topic.is_announcement
|
|
@errors[:published] = t(:error_draft_state_announcement, "This topic cannot be set to draft state because it is an announcement.")
|
|
elsif @topic.discussion_subentry_count > 0
|
|
@errors[:published] = t(:error_draft_state_with_posts, "This topic cannot be set to draft state because it contains posts.")
|
|
elsif user_can_moderate
|
|
discussion_topic_hash[:delayed_post_at] = nil
|
|
@topic.workflow_state = 'post_delayed'
|
|
else
|
|
@errors[:published] = t(:error_draft_state_unauthorized, "You do not have permission to set this topic to draft state.")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def apply_positioning_parameters
|
|
if params[:position_after] && user_can_moderate
|
|
other_topic = @context.discussion_topics.active.find(params[:position_after])
|
|
@topic.insert_at(other_topic.position)
|
|
end
|
|
|
|
if params[:position_at] && user_can_moderate
|
|
@topic.insert_at(params[:position_at].to_i)
|
|
end
|
|
end
|
|
|
|
def apply_attachment_parameters
|
|
# handle creating/removing attachment
|
|
if @topic.grants_right?(@current_user, session, :attach)
|
|
attachment = params[:attachment] &&
|
|
params[:attachment].size > 0 &&
|
|
params[:attachment]
|
|
|
|
return if attachment && attachment.size > 1.kilobytes &&
|
|
quota_exceeded(named_context_url(@context, :context_discussion_topics_url))
|
|
|
|
if (params.has_key?(:remove_attachment) || attachment) && @topic.attachment
|
|
@topic.transaction do
|
|
att = @topic.attachment
|
|
@topic.attachment = nil
|
|
@topic.save! if !@topic.new_record?
|
|
att.destroy!
|
|
end
|
|
end
|
|
|
|
if attachment
|
|
@attachment = @context.attachments.create!(:uploaded_data => attachment)
|
|
@topic.attachment = @attachment
|
|
@topic.save
|
|
end
|
|
end
|
|
end
|
|
|
|
def apply_assignment_parameters
|
|
# handle creating/deleting assignment
|
|
if params[:assignment] && !@topic.root_topic_id?
|
|
if params[:assignment].has_key?(:set_assignment) && !value_to_boolean(params[:assignment][:set_assignment])
|
|
if @topic.assignment && @topic.assignment.grants_right?(@current_user, session, :update)
|
|
assignment = @topic.assignment
|
|
@topic.assignment = nil
|
|
@topic.save!
|
|
assignment.destroy
|
|
end
|
|
|
|
elsif (@assignment = @topic.assignment || @topic.restore_old_assignment || (@topic.assignment = @context.assignments.build)) &&
|
|
@assignment.grants_right?(@current_user, session, :update)
|
|
update_api_assignment(@assignment, params[:assignment].merge(@topic.attributes.slice('title')))
|
|
@assignment.submission_types = 'discussion_topic'
|
|
@assignment.saved_by = :discussion_topic
|
|
@topic.assignment = @assignment
|
|
@topic.save!
|
|
end
|
|
end
|
|
end
|
|
|
|
def child_topic
|
|
extra_params = {:headless => 1} if params[:headless]
|
|
@root_topic = @context.context.discussion_topics.find(params[:root_discussion_topic_id])
|
|
@topic = @context.discussion_topics.find_or_initialize_by_root_topic_id(params[:root_discussion_topic_id])
|
|
@topic.message = @root_topic.message
|
|
@topic.title = @root_topic.title
|
|
@topic.assignment_id = @root_topic.assignment_id
|
|
@topic.user_id = @root_topic.user_id
|
|
@topic.save
|
|
redirect_to named_context_url(@context, :context_discussion_topic_url, @topic.id, extra_params)
|
|
end
|
|
|
|
def user_can_edit_course_settings?
|
|
@context.is_a?(Course) && @context.grants_right?(@current_user, session, :update)
|
|
end
|
|
|
|
def handle_assignment_edit_params(hash)
|
|
hash[:title] = params[:title] if params[:title]
|
|
if params.slice(*[:due_at, :points_possible, :assignment_group_id]).present?
|
|
if hash[:assignment].nil? && @context.respond_to?(:assignments) && @context.assignments.new.grants_right?(@current_user, session, :create)
|
|
hash[:assignment] ||= {}
|
|
end
|
|
if !hash[:assignment].nil?
|
|
hash[:assignment][:due_at] = params[:due_at].to_date if params[:due_at]
|
|
hash[:assignment][:points_possible] = params[:points_possible] if params[:points_possible]
|
|
hash[:assignment][:assignment_group_id] = params[:assignment_group_id] if params[:assignment_group_id]
|
|
end
|
|
end
|
|
end
|
|
end
|