moodle2cc 0.0.1
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.
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/Guardfile +14 -0
- data/LICENSE +661 -0
- data/README.md +38 -0
- data/Rakefile +12 -0
- data/bin/moodle2cc +6 -0
- data/lib/moodle2cc.rb +60 -0
- data/lib/moodle2cc/canvas/.converter.rb.swo +0 -0
- data/lib/moodle2cc/canvas/.wiki.rb.swo +0 -0
- data/lib/moodle2cc/canvas/assessment.rb +109 -0
- data/lib/moodle2cc/canvas/assignment.rb +108 -0
- data/lib/moodle2cc/canvas/converter.rb +28 -0
- data/lib/moodle2cc/canvas/course.rb +145 -0
- data/lib/moodle2cc/canvas/discussion_topic.rb +64 -0
- data/lib/moodle2cc/canvas/label.rb +8 -0
- data/lib/moodle2cc/canvas/question.rb +468 -0
- data/lib/moodle2cc/canvas/question_bank.rb +54 -0
- data/lib/moodle2cc/canvas/resource.rb +16 -0
- data/lib/moodle2cc/canvas/templates/assignment.html.erb +9 -0
- data/lib/moodle2cc/canvas/templates/syllabus.html.erb +9 -0
- data/lib/moodle2cc/canvas/templates/wiki_content.html.erb +10 -0
- data/lib/moodle2cc/canvas/web_content.rb +10 -0
- data/lib/moodle2cc/canvas/web_link.rb +17 -0
- data/lib/moodle2cc/canvas/wiki.rb +25 -0
- data/lib/moodle2cc/cc/assessment.rb +25 -0
- data/lib/moodle2cc/cc/assignment.rb +66 -0
- data/lib/moodle2cc/cc/cc_helper.rb +282 -0
- data/lib/moodle2cc/cc/converter.rb +136 -0
- data/lib/moodle2cc/cc/course.rb +16 -0
- data/lib/moodle2cc/cc/discussion_topic.rb +53 -0
- data/lib/moodle2cc/cc/label.rb +6 -0
- data/lib/moodle2cc/cc/question.rb +23 -0
- data/lib/moodle2cc/cc/resource.rb +30 -0
- data/lib/moodle2cc/cc/templates/assignment.html.erb +9 -0
- data/lib/moodle2cc/cc/templates/syllabus.html.erb +9 -0
- data/lib/moodle2cc/cc/templates/wiki_content.html.erb +10 -0
- data/lib/moodle2cc/cc/web_content.rb +40 -0
- data/lib/moodle2cc/cc/web_link.rb +53 -0
- data/lib/moodle2cc/cc/wiki.rb +72 -0
- data/lib/moodle2cc/cli.rb +14 -0
- data/lib/moodle2cc/error.rb +3 -0
- data/lib/moodle2cc/migrator.rb +23 -0
- data/lib/moodle2cc/moodle/backup.rb +38 -0
- data/lib/moodle2cc/moodle/course.rb +24 -0
- data/lib/moodle2cc/moodle/grade_item.rb +24 -0
- data/lib/moodle2cc/moodle/info.rb +8 -0
- data/lib/moodle2cc/moodle/mod.rb +127 -0
- data/lib/moodle2cc/moodle/question.rb +98 -0
- data/lib/moodle2cc/moodle/question_category.rb +17 -0
- data/lib/moodle2cc/moodle/section.rb +37 -0
- data/lib/moodle2cc/resource_factory.rb +32 -0
- data/lib/moodle2cc/version.rb +3 -0
- data/moodle2cc.gemspec +29 -0
- data/test/acceptance/migrator_test.rb +23 -0
- data/test/fixtures/cc.imscc +0 -0
- data/test/fixtures/moodle_backup/course_files/folder/test.txt +1 -0
- data/test/fixtures/moodle_backup/course_files/test.txt +1 -0
- data/test/fixtures/moodle_backup/moodle.xml +916 -0
- data/test/test_helper.rb +43 -0
- data/test/test_question_helper.rb +107 -0
- data/test/test_wiki_helper.rb +21 -0
- data/test/unit/canvas/assessment_test.rb +218 -0
- data/test/unit/canvas/assignment_test.rb +220 -0
- data/test/unit/canvas/converter_test.rb +159 -0
- data/test/unit/canvas/course_test.rb +176 -0
- data/test/unit/canvas/discussion_topic_test.rb +129 -0
- data/test/unit/canvas/label_test.rb +32 -0
- data/test/unit/canvas/question_bank_test.rb +73 -0
- data/test/unit/canvas/question_test.rb +824 -0
- data/test/unit/canvas/web_content_test.rb +37 -0
- data/test/unit/canvas/web_link_test.rb +48 -0
- data/test/unit/canvas/wiki_test.rb +74 -0
- data/test/unit/cc/assessment_test.rb +48 -0
- data/test/unit/cc/assignment_test.rb +67 -0
- data/test/unit/cc/cc_helper_test.rb +19 -0
- data/test/unit/cc/converter_test.rb +159 -0
- data/test/unit/cc/course_test.rb +36 -0
- data/test/unit/cc/discussion_topic_test.rb +83 -0
- data/test/unit/cc/label_test.rb +29 -0
- data/test/unit/cc/question_test.rb +49 -0
- data/test/unit/cc/web_content_test.rb +76 -0
- data/test/unit/cc/web_link_test.rb +79 -0
- data/test/unit/cc/wiki_test.rb +139 -0
- data/test/unit/migrator_test.rb +56 -0
- data/test/unit/moodle/backup_test.rb +30 -0
- data/test/unit/moodle/course_test.rb +57 -0
- data/test/unit/moodle/grade_item_test.rb +72 -0
- data/test/unit/moodle/info_test.rb +21 -0
- data/test/unit/moodle/mod_test.rb +301 -0
- data/test/unit/moodle/question_category_test.rb +34 -0
- data/test/unit/moodle/question_test.rb +185 -0
- data/test/unit/moodle/section_test.rb +75 -0
- data/test/unit/resource_factory_test.rb +119 -0
- 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,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
|