moodle2cc 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. data/.gitignore +20 -0
  2. data/Gemfile +4 -0
  3. data/Guardfile +14 -0
  4. data/LICENSE +661 -0
  5. data/README.md +38 -0
  6. data/Rakefile +12 -0
  7. data/bin/moodle2cc +6 -0
  8. data/lib/moodle2cc.rb +60 -0
  9. data/lib/moodle2cc/canvas/.converter.rb.swo +0 -0
  10. data/lib/moodle2cc/canvas/.wiki.rb.swo +0 -0
  11. data/lib/moodle2cc/canvas/assessment.rb +109 -0
  12. data/lib/moodle2cc/canvas/assignment.rb +108 -0
  13. data/lib/moodle2cc/canvas/converter.rb +28 -0
  14. data/lib/moodle2cc/canvas/course.rb +145 -0
  15. data/lib/moodle2cc/canvas/discussion_topic.rb +64 -0
  16. data/lib/moodle2cc/canvas/label.rb +8 -0
  17. data/lib/moodle2cc/canvas/question.rb +468 -0
  18. data/lib/moodle2cc/canvas/question_bank.rb +54 -0
  19. data/lib/moodle2cc/canvas/resource.rb +16 -0
  20. data/lib/moodle2cc/canvas/templates/assignment.html.erb +9 -0
  21. data/lib/moodle2cc/canvas/templates/syllabus.html.erb +9 -0
  22. data/lib/moodle2cc/canvas/templates/wiki_content.html.erb +10 -0
  23. data/lib/moodle2cc/canvas/web_content.rb +10 -0
  24. data/lib/moodle2cc/canvas/web_link.rb +17 -0
  25. data/lib/moodle2cc/canvas/wiki.rb +25 -0
  26. data/lib/moodle2cc/cc/assessment.rb +25 -0
  27. data/lib/moodle2cc/cc/assignment.rb +66 -0
  28. data/lib/moodle2cc/cc/cc_helper.rb +282 -0
  29. data/lib/moodle2cc/cc/converter.rb +136 -0
  30. data/lib/moodle2cc/cc/course.rb +16 -0
  31. data/lib/moodle2cc/cc/discussion_topic.rb +53 -0
  32. data/lib/moodle2cc/cc/label.rb +6 -0
  33. data/lib/moodle2cc/cc/question.rb +23 -0
  34. data/lib/moodle2cc/cc/resource.rb +30 -0
  35. data/lib/moodle2cc/cc/templates/assignment.html.erb +9 -0
  36. data/lib/moodle2cc/cc/templates/syllabus.html.erb +9 -0
  37. data/lib/moodle2cc/cc/templates/wiki_content.html.erb +10 -0
  38. data/lib/moodle2cc/cc/web_content.rb +40 -0
  39. data/lib/moodle2cc/cc/web_link.rb +53 -0
  40. data/lib/moodle2cc/cc/wiki.rb +72 -0
  41. data/lib/moodle2cc/cli.rb +14 -0
  42. data/lib/moodle2cc/error.rb +3 -0
  43. data/lib/moodle2cc/migrator.rb +23 -0
  44. data/lib/moodle2cc/moodle/backup.rb +38 -0
  45. data/lib/moodle2cc/moodle/course.rb +24 -0
  46. data/lib/moodle2cc/moodle/grade_item.rb +24 -0
  47. data/lib/moodle2cc/moodle/info.rb +8 -0
  48. data/lib/moodle2cc/moodle/mod.rb +127 -0
  49. data/lib/moodle2cc/moodle/question.rb +98 -0
  50. data/lib/moodle2cc/moodle/question_category.rb +17 -0
  51. data/lib/moodle2cc/moodle/section.rb +37 -0
  52. data/lib/moodle2cc/resource_factory.rb +32 -0
  53. data/lib/moodle2cc/version.rb +3 -0
  54. data/moodle2cc.gemspec +29 -0
  55. data/test/acceptance/migrator_test.rb +23 -0
  56. data/test/fixtures/cc.imscc +0 -0
  57. data/test/fixtures/moodle_backup/course_files/folder/test.txt +1 -0
  58. data/test/fixtures/moodle_backup/course_files/test.txt +1 -0
  59. data/test/fixtures/moodle_backup/moodle.xml +916 -0
  60. data/test/test_helper.rb +43 -0
  61. data/test/test_question_helper.rb +107 -0
  62. data/test/test_wiki_helper.rb +21 -0
  63. data/test/unit/canvas/assessment_test.rb +218 -0
  64. data/test/unit/canvas/assignment_test.rb +220 -0
  65. data/test/unit/canvas/converter_test.rb +159 -0
  66. data/test/unit/canvas/course_test.rb +176 -0
  67. data/test/unit/canvas/discussion_topic_test.rb +129 -0
  68. data/test/unit/canvas/label_test.rb +32 -0
  69. data/test/unit/canvas/question_bank_test.rb +73 -0
  70. data/test/unit/canvas/question_test.rb +824 -0
  71. data/test/unit/canvas/web_content_test.rb +37 -0
  72. data/test/unit/canvas/web_link_test.rb +48 -0
  73. data/test/unit/canvas/wiki_test.rb +74 -0
  74. data/test/unit/cc/assessment_test.rb +48 -0
  75. data/test/unit/cc/assignment_test.rb +67 -0
  76. data/test/unit/cc/cc_helper_test.rb +19 -0
  77. data/test/unit/cc/converter_test.rb +159 -0
  78. data/test/unit/cc/course_test.rb +36 -0
  79. data/test/unit/cc/discussion_topic_test.rb +83 -0
  80. data/test/unit/cc/label_test.rb +29 -0
  81. data/test/unit/cc/question_test.rb +49 -0
  82. data/test/unit/cc/web_content_test.rb +76 -0
  83. data/test/unit/cc/web_link_test.rb +79 -0
  84. data/test/unit/cc/wiki_test.rb +139 -0
  85. data/test/unit/migrator_test.rb +56 -0
  86. data/test/unit/moodle/backup_test.rb +30 -0
  87. data/test/unit/moodle/course_test.rb +57 -0
  88. data/test/unit/moodle/grade_item_test.rb +72 -0
  89. data/test/unit/moodle/info_test.rb +21 -0
  90. data/test/unit/moodle/mod_test.rb +301 -0
  91. data/test/unit/moodle/question_category_test.rb +34 -0
  92. data/test/unit/moodle/question_test.rb +185 -0
  93. data/test/unit/moodle/section_test.rb +75 -0
  94. data/test/unit/resource_factory_test.rb +119 -0
  95. metadata +342 -0
@@ -0,0 +1,54 @@
1
+ module Moodle2CC::Canvas
2
+ class QuestionBank
3
+ include Moodle2CC::CC::CCHelper
4
+ attr_accessor :id, :title, :identifier
5
+
6
+ def initialize(question_category)
7
+ @id = question_category.id
8
+ @title = question_category.name
9
+ @identifier = create_key(@id, 'objectbank_')
10
+ @question_category = question_category
11
+ end
12
+
13
+ def create_resource_node(resources_node)
14
+ href = File.join(ASSESSMENT_NON_CC_FOLDER, "#{identifier}.xml.qti")
15
+ resources_node.resource(
16
+ :href => href,
17
+ :type => LOR,
18
+ :identifier => identifier
19
+ ) do |resource_node|
20
+ resource_node.file(:href => href)
21
+ end
22
+ end
23
+
24
+ def create_files(export_dir)
25
+ create_qti_xml(export_dir)
26
+ end
27
+
28
+ def create_qti_xml(export_dir)
29
+ path = File.join(export_dir, ASSESSMENT_NON_CC_FOLDER, "#{identifier}.xml.qti")
30
+ FileUtils.mkdir_p(File.dirname(path))
31
+ File.open(path, 'w') do |file|
32
+ node = Builder::XmlMarkup.new(:target => file, :indent => 2)
33
+ node.instruct!
34
+ node.questestinterop(
35
+ 'xsi:schemaLocation' => "http://www.imsglobal.org/xsd/ims_qtiasiv1p2 http://www.imsglobal.org/xsd/ims_qtiasiv1p2p1.xsd",
36
+ 'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance",
37
+ 'xmlns' => "http://www.imsglobal.org/xsd/ims_qtiasiv1p2"
38
+ ) do |root_node|
39
+ root_node.objectbank(:ident => identifier) do |objectbank_node|
40
+ objectbank_node.qtimetadata do |qtimetadata_node|
41
+ qtimetadata_node.qtimetadatafield do |qtimetadatafield_node|
42
+ qtimetadatafield_node.fieldlabel "bank_title"
43
+ qtimetadatafield_node.fieldentry @title
44
+ end
45
+ end
46
+ @question_category.questions.each do |question|
47
+ Question.new(question).create_item_xml(objectbank_node) if question
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,16 @@
1
+ module Moodle2CC::Canvas
2
+ module Resource
3
+ def create_module_meta_item_node(items_node, position)
4
+ items_node.item(:identifier => create_mod_key(@mod)) do |item_node|
5
+ item_node.title @title
6
+ item_node.position position
7
+ item_node.new_tab ''
8
+ item_node.indent @indent
9
+ create_module_meta_item_elements(item_node)
10
+ end
11
+ end
12
+
13
+ def create_module_meta_item_elements(item_node)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ <html>
2
+ <head>
3
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
4
+ <title>Assignment: <%= title %></title>
5
+ </head>
6
+ <body>
7
+ <%= body %>
8
+ </body>
9
+ </html>
@@ -0,0 +1,9 @@
1
+ <html>
2
+ <head>
3
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
4
+ <title>Syllabus</title>
5
+ </head>
6
+ <body>
7
+ <%= syllabus_body %>
8
+ </body>
9
+ </html>
@@ -0,0 +1,10 @@
1
+ <html>
2
+ <head>
3
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
4
+ <title><%= title %></title>
5
+ <meta name="identifier" content="<%= identifier %>"/>
6
+ </head>
7
+ <body>
8
+ <%= body %>
9
+ </body>
10
+ </html>
@@ -0,0 +1,10 @@
1
+ module Moodle2CC::Canvas
2
+ class WebContent < Moodle2CC::CC::WebContent
3
+ include Resource
4
+
5
+ def create_module_meta_item_elements(item_node)
6
+ item_node.content_type 'WikiPage'
7
+ item_node.identifierref @identifier
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,17 @@
1
+ module Moodle2CC::Canvas
2
+ class WebLink < Moodle2CC::CC::WebLink
3
+ include Resource
4
+
5
+ def create_module_meta_item_elements(item_node)
6
+ item_node.identifierref @identifier
7
+
8
+ uri = URI.parse(@url)
9
+ if uri.scheme
10
+ item_node.url @url
11
+ item_node.content_type 'ExternalUrl'
12
+ else
13
+ item_node.content_type 'Attachment'
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ module Moodle2CC::Canvas
2
+ class Wiki < Moodle2CC::CC::Wiki
3
+ include Resource
4
+ attr_accessor :pages
5
+
6
+ def initialize(mod)
7
+ super
8
+
9
+ @pages.map! do |page|
10
+ page.body.gsub!(/\[(.*?)\]/) do |match|
11
+ title_slug = file_slug(@title)
12
+ slug = [title_slug, file_slug(match)].join('-')
13
+ href = File.join(CGI.escape(WIKI_TOKEN), 'wiki', slug)
14
+ %(<a href="#{href}" title="#{$1}">#{$1}</a>)
15
+ end
16
+ page
17
+ end
18
+ end
19
+
20
+ def create_module_meta_item_elements(item_node)
21
+ item_node.content_type 'WikiPage'
22
+ item_node.identifierref @identifier
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ module Moodle2CC::CC
2
+ class Assessment
3
+ include CCHelper
4
+ include Resource
5
+
6
+ def initialize(mod, position=0)
7
+ super
8
+ end
9
+
10
+ def create_resource_node(resources_node)
11
+ resources_node.resource(
12
+ :identifier => identifier,
13
+ :type => ASSESSMENT_TYPE
14
+ ) do |resource_node|
15
+ create_resource_sub_nodes(resource_node)
16
+ end
17
+ end
18
+
19
+ def create_resource_sub_nodes(resource_node)
20
+ end
21
+
22
+ def create_files(export_dir)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,66 @@
1
+ module Moodle2CC::CC
2
+ class Assignment
3
+ include CCHelper
4
+ include Resource
5
+
6
+
7
+ attr_accessor :body
8
+
9
+ def initialize(mod, position=0)
10
+ super
11
+ @body = convert_file_path_tokens(mod.description)
12
+ end
13
+
14
+ def get_submission_types(mod)
15
+ if mod.mod_type == 'assignment'
16
+ case mod.assignment_type
17
+ when 'online'
18
+ 'online_text_entry'
19
+ when 'upload'
20
+ if mod.var2 == 1
21
+ 'online_upload,online_text_entry'
22
+ else
23
+ 'online_upload'
24
+ end
25
+ when 'uploadsingle'
26
+ 'online_upload'
27
+ else
28
+ 'none'
29
+ end
30
+ elsif mod.mod_type == 'workshop'
31
+ submission_types = ['online_text_entry']
32
+ submission_types.unshift('online_upload') if mod.number_of_attachments > 0
33
+ submission_types.join(',')
34
+ end
35
+ end
36
+
37
+ def create_resource_node(resources_node)
38
+ href = "#{identifier}/#{file_slug(@title)}.html"
39
+ resources_node.resource(
40
+ :href => href,
41
+ :type => LOR,
42
+ :identifier => identifier
43
+ ) do |resource_node|
44
+ resource_node.file(:href => href)
45
+ create_resource_sub_nodes(resource_node)
46
+ end
47
+ end
48
+
49
+ def create_resource_sub_nodes(resource_node)
50
+ end
51
+
52
+ def create_files(export_dir)
53
+ create_html(export_dir)
54
+ end
55
+
56
+ def create_html(export_dir)
57
+ template = File.expand_path('../templates/assignment.html.erb', __FILE__)
58
+ path = File.join(export_dir, identifier, "#{file_slug(title)}.html")
59
+ FileUtils.mkdir_p(File.dirname(path))
60
+ File.open(path, 'w') do |file|
61
+ erb = ERB.new(File.read(template))
62
+ file.write(erb.result(binding))
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,282 @@
1
+ require 'digest/md5'
2
+
3
+ module Moodle2CC::CC
4
+ module CCHelper
5
+
6
+ CANVAS_NAMESPACE = 'http://canvas.instructure.com/xsd/cccv1p0'
7
+ XSD_URI = 'http://canvas.instructure.com/xsd/cccv1p0.xsd'
8
+
9
+ # IMS formats/types
10
+ IMS_DATE = "%Y-%m-%d"
11
+ IMS_DATETIME = "%Y-%m-%dT%H:%M:%S"
12
+ CC_EXTENSION = 'imscc'
13
+ QTI_EXTENSION = ".xml.qti"
14
+ CANVAS_PLATFORM = 'canvas.instructure.com'
15
+
16
+ # Common Cartridge 1.0
17
+ # associatedcontent/imscc_xmlv1p0/learning-application-resource
18
+ # imsdt_xmlv1p0
19
+ # imswl_xmlv1p0
20
+ # imsqti_xmlv1p2/imscc_xmlv1p0/assessment
21
+ # imsqti_xmlv1p2/imscc_xmlv1p0/question-bank
22
+
23
+ # Common Cartridge 1.1 (What Canvas exports)
24
+ ASSESSMENT_TYPE = 'imsqti_xmlv1p2/imscc_xmlv1p1/assessment'
25
+ QUESTION_BANK = 'imsqti_xmlv1p2/imscc_xmlv1p1/question-bank'
26
+ DISCUSSION_TOPIC = "imsdt_xmlv1p1"
27
+ LOR = "associatedcontent/imscc_xmlv1p1/learning-application-resource"
28
+ WEB_LINK = "imswl_xmlv1p1"
29
+ WEBCONTENT = "webcontent"
30
+ BASIC_LTI = 'imsbasiclti_xmlv1p0'
31
+ BLTI_NAMESPACE = "http://www.imsglobal.org/xsd/imsbasiclti_v1p0"
32
+
33
+ # Common Cartridge 1.2
34
+ # associatedcontent/imscc_xmlv1p2/learning-application-resource
35
+ # imsdt_xmlv1p2
36
+ # imswl_xmlv1p2
37
+ # imsqti_xmlv1p2/imscc_xmlv1p2/assessment
38
+ # imsqti_xmlv1p2/imscc_xmlv1p2/question-bank
39
+ # imsbasiclti_xmlv1p0
40
+
41
+ # QTI-only export
42
+ QTI_ASSESSMENT_TYPE = 'imsqti_xmlv1p2'
43
+
44
+ # substitution tokens
45
+ OBJECT_TOKEN = "$CANVAS_OBJECT_REFERENCE$"
46
+ COURSE_TOKEN = "$CANVAS_COURSE_REFERENCE$"
47
+ WIKI_TOKEN = "$WIKI_REFERENCE$"
48
+ WEB_CONTENT_TOKEN = "$IMS_CC_FILEBASE$"
49
+ MOODLE_FILEBASE_TOKEN = "$@FILEPHP@$"
50
+ MOODLE_SLASH_TOKEN = "$@SLASH@$"
51
+
52
+ # file names/paths
53
+ ASSESSMENT_CC_QTI = "assessment_qti.xml"
54
+ ASSESSMENT_NON_CC_FOLDER = 'non_cc_assessments'
55
+ ASSESSMENT_META = "assessment_meta.xml"
56
+ ASSIGNMENT_GROUPS = "assignment_groups.xml"
57
+ ASSIGNMENT_SETTINGS = "assignment_settings.xml"
58
+ COURSE_SETTINGS = "course_settings.xml"
59
+ COURSE_SETTINGS_DIR = "course_settings"
60
+ EXTERNAL_FEEDS = "external_feeds.xml"
61
+ GRADING_STANDARDS = "grading_standards.xml"
62
+ EVENTS = "events.xml"
63
+ LEARNING_OUTCOMES = "learning_outcomes.xml"
64
+ MANIFEST = 'imsmanifest.xml'
65
+ MODULE_META = "module_meta.xml"
66
+ RUBRICS = "rubrics.xml"
67
+ EXTERNAL_TOOLS = "external_tools.xml"
68
+ FILES_META = "files_meta.xml"
69
+ SYLLABUS = "syllabus.html"
70
+ WEB_RESOURCES_FOLDER = 'web_resources'
71
+ WIKI_FOLDER = 'wiki_content'
72
+ MEDIA_OBJECTS_FOLDER = 'media_objects'
73
+
74
+ def create_key(object, prepend="")
75
+ CCHelper.create_key(object, prepend)
76
+ end
77
+
78
+ def create_mod_key(mod)
79
+ CCHelper.create_mod_key(mod)
80
+ end
81
+
82
+ def create_resource_key(mod)
83
+ CCHelper.create_resource_key(mod)
84
+ end
85
+
86
+ def ims_date(date=nil)
87
+ CCHelper.ims_date(date)
88
+ end
89
+
90
+ def ims_datetime(date=nil)
91
+ CCHelper.ims_datetime(date)
92
+ end
93
+
94
+ def file_slug(name)
95
+ CCHelper.file_slug(name)
96
+ end
97
+
98
+ def convert_file_path_tokens(content)
99
+ CCHelper.convert_file_path_tokens(content)
100
+ end
101
+
102
+ def self.create_key(object, prepend="")
103
+ key = object.to_s
104
+ "i" + Digest::MD5.hexdigest(prepend + key)
105
+ end
106
+
107
+ def self.create_mod_key(mod)
108
+ create_key("#{mod.mod_type}_#{mod.id}", 'mod_')
109
+ end
110
+
111
+ def self.create_resource_key(mod)
112
+ create_key("#{mod.mod_type}_#{mod.id}", 'resource_')
113
+ end
114
+
115
+ def self.ims_date(date=nil)
116
+ date ||= Time.now
117
+ date.respond_to?(:utc) ? date.utc.strftime(IMS_DATE) : date.strftime(IMS_DATE)
118
+ end
119
+
120
+ def self.ims_datetime(date=nil)
121
+ date ||= Time.now
122
+ date.respond_to?(:utc) ? date.utc.strftime(IMS_DATETIME) : date.strftime(IMS_DATETIME)
123
+ end
124
+
125
+ def self.file_slug(name)
126
+ slug = name.downcase.gsub(/\s/, '-').gsub(/[^a-z0-9\.\-]/, '').gsub(/\.*$/, '')
127
+ end
128
+
129
+ def self.convert_file_path_tokens(content)
130
+ content.gsub(MOODLE_FILEBASE_TOKEN, WEB_CONTENT_TOKEN).gsub(MOODLE_SLASH_TOKEN, '/')
131
+ end
132
+
133
+ def get_html_title_and_body_and_id(doc)
134
+ id = get_node_val(doc, 'html head meta[name=identifier] @content')
135
+ get_html_title_and_body(doc) << id
136
+ end
137
+
138
+ def get_html_title_and_body_and_meta_fields(doc)
139
+ meta_fields = {}
140
+ doc.css('html head meta').each do |meta_node|
141
+ if key = meta_node['name']
142
+ meta_fields[key] = meta_node['content']
143
+ end
144
+ end
145
+ get_html_title_and_body(doc) << meta_fields
146
+ end
147
+
148
+ def get_html_title_and_body(doc)
149
+ title = get_node_val(doc, 'html head title')
150
+ body = doc.at_css('html body').to_s.gsub(%r{</?body>}, '').strip
151
+ [title, body]
152
+ end
153
+
154
+ require 'set'
155
+ class HtmlContentExporter
156
+ attr_reader :used_media_objects, :media_object_flavor, :media_object_infos
157
+ attr_accessor :referenced_files
158
+
159
+ def initialize(course, user, opts = {})
160
+ @media_object_flavor = opts[:media_object_flavor]
161
+ @used_media_objects = Set.new
162
+ @media_object_infos = {}
163
+ @rewriter = UserContent::HtmlRewriter.new(course, user)
164
+ @course = course
165
+ @user = user
166
+ @track_referenced_files = opts[:track_referenced_files]
167
+ @for_course_copy = opts[:for_course_copy]
168
+ @referenced_files = {}
169
+
170
+ @rewriter.set_handler('file_contents') do |match|
171
+ if match.url =~ %r{/media_objects/(\d_\w+)}
172
+ # This is a media object referencing an attachment that it shouldn't be
173
+ "/media_objects/#{$1}"
174
+ else
175
+ match.url.gsub(/course( |%20)files/, WEB_CONTENT_TOKEN)
176
+ end
177
+ end
178
+ @rewriter.set_handler('files') do |match|
179
+ obj = match.obj_class.find_by_id(match.obj_id)
180
+ next(match.url) unless obj && @rewriter.user_can_view_content?(obj)
181
+ folder = obj.folder.full_name.gsub(/course( |%20)files/, WEB_CONTENT_TOKEN)
182
+ @referenced_files[obj.id] = CCHelper.create_key(obj) if @track_referenced_files && !@referenced_files[obj.id]
183
+ # for files, turn it into a relative link by path, rather than by file id
184
+ # we retain the file query string parameters
185
+ "#{folder}/#{URI.escape(obj.display_name)}#{CCHelper.file_query_string(match.rest)}"
186
+ end
187
+ @rewriter.set_handler('wiki') do |match|
188
+ "#{WIKI_TOKEN}/#{match.type}#{match.rest}"
189
+ end
190
+ @rewriter.set_default_handler do |match|
191
+ new_url = match.url
192
+ if match.obj_id
193
+ obj = match.obj_class.find_by_id(match.obj_id)
194
+ if obj && @rewriter.user_can_view_content?(obj)
195
+ # for all other types,
196
+ # create a migration id for the object, and use that as the new link
197
+ migration_id = CCHelper.create_key(obj)
198
+ new_url = "#{OBJECT_TOKEN}/#{match.type}/#{migration_id}"
199
+ end
200
+ else
201
+ new_url = "#{COURSE_TOKEN}/#{match.type}#{match.rest}"
202
+ end
203
+ new_url
204
+ end
205
+ end
206
+
207
+ attr_reader :course, :user
208
+
209
+ def html_page(html, title, meta_fields={})
210
+ content = html_content(html)
211
+ meta_html = ""
212
+ meta_fields.each_pair do |k, v|
213
+ next unless v.present?
214
+ meta_html += %{<meta name="#{k}" content="#{v}"/>\n}
215
+ end
216
+
217
+ %{<html>\n<head>\n<meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n<title>#{title}</title>\n#{meta_html}</head>\n<body>\n#{content}\n</body>\n</html>}
218
+ end
219
+
220
+ def html_content(html)
221
+ html = @rewriter.translate_content(html)
222
+ return html if html.blank? || @for_course_copy
223
+
224
+ # keep track of found media comments, and translate them into links into the files tree
225
+ # if imported back into canvas, they'll get uploaded to the media server
226
+ # and translated back into media comments
227
+ doc = Nokogiri::HTML::DocumentFragment.parse(html)
228
+ doc.css('a.instructure_inline_media_comment').each do |anchor|
229
+ next unless anchor['id']
230
+ media_id = anchor['id'].gsub(/^media_comment_/, '')
231
+ obj = course.media_objects.by_media_id(media_id).first
232
+ if obj && obj.context == course && migration_id = CCHelper.create_key(obj)
233
+ @used_media_objects << obj
234
+ info = CCHelper.media_object_info(obj, nil, media_object_flavor)
235
+ @media_object_infos[obj.id] = info
236
+ anchor['href'] = File.join(WEB_CONTENT_TOKEN, MEDIA_OBJECTS_FOLDER, info[:filename])
237
+ end
238
+ end
239
+
240
+ return doc.to_s
241
+ end
242
+ end
243
+
244
+ def self.media_object_info(obj, client = nil, flavor = nil)
245
+ unless client
246
+ client = Kaltura::ClientV3.new
247
+ client.startSession(Kaltura::SessionType::ADMIN)
248
+ end
249
+ if flavor
250
+ assets = client.flavorAssetGetByEntryId(obj.media_id)
251
+ asset = assets.sort_by { |f| f[:size].to_i }.reverse.find { |f| f[:containerFormat] == flavor }
252
+ asset ||= assets.first
253
+ else
254
+ asset = client.flavorAssetGetOriginalAsset(obj.media_id)
255
+ end
256
+ # we use the media_id as the export filename, since it is guaranteed to
257
+ # be unique
258
+ filename = "#{obj.media_id}.#{asset[:fileExt]}" if asset
259
+ { :asset => asset, :filename => filename }
260
+ end
261
+
262
+ # sub_path is the last part of a file url: /courses/1/files/1(/download)
263
+ # we want to handle any sort of extra params to the file url, both in the
264
+ # path components and the query string
265
+ def self.file_query_string(sub_path)
266
+ return if sub_path.blank?
267
+ qs = []
268
+ uri = URI.parse(sub_path)
269
+ unless uri.path == "/preview" # defaults to preview, so no qs added
270
+ qs << "canvas_#{Rack::Utils.escape(uri.path[1..-1])}=1"
271
+ end
272
+
273
+ Rack::Utils.parse_query(uri.query).each do |k,v|
274
+ qs << "canvas_qs_#{Rack::Utils.escape(k)}=#{Rack::Utils.escape(v)}"
275
+ end
276
+
277
+ return nil if qs.blank?
278
+ "?#{qs.join("&")}"
279
+ end
280
+
281
+ end
282
+ end