diff --git a/app/models/assignment.rb b/app/models/assignment.rb index 57840cd7bb865..8a5bc3ad919e9 100644 --- a/app/models/assignment.rb +++ b/app/models/assignment.rb @@ -740,6 +740,12 @@ def locked_for?(user, opts={}) end end + def clear_locked_cache(user) + super + Rails.cache.delete(discussion_topic.locked_cache_key(user)) if self.submission_types == 'discussion_topic' && discussion_topic + Rails.cache.delete(quiz.locked_cache_key(user)) if self.submission_types == 'online_quiz' && quiz + end + def submission_types_array (self.submission_types || "").split(",") end diff --git a/app/models/content_tag.rb b/app/models/content_tag.rb index a16b0afb4340c..9adb6ac10650b 100644 --- a/app/models/content_tag.rb +++ b/app/models/content_tag.rb @@ -191,43 +191,72 @@ def asset_safe_title(column) name end + def self.asset_workflow_state(asset) + if asset.respond_to?(:published?) + if asset.respond_to?(:deleted?) && asset.deleted? + 'deleted' + elsif asset.published? + 'active' + else + 'unpublished' + end + else + if asset.respond_to?(:workflow_state) + workflow_state = asset.workflow_state.to_s + if ['active', 'available', 'published'].include?(workflow_state) + 'active' + elsif ['unpublished', 'deleted'].include?(workflow_state) + workflow_state + end + else + nil + end + end + end + + def asset_workflow_state + ContentTag.asset_workflow_state(self.content) + end + + def asset_context_matches? + self.content && self.content.respond_to?(:context) && self.content.context == context + end + def update_asset_name! return unless self.sync_title_to_asset_title? - correct_context = content && content.respond_to?(:context) && content.context == context - if correct_context - # Assignment proxies name= and name to title= and title, which breaks the asset_safe_title logic - if content.respond_to?("name=") && content.respond_to?("name") && !content.is_a?(Assignment) - content.name = asset_safe_title('name') - elsif content.respond_to?("title=") - content.title = asset_safe_title('title') - elsif content.respond_to?("display_name=") - content.display_name = asset_safe_title('display_name') - end - content.save if content.changed? + return unless self.asset_context_matches? + + # Assignment proxies name= and name to title= and title, which breaks the asset_safe_title logic + if content.respond_to?("name=") && content.respond_to?("name") && !content.is_a?(Assignment) + content.name = asset_safe_title('name') + elsif content.respond_to?("title=") + content.title = asset_safe_title('title') + elsif content.respond_to?("display_name=") + content.display_name = asset_safe_title('display_name') end + content.save if content.changed? end def update_asset_workflow_state! return unless self.sync_workflow_state_to_asset? - correct_context = self.content && self.content.respond_to?(:context) && self.content.context == self.context - if correct_context - asset_workflow_state = nil - if self.unpublished? && self.content.respond_to?(:unpublished?) - asset_workflow_state = 'unpublished' - elsif self.active? - if self.content.respond_to?(:active?) - asset_workflow_state = 'active' - elsif self.content.respond_to?(:available?) - asset_workflow_state = 'available' - elsif self.content.respond_to?(:published?) - asset_workflow_state = 'published' - end - end - if asset_workflow_state - self.content.update_attribute(:workflow_state, asset_workflow_state) - self.class.update_for(self.content) + return unless self.asset_context_matches? + + new_asset_workflow_state = nil + if self.unpublished? && self.content.respond_to?(:unpublished?) + new_asset_workflow_state = 'unpublished' + elsif self.active? + if self.content.respond_to?(:active?) + new_asset_workflow_state = 'active' + elsif self.content.respond_to?(:available?) + new_asset_workflow_state = 'available' + elsif self.content.respond_to?(:published?) + new_asset_workflow_state = 'published' end end + if new_asset_workflow_state + self.content.update_attribute(:workflow_state, new_asset_workflow_state) + self.class.update_for(self.content) + end end def self.delete_for(asset) @@ -296,15 +325,12 @@ def self.update_for(asset) # update workflow_state tag_ids = tags.select{|t| t.sync_workflow_state_to_asset? }.map(&:id) attr_hash = {:updated_at => Time.now.utc} - if asset.respond_to?(:workflow_state) - if ['active', 'available', 'published'].include?(asset.workflow_state) - attr_hash[:workflow_state] = 'active' - elsif ['unpublished', 'deleted'].include?(asset.workflow_state) - attr_hash[:workflow_state] = asset.workflow_state - end - end + + workflow_state = asset_workflow_state(asset) + attr_hash[:workflow_state] = workflow_state if workflow_state ContentTag.where(:id => tag_ids).update_all(attr_hash) if attr_hash[:workflow_state] && !tag_ids.empty? + # update the module timestamp ContentTag.touch_context_modules(module_ids) end diff --git a/app/models/context_module.rb b/app/models/context_module.rb index 43ca39464da78..3732f6dfe2aa5 100644 --- a/app/models/context_module.rb +++ b/app/models/context_module.rb @@ -249,7 +249,7 @@ def content_tags_visible_to(user) def add_item(params, added_item=nil, opts={}) params[:type] = params[:type].underscore if params[:type] - position = opts[:position] || (self.content_tags.active.maximum(:position) || 0) + 1 + position = opts[:position] || (self.content_tags.not_deleted.maximum(:position) || 0) + 1 if params[:type] == "wiki_page" || params[:type] == "page" item = opts[:wiki_page] || self.context.wiki.wiki_pages.find_by_id(params[:id]) elsif params[:type] == "attachment" || params[:type] == "file" @@ -261,7 +261,7 @@ def add_item(params, added_item=nil, opts={}) elsif params[:type] == "quiz" item = opts[:quiz] || self.context.quizzes.active.find_by_id(params[:id]) end - workflow_state = item.workflow_state if item && item.respond_to?(:workflow_state) && ['active', 'unpublished'].include?(item.workflow_state) + workflow_state = ContentTag.asset_workflow_state(item) if item workflow_state ||= 'active' if params[:type] == 'external_url' title = params[:title] diff --git a/app/models/discussion_topic.rb b/app/models/discussion_topic.rb index e5a0f4e13401d..40695f3f94674 100644 --- a/app/models/discussion_topic.rb +++ b/app/models/discussion_topic.rb @@ -901,6 +901,12 @@ def locked_for?(user, opts={}) end end + def clear_locked_cache(user) + super + Rails.cache.delete(assignment.locked_cache_key(user)) if assignment + Rails.cache.delete(root_topic.locked_cache_key(user)) if root_topic + end + def self.process_migration(data, migration) process_announcements_migration(Array(data['announcements']), migration) process_discussion_topics_migration(Array(data['discussion_topics']), migration) diff --git a/app/models/quizzes/quiz.rb b/app/models/quizzes/quiz.rb index f6d05be2194b3..10e2344fc9023 100644 --- a/app/models/quizzes/quiz.rb +++ b/app/models/quizzes/quiz.rb @@ -771,6 +771,11 @@ def locked_for?(user, opts={}) end end + def clear_locked_cache(user) + super + Rails.cache.delete(assignment.locked_cache_key(user)) if self.for_assignment? + end + def context_module_action(user, action, points=nil) tags_to_update = self.context_module_tags.to_a if self.assignment diff --git a/lib/features/draft_state.rb b/lib/features/draft_state.rb index 9cc200fb91560..6f00824dddce0 100644 --- a/lib/features/draft_state.rb +++ b/lib/features/draft_state.rb @@ -24,8 +24,16 @@ def perform course.quizzes.where(workflow_state: 'unpublished').update_all(workflow_state: 'edited') course.assignments.where(workflow_state: 'unpublished').update_all(workflow_state: 'published') course.context_modules.where(workflow_state: 'unpublished').update_all(workflow_state: 'active') - course.context_module_tags.where(workflow_state: 'unpublished').update_all(workflow_state: 'active') course.discussion_topics.where(workflow_state: 'unpublished').update_all(workflow_state: 'active') + + # content tags referencing wiki pages or quizzes do not need to be updated + # wiki pages and quizzes handle unpublished values correctly in non-draft state + course.context_module_tags.where(workflow_state: 'unpublished') + .where("content_type NOT IN ('WikiPage', 'Quiz', 'Quizzes::Quiz')") + .update_all(workflow_state: 'active') + + # invalidate cache for modules + course.context_modules.where("workflow_state<>'deleted'").update_all(updated_at: Time.now.utc) end end end diff --git a/spec/apis/v1/context_module_items_api_spec.rb b/spec/apis/v1/context_module_items_api_spec.rb index 94fa2c73332ff..08daf047d729d 100644 --- a/spec/apis/v1/context_module_items_api_spec.rb +++ b/spec/apis/v1/context_module_items_api_spec.rb @@ -25,6 +25,7 @@ @assignment = @course.assignments.create!(:name => "pls submit", :submission_types => ["online_text_entry"], :points_possible => 20) @assignment_tag = @module1.add_item(:id => @assignment.id, :type => 'assignment') @quiz = @course.quizzes.create!(:title => "score 10") + @quiz.publish! @quiz_tag = @module1.add_item(:id => @quiz.id, :type => 'quiz') @topic = @course.discussion_topics.create!(:message => 'pls contribute') @topic_tag = @module1.add_item(:id => @topic.id, :type => 'discussion_topic') diff --git a/spec/controllers/context_modules_controller_spec.rb b/spec/controllers/context_modules_controller_spec.rb index e1ab1de6262aa..0387f12df70e3 100644 --- a/spec/controllers/context_modules_controller_spec.rb +++ b/spec/controllers/context_modules_controller_spec.rb @@ -233,6 +233,7 @@ @module = @course.context_modules.create! quiz = @course.quizzes.create! + quiz.publish! tag = @module.add_item :type => 'quiz', :id => quiz.id diff --git a/spec/integration/context_module_spec.rb b/spec/integration/context_module_spec.rb index 2bcbb46dbbe6b..932e5dbb83c2c 100644 --- a/spec/integration/context_module_spec.rb +++ b/spec/integration/context_module_spec.rb @@ -112,7 +112,8 @@ def progression_testing(progress_by_item_link) @is_attachment = false course_with_student_logged_in(:active_all => true) @quiz = @course.quizzes.create!(:title => "new quiz", :shuffle_answers => true) - + @quiz.publish! + # separate timestamps so touch_context will actually invalidate caches Timecop.freeze(4.seconds.ago) do @mod1 = @course.context_modules.create!(:name => "some module") @@ -129,33 +130,44 @@ def progression_testing(progress_by_item_link) @mod2.save! end + # all modules, tags, etc need to be published + @mod1.should be_published + @mod2.should be_published + @quiz.should be_published + @tag1.should be_published + yield '