convert links in New Quizzes QTI/XML during CC exports
closes QUIZ-13331 flag = none test plan: - create a course in Canvas - add a wiki page - add an assignment - create a New Quiz with at least one question - on rich content fields, insert "internal links" to the wiki page and the assignment (feel free t add more internal links if you have more resources that could be linked) - on a rich content field, attach an image - export the course and download the .imscc file - go to an empty course and import the .imscc package without migrating to New Quizzes - observe that all the links on rich content fields work - go to an empty course and import the .imscc file, migrating CQ to NQ - observe that links appear on the rich content fields - Important note: there is a known issue with Common Cartridge imports where some internal links don't work in New Quizzes generated by converting CQ into NQ. For example, wiki page links, and links to classic quizzes that were migrated into NQ during the import. Change-Id: I109a63daf5996dec2dfcecd030dc43852c3d9766 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/344713 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Griffin Zody <griffin.zody@instructure.com> QA-Review: Griffin Zody <griffin.zody@instructure.com> Product-Review: Marissa Pio Roda <marissa.pioroda@instructure.com>
This commit is contained in:
parent
9cb5ef8fac
commit
fe6dffd959
|
@ -0,0 +1,49 @@
|
||||||
|
# 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"
|
||||||
|
|
||||||
|
module CC
|
||||||
|
class NewQuizzesLinksReplacer
|
||||||
|
def initialize(manifest)
|
||||||
|
@course = manifest.exporter.course
|
||||||
|
@user = manifest.exporter.user
|
||||||
|
@manifest = manifest
|
||||||
|
end
|
||||||
|
|
||||||
|
def replace_links(xml)
|
||||||
|
doc = Nokogiri::XML(xml || "")
|
||||||
|
doc.search("*").each do |node|
|
||||||
|
next unless node.node_name == "mattext" && node["texttype"] == "text/html"
|
||||||
|
|
||||||
|
node.content = html_exporter.html_content(node.content)
|
||||||
|
end
|
||||||
|
|
||||||
|
doc.to_xml
|
||||||
|
end
|
||||||
|
|
||||||
|
def html_exporter
|
||||||
|
@html_exporter ||= CCHelper::HtmlContentExporter.new(@course,
|
||||||
|
@user,
|
||||||
|
for_course_copy: false,
|
||||||
|
key_generator: @manifest)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -43,7 +43,13 @@ module CC
|
||||||
dest_dir = File.join(export_dir, file_dir)
|
dest_dir = File.join(export_dir, file_dir)
|
||||||
FileUtils.mkdir_p(dest_dir)
|
FileUtils.mkdir_p(dest_dir)
|
||||||
|
|
||||||
File.binwrite(File.join(dest_dir, file_name), File.read(f))
|
file_content = File.read(f)
|
||||||
|
|
||||||
|
if file_name.end_with?(".xml", ".qti")
|
||||||
|
file_content = links_replacer.replace_links(file_content)
|
||||||
|
end
|
||||||
|
|
||||||
|
File.binwrite(File.join(dest_dir, file_name), file_content)
|
||||||
file_path
|
file_path
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -98,6 +104,10 @@ module CC
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def links_replacer
|
||||||
|
@links_replacer ||= CC::NewQuizzesLinksReplacer.new(@manifest)
|
||||||
|
end
|
||||||
|
|
||||||
def uploaded_media_resources(file_paths)
|
def uploaded_media_resources(file_paths)
|
||||||
file_paths.each do |file_path|
|
file_paths.each do |file_path|
|
||||||
file_uuid = file_path.split("/").last
|
file_uuid = file_path.split("/").last
|
||||||
|
|
|
@ -0,0 +1,178 @@
|
||||||
|
# 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"
|
||||||
|
|
||||||
|
describe CC::NewQuizzesLinksReplacer do
|
||||||
|
describe "#replace_links" do
|
||||||
|
subject { described_class.new(@manifest) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
@course = course_model
|
||||||
|
@content_export = @course.content_exports.build(global_identifiers: true,
|
||||||
|
export_type: ContentExport::COMMON_CARTRIDGE,
|
||||||
|
user: @user)
|
||||||
|
@exporter = CC::CCExporter.new(@content_export, course: @course, user: @user)
|
||||||
|
@manifest = CC::Manifest.new(@exporter)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the xml contains file links" do
|
||||||
|
before do
|
||||||
|
folder = folder_model(name: "Uploaded Media", context: @course)
|
||||||
|
@attachment = attachment_model(display_name: "aws_opensearch-2.png",
|
||||||
|
context: @course,
|
||||||
|
folder:,
|
||||||
|
uploaded_data: stub_file_data("aws_opensearch-2.png", "...", "image/png"))
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:xml) do
|
||||||
|
<<~XML
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<questestinterop xmlns="http://www.imsglobal.org/xsd/ims_qtiasiv1p2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/ims_qtiasiv1p2 http://www.imsglobal.org/xsd/ims_qtiasiv1p2p1.xsd">
|
||||||
|
<assessment ident="g1ac74172132891415c3a61888ca1c1bb" title="my quiz">
|
||||||
|
<section ident="root_section">
|
||||||
|
<item ident="g31feb7778351b898105e2ab5150f162d" title="Question">
|
||||||
|
<presentation>
|
||||||
|
<material>
|
||||||
|
<mattext texttype="text/html"><div><p>insert question here</p>
|
||||||
|
<p><img src="/courses/#{@course.id}/files/#{@attachment.id}/preview" alt="aws_opensearch-2.png"></p></div></mattext>
|
||||||
|
</material>
|
||||||
|
</presentation>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
|
</assessment>
|
||||||
|
</questestinterop>
|
||||||
|
XML
|
||||||
|
end
|
||||||
|
|
||||||
|
it "replaces course file links" do
|
||||||
|
expected_xml = <<~XML
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<questestinterop xmlns="http://www.imsglobal.org/xsd/ims_qtiasiv1p2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/ims_qtiasiv1p2 http://www.imsglobal.org/xsd/ims_qtiasiv1p2p1.xsd">
|
||||||
|
<assessment ident="g1ac74172132891415c3a61888ca1c1bb" title="my quiz">
|
||||||
|
<section ident="root_section">
|
||||||
|
<item ident="g31feb7778351b898105e2ab5150f162d" title="Question">
|
||||||
|
<presentation>
|
||||||
|
<material>
|
||||||
|
<mattext texttype="text/html"><div><p>insert question here</p>
|
||||||
|
<p><img src="$IMS-CC-FILEBASE$/Uploaded%20Media/aws_opensearch-2.png" alt="aws_opensearch-2.png"></p></div></mattext>
|
||||||
|
</material>
|
||||||
|
</presentation>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
|
</assessment>
|
||||||
|
</questestinterop>
|
||||||
|
XML
|
||||||
|
|
||||||
|
expect(subject.replace_links(xml)).to eq(expected_xml)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the xml contains wiki page links" do
|
||||||
|
before do
|
||||||
|
@page = @course.wiki_pages.create(title: "My wiki page")
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:xml) do
|
||||||
|
<<~XML
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<questestinterop xmlns="http://www.imsglobal.org/xsd/ims_qtiasiv1p2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/ims_qtiasiv1p2 http://www.imsglobal.org/xsd/ims_qtiasiv1p2p1.xsd">
|
||||||
|
<assessment ident="g2267932abda3f486f8304c1beac5a7bf" title="CQ with wiki page link">
|
||||||
|
<section ident="root_section">
|
||||||
|
<item ident="g23bd2508145295f459a42456716f8993" title="Question">
|
||||||
|
<presentation>
|
||||||
|
<material>
|
||||||
|
<mattext texttype="text/html"><div><p><a title="My wiki page" href="/courses/#{@course.id}/pages/my-wiki-page" data-course-type="wikiPages" data-published="false">My wiki page</a></p></div></mattext>
|
||||||
|
</material>
|
||||||
|
</presentation>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
|
</assessment>
|
||||||
|
</questestinterop>
|
||||||
|
XML
|
||||||
|
end
|
||||||
|
|
||||||
|
it "replaces wiki page links" do
|
||||||
|
expected_xml = <<~XML
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<questestinterop xmlns="http://www.imsglobal.org/xsd/ims_qtiasiv1p2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/ims_qtiasiv1p2 http://www.imsglobal.org/xsd/ims_qtiasiv1p2p1.xsd">
|
||||||
|
<assessment ident="g2267932abda3f486f8304c1beac5a7bf" title="CQ with wiki page link">
|
||||||
|
<section ident="root_section">
|
||||||
|
<item ident="g23bd2508145295f459a42456716f8993" title="Question">
|
||||||
|
<presentation>
|
||||||
|
<material>
|
||||||
|
<mattext texttype="text/html"><div><p><a title="My wiki page" href="$WIKI_REFERENCE$/pages/#{CC::CCHelper.create_key(@page, global: true)}" data-course-type="wikiPages" data-published="false">My wiki page</a></p></div></mattext>
|
||||||
|
</material>
|
||||||
|
</presentation>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
|
</assessment>
|
||||||
|
</questestinterop>
|
||||||
|
XML
|
||||||
|
|
||||||
|
expect(subject.replace_links(xml)).to eq(expected_xml)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the xml contains internal links" do
|
||||||
|
before do
|
||||||
|
@assignment = @course.assignments.create!(name: "my quiz")
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:xml) do
|
||||||
|
<<~XML
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<questestinterop xmlns="http://www.imsglobal.org/xsd/ims_qtiasiv1p2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/ims_qtiasiv1p2 http://www.imsglobal.org/xsd/ims_qtiasiv1p2p1.xsd">
|
||||||
|
<assessment ident="gb6738794b6587d8959a0de075def4957" title="CQ with internal links">
|
||||||
|
<section ident="root_section">
|
||||||
|
<item ident="gb2c34ff9aaf42001322d3ce85dd8a433" title="Question">
|
||||||
|
<presentation>
|
||||||
|
<material>
|
||||||
|
<mattext texttype="text/html"><div><p><a title="my quiz" href="/courses/#{@course.id}/assignments/#{@assignment.id}" data-course-type="assignments" data-published="false">my quiz</a></p></div></mattext>
|
||||||
|
</material>
|
||||||
|
</presentation>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
|
</assessment>
|
||||||
|
</questestinterop>
|
||||||
|
XML
|
||||||
|
end
|
||||||
|
|
||||||
|
it "replaces internal links" do
|
||||||
|
expected_xml = <<~XML
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<questestinterop xmlns="http://www.imsglobal.org/xsd/ims_qtiasiv1p2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/ims_qtiasiv1p2 http://www.imsglobal.org/xsd/ims_qtiasiv1p2p1.xsd">
|
||||||
|
<assessment ident="gb6738794b6587d8959a0de075def4957" title="CQ with internal links">
|
||||||
|
<section ident="root_section">
|
||||||
|
<item ident="gb2c34ff9aaf42001322d3ce85dd8a433" title="Question">
|
||||||
|
<presentation>
|
||||||
|
<material>
|
||||||
|
<mattext texttype="text/html"><div><p><a title="my quiz" href="$CANVAS_OBJECT_REFERENCE$/assignments/#{CC::CCHelper.create_key(@assignment, global: true)}" data-course-type="assignments" data-published="false">my quiz</a></p></div></mattext>
|
||||||
|
</material>
|
||||||
|
</presentation>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
|
</assessment>
|
||||||
|
</questestinterop>
|
||||||
|
XML
|
||||||
|
|
||||||
|
expect(subject.replace_links(xml)).to eq(expected_xml)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue