Skip to content

Commit 76bfff3

Browse files
committed
topic podcasts
Each topic can now have its own podcast stream. Teachers have to manually turn on the podcast for the topic, and can specify whether student comments show up in the stream or not. fixes #3538 Change-Id: I19b1b44fc2eec864cfeb298163ef34a0b0181067 Reviewed-on: https://gerrit.instructure.com/2369 Tested-by: Hudson <hudson@instructure.com> Reviewed-by: Brian Palmer <brianp@instructure.com>
1 parent 471267d commit 76bfff3

15 files changed

Lines changed: 270 additions & 66 deletions

app/controllers/discussion_entries_controller.rb

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -122,21 +122,55 @@ def destroy
122122

123123
def public_feed
124124
return unless get_feed_context
125-
topic = @context.discussion_topics.active.find(params[:discussion_topic_id])
126-
feed = Atom::Feed.new do |f|
127-
f.title = "#{topic.title}: #{@context.name} Discussion Feed"
128-
f.links << Atom::Link.new(:href => named_context_url(@context, :context_discussion_topic_url, topic.id))
129-
f.updated = Time.now
130-
f.id = named_context_url(@context, :context_discussion_topic_url, topic.id)
125+
@topic = @context.discussion_topics.active.find(params[:discussion_topic_id])
126+
if !@topic.podcast_enabled && request.format == :rss
127+
@problem = "Podcasts have not been enabled for this topic."
128+
@template_format = 'html'
129+
@template.template_format = 'html'
130+
render :text => @template.render(:file => "shared/unauthorized_feed", :layout => "layouts/application"), :status => :bad_request # :template => "shared/unauthorized_feed", :status => :bad_request
131+
return
131132
end
132-
@entries = []
133-
@entries.concat topic.discussion_entries
134-
@entries = @entries.sort_by{|e| e.updated_at}
135-
@entries.each do |entry|
136-
feed.entries << entry.to_atom
137-
end
138-
respond_to do |format|
139-
format.atom { render :text => feed.to_xml }
133+
if authorized_action(@topic, @current_user, :read)
134+
@all_discussion_entries = @topic.discussion_entries.active
135+
@discussion_entries = @all_discussion_entries
136+
if request.format == :rss && !@topic.podcast_has_student_posts
137+
@admins = @context.admins
138+
@discussion_entries = @discussion_entries.find_all_by_user_id(@admins.map(&:id))
139+
end
140+
if @topic.locked_for?(@current_user) && !@topic.grants_right?(@current_user, nil, :update)
141+
@discussion_entries = []
142+
end
143+
respond_to do |format|
144+
format.atom {
145+
feed = Atom::Feed.new do |f|
146+
f.title = "#{@topic.title} Posts Feed"
147+
f.links << Atom::Link.new(:href => named_context_url(@context, :context_discussion_topic_url, @topic.id))
148+
f.updated = Time.now
149+
f.id = named_context_url(@context, :context_discussion_topic_url, @topic.id)
150+
end
151+
feed.entries << @topic.to_atom
152+
@discussion_entries.sort_by{|e| e.updated_at}.each do |e|
153+
feed.entries << e.to_atom
154+
end
155+
render :text => feed.to_xml
156+
}
157+
format.rss {
158+
@entries = [@topic] + @discussion_entries
159+
require 'rss/2.0'
160+
rss = RSS::Rss.new("2.0")
161+
channel = RSS::Rss::Channel.new
162+
channel.title = "#{@topic.title} Posts Podcast Feed"
163+
channel.description = "Any media files linked from or embedded within entries in the topic \"#{@topic.title}\" will appear in this feed."
164+
channel.link = named_context_url(@context, :context_discussion_topic_url, @topic.id)
165+
channel.pubDate = Time.now.strftime("%a, %d %b %Y %H:%M:%S %z")
166+
elements = Announcement.podcast_elements(@entries, @context)
167+
elements.each do |item|
168+
channel.items << item
169+
end
170+
rss.channel = channel
171+
render :text => rss.to_s
172+
}
173+
end
140174
end
141175
end
142176
end

app/controllers/discussion_topics_controller.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ def create
151151
assignment = params[:discussion_topic].delete(:assignment)
152152
generate_assignment(assignment) if assignment && assignment[:set_assignment]
153153

154+
unless @context.grants_right?(@current_user, session, :moderate_forum)
155+
params[:discussion_topic].delete :podcast_enabled
156+
params[:discussion_topic].delete :podcast_has_student_posts
157+
end
154158
if params[:discussion_topic].delete(:is_announcement) == "1" && @context.announcements.new.grants_right?(@current_user, session, :create)
155159
@topic = @context.announcements.build(params[:discussion_topic])
156160
else
@@ -204,6 +208,10 @@ def update
204208
@topic.workflow_state = (params[:discussion_topic][:lock] == '1') ? 'locked' : 'active'
205209
params[:discussion_topic].delete :lock
206210
end
211+
unless @context.grants_right?(@current_user, session, :moderate_forum)
212+
params[:discussion_topic].delete :podcast_enabled
213+
params[:discussion_topic].delete :podcast_has_student_posts
214+
end
207215
delay_posting = params[:discussion_topic].delete :delay_posting
208216
delayed_post_at = params[:discussion_topic].delete :delayed_post_at
209217
if @topic.post_delayed?
@@ -270,4 +278,7 @@ def public_feed
270278
format.atom { render :text => feed.to_xml }
271279
end
272280
end
281+
282+
def public_topic_feed
283+
end
273284
end

app/models/attachment.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class Attachment < ActiveRecord::Base
4848
after_save :touch_context
4949
after_create :build_media_object
5050

51-
attr_accessor :podcast_associated_announcement
51+
attr_accessor :podcast_associated_asset
5252

5353
# this is a magic method that gets run by attachment-fu after it is done sending to s3,
5454
# that is the moment that we also want to submit it to scribd.

app/models/discussion_entry.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,10 +219,13 @@ def update_topic
219219
named_scope :after, lambda{|date|
220220
{:conditions => ['created_at > ?', date] }
221221
}
222+
named_scope :include_subentries, lambda{
223+
{:include => discussion_subentries}
224+
}
222225

223226
def to_atom(opts={})
224227
Atom::Entry.new do |entry|
225-
entry.title = "Entry#{", " + self.discussion_topic.context.name if opts[:include_context]}: #{self.discussion_topic.title}"
228+
entry.title = "#{"Re: " if parent_id != 0}#{self.discussion_topic.title}#{", " + self.discussion_topic.context.name if opts[:include_context]}"
226229
entry.updated = self.updated_at
227230
entry.published = self.created_at
228231
entry.id = "tag:#{HostUrl.default_host},#{self.created_at.strftime("%Y-%m-%d")}:/discussion_entries/#{self.feed_code}"

app/models/discussion_topic.rb

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class DiscussionTopic < ActiveRecord::Base
2323
include HasContentTags
2424
include CopyAuthorizedLinks
2525

26-
attr_accessible :title, :message, :user, :delayed_post_at, :assignment, :plaintext_message
26+
attr_accessible :title, :message, :user, :delayed_post_at, :assignment, :plaintext_message, :podcast_enabled, :podcast_has_student_posts
2727

2828
attr_readonly :context_id, :context_type, :user_id
2929
adheres_to_policy
@@ -518,34 +518,34 @@ def self.import_from_migration(hash, context, item=nil)
518518
item
519519
end
520520

521-
def self.podcast_elements(announcements, context)
521+
def self.podcast_elements(messages, context)
522522
attachment_ids = []
523523
media_object_ids = []
524-
announcements_hash = {}
525-
announcements.each do |announcement|
526-
txt = (announcement.message || "")
524+
messages_hash = {}
525+
messages.each do |message|
526+
txt = (message.message || "")
527527
attachment_matches = txt.scan(/\/#{context.class.to_s.pluralize.underscore}\/#{context.id}\/files\/(\d+)\/download/)
528528
attachment_ids += (attachment_matches || []).map{|m| m[0] }
529529
media_object_matches = txt.scan(/media_comment_([0-9a-z_]+)/)
530530
media_object_ids += (media_object_matches || []).map{|m| m[0] }
531531
(attachment_ids + media_object_ids).each do |id|
532-
announcements_hash[id] ||= announcement
532+
messages_hash[id] ||= message
533533
end
534534
end
535535
media_object_ids = media_object_ids.uniq.compact
536536
attachment_ids = attachment_ids.uniq.compact
537537
attachments = context.attachments.active.find_all_by_id(attachment_ids).compact
538538
attachments = attachments.select{|a| a.content_type && a.content_type.match(/(video|audio)/) }
539539
attachments.each do |attachment|
540-
attachment.podcast_associated_announcement = announcements_hash[attachment.id]
540+
attachment.podcast_associated_asset = messages_hash[attachment.id]
541541
end
542542
media_objects = MediaObject.find_all_by_media_id(media_object_ids)
543543
media_objects += media_object_ids.map{|id| MediaObject.new(:media_id => id) }
544544
media_objects = media_objects.once_per(&:media_id)
545545
media_objects = media_objects.map do |media_object|
546546
if media_object.new_record?
547547
media_object.context = context
548-
media_object.user_id = announcements_hash[media_object.media_id].user_id rescue nil
548+
media_object.user_id = messages_hash[media_object.media_id].user_id rescue nil
549549
media_object.root_account_id = context.root_account_id rescue nil
550550
media_object.save
551551
elsif media_object.deleted? || media_object.context != context
@@ -554,7 +554,7 @@ def self.podcast_elements(announcements, context)
554554
if !media_object.podcast_format_details
555555
media_object = nil
556556
end
557-
media_object.podcast_associated_announcement = announcements_hash[media_object.media_id]
557+
media_object.podcast_associated_asset = messages_hash[media_object.media_id] if media_object
558558
media_object
559559
end
560560
to_podcast(attachments + media_objects.compact)
@@ -563,15 +563,21 @@ def self.podcast_elements(announcements, context)
563563
def self.to_podcast(elements, opts={})
564564
require 'rss/2.0'
565565
elements.map do |elem|
566-
announcement = elem.podcast_associated_announcement rescue nil
567-
next unless announcement
566+
asset = elem.podcast_associated_asset
567+
next unless asset
568568
item = RSS::Rss::Channel::Item.new
569-
item.title = (announcement.title rescue "") + ": " + elem.name
570-
link = "http://#{HostUrl.context_host(announcement.context)}/#{announcement.context_url_prefix}/announcements/#{announcement.id}"
569+
item.title = (asset.title rescue "") + ": " + elem.name
570+
link = nil
571+
if asset.is_a?(DiscussionTopic)
572+
link = "http://#{HostUrl.context_host(asset.context)}/#{asset.context_url_prefix}/discussion_topics/#{asset.id}"
573+
elsif asset.is_a?(DiscussionEntry)
574+
link = "http://#{HostUrl.context_host(asset.context)}/#{asset.context_url_prefix}/discussion_topics/#{asset.discussion_topic_id}"
575+
end
576+
571577
item.link = link
572578
item.guid = RSS::Rss::Channel::Item::Guid.new
573579
item.pubDate = elem.updated_at.utc
574-
item.description = announcement ? announcement.message : elem.name
580+
item.description = asset ? asset.message : elem.name
575581
item.enclosure
576582
if elem.is_a?(Attachment)
577583
item.guid.content = link + "/#{elem.uuid}"

app/models/media_object.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ class MediaObject < ActiveRecord::Base
2727
after_save :update_title_on_kaltura_later
2828
serialize :data
2929

30+
attr_accessor :podcast_associated_asset
31+
3032
def infer_defaults
3133
self.user_type = "admin" if self.user && self.cached_context_grants_right?(self.user, nil, :manage_content)
3234
self.user_type ||= "student"

app/stylesheets/g_instructure.sass

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,8 @@ ul.group_list
753753
:font-size 0.8em
754754
:padding-right 5px
755755
:padding-left 0
756+
.podcast
757+
display: none
756758

757759
.user_name
758760
:white-space nowrap
@@ -857,6 +859,12 @@ ul.group_list
857859
.user_content
858860
p:last-child
859861
margin-bottom: 0
862+
&.has_podcast
863+
.header
864+
.podcast
865+
display: block
866+
float: right
867+
+opacity(.7)
860868
.communication_message_hover
861869
div.header
862870
.link_box

app/stylesheets/g_util_fancy_links.sass

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ a.email
7575
a.expand
7676
+icon_link
7777
:background-image url(/images/expand.png)
78+
a.feed
79+
+icon_link
80+
:background-image url(/images/atom.png)
7881
a.file-multiple
7982
+icon_link
8083
:background-image url(/images/file_multiple.png)

app/views/announcements/index.html.erb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
<% content_for :auto_discovery do %>
44
<% if @context_enrollment %>
55
<%= auto_discovery_link_tag(:atom, feeds_announcements_format_path(@context_enrollment.feed_code, :atom), {:title => "#{@context.class.to_s} Announcements Atom Feed"}) %>
6-
<%= auto_discovery_link_tag(:atom, feeds_announcements_format_path(@context_enrollment.feed_code, :rss), {:title => "#{@context.class.to_s} Announcements Podcast Feed"}) %>
6+
<%= auto_discovery_link_tag(:rss, feeds_announcements_format_path(@context_enrollment.feed_code, :rss), {:title => "#{@context.class.to_s} Announcements Podcast Feed"}) %>
77
<% elsif can_do(@context, @current_user, :manage) %>
88
<%= auto_discovery_link_tag(:atom, feeds_announcements_format_path(@context.feed_code, :atom), {:title => "#{@context.class.to_s} Announcements Atom Feed"}) %>
9-
<%= auto_discovery_link_tag(:atom, feeds_announcements_format_path(@context.feed_code, :rss), {:title => "#{@context.class.to_s} Announcements Podcast Feed"}) %>
9+
<%= auto_discovery_link_tag(:rss, feeds_announcements_format_path(@context.feed_code, :rss), {:title => "#{@context.class.to_s} Announcements Podcast Feed"}) %>
1010
<% elsif @context.available? && @context.respond_to?(:is_public) && @context.is_public %>
1111
<%= auto_discovery_link_tag(:atom, feeds_announcements_format_path(@context.asset_string, :atom), {:title => "#{@context.class.to_s} Announcements Atom Feed"}) %>
12-
<%= auto_discovery_link_tag(:atom, feeds_announcements_format_path(@context.asset_string, :rss), {:title => "#{@context.class.to_s} Announcements Podcast Feed"}) %>
12+
<%= auto_discovery_link_tag(:rss, feeds_announcements_format_path(@context.asset_string, :rss), {:title => "#{@context.class.to_s} Announcements Podcast Feed"}) %>
1313
<% end %>
1414
<% end %>
1515

app/views/discussion_topics/show.html.erb

Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,14 @@
33
<% content_for :auto_discovery do %>
44
<% if @context_enrollment %>
55
<%= auto_discovery_link_tag(:atom, feeds_topic_format_path(@topic.id, @context_enrollment.feed_code, :atom), {:title => "Discussion Atom Feed"}) %>
6+
<% if @topic.podcast_enabled %>
7+
<%= auto_discovery_link_tag(:rss, feeds_topic_format_path(@topic.id, @context_enrollment.feed_code, :rss), {:title => "Discussion Podcast Feed"}) %>
8+
<% end %>
69
<% elsif @context.available? %>
710
<%= auto_discovery_link_tag(:atom, feeds_topic_format_path(@topic.id, @context.feed_code, :atom), {:title => "Discussion Atom Feed"}) %>
11+
<% if @topic.podcast_enabled %>
12+
<%= auto_discovery_link_tag(:rss, feeds_topic_format_path(@topic.id, @context.feed_code, :rss), {:title => "Discussion Podcast Feed"}) %>
13+
<% end %>
814
<% end %>
915
<% end %>
1016

@@ -30,38 +36,49 @@
3036
<% else %>
3137
<div class="rs-margin-all">
3238
<div id="sidebar_content">
33-
<p>
34-
<b><span class="message_count"><%= @entries.length %></span> <span class="message_count_text"><%= @entries.length == 1 ? 'post' : 'posts' %></span></b>
35-
<% if @entries.length > 0 && !@topic_agglomerated %>
36-
<span style="font-size: 0.8em; padding-left: 10px;">( <span class="total_message_count"><%= @topic.discussion_entries.active.length %></span> including subtopics )</span>
37-
<% end %>
38-
</p>
39-
<p>
40-
<% if @topic_agglomerated %>
41-
This view shows all the messages from all this topic's group topics. If you want to
42-
comment or edit posts, you'll have to visit each topic individually.
43-
<ul class="unstyled_list" style="line-height: 1.8em; margin: 5px 20px 10px;">
44-
<% @groups.select{|g| can_do(g, @current_user, :read) }.each do |group| %>
45-
<li class="unstyled_list">
46-
<% cnt = (@topics || []).find{|t| t.context == group}.discussion_entries.count rescue 0 %>
47-
<b><a href="<%= context_url(group, :context_discussion_topics_url, :root_discussion_topic_id => @topic.id) %>"><%= group.name %></a></b> - <%= pluralize(cnt, 'Post') %>
48-
</li>
39+
<p>
40+
<b><span class="message_count"><%= @entries.length %></span> <span class="message_count_text"><%= @entries.length == 1 ? 'post' : 'posts' %></span></b>
41+
<% if @entries.length > 0 && !@topic_agglomerated %>
42+
<span style="font-size: 0.8em; padding-left: 10px;">( <span class="total_message_count"><%= @topic.discussion_entries.active.length %></span> including subtopics )</span>
4943
<% end %>
50-
</ul>
51-
<% else %>
52-
<% end %>
53-
</p>
54-
<p>
55-
<% if can_do(@topic, @current_user, :update) %>
56-
<a href="#" class="edit_topic_link button button-sidebar-wide"><%= image_tag "edit.png", :alt => "" %> Edit Topic</a>
57-
<% end %>
58-
<% if can_do(@topic, @current_user, :reply) && !params[:combined] %>
59-
<a href="#" class="add_entry_link button button-sidebar-wide"><%= image_tag "add.png", :alt => "" %> Add New Entry</a>
60-
<% end %>
61-
<% if can_do(@topic, @current_user, :delete) && !params[:combined] %>
62-
<a href="#" class="delete_topic_link button button-sidebar-wide"><%= image_tag "delete.png", :alt => "" %> Delete Topic</a>
63-
<% end %>
64-
</p>
44+
</p>
45+
<p>
46+
<% if @topic_agglomerated %>
47+
This view shows all the messages from all this topic's group topics. If you want to
48+
comment or edit posts, you'll have to visit each topic individually.
49+
<ul class="unstyled_list" style="line-height: 1.8em; margin: 5px 20px 10px;">
50+
<% @groups.select{|g| can_do(g, @current_user, :read) }.each do |group| %>
51+
<li class="unstyled_list">
52+
<% cnt = (@topics || []).find{|t| t.context == group}.discussion_entries.count rescue 0 %>
53+
<b><a href="<%= context_url(group, :context_discussion_topics_url, :root_discussion_topic_id => @topic.id) %>"><%= group.name %></a></b> - <%= pluralize(cnt, 'Post') %>
54+
</li>
55+
<% end %>
56+
</ul>
57+
<% else %>
58+
<% end %>
59+
</p>
60+
<p>
61+
<% if can_do(@topic, @current_user, :update) %>
62+
<a href="#" class="edit_topic_link button button-sidebar-wide"><%= image_tag "edit.png", :alt => "" %> Edit Topic</a>
63+
<% end %>
64+
<% if can_do(@topic, @current_user, :reply) && !params[:combined] %>
65+
<a href="#" class="add_entry_link button button-sidebar-wide"><%= image_tag "add.png", :alt => "" %> Add New Entry</a>
66+
<% end %>
67+
<% if can_do(@topic, @current_user, :delete) && !params[:combined] %>
68+
<a href="#" class="delete_topic_link button button-sidebar-wide"><%= image_tag "delete.png", :alt => "" %> Delete Topic</a>
69+
<% end %>
70+
</p>
71+
<div id="podcast_link_holder" style="<%= hidden unless @topic.podcast_enabled %>">
72+
<% if @context_enrollment %>
73+
<p>
74+
<a class="feed" href="<%= feeds_topic_format_path(@topic.id, @context_enrollment.feed_code, :rss) %>">Topic Podcast Feed</a>
75+
</p>
76+
<% elsif @context.available? %>
77+
<p>
78+
<a class="feed" href="<%= feeds_topic_format_path(@topic.id, @context.feed_code, :rss) %>">Topic Podcast Feed</a>
79+
</p>
80+
<% end %>
81+
</div>
6582
</div>
6683
</div>
6784
<%= render :partial => "shared/wiki_sidebar" %>

0 commit comments

Comments
 (0)