canvas-lms/lib/canvas_imported_html_conver...

232 lines
7.8 KiB
Ruby

# frozen_string_literal: true
#
# 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/>.
#
require "nokogiri"
class CanvasImportedHtmlConverter < CanvasLinkMigrator::ImportedHtmlConverter
include CanvasLinkMigrator
LINK_TYPE_TO_CLASS = {
announcement: Announcement,
assessment_question: AssessmentQuestion,
assignment: Assignment,
calendar_event: CalendarEvent,
discussion_topic: DiscussionTopic,
quiz: Quizzes::Quiz,
learning_outcome: LearningOutcome,
wiki_page: WikiPage
}.freeze
def initialize(migration)
@migration = migration
super(migration_id_converter: Importers::DbMigrationQueryService.new(migration))
end
def context
@migration.context
end
def context_path
@migration_id_converter.context_path
end
def resolve_content_links!
link_map = link_parser.unresolved_link_map
return unless link_map.present?
link_resolver.resolve_links!(link_map)
replace_placeholders!(link_map)
link_parser.reset!
end
def replace_placeholders!(link_map)
load_questions!(link_map)
link_map.each do |item_key, field_links|
item_key[:item] ||= retrieve_item(item_key)
replace_item_placeholders!(item_key, field_links)
add_missing_link_warnings!(item_key, field_links)
rescue
@migration.add_warning("An error occurred while translating content links", $!)
end
end
# these don't get added to the list of imported migration items
def load_questions!(link_map)
aq_item_keys = link_map.keys.select { |item_key| item_key[:type] == :assessment_question }
aq_item_keys.each_slice(100) do |item_keys|
context.assessment_questions.where(migration_id: item_keys.pluck(:migration_id)).preload(:assessment_question_bank).each do |aq|
item_keys.detect { |ikey| ikey[:migration_id] == aq.migration_id }[:item] = aq
end
end
qq_item_keys = link_map.keys.select { |item_key| item_key[:type] == :quiz_question }
qq_item_keys.each_slice(100) do |item_keys|
context.quiz_questions.where(migration_id: item_keys.pluck(:migration_id)).each do |qq|
item_keys.detect { |ikey| ikey[:migration_id] == qq.migration_id }[:item] = qq
end
end
end
def retrieve_item(item_key)
klass = LINK_TYPE_TO_CLASS[item_key[:type]]
return unless klass
item = @migration.find_imported_migration_item(klass, item_key[:migration_id])
raise "item not found" unless item
item
end
def add_missing_link_warnings!(item_key, field_links)
fix_issue_url = nil
field_links.each do |field, links|
missing_links = links.select { |link| link[:replaced] && (link[:missing_url] || !link[:new_value]) }
next unless missing_links.any?
fix_issue_url ||= fix_issue_url(item_key)
type = item_key[:type].to_s.humanize.titleize
@migration.add_warning_for_missing_content_links(type, field, missing_links, fix_issue_url)
end
end
def fix_issue_url(item_key)
item = item_key[:item]
case item_key[:type]
when :assessment_question
"#{context_path}/question_banks/#{item.assessment_question_bank_id}#question_#{item.id}_question_text"
when :quiz_question
"#{context_path}/quizzes/#{item.quiz_id}/edit" # can't jump to the question unfortunately
when :syllabus
"#{context_path}/assignments/syllabus"
when :wiki_page
"#{context_path}/pages/#{item.url}"
else
"#{context_path}/#{item.class.to_s.demodulize.underscore.pluralize}/#{item.id}"
end
end
def replace_item_placeholders!(item_key, field_links, skip_associations = false)
case item_key[:type]
when :syllabus
syllabus = context.syllabus_body
if LinkReplacer.sub_placeholders!(syllabus, field_links.values.flatten)
context.class.where(id: context.id).update_all(syllabus_body: syllabus)
end
when :assessment_question
process_assessment_question!(item_key[:item], field_links.values.flatten)
when :quiz_question
process_quiz_question!(item_key[:item], field_links.values.flatten)
else
item = item_key[:item]
item_updates = {}
field_links.each do |field, links|
html = item.read_attribute(field)
if LinkReplacer.sub_placeholders!(html, links)
item_updates[field] = html
end
end
if item_updates.present?
item.class.where(id: item.id).update_all(item_updates)
# we don't want the placeholders sticking around in any
# version we've created.
rewrite_item_version!(item)
end
unless skip_associations
process_assignment_types!(item, field_links.values.flatten)
end
end
end
def rewrite_item_version!(item)
if (version = (item.current_version rescue nil))
# if there's a current version of this thing, it has placeholders
# in it. rather than replace them in the yaml, which is finnicky, let's just
# make sure the current version is represented by the current model state
# by overwritting it
version.model = item.reload
version.save
end
end
def process_assignment_types!(item, links)
case item
when Assignment
if item.discussion_topic
replace_item_placeholders!({ item: item.discussion_topic }, { message: links }, true)
end
if item.quiz
replace_item_placeholders!({ item: item.quiz }, { description: links }, true)
end
when DiscussionTopic, Quizzes::Quiz
if item.assignment
replace_item_placeholders!({ item: item.assignment }, { description: links }, true)
end
end
end
def process_assessment_question!(aq, links)
# we have to do a little bit more here because the question_data can get copied all over
quiz_ids = []
Quizzes::QuizQuestion.where(assessment_question_id: aq.id).find_each do |qq|
if LinkReplacer.recursively_sub_placeholders!(qq["question_data"], links)
Quizzes::QuizQuestion.where(id: qq.id).update_all(question_data: qq["question_data"])
quiz_ids << qq.quiz_id
end
end
if quiz_ids.any?
Quizzes::Quiz.where(id: quiz_ids.uniq).where.not(quiz_data: nil).find_each do |quiz|
if LinkReplacer.recursively_sub_placeholders!(quiz["quiz_data"], links)
Quizzes::Quiz.where(id: quiz.id).update_all(quiz_data: quiz["quiz_data"])
end
end
end
# we have to do some special link translations for files in assessment questions
# because we stopped doing them in the regular importer
# basically just moving them to the question context
links.each do |link|
next unless link[:new_value]
link[:new_value] = aq.translate_file_link(link[:new_value])
end
if LinkReplacer.recursively_sub_placeholders!(aq["question_data"], links)
AssessmentQuestion.where(id: aq.id).update_all(question_data: aq["question_data"])
end
end
def process_quiz_question!(qq, links)
if LinkReplacer.recursively_sub_placeholders!(qq["question_data"], links)
Quizzes::QuizQuestion.where(id: qq.id).update_all(question_data: qq["question_data"])
end
quiz = Quizzes::Quiz.where(id: qq.quiz_id).where.not(quiz_data: nil).first
if quiz && LinkReplacer.recursively_sub_placeholders!(quiz["quiz_data"], links)
Quizzes::Quiz.where(id: quiz.id).update_all(quiz_data: quiz["quiz_data"])
end
end
end