canvas_link_migrator 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/canvas_link_migrator/imported_html_converter.rb +68 -0
- data/lib/canvas_link_migrator/link_parser.rb +243 -0
- data/lib/canvas_link_migrator/link_replacer.rb +48 -0
- data/lib/canvas_link_migrator/link_resolver.rb +251 -0
- data/lib/canvas_link_migrator/resource_map_service.rb +88 -0
- data/lib/canvas_link_migrator/version.rb +3 -0
- data/lib/canvas_link_migrator.rb +33 -0
- data/spec/canvas_link_migrator/imported_html_converter_spec.rb +279 -0
- data/spec/canvas_link_migrator/link_resolver_spec.rb +66 -0
- data/spec/canvas_link_migrator_spec.rb +41 -0
- data/spec/fixtures/canvas_resource_map.json +73 -0
- data/spec/spec_helper.rb +21 -0
- data/test.sh +4 -0
- metadata +139 -0
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (C) 2023 - present Instructure, Inc.
|
5
|
+
#
|
6
|
+
# This file is part of Canvas.
|
7
|
+
#
|
8
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
9
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
10
|
+
# Software Foundation, version 3 of the License.
|
11
|
+
#
|
12
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
13
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
14
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
15
|
+
# details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Affero General Public License along
|
18
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
module CanvasLinkMigrator
|
20
|
+
# This class encapsulates the logic to retrieve metadata (for various types of assets)
|
21
|
+
# given a migration id. This particular implementation relies on the migration object Canvas
|
22
|
+
# creates
|
23
|
+
#
|
24
|
+
# Each function returns exactly one id (if available), and nil if an id
|
25
|
+
# cannot be resolved
|
26
|
+
#
|
27
|
+
class ResourceMapService
|
28
|
+
attr_reader :migration_data
|
29
|
+
|
30
|
+
def initialize(migration_data)
|
31
|
+
@migration_data = migration_data
|
32
|
+
end
|
33
|
+
|
34
|
+
def resources
|
35
|
+
migration_data["resource_mapping"]
|
36
|
+
end
|
37
|
+
|
38
|
+
### Overwritable methods
|
39
|
+
def supports_embedded_images
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
def fix_relative_urls?
|
44
|
+
true
|
45
|
+
end
|
46
|
+
|
47
|
+
def process_domain_substitutions(url)
|
48
|
+
url
|
49
|
+
end
|
50
|
+
|
51
|
+
def context_hosts
|
52
|
+
migration_data["destination_hosts"]
|
53
|
+
end
|
54
|
+
|
55
|
+
def attachment_path_id_lookup; end
|
56
|
+
|
57
|
+
def attachment_path_id_lookup_lower; end
|
58
|
+
|
59
|
+
def root_folder_name
|
60
|
+
migration_data["destination_root_folder"]
|
61
|
+
end
|
62
|
+
### End of Ovewritable methods
|
63
|
+
|
64
|
+
# Returns the path for the context, for a course, it should return something like
|
65
|
+
# "courses/1"
|
66
|
+
def context_path
|
67
|
+
"/courses/#{migration_data["destination_course"]}"
|
68
|
+
end
|
69
|
+
|
70
|
+
# Looks up a wiki page slug for a migration id
|
71
|
+
def convert_wiki_page_migration_id_to_slug(migration_id)
|
72
|
+
resources.dig("wiki_pages", migration_id, "destination", "url")
|
73
|
+
end
|
74
|
+
|
75
|
+
# looks up a discussion topic
|
76
|
+
def convert_discussion_topic_migration_id(migration_id)
|
77
|
+
resources.dig("discussion_topics", migration_id, "destination", "id")
|
78
|
+
end
|
79
|
+
|
80
|
+
def convert_context_module_tag_migration_id(migration_id)
|
81
|
+
resources.dig("module_items", migration_id, "destination", "id")
|
82
|
+
end
|
83
|
+
|
84
|
+
def convert_attachment_migration_id(migration_id)
|
85
|
+
resources.dig("files", migration_id, "destination", "id")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (C) 2023 - present Instructure, Inc.
|
5
|
+
#
|
6
|
+
# This file is part of Canvas.
|
7
|
+
#
|
8
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
9
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
10
|
+
# Software Foundation, version 3 of the License.
|
11
|
+
#
|
12
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
13
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
14
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
15
|
+
# details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Affero General Public License along
|
18
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
|
20
|
+
require "canvas_link_migrator/resource_map_service"
|
21
|
+
require "canvas_link_migrator/imported_html_converter"
|
22
|
+
require "canvas_link_migrator/link_parser"
|
23
|
+
require "canvas_link_migrator/link_replacer"
|
24
|
+
require "canvas_link_migrator/link_resolver"
|
25
|
+
|
26
|
+
module CanvasLinkMigrator
|
27
|
+
def self.relative_url?(url)
|
28
|
+
URI.parse(url).relative? && !url.to_s.start_with?("//")
|
29
|
+
rescue URI::Error
|
30
|
+
# leave the url as it was
|
31
|
+
false
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,279 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (C) 2012 - present Instructure, Inc.
|
5
|
+
#
|
6
|
+
# This file is part of Canvas.
|
7
|
+
#
|
8
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
9
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
10
|
+
# Software Foundation, version 3 of the License.
|
11
|
+
#
|
12
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
13
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
14
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
15
|
+
# details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Affero General Public License along
|
18
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
#
|
20
|
+
|
21
|
+
require "spec_helper"
|
22
|
+
require "json"
|
23
|
+
|
24
|
+
describe CanvasLinkMigrator::ImportedHtmlConverter do
|
25
|
+
# tests link_parser and link_resolver
|
26
|
+
|
27
|
+
describe ".convert" do
|
28
|
+
before do
|
29
|
+
@path = "/courses/2/"
|
30
|
+
@converter = CanvasLinkMigrator::ImportedHtmlConverter.new(resource_map: JSON.parse(File.read("spec/fixtures/canvas_resource_map.json")))
|
31
|
+
end
|
32
|
+
|
33
|
+
it "converts a wiki reference" do
|
34
|
+
test_string = %(<a href="%24WIKI_REFERENCE%24/wiki/test-wiki-page?query=blah">Test Wiki Page</a>)
|
35
|
+
html, bad_links = @converter.convert_exported_html(test_string)
|
36
|
+
expect(html).to eq %(<a href="#{@path}pages/test-wiki-page?query=blah">Test Wiki Page</a>)
|
37
|
+
expect(bad_links).to be_nil
|
38
|
+
end
|
39
|
+
|
40
|
+
context "when course attachments exist" do
|
41
|
+
subject { @converter.convert_exported_html(test_string) }
|
42
|
+
|
43
|
+
let(:migration_id) { "E" }
|
44
|
+
|
45
|
+
context "and a data-download-url attribute references an icon maker icon" do
|
46
|
+
let(:test_string) do
|
47
|
+
%(<img src="$CANVAS_COURSE_REFERENCE$/file_ref/#{migration_id}/download?download_frd=1" alt="" data-inst-icon-maker-icon="true" data-download-url="$CANVAS_COURSE_REFERENCE$/file_ref/#{migration_id}/download?download_frd=1&icon_maker_icon=1">)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "converts data-download-url for files without appending a context" do
|
51
|
+
html, bad_links = subject
|
52
|
+
expect(html).to eq(
|
53
|
+
"<img src=\"#{@path}files/5/download?download_frd=1\" alt=\"\" data-inst-icon-maker-icon=\"true\" data-download-url=\"/files/5/download?download_frd=1&icon_maker_icon=1\">"
|
54
|
+
)
|
55
|
+
expect(bad_links).to be_nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# it "finds an attachment by migration id" do
|
60
|
+
# test_string = %{<p>This is an image: <br /><img src="%24CANVAS_OBJECT_REFERENCE%24/attachments/F" alt=":(" /></p>}
|
61
|
+
# expect(@converter.convert_exported_html(test_string)).to eq([%{<p>This is an image: <br><img src="#{@path}files/6/preview" alt=":("></p>}, nil])
|
62
|
+
# end
|
63
|
+
|
64
|
+
# it "finds an attachment by path" do
|
65
|
+
# test_string = %{<p>This is an image: <br /><img src="%24IMS_CC_FILEBASE%24/test.png" alt=":(" /></p>}
|
66
|
+
|
67
|
+
# # if there isn't a path->migration id map it'll be a relative course file path
|
68
|
+
# expect(@converter.convert_exported_html(test_string)).to eq %{<p>This is an image: <br><img src="#{@path}file_contents/course%20files/test.png" alt=":("></p>}
|
69
|
+
|
70
|
+
# @migration.attachment_path_id_lookup = { "test.png" => att.migration_id }
|
71
|
+
# expect(@converter.convert_exported_html(test_string)).to eq %{<p>This is an image: <br><img src="#{@path}files/#{att.id}/preview" alt=":("></p>}
|
72
|
+
# end
|
73
|
+
|
74
|
+
# it "finds an attachment by a path with a space" do
|
75
|
+
# att = make_test_att
|
76
|
+
# @migration.attachment_path_id_lookup = { "subfolder/with a space/test.png" => att.migration_id }
|
77
|
+
|
78
|
+
# test_string = %(<img src="subfolder/with%20a%20space/test.png" alt="nope" />)
|
79
|
+
# expect(@converter.convert_exported_html(test_string)).to eq %(<img src="#{@path}files/#{att.id}/preview" alt="nope">)
|
80
|
+
|
81
|
+
# test_string = %(<img src="subfolder/with+a+space/test.png" alt="nope" />)
|
82
|
+
# expect(@converter.convert_exported_html(test_string)).to eq %(<img src="#{@path}files/#{att.id}/preview" alt="nope">)
|
83
|
+
# end
|
84
|
+
|
85
|
+
# it "finds an attachment even if the link has an extraneous folder" do
|
86
|
+
# att = make_test_att
|
87
|
+
# @migration.attachment_path_id_lookup = { "subfolder/test.png" => att.migration_id }
|
88
|
+
|
89
|
+
# test_string = %(<img src="anotherfolder/subfolder/test.png" alt="nope" />)
|
90
|
+
# expect(@converter.convert_exported_html(test_string)).to eq %(<img src="#{@path}files/#{att.id}/preview" alt="nope">)
|
91
|
+
# end
|
92
|
+
|
93
|
+
# it "finds an attachment by path if capitalization is different" do
|
94
|
+
# att = make_test_att
|
95
|
+
# @migration.attachment_path_id_lookup = { "subfolder/withCapital/test.png" => "wrong!" }
|
96
|
+
# @migration.attachment_path_id_lookup_lower = { "subfolder/withcapital/test.png" => att.migration_id }
|
97
|
+
|
98
|
+
# test_string = %(<img src="subfolder/WithCapital/TEST.png" alt="nope" />)
|
99
|
+
# expect(@converter.convert_exported_html(test_string)).to eq %(<img src="#{@path}files/#{att.id}/preview" alt="nope">)
|
100
|
+
# end
|
101
|
+
|
102
|
+
# it "finds an attachment with query params" do
|
103
|
+
# att = make_test_att
|
104
|
+
# @migration.attachment_path_id_lookup = { "test.png" => att.migration_id }
|
105
|
+
|
106
|
+
# test_string = %(<img src="%24IMS_CC_FILEBASE%24/test.png?canvas_customaction=1&canvas_qs_customparam=1" alt="nope" />)
|
107
|
+
# expect(@converter.convert_exported_html(test_string)).to eq %(<img src="#{@path}files/#{att.id}/customaction?customparam=1" alt="nope">)
|
108
|
+
|
109
|
+
# test_string = %(<img src="%24IMS_CC_FILEBASE%24/test.png?canvas_qs_customparam2=3" alt="nope" />)
|
110
|
+
# expect(@converter.convert_exported_html(test_string)).to eq %(<img src="#{@path}files/#{att.id}/preview?customparam2=3" alt="nope">)
|
111
|
+
|
112
|
+
# test_string = %(<img src="%24IMS_CC_FILEBASE%24/test.png?notarelevantparam" alt="nope" />)
|
113
|
+
# expect(@converter.convert_exported_html(test_string)).to eq %(<img src="#{@path}files/#{att.id}/preview" alt="nope">)
|
114
|
+
# end
|
115
|
+
end
|
116
|
+
|
117
|
+
it "converts picture source srcsets" do
|
118
|
+
test_string = %(<source srcset="$CANVAS_COURSE_REFERENCE$/img.src">)
|
119
|
+
expect(@converter.convert_exported_html(test_string)).to eq([%(<source srcset="/courses/2/img.src">), nil])
|
120
|
+
end
|
121
|
+
|
122
|
+
it "converts a wiki reference without $ escaped" do
|
123
|
+
test_string = %(<a href="$WIKI_REFERENCE$/wiki/test-wiki-page?query=blah">Test Wiki Page</a>)
|
124
|
+
|
125
|
+
expect(@converter.convert_exported_html(test_string)).to eq([%(<a href="#{@path}pages/test-wiki-page?query=blah">Test Wiki Page</a>), nil])
|
126
|
+
end
|
127
|
+
|
128
|
+
it "converts a wiki reference by migration id" do
|
129
|
+
test_string = %(<a href="wiki_page_migration_id=A">Test Wiki Page</a>)
|
130
|
+
|
131
|
+
expect(@converter.convert_exported_html(test_string)).to eq([%(<a href="#{@path}pages/slug-a">Test Wiki Page</a>), nil])
|
132
|
+
end
|
133
|
+
|
134
|
+
it "converts a discussion reference by migration id" do
|
135
|
+
test_string = %(<a href="discussion_topic_migration_id=G">Test topic</a>)
|
136
|
+
|
137
|
+
expect(@converter.convert_exported_html(test_string)).to eq([%(<a href="#{@path}discussion_topics/7">Test topic</a>), nil])
|
138
|
+
end
|
139
|
+
|
140
|
+
it "converts course section urls" do
|
141
|
+
test_string = %(<a href="%24CANVAS_COURSE_REFERENCE%24/discussion_topics">discussions</a>)
|
142
|
+
expect(@converter.convert_exported_html(test_string)).to eq([%(<a href="#{@path}discussion_topics">discussions</a>), nil])
|
143
|
+
end
|
144
|
+
|
145
|
+
it "leaves invalid and absolute urls alone" do
|
146
|
+
test_string = %(<a href="stupid &^%$ url">Linkage</a><br><a href="http://www.example.com/poop">Linkage</a>)
|
147
|
+
expect(@converter.convert_exported_html(test_string)).to eq([%(<a href="stupid &^%$ url">Linkage</a><br><a href="http://www.example.com/poop">Linkage</a>), nil])
|
148
|
+
end
|
149
|
+
|
150
|
+
it "leaves invalid mailto addresses alone" do
|
151
|
+
test_string = %(<a href="mailto:.">Bad mailto</a><br><a href="mailto:test@example.com">Good mailto</a>)
|
152
|
+
expect(@converter.convert_exported_html(test_string)).to eq(
|
153
|
+
[
|
154
|
+
%(<a href="mailto:.">Bad mailto</a><br><a href="mailto:test@example.com">Good mailto</a>),
|
155
|
+
nil
|
156
|
+
]
|
157
|
+
)
|
158
|
+
end
|
159
|
+
|
160
|
+
it "recognizes and relative-ize absolute links outside the course but in one of the course's domains" do
|
161
|
+
test_string = %(<a href="https://apple.edu/courses/123">Mine</a><br><a href="https://kiwi.edu/courses/456">Vain</a><br><a href="http://other-canvas.example.com/">Other Instance</a>)
|
162
|
+
expect(@converter.convert_exported_html(test_string)).to eq([%(<a href="/courses/123">Mine</a><br><a href="/courses/456">Vain</a><br><a href="http://other-canvas.example.com/">Other Instance</a>), nil])
|
163
|
+
end
|
164
|
+
|
165
|
+
it "prepends course files for unrecognized relative urls" do
|
166
|
+
test_string = %(<a href="/relative/path/to/file">Linkage</a>)
|
167
|
+
html, bad_links = @converter.convert_exported_html(test_string)
|
168
|
+
expect(html).to eq %(<a href="#{@path}file_contents/course%20files/relative/path/to/file">Linkage</a>)
|
169
|
+
expect(bad_links.length).to eq 1
|
170
|
+
expect(bad_links[0]).to include({ link_type: :file, missing_url: "/courses/2/file_contents/course%20files/relative/path/to/file" })
|
171
|
+
|
172
|
+
test_string = %(<a href="relative/path/to/file">Linkage</a>)
|
173
|
+
html, bad_links = @converter.convert_exported_html(test_string)
|
174
|
+
expect(html).to eq %(<a href="#{@path}file_contents/course%20files/relative/path/to/file">Linkage</a>)
|
175
|
+
expect(bad_links.length).to eq 1
|
176
|
+
expect(bad_links[0]).to include({ link_type: :file, missing_url: "/courses/2/file_contents/course%20files/relative/path/to/file" })
|
177
|
+
|
178
|
+
test_string = %(<a href="relative/path/to/file%20with%20space.html">Linkage</a>)
|
179
|
+
html, bad_links = @converter.convert_exported_html(test_string)
|
180
|
+
expect(html).to eq %(<a href="#{@path}file_contents/course%20files/relative/path/to/file%20with%20space.html">Linkage</a>)
|
181
|
+
expect(bad_links.length).to eq 1
|
182
|
+
expect(bad_links[0]).to include({ link_type: :file, missing_url: "/courses/2/file_contents/course%20files/relative/path/to/file%20with%20space.html" })
|
183
|
+
end
|
184
|
+
|
185
|
+
it "preserves media comment links" do
|
186
|
+
test_string = <<~HTML.strip
|
187
|
+
<p>
|
188
|
+
with media object url: <a id="media_comment_m-stuff" class="instructure_inline_media_comment video_comment" href="/media_objects/m-stuff">this is a media comment</a>
|
189
|
+
with file content url: <a id="media_comment_0_bq09qam2" class="instructure_inline_media_comment video_comment" href="/courses/2/file_contents/course%20files/media_objects/0_bq09qam2">this is a media comment</a>
|
190
|
+
</p>
|
191
|
+
HTML
|
192
|
+
|
193
|
+
expect(@converter.convert_exported_html(test_string)).to eq([test_string, nil])
|
194
|
+
end
|
195
|
+
|
196
|
+
it "handles and repair half broken media links" do
|
197
|
+
test_string = %(<p><a href="/courses/2/file_contents/%24IMS_CC_FILEBASE%24/#" class="instructure_inline_media_comment video_comment" id="media_comment_m-stuff">this is a media comment</a><br><br></p>)
|
198
|
+
|
199
|
+
expect(@converter.convert_exported_html(test_string)).to eq([%(<p><a href="/media_objects/m-stuff" class="instructure_inline_media_comment video_comment" id="media_comment_m-stuff">this is a media comment</a><br><br></p>), nil])
|
200
|
+
end
|
201
|
+
|
202
|
+
it "preserves new RCE media iframes" do
|
203
|
+
test_string = %(<iframe style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="video" src="/media_objects_iframe/m-stuff?type=video" allowfullscreen="allowfullscreen" allow="fullscreen" data-media-id="m-stuff"></iframe>)
|
204
|
+
expect(@converter.convert_exported_html(test_string)).to eq([test_string, nil])
|
205
|
+
end
|
206
|
+
|
207
|
+
it "handles and repair half broken new RCE media iframes" do
|
208
|
+
test_string = %(<iframe style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="video" src="%24IMS_CC_FILEBASE%24/#" allowfullscreen="allowfullscreen" allow="fullscreen" data-media-id="m-abcde"></iframe>)
|
209
|
+
repaired_string = %(<iframe style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="video" src="/media_objects_iframe/m-abcde?type=video" allowfullscreen="allowfullscreen" allow="fullscreen" data-media-id="m-abcde"></iframe>)
|
210
|
+
expect(@converter.convert_exported_html(test_string)).to eq([repaired_string, nil])
|
211
|
+
end
|
212
|
+
|
213
|
+
it "converts source tags to RCE media iframes" do
|
214
|
+
test_string = %(<video style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="video" allowfullscreen="allowfullscreen" allow="fullscreen" data-media-id="m-stuff"><source src="/media_objects_iframe/m-stuff?type=video" data-media-id="m-stuff" data-media-type="video"></video>)
|
215
|
+
converted_string = %(<iframe style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="video" allowfullscreen="allowfullscreen" allow="fullscreen" data-media-id="m-stuff" src="/media_objects_iframe/m-stuff?type=video"></iframe>)
|
216
|
+
expect(@converter.convert_exported_html(test_string)).to eq([converted_string, nil])
|
217
|
+
|
218
|
+
test_string = %(<audio style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="audio" data-media-id="m-stuff"><source src="/media_objects_iframe/m-stuff?type=audio" data-media-id="m-stuff" data-media-type="audio"></audio>)
|
219
|
+
converted_string = %(<iframe style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="audio" data-media-id="m-stuff" src="/media_objects_iframe/m-stuff?type=audio"></iframe>)
|
220
|
+
expect(@converter.convert_exported_html(test_string)).to eq([converted_string, nil])
|
221
|
+
end
|
222
|
+
|
223
|
+
it "converts source tags to RCE media attachment iframes" do
|
224
|
+
test_string = %(<video style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="video" allowfullscreen="allowfullscreen" allow="fullscreen" data-media-id="m-stuff"><source src="$CANVAS_OBJECT_REFERENCE$/media_attachments_iframe/E?type=video" data-media-id="m-stuff" data-media-type="video"></video>)
|
225
|
+
converted_string = %(<iframe style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="video" allowfullscreen="allowfullscreen" allow="fullscreen" data-media-id="m-stuff" src="/media_attachments_iframe/5?type=video"></iframe>)
|
226
|
+
expect(@converter.convert_exported_html(test_string)).to eq([converted_string, nil])
|
227
|
+
|
228
|
+
test_string = %(<audio style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="audio" data-media-id="m-stuff"><source src="$CANVAS_OBJECT_REFERENCE$/media_attachments_iframe/E?type=audio" data-media-id="m-stuff" data-media-type="audio"></video>)
|
229
|
+
converted_string = %(<iframe style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="audio" data-media-id="m-stuff" src="/media_attachments_iframe/5?type=audio"></iframe>)
|
230
|
+
expect(@converter.convert_exported_html(test_string)).to eq([converted_string, nil])
|
231
|
+
end
|
232
|
+
|
233
|
+
it "converts source tags to RCE media attachment iframes when link is untranslated" do
|
234
|
+
test_string = %(<video style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="video" allowfullscreen="allowfullscreen" allow="fullscreen" data-media-id="m-stuff"><source src="/media_attachments_iframe/5?type=video" data-media-id="m-stuff" data-media-type="video"></video>)
|
235
|
+
converted_string = %(<iframe style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="video" allowfullscreen="allowfullscreen" allow="fullscreen" data-media-id="m-stuff" src="/media_attachments_iframe/5?type=video"></iframe>)
|
236
|
+
expect(@converter.convert_exported_html(test_string)).to eq([converted_string, nil])
|
237
|
+
|
238
|
+
test_string = %(<audio style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="audio" data-media-id="m-stuff"><source src="/media_attachments_iframe/5?type=audio" data-media-id="m-stuff" data-media-type="audio"></video>)
|
239
|
+
converted_string = %(<iframe style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="audio" data-media-id="m-stuff" src="/media_attachments_iframe/5?type=audio"></iframe>)
|
240
|
+
expect(@converter.convert_exported_html(test_string)).to eq([converted_string, nil])
|
241
|
+
end
|
242
|
+
|
243
|
+
it "leaves source tags without data-media-id alone" do
|
244
|
+
test_string = %(<video style="width: 400px; height: 225px; display: inline-block;" title="this is a non-canvas video" allowfullscreen="allowfullscreen" allow="fullscreen"><source src="http://www.example.com/video.mov"></video>)
|
245
|
+
expect(@converter.convert_exported_html(test_string)).to eq([test_string, nil])
|
246
|
+
end
|
247
|
+
|
248
|
+
it "only converts url params" do
|
249
|
+
test_string = <<~HTML
|
250
|
+
<object>
|
251
|
+
<param name="controls" value="CONSOLE" />
|
252
|
+
<param name="controller" value="true" />
|
253
|
+
<param name="autostart" value="false" />
|
254
|
+
<param name="loop" value="false" />
|
255
|
+
<param name="src" value="%24IMS_CC_FILEBASE%24/test.mp3" />
|
256
|
+
<EMBED name="tag" src="%24IMS_CC_FILEBASE%24/test.mp3" loop="false" autostart="false" controller="true" controls="CONSOLE" >
|
257
|
+
</EMBED>
|
258
|
+
</object>
|
259
|
+
HTML
|
260
|
+
|
261
|
+
expect(@converter.convert_exported_html(test_string)[0]).to match(<<~HTML.strip)
|
262
|
+
<object>
|
263
|
+
<param name="controls" value="CONSOLE">
|
264
|
+
<param name="controller" value="true">
|
265
|
+
<param name="autostart" value="false">
|
266
|
+
<param name="loop" value="false">
|
267
|
+
<param name="src" value="/courses/2/file_contents/course%20files/test.mp3">
|
268
|
+
<embed name="tag" src="/courses/2/file_contents/course%20files/test.mp3" loop="false" autostart="false" controller="true" controls="CONSOLE">
|
269
|
+
|
270
|
+
</object>
|
271
|
+
HTML
|
272
|
+
end
|
273
|
+
|
274
|
+
it "leaves an anchor tag alone" do
|
275
|
+
test_string = '<p><a href="#anchor_ref">ref</a></p>'
|
276
|
+
expect(@converter.convert_exported_html(test_string)).to eq([test_string, nil])
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (C) 2023 - present Instructure, Inc.
|
5
|
+
#
|
6
|
+
# This file is part of Canvas.
|
7
|
+
#
|
8
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
9
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
10
|
+
# Software Foundation, version 3 of the License.
|
11
|
+
#
|
12
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
13
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
14
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
15
|
+
# details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Affero General Public License along
|
18
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
#
|
20
|
+
|
21
|
+
require "spec_helper"
|
22
|
+
require "json"
|
23
|
+
|
24
|
+
describe CanvasLinkMigrator::LinkResolver do
|
25
|
+
def resolver(assets = JSON.parse(File.read("spec/fixtures/canvas_resource_map.json")))
|
26
|
+
CanvasLinkMigrator::LinkResolver.new(CanvasLinkMigrator::ResourceMapService.new(assets))
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "resolve_link!" do
|
30
|
+
it "converts wiki_pages links" do
|
31
|
+
link = { link_type: :wiki_page, migration_id: "A", query: "?foo=bar" }
|
32
|
+
resolver.resolve_link!(link)
|
33
|
+
expect(link[:new_value]).to eq("/courses/2/pages/slug-a?foo=bar")
|
34
|
+
end
|
35
|
+
|
36
|
+
it "converts module_item links" do
|
37
|
+
link = { link_type: :module_item, migration_id: "C", query: "?foo=bar" }
|
38
|
+
resolver.resolve_link!(link)
|
39
|
+
expect(link[:new_value]).to eq("/courses/2/modules/items/3?foo=bar")
|
40
|
+
end
|
41
|
+
|
42
|
+
it "converts file_ref urls" do
|
43
|
+
link = { link_type: :file_ref, migration_id: "F" }
|
44
|
+
resolver.resolve_link!(link)
|
45
|
+
expect(link[:new_value]).to eq("/courses/2/files/6/preview")
|
46
|
+
end
|
47
|
+
|
48
|
+
it "converts attachment urls" do
|
49
|
+
link = { link_type: :object, type: "attachments", migration_id: "E", query: "?foo=bar" }
|
50
|
+
resolver.resolve_link!(link)
|
51
|
+
expect(link[:new_value]).to eq("/courses/2/files/5/preview")
|
52
|
+
end
|
53
|
+
|
54
|
+
it "converts media_attachments_iframe urls" do
|
55
|
+
link = { link_type: :object, type: "media_attachments_iframe", migration_id: "F", query: "?foo=bar" }
|
56
|
+
resolver.resolve_link!(link)
|
57
|
+
expect(link[:new_value]).to eq("/media_attachments_iframe/6?foo=bar")
|
58
|
+
end
|
59
|
+
|
60
|
+
it "converts discussion_topic links" do
|
61
|
+
link = { link_type: :discussion_topic, migration_id: "G", query: "?foo=bar" }
|
62
|
+
resolver.resolve_link!(link)
|
63
|
+
expect(link[:new_value]).to eq("/courses/2/discussion_topics/7?foo=bar")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (C) 2012 - present Instructure, Inc.
|
5
|
+
#
|
6
|
+
# This file is part of Canvas.
|
7
|
+
#
|
8
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
9
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
10
|
+
# Software Foundation, version 3 of the License.
|
11
|
+
#
|
12
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
13
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
14
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
15
|
+
# details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Affero General Public License along
|
18
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
#
|
20
|
+
|
21
|
+
require "spec_helper"
|
22
|
+
|
23
|
+
describe CanvasLinkMigrator do
|
24
|
+
describe ".relative_url?" do
|
25
|
+
it "recognizes an absolute url" do
|
26
|
+
expect(CanvasLinkMigrator.relative_url?("http://example.com")).to be false
|
27
|
+
end
|
28
|
+
|
29
|
+
it "recognizes relative urls" do
|
30
|
+
expect(CanvasLinkMigrator.relative_url?("/relative/eh")).to be true
|
31
|
+
expect(CanvasLinkMigrator.relative_url?("also/relative")).to be true
|
32
|
+
expect(CanvasLinkMigrator.relative_url?("watup/nothing.html#anchoritbaby")).to be true
|
33
|
+
expect(CanvasLinkMigrator.relative_url?("watup/nothing?absolutely=1")).to be true
|
34
|
+
end
|
35
|
+
|
36
|
+
it "does not error on invalid urls" do
|
37
|
+
expect(CanvasLinkMigrator.relative_url?("stupid &^%$ url")).to be_falsey
|
38
|
+
expect(CanvasLinkMigrator.relative_url?("mailto:jfarnsworth@instructure.com,")).to be_falsey
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
{
|
2
|
+
"contains_migration_ids": true,
|
3
|
+
"destination_course": "2",
|
4
|
+
"destination_hosts": [
|
5
|
+
"apple.edu",
|
6
|
+
"kiwi.edu"
|
7
|
+
],
|
8
|
+
"destination_root_folder": "course files/",
|
9
|
+
"resource_mapping": {
|
10
|
+
"discussion_topics": {
|
11
|
+
"G": {
|
12
|
+
"destination": {
|
13
|
+
"id": "7"
|
14
|
+
},
|
15
|
+
"source": {
|
16
|
+
"id": "6"
|
17
|
+
}
|
18
|
+
}
|
19
|
+
},
|
20
|
+
"files": {
|
21
|
+
"E": {
|
22
|
+
"destination": {
|
23
|
+
"id": "5",
|
24
|
+
"media_entry_id": "m-stuff"
|
25
|
+
},
|
26
|
+
"source": {
|
27
|
+
"id": "3",
|
28
|
+
"media_entry_id": "m-stuff"
|
29
|
+
}
|
30
|
+
},
|
31
|
+
"F": {
|
32
|
+
"destination": {
|
33
|
+
"id": "6",
|
34
|
+
"media_entry_id": "m-stuff"
|
35
|
+
},
|
36
|
+
"source": {
|
37
|
+
"id": "4",
|
38
|
+
"media_entry_id": "m-stuff"
|
39
|
+
}
|
40
|
+
}
|
41
|
+
},
|
42
|
+
"module_items": {
|
43
|
+
"C": {
|
44
|
+
"destination": {
|
45
|
+
"id": "3"
|
46
|
+
},
|
47
|
+
"source": {
|
48
|
+
"id": "2"
|
49
|
+
}
|
50
|
+
}
|
51
|
+
},
|
52
|
+
"wiki_pages": {
|
53
|
+
"A": {
|
54
|
+
"destination": {
|
55
|
+
"id": "2",
|
56
|
+
"url": "slug-a"
|
57
|
+
},
|
58
|
+
"source": {
|
59
|
+
"id": "1",
|
60
|
+
"url": "slug-a"
|
61
|
+
}
|
62
|
+
}
|
63
|
+
}
|
64
|
+
},
|
65
|
+
"file_tree": {
|
66
|
+
"folder 1": {
|
67
|
+
"id": "1",
|
68
|
+
"sub_folders": {}
|
69
|
+
}
|
70
|
+
},
|
71
|
+
"source_course": "1",
|
72
|
+
"source_host": "pineapple.edu"
|
73
|
+
}
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (C) 2021 - present Instructure, Inc.
|
5
|
+
#
|
6
|
+
# This file is part of Canvas.
|
7
|
+
#
|
8
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
9
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
10
|
+
# Software Foundation, version 3 of the License.
|
11
|
+
#
|
12
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
13
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
14
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
15
|
+
# details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Affero General Public License along
|
18
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
|
20
|
+
require "byebug"
|
21
|
+
require "canvas_link_migrator"
|
data/test.sh
ADDED