canvas_cc 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/canvas_cc.gemspec +37 -0
- data/lib/canvas_cc/canvas/assessment.rb +124 -0
- data/lib/canvas_cc/canvas/assignment.rb +98 -0
- data/lib/canvas_cc/canvas/converter.rb +30 -0
- data/lib/canvas_cc/canvas/course.rb +143 -0
- data/lib/canvas_cc/canvas/discussion_topic.rb +64 -0
- data/lib/canvas_cc/canvas/label.rb +8 -0
- data/lib/canvas_cc/canvas/question.rb +492 -0
- data/lib/canvas_cc/canvas/question_bank.rb +57 -0
- data/lib/canvas_cc/canvas/question_group.rb +35 -0
- data/lib/canvas_cc/canvas/resource.rb +16 -0
- data/lib/canvas_cc/canvas/templates/assignment.html.erb +9 -0
- data/lib/canvas_cc/canvas/templates/syllabus.html.erb +9 -0
- data/lib/canvas_cc/canvas/templates/wiki_content.html.erb +10 -0
- data/lib/canvas_cc/canvas/web_content.rb +15 -0
- data/lib/canvas_cc/canvas/web_link.rb +16 -0
- data/lib/canvas_cc/canvas/wiki.rb +26 -0
- data/lib/canvas_cc/canvas_cc/assessment_writer.rb +82 -0
- data/lib/canvas_cc/canvas_cc/assignment_group_writer.rb +48 -0
- data/lib/canvas_cc/canvas_cc/assignment_writer.rb +68 -0
- data/lib/canvas_cc/canvas_cc/calculated_question_writer.rb +72 -0
- data/lib/canvas_cc/canvas_cc/canvas_export_writer.rb +18 -0
- data/lib/canvas_cc/canvas_cc/cartridge_creator.rb +60 -0
- data/lib/canvas_cc/canvas_cc/course_setting_writer.rb +35 -0
- data/lib/canvas_cc/canvas_cc/discussion_writer.rb +52 -0
- data/lib/canvas_cc/canvas_cc/essay_question_writer.rb +19 -0
- data/lib/canvas_cc/canvas_cc/file_meta_writer.rb +38 -0
- data/lib/canvas_cc/canvas_cc/ims_manifest_generator.rb +123 -0
- data/lib/canvas_cc/canvas_cc/matching_question_writer.rb +43 -0
- data/lib/canvas_cc/canvas_cc/models/answer.rb +12 -0
- data/lib/canvas_cc/canvas_cc/models/assessment.rb +102 -0
- data/lib/canvas_cc/canvas_cc/models/assignment.rb +30 -0
- data/lib/canvas_cc/canvas_cc/models/assignment_group.rb +9 -0
- data/lib/canvas_cc/canvas_cc/models/calculated_question.rb +19 -0
- data/lib/canvas_cc/canvas_cc/models/canvas_file.rb +22 -0
- data/lib/canvas_cc/canvas_cc/models/canvas_module.rb +10 -0
- data/lib/canvas_cc/canvas_cc/models/course.rb +53 -0
- data/lib/canvas_cc/canvas_cc/models/discussion.rb +36 -0
- data/lib/canvas_cc/canvas_cc/models/discussion_topic.rb +5 -0
- data/lib/canvas_cc/canvas_cc/models/matching_question.rb +7 -0
- data/lib/canvas_cc/canvas_cc/models/module_item.rb +16 -0
- data/lib/canvas_cc/canvas_cc/models/multiple_dropdowns_question.rb +7 -0
- data/lib/canvas_cc/canvas_cc/models/numerical_question.rb +7 -0
- data/lib/canvas_cc/canvas_cc/models/page.rb +41 -0
- data/lib/canvas_cc/canvas_cc/models/question.rb +37 -0
- data/lib/canvas_cc/canvas_cc/models/question_bank.rb +42 -0
- data/lib/canvas_cc/canvas_cc/models/question_group.rb +10 -0
- data/lib/canvas_cc/canvas_cc/models/resource.rb +23 -0
- data/lib/canvas_cc/canvas_cc/models/web_content.rb +7 -0
- data/lib/canvas_cc/canvas_cc/models/web_link.rb +7 -0
- data/lib/canvas_cc/canvas_cc/models/workflow_state.rb +6 -0
- data/lib/canvas_cc/canvas_cc/module_meta_writer.rb +52 -0
- data/lib/canvas_cc/canvas_cc/multiple_answers_question_writer.rb +41 -0
- data/lib/canvas_cc/canvas_cc/multiple_blanks_question_writer.rb +51 -0
- data/lib/canvas_cc/canvas_cc/multiple_choice_question_writer.rb +48 -0
- data/lib/canvas_cc/canvas_cc/multiple_dropdowns_question_writer.rb +30 -0
- data/lib/canvas_cc/canvas_cc/numerical_question_writer.rb +40 -0
- data/lib/canvas_cc/canvas_cc/page_writer.rb +38 -0
- data/lib/canvas_cc/canvas_cc/question_bank_writer.rb +44 -0
- data/lib/canvas_cc/canvas_cc/question_group_writer.rb +23 -0
- data/lib/canvas_cc/canvas_cc/question_writer.rb +129 -0
- data/lib/canvas_cc/canvas_cc/short_answer_question_writer.rb +42 -0
- data/lib/canvas_cc/canvas_cc/text_only_question_writer.rb +19 -0
- data/lib/canvas_cc/canvas_cc/true_false_question_writer.rb +38 -0
- data/lib/canvas_cc/cc/assessment.rb +25 -0
- data/lib/canvas_cc/cc/assignment.rb +61 -0
- data/lib/canvas_cc/cc/cc_helper.rb +285 -0
- data/lib/canvas_cc/cc/converter.rb +137 -0
- data/lib/canvas_cc/cc/course.rb +16 -0
- data/lib/canvas_cc/cc/discussion_topic.rb +53 -0
- data/lib/canvas_cc/cc/label.rb +6 -0
- data/lib/canvas_cc/cc/question.rb +23 -0
- data/lib/canvas_cc/cc/resource.rb +30 -0
- data/lib/canvas_cc/cc/templates/assignment.html.erb +13 -0
- data/lib/canvas_cc/cc/templates/syllabus.html.erb +9 -0
- data/lib/canvas_cc/cc/templates/wiki_content.html.erb +10 -0
- data/lib/canvas_cc/cc/web_content.rb +48 -0
- data/lib/canvas_cc/cc/web_link.rb +75 -0
- data/lib/canvas_cc/cc/wiki.rb +73 -0
- data/lib/canvas_cc/version.rb +3 -0
- data/lib/canvas_cc.rb +94 -0
- data/spec/moodle2cc/canvas_cc/assessment_writer_spec.rb +95 -0
- data/spec/moodle2cc/canvas_cc/assignment_group_writer_spec.rb +51 -0
- data/spec/moodle2cc/canvas_cc/assignment_writer_spec.rb +74 -0
- data/spec/moodle2cc/canvas_cc/calculated_question_writer_spec.rb +88 -0
- data/spec/moodle2cc/canvas_cc/canvas_export_writer_spec.rb +26 -0
- data/spec/moodle2cc/canvas_cc/cartridge_creator_spec.rb +50 -0
- data/spec/moodle2cc/canvas_cc/course_setting_writer_spec.rb +47 -0
- data/spec/moodle2cc/canvas_cc/discussion_writer_spec.rb +38 -0
- data/spec/moodle2cc/canvas_cc/essay_question_writer_spec.rb +31 -0
- data/spec/moodle2cc/canvas_cc/file_meta_writer_spec.rb +57 -0
- data/spec/moodle2cc/canvas_cc/ims_manifest_generator_spec.rb +141 -0
- data/spec/moodle2cc/canvas_cc/matching_question_writer_spec.rb +51 -0
- data/spec/moodle2cc/canvas_cc/models/assessment_spec.rb +131 -0
- data/spec/moodle2cc/canvas_cc/models/assignment_group_spec.rb +9 -0
- data/spec/moodle2cc/canvas_cc/models/assignment_spec.rb +45 -0
- data/spec/moodle2cc/canvas_cc/models/calculated_question_spec.rb +25 -0
- data/spec/moodle2cc/canvas_cc/models/canvas_file_spec.rb +23 -0
- data/spec/moodle2cc/canvas_cc/models/canvas_module_spec.rb +16 -0
- data/spec/moodle2cc/canvas_cc/models/course_spec.rb +43 -0
- data/spec/moodle2cc/canvas_cc/models/discussion_spec.rb +47 -0
- data/spec/moodle2cc/canvas_cc/models/discussion_topic_spec.rb +7 -0
- data/spec/moodle2cc/canvas_cc/models/module_item_spec.rb +15 -0
- data/spec/moodle2cc/canvas_cc/models/page_spec.rb +34 -0
- data/spec/moodle2cc/canvas_cc/models/question_spec.rb +8 -0
- data/spec/moodle2cc/canvas_cc/models/resource_spec.rb +17 -0
- data/spec/moodle2cc/canvas_cc/models/web_content_spec.rb +7 -0
- data/spec/moodle2cc/canvas_cc/models/web_link_spec.rb +9 -0
- data/spec/moodle2cc/canvas_cc/module_meta_writer_spec.rb +118 -0
- data/spec/moodle2cc/canvas_cc/multiple_answers_question_writer_spec.rb +50 -0
- data/spec/moodle2cc/canvas_cc/multiple_blanks_question_writer_spec.rb +77 -0
- data/spec/moodle2cc/canvas_cc/multiple_choice_question_writer_spec.rb +69 -0
- data/spec/moodle2cc/canvas_cc/multiple_dropdowns_question_writer_spec.rb +46 -0
- data/spec/moodle2cc/canvas_cc/numerical_question_writer_spec.rb +69 -0
- data/spec/moodle2cc/canvas_cc/page_writer_spec.rb +29 -0
- data/spec/moodle2cc/canvas_cc/question_bank_writer_spec.rb +43 -0
- data/spec/moodle2cc/canvas_cc/question_writer_spec.rb +129 -0
- data/spec/moodle2cc/canvas_cc/short_answer_question_writer_spec.rb +50 -0
- data/spec/moodle2cc/canvas_cc/text_only_question_writer_spec.rb +23 -0
- data/spec/moodle2cc/canvas_cc/true_false_question_writer_spec.rb +62 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/support/shared_examples_for_models.rb +26 -0
- data/test/acceptance/migrator_test.rb +24 -0
- data/test/fixtures/cc.imscc +0 -0
- data/test/fixtures/common_cartridge/ims_manifest_simple.xml +45 -0
- data/test/fixtures/common_cartridge/schema/cccv1p0.xsd +563 -0
- data/test/fixtures/common_cartridge/schema/ccv1p1_imsccauth_v1p1.xsd +203 -0
- data/test/fixtures/common_cartridge/schema/ccv1p1_imscp_v1p2_v1p0.xsd +827 -0
- data/test/fixtures/common_cartridge/schema/ccv1p1_lommanifest_v1p0.xsd +822 -0
- data/test/fixtures/common_cartridge/schema/ccv1p1_lomresource_v1p0.xsd +273 -0
- data/test/fixtures/common_cartridge/schema/xml.xsd +286 -0
- data/test/test_helper.rb +71 -0
- data/test/test_question_helper.rb +122 -0
- data/test/test_wiki_helper.rb +21 -0
- data/test/unit/canvas/assessment_test.rb +261 -0
- data/test/unit/canvas/assignment_test.rb +220 -0
- data/test/unit/canvas/converter_test.rb +171 -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 +92 -0
- data/test/unit/canvas/question_group_test.rb +90 -0
- data/test/unit/canvas/question_test.rb +876 -0
- data/test/unit/canvas/web_content_test.rb +46 -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 +72 -0
- data/test/unit/cc/cc_helper_test.rb +19 -0
- data/test/unit/cc/converter_test.rb +173 -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 +54 -0
- data/test/unit/cc/web_content_test.rb +76 -0
- data/test/unit/cc/web_link_test.rb +159 -0
- data/test/unit/cc/wiki_test.rb +139 -0
- data/test/unit/logger_test.rb +33 -0
- data/test/unit/migrator_test.rb +67 -0
- data/test/unit/resource_factory_test.rb +216 -0
- metadata +499 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
module CanvasCc::CanvasCC
|
2
|
+
class MatchingQuestionWriter < QuestionWriter
|
3
|
+
register_writer_type 'matching_question'
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
def self.write_responses(presentation_node, question)
|
8
|
+
question.matches.each do |match|
|
9
|
+
presentation_node.response_lid(:ident => "response_#{match[:id]}") do |response_node|
|
10
|
+
next unless match[:question_text].length > 0
|
11
|
+
response_node.material do |material_node|
|
12
|
+
material_node.mattext(match[:question_text], :texttype => 'text/plain')
|
13
|
+
end
|
14
|
+
response_node.render_choice do |choice_node|
|
15
|
+
question.matches.each do |possible_match|
|
16
|
+
choice_node.response_label(:ident => possible_match[:id]) do |label_node|
|
17
|
+
label_node.material do |material_node|
|
18
|
+
material_node.mattext(possible_match[:answer_text], :texttype => 'text/plain')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.write_response_conditions(processing_node, question)
|
28
|
+
real_matches = question.matches.select{|m| m[:question_text].length > 0}
|
29
|
+
score = 100.0 / real_matches.length.to_i
|
30
|
+
real_matches.each do |match|
|
31
|
+
processing_node.respcondition do |condition_node|
|
32
|
+
condition_node.conditionvar do |var_node|
|
33
|
+
var_node.varequal match[:id], :respident => "response_#{match[:id]}"
|
34
|
+
end
|
35
|
+
condition_node.setvar "%.2f" % score, :varname => 'SCORE', :action => 'Add'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.write_additional_nodes(item_node, question)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module CanvasCc::CanvasCC::Models
|
2
|
+
class Answer
|
3
|
+
attr_accessor :id, :answer_text, :answer_format, :fraction, :feedback, :feedback_format, :resp_ident
|
4
|
+
|
5
|
+
def initialize(answer=nil)
|
6
|
+
return unless answer
|
7
|
+
answer.instance_variables.each do |var|
|
8
|
+
self.instance_variable_set(var, answer.instance_variable_get(var))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module CanvasCc::CanvasCC::Models
|
2
|
+
class Assessment
|
3
|
+
META_ATTRIBUTES = [:title, :description, :allowed_attempts,
|
4
|
+
:scoring_policy, :access_code, :ip_filter, :shuffle_answers, :time_limit, :quiz_type]
|
5
|
+
DATETIME_ATTRIBUTES = [:lock_at, :unlock_at]
|
6
|
+
|
7
|
+
ASSESSMENT_TYPE = 'imsqti_xmlv1p2/imscc_xmlv1p1/assessment'
|
8
|
+
LAR_TYPE = 'associatedcontent/imscc_xmlv1p1/learning-application-resource'
|
9
|
+
ASSESSMENT_NON_CC_FOLDER = 'non_cc_assessments'
|
10
|
+
|
11
|
+
attr_accessor :identifier, :workflow_state, :question_references, :items, *META_ATTRIBUTES, *DATETIME_ATTRIBUTES
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@question_references = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def resources
|
18
|
+
[cc_assessment_resource, canvas_assessment_resource]
|
19
|
+
end
|
20
|
+
|
21
|
+
def cc_assessment_resource
|
22
|
+
resource = CanvasCc::CanvasCC::Models::Resource.new
|
23
|
+
resource.identifier = @identifier
|
24
|
+
resource.type = ASSESSMENT_TYPE
|
25
|
+
resource.dependencies = ["#{@identifier}_meta"]
|
26
|
+
resource.files = [] # TODO: export cc qti file
|
27
|
+
resource
|
28
|
+
end
|
29
|
+
|
30
|
+
def canvas_assessment_resource
|
31
|
+
resource = CanvasCc::CanvasCC::Models::Resource.new
|
32
|
+
resource.identifier = "#{@identifier}_meta"
|
33
|
+
resource.href = meta_file_path
|
34
|
+
resource.type = LAR_TYPE
|
35
|
+
resource.files = [meta_file_path, qti_file_path]
|
36
|
+
resource
|
37
|
+
end
|
38
|
+
|
39
|
+
def meta_file_path
|
40
|
+
"#{@identifier}/assessment_meta.xml"
|
41
|
+
end
|
42
|
+
|
43
|
+
def qti_file_path
|
44
|
+
"#{ASSESSMENT_NON_CC_FOLDER}/#{@identifier}.xml.qti"
|
45
|
+
end
|
46
|
+
|
47
|
+
def resolve_question_references!(question_banks)
|
48
|
+
@items ||= []
|
49
|
+
|
50
|
+
# keep track of each time a bank is referenced through a random question reference
|
51
|
+
random_bank_counts = {}
|
52
|
+
|
53
|
+
@question_references.each do |ref|
|
54
|
+
question = nil
|
55
|
+
group = nil
|
56
|
+
question_banks.each do |bank|
|
57
|
+
break if (question = bank.questions.detect{|q| q.original_identifier.to_s == ref[:question]}) ||
|
58
|
+
(group = bank.question_groups.detect{|g| g.identifier.to_s == ref[:question]})
|
59
|
+
|
60
|
+
if bank.random_question_references.any?{|r| r.to_s == ref[:question]}
|
61
|
+
random_bank_counts[bank] ||= 0
|
62
|
+
random_bank_counts[bank] += 1
|
63
|
+
break
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
if question
|
68
|
+
question.assessment_question_identifierref ||= "question_#{question.identifier}"
|
69
|
+
copy = question.dup
|
70
|
+
copy.points_possible = ref[:grade] if ref[:grade]
|
71
|
+
@items << copy
|
72
|
+
elsif group
|
73
|
+
copy = group.dup
|
74
|
+
if ref[:grade] && copy.selection_number.to_i != 0
|
75
|
+
copy.points_per_item = ref[:grade].to_f / copy.selection_number.to_i
|
76
|
+
end
|
77
|
+
@items << copy
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
random_bank_counts.each do |bank, count|
|
82
|
+
new_group = CanvasCc::CanvasCC::Models::QuestionGroup.new
|
83
|
+
new_group.identifier = "#{@identifier}#{bank.identifier}_random_group"
|
84
|
+
new_group.selection_number = count
|
85
|
+
|
86
|
+
new_group.questions = bank.questions.map(&:dup)
|
87
|
+
child_banks = bank.find_children_banks(question_banks)
|
88
|
+
child_banks.each do |child|
|
89
|
+
new_group.questions += child.questions.map(&:dup)
|
90
|
+
end
|
91
|
+
|
92
|
+
@items << new_group
|
93
|
+
end
|
94
|
+
|
95
|
+
@items.select{|i| i.is_a?(CanvasCc::CanvasCC::Models::QuestionGroup)}.each do |group|
|
96
|
+
group.questions.each do |q|
|
97
|
+
@items.delete_if{|i| i.respond_to?(:original_identifier) && i.original_identifier == q.original_identifier}
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module CanvasCc::CanvasCC::Models
|
2
|
+
class Assignment
|
3
|
+
attr_accessor :identifier, :title, :body, :due_at, :lock_at, :unlock_at, :all_day_date, :peer_reviews_due_at,
|
4
|
+
:assignment_group_identifier_ref, :workflow_state, :points_possible, :grading_type, :all_day,
|
5
|
+
:submission_types, :position, :peer_review_count, :peer_reviews_assigned, :peer_reviews,
|
6
|
+
:automatic_peer_reviews, :grade_group_students_individually, :muted
|
7
|
+
|
8
|
+
LAR_TYPE = 'associatedcontent/imscc_xmlv1p1/learning-application-resource'
|
9
|
+
ASSIGNMENT_SETTINGS_FILE = 'assignment_settings.xml'
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@submission_types = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def resources
|
16
|
+
[assignment_resource]
|
17
|
+
end
|
18
|
+
|
19
|
+
def assignment_resource
|
20
|
+
resource = CanvasCc::CanvasCC::Models::Resource.new
|
21
|
+
resource.identifier = @identifier
|
22
|
+
resource.href = "#{resource.identifier}/assignment-#{CGI::escape(title.downcase.gsub(/\s/, '-'))}.html"
|
23
|
+
resource.type = LAR_TYPE
|
24
|
+
resource.files = [resource.href, "#{resource.identifier}/#{ASSIGNMENT_SETTINGS_FILE}"]
|
25
|
+
|
26
|
+
resource
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module CanvasCc::CanvasCC::Models
|
2
|
+
class CalculatedQuestion < Question
|
3
|
+
register_question_type 'calculated_question'
|
4
|
+
|
5
|
+
attr_accessor :correct_answer_format, :correct_answer_length, :dataset_definitions, :tolerance, :var_sets
|
6
|
+
|
7
|
+
def post_process!
|
8
|
+
@material.gsub!(/\{([\w\s]*?)\}/, '[\1]') if @material
|
9
|
+
|
10
|
+
return unless @answers
|
11
|
+
@answers.each do |answer|
|
12
|
+
if answer.answer_text
|
13
|
+
answer.answer_text.gsub!(/\{([\w\s]*?)\}/, '\1')
|
14
|
+
answer.answer_text = answer.answer_text.sub('=', '') if answer.answer_text.start_with?('=')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module CanvasCc::CanvasCC::Models
|
2
|
+
class CanvasFile < CanvasCc::CanvasCC::Models::Resource
|
3
|
+
|
4
|
+
WEB_RESOURCES = 'web_resources'
|
5
|
+
|
6
|
+
attr_reader :file_path
|
7
|
+
attr_accessor :file_location
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
super
|
11
|
+
@type = WEB_CONTENT_TYPE
|
12
|
+
end
|
13
|
+
|
14
|
+
def file_path=(file_path)
|
15
|
+
@href = File.join(WEB_RESOURCES, file_path)
|
16
|
+
@files << self.href
|
17
|
+
@file_path = file_path
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module CanvasCc::CanvasCC::Models
|
2
|
+
class Course
|
3
|
+
|
4
|
+
attr_accessor :format, :identifier, :copyright, :settings, :resources, :canvas_modules, :files, :pages, :discussions,
|
5
|
+
:assignments, :assessments, :question_banks, :assignment_groups
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@settings = {}
|
9
|
+
@resources = []
|
10
|
+
@canvas_modules = []
|
11
|
+
@files = []
|
12
|
+
@pages = []
|
13
|
+
@discussions = []
|
14
|
+
@assignments = []
|
15
|
+
@assessments = []
|
16
|
+
@question_banks = []
|
17
|
+
@assignment_groups = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def start_at
|
21
|
+
CanvasCc::CC::CCHelper.ims_datetime(@settings[:start_at]) if @settings[:start_at]
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def conclude_at
|
26
|
+
CanvasCc::CC::CCHelper.ims_datetime(@settings[:conclude_at]) if @settings[:conclude_at]
|
27
|
+
end
|
28
|
+
|
29
|
+
def all_resources
|
30
|
+
@resources + @files + @pages + @discussions.map(&:resources).flatten + @assignments.map(&:resources).flatten + @assessments.map(&:resources).flatten
|
31
|
+
end
|
32
|
+
|
33
|
+
def method_missing(m, *args, &block)
|
34
|
+
method = m.to_s
|
35
|
+
if method[-1, 1] == '='
|
36
|
+
method.chomp!('=')
|
37
|
+
@settings[method.to_sym] = args.first
|
38
|
+
end
|
39
|
+
@settings[method.to_sym]
|
40
|
+
end
|
41
|
+
|
42
|
+
def resolve_question_references!
|
43
|
+
return unless @assessments && @question_banks
|
44
|
+
@assessments.each{|a| a.resolve_question_references!(@question_banks)}
|
45
|
+
end
|
46
|
+
|
47
|
+
def mute_assignments!
|
48
|
+
@assignments.each do |assignment|
|
49
|
+
assignment.muted = true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module CanvasCc::CanvasCC::Models
|
2
|
+
class Discussion
|
3
|
+
attr_accessor :identifier, :title, :text, :discussion_type, :workflow_state, :require_initial_post
|
4
|
+
DISCUSSION_ID_POSTFIX = '_DISCUSSION'
|
5
|
+
DISCUSSION_META_POSTFIX = '_meta'
|
6
|
+
IMSDT_TYPE = 'imsdt_xmlv1p1'
|
7
|
+
LAR_TYPE = 'associatedcontent/imscc_xmlv1p1/learning-application-resource'
|
8
|
+
|
9
|
+
def resources
|
10
|
+
#generate_meta_resource
|
11
|
+
[discussion_resource, meta_resource]
|
12
|
+
end
|
13
|
+
|
14
|
+
def discussion_resource
|
15
|
+
resource = CanvasCc::CanvasCC::Models::Resource.new
|
16
|
+
resource.identifier = @identifier
|
17
|
+
resource.dependencies << resource.identifier + DISCUSSION_META_POSTFIX
|
18
|
+
resource.type = IMSDT_TYPE
|
19
|
+
resource.files << resource.identifier + '.xml'
|
20
|
+
|
21
|
+
resource
|
22
|
+
end
|
23
|
+
|
24
|
+
def meta_resource
|
25
|
+
resource = CanvasCc::CanvasCC::Models::Resource.new
|
26
|
+
resource.identifier = @identifier + DISCUSSION_META_POSTFIX
|
27
|
+
resource.type = LAR_TYPE
|
28
|
+
file_name = resource.identifier + '.xml'
|
29
|
+
resource.href = file_name
|
30
|
+
resource.files << file_name
|
31
|
+
|
32
|
+
resource
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module CanvasCc::CanvasCC::Models
|
2
|
+
class ModuleItem
|
3
|
+
attr_accessor :identifier, :content_type, :workflow_state, :title,
|
4
|
+
:new_tab, :indent, :resource, :identifierref, :url
|
5
|
+
|
6
|
+
CONTENT_TYPE_WIKI_PAGE = 'WikiPage'
|
7
|
+
CONTENT_TYPE_ATTACHMENT = 'Attachment'
|
8
|
+
CONTENT_TYPE_CONTEXT_MODULE_SUB_HEADER = 'ContextModuleSubHeader'
|
9
|
+
CONTENT_TYPE_DISCUSSION_TOPIC = 'DiscussionTopic'
|
10
|
+
CONTENT_TYPE_ASSIGNMENT = 'Assignment'
|
11
|
+
CONTENT_TYPE_CONTEXT_EXTERNAL_TOOL = 'ContextExternalTool'
|
12
|
+
CONTENT_TYPE_QUIZ = 'Quizzes::Quiz'
|
13
|
+
CONTENT_TYPE_EXTERNAL_URL = 'ExternalUrl'
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module CanvasCc::CanvasCC::Models
|
2
|
+
class Page < CanvasCc::CanvasCC::Models::Resource
|
3
|
+
|
4
|
+
PAGE_ID_POSTFIX = '_PAGE'
|
5
|
+
WIKI_CONTENT = 'wiki_content'
|
6
|
+
BOOK_PATH = WIKI_CONTENT + '/books'
|
7
|
+
|
8
|
+
EDITING_ROLE_TEACHER = 'teachers'
|
9
|
+
|
10
|
+
MAX_URL_LENGTH = 80
|
11
|
+
|
12
|
+
attr_accessor :workflow_state, :editing_roles, :body, :title
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
super
|
16
|
+
@type = WEB_CONTENT_TYPE
|
17
|
+
end
|
18
|
+
|
19
|
+
def identifier=(identifier)
|
20
|
+
@identifier = identifier
|
21
|
+
end
|
22
|
+
|
23
|
+
def identifier
|
24
|
+
@identifier
|
25
|
+
end
|
26
|
+
|
27
|
+
def page_name= name
|
28
|
+
@title = name
|
29
|
+
@href = "#{WIKI_CONTENT}/#{self.class.convert_name_to_url(name)}.html"
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.convert_name_to_url(name)
|
33
|
+
url = CGI::escape(name.downcase.gsub(/\s/, '-').gsub('.', 'dot'))
|
34
|
+
if url.length > MAX_URL_LENGTH
|
35
|
+
url = url[0,MAX_URL_LENGTH][/.{0,#{MAX_URL_LENGTH}}/mu]
|
36
|
+
end
|
37
|
+
url
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module CanvasCc::CanvasCC::Models
|
2
|
+
class Question
|
3
|
+
QTI_META_ATTRIBUTES = [:question_type, :points_possible, :assessment_question_identifierref]
|
4
|
+
STANDARD_QUESTION_TYPES = ['essay_question', 'fill_in_multiple_blanks_question',
|
5
|
+
'multiple_choice_question', 'multiple_answers_question',
|
6
|
+
'short_answer_question', 'text_only_question', 'true_false_question']
|
7
|
+
|
8
|
+
attr_accessor :identifier, :original_identifier, :title, :material, :answers, :general_feedback, *QTI_META_ATTRIBUTES
|
9
|
+
|
10
|
+
@@subclasses = {}
|
11
|
+
|
12
|
+
def self.create(type)
|
13
|
+
q = if STANDARD_QUESTION_TYPES.include?(type)
|
14
|
+
self.new
|
15
|
+
elsif c = @@subclasses[type]
|
16
|
+
c.new
|
17
|
+
else
|
18
|
+
raise "Unknown question type: #{type}"
|
19
|
+
end
|
20
|
+
q.question_type = type
|
21
|
+
q
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.register_question_type(name)
|
25
|
+
@@subclasses[name] = self
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
@answers = []
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
require_relative 'calculated_question'
|
35
|
+
require_relative 'matching_question'
|
36
|
+
require_relative 'multiple_dropdowns_question'
|
37
|
+
require_relative 'numerical_question'
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module CanvasCc::CanvasCC::Models
|
2
|
+
class QuestionBank
|
3
|
+
|
4
|
+
attr_accessor :identifier, :title, :questions, :question_groups, :random_question_references, :original_id, :parent_id
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@questions = []
|
8
|
+
@question_groups = []
|
9
|
+
@random_question_references = []
|
10
|
+
end
|
11
|
+
|
12
|
+
LAR_TYPE = 'associatedcontent/imscc_xmlv1p1/learning-application-resource'
|
13
|
+
ASSESSMENT_NON_CC_FOLDER = 'non_cc_assessments'
|
14
|
+
|
15
|
+
def resources
|
16
|
+
[question_bank_resource]
|
17
|
+
end
|
18
|
+
|
19
|
+
def question_bank_resource
|
20
|
+
resource = CanvasCc::CanvasCC::Models::Resource.new
|
21
|
+
resource.identifier = @identifier
|
22
|
+
resource.href = "#{ASSESSMENT_NON_CC_FOLDER}/#{resource.identifier}.xml.qti"
|
23
|
+
resource.type = LAR_TYPE
|
24
|
+
resource.files = [resource.href]
|
25
|
+
resource
|
26
|
+
end
|
27
|
+
|
28
|
+
# recursively find all banks that belong to this one
|
29
|
+
def find_children_banks(all_banks, visited_banks=[])
|
30
|
+
visited_banks << self
|
31
|
+
children = []
|
32
|
+
sub_children = []
|
33
|
+
all_banks.each do |bank|
|
34
|
+
children << bank if bank.parent_id && bank.parent_id == self.original_id && !visited_banks.include?(bank)
|
35
|
+
end
|
36
|
+
children.each do |child|
|
37
|
+
sub_children += child.find_children_banks(all_banks, visited_banks)
|
38
|
+
end
|
39
|
+
return children + sub_children
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module CanvasCc::CanvasCC::Models
|
2
|
+
class Resource
|
3
|
+
|
4
|
+
WEB_CONTENT_TYPE = 'webcontent'
|
5
|
+
|
6
|
+
attr_accessor :files, :href, :type, :dependencies, :identifier
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@files = []
|
10
|
+
@dependencies = []
|
11
|
+
@ident_postfix = ''
|
12
|
+
end
|
13
|
+
|
14
|
+
def attributes
|
15
|
+
{
|
16
|
+
href: href,
|
17
|
+
type: type,
|
18
|
+
identifier: identifier
|
19
|
+
}.delete_if { |_, v| v.nil? }
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module CanvasCc::CanvasCC
|
2
|
+
class ModuleMetaWriter
|
3
|
+
MODULE_META_FILE = 'module_meta.xml'
|
4
|
+
|
5
|
+
def initialize(work_dir, *canvas_modules)
|
6
|
+
@work_dir = work_dir
|
7
|
+
@canvas_modules = canvas_modules
|
8
|
+
end
|
9
|
+
|
10
|
+
def write
|
11
|
+
xml = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
|
12
|
+
xml.modules(
|
13
|
+
'xmlns' => 'http://canvas.instructure.com/xsd/cccv1p0',
|
14
|
+
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
|
15
|
+
'xsi:schemaLocation' => 'http://canvas.instructure.com/xsd/cccv1p0 http://canvas.instructure.com/xsd/cccv1p0.xsd'
|
16
|
+
) { |xml|
|
17
|
+
@canvas_modules.each_with_index { |mod, position| canvas_module(mod, xml, position) }
|
18
|
+
}
|
19
|
+
end.to_xml
|
20
|
+
File.open(File.join(@work_dir, CanvasCc::CanvasCC::CartridgeCreator::COURSE_SETTINGS_DIR, MODULE_META_FILE), 'w') { |f| f.write(xml) }
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def canvas_module(mod, xml, position)
|
26
|
+
xml.module('identifier' => mod.identifier) {
|
27
|
+
xml.title mod.title
|
28
|
+
xml.workflow_state mod.workflow_state
|
29
|
+
xml.position(position)
|
30
|
+
xml.items { |xml|
|
31
|
+
add_module_items_to_xml(mod.module_items, xml)
|
32
|
+
}
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def add_module_items_to_xml(module_items, xml)
|
37
|
+
module_items.each_with_index do |item, position|
|
38
|
+
xml.item('identifier' => item.identifier) { |xml|
|
39
|
+
xml.content_type(item.content_type)
|
40
|
+
xml.workflow_state(item.workflow_state)
|
41
|
+
xml.title(item.title)
|
42
|
+
xml.position(position)
|
43
|
+
xml.new_tab(item.new_tab) if item.new_tab
|
44
|
+
xml.indent(item.indent)
|
45
|
+
xml.identifierref(item.identifierref) if item.identifierref
|
46
|
+
xml.url(item.url) if item.url
|
47
|
+
}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module CanvasCc::CanvasCC
|
2
|
+
class MultipleAnswersQuestionWriter < QuestionWriter
|
3
|
+
register_writer_type 'multiple_answers_question'
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
def self.write_responses(presentation_node, question)
|
8
|
+
presentation_node.response_lid(:ident => 'response1', :rcardinality => 'Multiple') do |response_node|
|
9
|
+
response_node.render_choice do |choice_node|
|
10
|
+
question.answers.each do |answer|
|
11
|
+
choice_node.response_label(:ident => answer.id) do |label_node|
|
12
|
+
label_node.material do |material_node|
|
13
|
+
material_node.mattext answer.answer_text, :texttype => 'text/html'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.write_response_conditions(processing_node, question)
|
22
|
+
return unless question.answers.count > 0
|
23
|
+
processing_node.respcondition(:continue => 'No') do |condition_node|
|
24
|
+
condition_node.conditionvar do |var_node|
|
25
|
+
var_node.and do |and_node|
|
26
|
+
question.answers.each do |answer|
|
27
|
+
if answer.fraction.to_f > 0
|
28
|
+
and_node.varequal answer.id, :respident => 'response1'
|
29
|
+
else
|
30
|
+
and_node.not do |not_node|
|
31
|
+
not_node.varequal answer.id, :respident => 'response1'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
condition_node.setvar('100', :varname => 'SCORE', :action => 'Set')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|