ims-lti 1.2.4 → 2.0.0.beta.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.
- checksums.yaml +5 -5
- data/Changelog.txt +0 -0
- data/LICENSE.txt +22 -0
- data/README.md +15 -103
- data/lib/ims.rb +3 -1
- data/lib/ims/lti.rb +6 -64
- data/lib/ims/lti/converters.rb +5 -0
- data/lib/ims/lti/converters/time_json_converter.rb +13 -0
- data/lib/ims/lti/models.rb +29 -0
- data/lib/ims/lti/models/base_url_choice.rb +15 -0
- data/lib/ims/lti/models/base_url_selector.rb +5 -0
- data/lib/ims/lti/models/contact.rb +5 -0
- data/lib/ims/lti/models/icon_endpoint.rb +5 -0
- data/lib/ims/lti/models/icon_info.rb +6 -0
- data/lib/ims/lti/models/localized_name.rb +11 -0
- data/lib/ims/lti/models/localized_text.rb +11 -0
- data/lib/ims/lti/models/lti_model.rb +169 -0
- data/lib/ims/lti/models/message_handler.rb +6 -0
- data/lib/ims/lti/models/messages.rb +6 -0
- data/lib/ims/lti/models/messages/basic_lti_launch_request.rb +8 -0
- data/lib/ims/lti/models/messages/message.rb +43 -0
- data/lib/ims/lti/models/messages/registration_request.rb +17 -0
- data/lib/ims/lti/models/parameter.rb +5 -0
- data/lib/ims/lti/models/product_family.rb +8 -0
- data/lib/ims/lti/models/product_info.rb +19 -0
- data/lib/ims/lti/models/product_instance.rb +10 -0
- data/lib/ims/lti/models/resource_handler.rb +18 -0
- data/lib/ims/lti/models/resource_type.rb +6 -0
- data/lib/ims/lti/models/rest_service.rb +14 -0
- data/lib/ims/lti/models/rest_service_profile.rb +7 -0
- data/lib/ims/lti/models/security_contract.rb +9 -0
- data/lib/ims/lti/models/service_owner.rb +8 -0
- data/lib/ims/lti/models/service_provider.rb +11 -0
- data/lib/ims/lti/models/tool_consumer_profile.rb +20 -0
- data/lib/ims/lti/models/tool_profile.rb +22 -0
- data/lib/ims/lti/models/tool_proxy.rb +11 -0
- data/lib/ims/lti/models/vendor.rb +28 -0
- data/lib/ims/lti/services.rb +5 -0
- data/lib/ims/lti/services/message_service.rb +40 -0
- data/lib/ims/lti/version.rb +5 -0
- metadata +68 -52
- data/Changelog +0 -54
- data/LICENSE +0 -18
- data/lib/ims/lti/deprecated_role_checks.rb +0 -52
- data/lib/ims/lti/extensions.rb +0 -45
- data/lib/ims/lti/extensions/canvas.rb +0 -122
- data/lib/ims/lti/extensions/content.rb +0 -209
- data/lib/ims/lti/extensions/outcome_data.rb +0 -216
- data/lib/ims/lti/launch_params.rb +0 -166
- data/lib/ims/lti/outcome_request.rb +0 -225
- data/lib/ims/lti/outcome_response.rb +0 -166
- data/lib/ims/lti/request_validator.rb +0 -56
- data/lib/ims/lti/role_checks.rb +0 -101
- data/lib/ims/lti/tool_base.rb +0 -29
- data/lib/ims/lti/tool_config.rb +0 -231
- data/lib/ims/lti/tool_consumer.rb +0 -86
- data/lib/ims/lti/tool_provider.rb +0 -143
@@ -1,166 +0,0 @@
|
|
1
|
-
module IMS::LTI
|
2
|
-
# Class for consuming/generating LTI Outcome Responses
|
3
|
-
#
|
4
|
-
# Response documentation: http://www.imsglobal.org/lti/v1p1pd/ltiIMGv1p1pd.html#_Toc309649691
|
5
|
-
#
|
6
|
-
# Error code documentation: http://www.imsglobal.org/gws/gwsv1p0/imsgws_baseProfv1p0.html#1639667
|
7
|
-
#
|
8
|
-
# This class can be used by both Tool Providers and Tool Consumers. Each will
|
9
|
-
# use it a bit differently. The Tool Provider will use it parse the result of
|
10
|
-
# an OutcomeRequest to the Tool Consumer. A Tool Consumer will use it generate
|
11
|
-
# proper response XML to send back to a Tool Provider
|
12
|
-
#
|
13
|
-
# === Tool Provider Usage
|
14
|
-
# An OutcomeResponse will generally be created when POSTing an OutcomeRequest
|
15
|
-
# through a configured ToolProvider. See the ToolProvider documentation for
|
16
|
-
# typical usage.
|
17
|
-
#
|
18
|
-
# === Tool Consumer Usage
|
19
|
-
# When an outcome request is sent from a Tool Provider the body of the request
|
20
|
-
# is XML. This class parses that XML and provides a simple interface for
|
21
|
-
# accessing the information in the request. Typical usage would be:
|
22
|
-
#
|
23
|
-
# # create a new response and set the appropriate values
|
24
|
-
# res = IMS::LTI::OutcomeResponse.new
|
25
|
-
# res.message_ref_identifier = outcome_request.message_identifier
|
26
|
-
# res.operation = outcome_request.operation
|
27
|
-
# res.code_major = 'success'
|
28
|
-
# res.severity = 'status'
|
29
|
-
#
|
30
|
-
# # set a description (optional) and other information based on the type of response
|
31
|
-
# if outcome_request.replace_request?
|
32
|
-
# res.description = "Your old score of 0 has been replaced with #{outcome_request.score}"
|
33
|
-
# elsif outcome_request.read_request?
|
34
|
-
# res.description = "You score is 50"
|
35
|
-
# res.score = 50
|
36
|
-
# elsif outcome_request.delete_request?
|
37
|
-
# res.description = "You score has been cleared"
|
38
|
-
# else
|
39
|
-
# res.code_major = 'unsupported'
|
40
|
-
# res.severity = 'status'
|
41
|
-
# res.description = "#{outcome_request.operation} is not supported"
|
42
|
-
# end
|
43
|
-
#
|
44
|
-
# # the generated xml is returned to the Tool Provider
|
45
|
-
# res.generate_response_xml
|
46
|
-
#
|
47
|
-
class OutcomeResponse
|
48
|
-
include IMS::LTI::Extensions::Base
|
49
|
-
|
50
|
-
attr_accessor :request_type, :score, :message_identifier, :response_code,
|
51
|
-
:post_response, :code_major, :severity, :description, :operation,
|
52
|
-
:message_ref_identifier
|
53
|
-
|
54
|
-
CODE_MAJOR_CODES = %w{success processing failure unsupported}
|
55
|
-
SEVERITY_CODES = %w{status warning error}
|
56
|
-
|
57
|
-
# Create a new OutcomeResponse
|
58
|
-
#
|
59
|
-
# @param opts [Hash] initialization hash
|
60
|
-
def initialize(opts={})
|
61
|
-
opts.each_pair do |key, val|
|
62
|
-
self.send("#{key}=", val) if self.respond_to?("#{key}=")
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
# Convenience method for creating a new OutcomeResponse from a response object
|
67
|
-
#
|
68
|
-
# req = IMS::LTI::OutcomeResponse.from_post_response(response)
|
69
|
-
def self.from_post_response(post_response)
|
70
|
-
response = OutcomeResponse.new
|
71
|
-
response.process_post_response(post_response)
|
72
|
-
end
|
73
|
-
|
74
|
-
def process_post_response(post_response)
|
75
|
-
self.post_response = post_response
|
76
|
-
self.response_code = post_response.code
|
77
|
-
xml = post_response.body
|
78
|
-
self.process_xml(xml)
|
79
|
-
self
|
80
|
-
end
|
81
|
-
|
82
|
-
def success?
|
83
|
-
@code_major == 'success'
|
84
|
-
end
|
85
|
-
|
86
|
-
def processing?
|
87
|
-
@code_major == 'processing'
|
88
|
-
end
|
89
|
-
|
90
|
-
def failure?
|
91
|
-
@code_major == 'failure'
|
92
|
-
end
|
93
|
-
|
94
|
-
def unsupported?
|
95
|
-
@code_major == 'unsupported'
|
96
|
-
end
|
97
|
-
|
98
|
-
def has_warning?
|
99
|
-
@severity == 'warning'
|
100
|
-
end
|
101
|
-
|
102
|
-
def has_error?
|
103
|
-
@severity == 'error'
|
104
|
-
end
|
105
|
-
|
106
|
-
# Parse Outcome Response data from XML
|
107
|
-
def process_xml(xml)
|
108
|
-
begin
|
109
|
-
doc = REXML::Document.new xml
|
110
|
-
rescue => e
|
111
|
-
raise IMS::LTI::XMLParseError, "#{e}\nOriginal xml: '#{xml}'"
|
112
|
-
end
|
113
|
-
@message_identifier = doc.text("//imsx_statusInfo/imsx_messageIdentifier").to_s
|
114
|
-
@code_major = doc.text("//imsx_statusInfo/imsx_codeMajor")
|
115
|
-
@code_major.downcase! if @code_major
|
116
|
-
@severity = doc.text("//imsx_statusInfo/imsx_severity")
|
117
|
-
@severity.downcase! if @severity
|
118
|
-
@description = doc.text("//imsx_statusInfo/imsx_description")
|
119
|
-
@description = @description.to_s if @description
|
120
|
-
@message_ref_identifier = doc.text("//imsx_statusInfo/imsx_messageRefIdentifier")
|
121
|
-
@operation = doc.text("//imsx_statusInfo/imsx_operationRefIdentifier")
|
122
|
-
@score = doc.text("//readResultResponse//resultScore/textString")
|
123
|
-
@score = @score.to_s if @score
|
124
|
-
end
|
125
|
-
|
126
|
-
# Generate XML based on the current configuration
|
127
|
-
# @return [String] The response xml
|
128
|
-
def generate_response_xml
|
129
|
-
builder = Builder::XmlMarkup.new
|
130
|
-
builder.instruct!
|
131
|
-
|
132
|
-
builder.imsx_POXEnvelopeResponse("xmlns" => "http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0") do |env|
|
133
|
-
env.imsx_POXHeader do |header|
|
134
|
-
header.imsx_POXResponseHeaderInfo do |info|
|
135
|
-
info.imsx_version "V1.0"
|
136
|
-
info.imsx_messageIdentifier @message_identifier || IMS::LTI::generate_identifier
|
137
|
-
info.imsx_statusInfo do |status|
|
138
|
-
status.imsx_codeMajor @code_major
|
139
|
-
status.imsx_severity @severity
|
140
|
-
status.imsx_description @description
|
141
|
-
status.imsx_messageRefIdentifier @message_ref_identifier
|
142
|
-
status.imsx_operationRefIdentifier @operation
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end #/header
|
146
|
-
env.imsx_POXBody do |body|
|
147
|
-
unless unsupported?
|
148
|
-
if @operation == OutcomeRequest::READ_REQUEST
|
149
|
-
body.tag!(@operation + 'Response') do |request|
|
150
|
-
request.result do |res|
|
151
|
-
res.resultScore do |res_score|
|
152
|
-
res_score.language "en" # 'en' represents the format of the number
|
153
|
-
res_score.textString @score.to_s
|
154
|
-
end
|
155
|
-
end #/result
|
156
|
-
end
|
157
|
-
else
|
158
|
-
body.tag!(@operation + 'Response')
|
159
|
-
end #/operationResponse
|
160
|
-
end
|
161
|
-
end #/body
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
end
|
166
|
-
end
|
@@ -1,56 +0,0 @@
|
|
1
|
-
module IMS::LTI
|
2
|
-
# A mixin for OAuth request validation
|
3
|
-
module RequestValidator
|
4
|
-
|
5
|
-
attr_reader :oauth_signature_validator
|
6
|
-
|
7
|
-
# Validates and OAuth request using the OAuth Gem - https://github.com/oauth/oauth-ruby
|
8
|
-
#
|
9
|
-
# To validate the OAuth signatures you need to require the appropriate
|
10
|
-
# request proxy for your application. For example:
|
11
|
-
#
|
12
|
-
# # For a sinatra app:
|
13
|
-
# require 'oauth/request_proxy/rack_request'
|
14
|
-
#
|
15
|
-
# # For a rails app:
|
16
|
-
# require 'oauth/request_proxy/action_controller_request'
|
17
|
-
# @return [Bool] Whether the request was valid
|
18
|
-
def valid_request?(request, handle_error=true)
|
19
|
-
begin
|
20
|
-
@oauth_signature_validator = OAuth::Signature.build(request, :consumer_secret => @consumer_secret)
|
21
|
-
@oauth_signature_validator.verify() or raise OAuth::Unauthorized.new(request)
|
22
|
-
true
|
23
|
-
rescue OAuth::Signature::UnknownSignatureMethod
|
24
|
-
if handle_error
|
25
|
-
false
|
26
|
-
else
|
27
|
-
raise $!
|
28
|
-
end
|
29
|
-
rescue OAuth::Unauthorized
|
30
|
-
if handle_error
|
31
|
-
false
|
32
|
-
else
|
33
|
-
raise OAuth::Unauthorized.new(request)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
# Check whether the OAuth-signed request is valid and throw error if not
|
39
|
-
#
|
40
|
-
# @return [Bool] Whether the request was valid
|
41
|
-
def valid_request!(request)
|
42
|
-
valid_request?(request, false)
|
43
|
-
end
|
44
|
-
|
45
|
-
# convenience method for getting the oauth nonce from the request
|
46
|
-
def request_oauth_nonce
|
47
|
-
@oauth_signature_validator && @oauth_signature_validator.request.oauth_nonce
|
48
|
-
end
|
49
|
-
|
50
|
-
# convenience method for getting the oauth timestamp from the request
|
51
|
-
def request_oauth_timestamp
|
52
|
-
@oauth_signature_validator && @oauth_signature_validator.request.oauth_timestamp
|
53
|
-
end
|
54
|
-
|
55
|
-
end
|
56
|
-
end
|
data/lib/ims/lti/role_checks.rb
DELETED
@@ -1,101 +0,0 @@
|
|
1
|
-
module IMS::LTI
|
2
|
-
# Some convenience methods for the most used roles
|
3
|
-
# Take care when using context_ helpers, as the context of an LTI launch
|
4
|
-
# determines the meaning of that role. For example, if the context is an
|
5
|
-
# institution context instead of a course context, then the short role of
|
6
|
-
# "Instructor" means they are a teacher at the institution, but not necessarily
|
7
|
-
# of the course you're working in.
|
8
|
-
#
|
9
|
-
# Also note that these only check for the base roles. So, asking context_student?
|
10
|
-
# only matches `urn:lti:role:ims/lis/Learner`, not `urn:lti:role:ims/lis/Learner/NonCreditLearner`
|
11
|
-
# If you make use of the more specific roles you'll need to ask specifically for those:
|
12
|
-
# @tool_provider.has_exact_role?("urn:lti:role:ims/lis/Learner/NonCreditLearner")
|
13
|
-
# Or you can use `has_base_role?`
|
14
|
-
module RoleChecks
|
15
|
-
|
16
|
-
# Check whether the Launch Parameters have a given role
|
17
|
-
def has_exact_role?(role)
|
18
|
-
role = role.downcase
|
19
|
-
@roles && @roles.any? { |r| r.downcase == role }
|
20
|
-
end
|
21
|
-
|
22
|
-
# Check whether the Launch Parameters have a given role ignoring
|
23
|
-
# sub roles. So asking:
|
24
|
-
# @tool_provider.has_base_role?("urn:lti:role:ims/lis/Instructor/")
|
25
|
-
# will return true if the role is `urn:lti:role:ims/lis/Instructor/GuestInstructor`
|
26
|
-
def has_base_role?(role)
|
27
|
-
role = role.downcase
|
28
|
-
@roles && @roles.any? { |r| r.downcase.start_with?(role) }
|
29
|
-
end
|
30
|
-
|
31
|
-
# Convenience method for checking if the user is the system administrator of the TC
|
32
|
-
def system_administrator?
|
33
|
-
has_exact_role?('urn:lti:sysrole:ims/lis/SysAdmin') ||
|
34
|
-
has_exact_role?('SysAdmin') ||
|
35
|
-
has_exact_role?('urn:lti:sysrole:ims/lis/Administrator')
|
36
|
-
end
|
37
|
-
|
38
|
-
### Institution-level roles
|
39
|
-
# Note, these only check if the role is explicitely an institution level role
|
40
|
-
# if the context of the LTI launch is the institution, the short names
|
41
|
-
# will apply, and you should use the context_x? helpers.
|
42
|
-
|
43
|
-
# Convenience method for checking if the user has 'student' or 'learner' roles at the institution
|
44
|
-
def institution_student?
|
45
|
-
has_exact_role?('urn:lti:instrole:ims/lis/Student') || has_exact_role?('urn:lti:instrole:ims/lis/Learner')
|
46
|
-
end
|
47
|
-
|
48
|
-
# Convenience method for checking if the user has 'Instructor' role at the institution
|
49
|
-
def institution_instructor?
|
50
|
-
has_exact_role?('urn:lti:instrole:ims/lis/Instructor')
|
51
|
-
end
|
52
|
-
|
53
|
-
# Convenience method for checking if the user has 'Administrator' role at the institution
|
54
|
-
def institution_admin?
|
55
|
-
has_exact_role?('urn:lti:instrole:ims/lis/Administrator')
|
56
|
-
end
|
57
|
-
|
58
|
-
|
59
|
-
### Context-level roles
|
60
|
-
# Note, the most common LTI context is a course, but that is not always the
|
61
|
-
# case. You should be aware of the context when using these helpers.
|
62
|
-
# The difference for the context_ helpers is that they check for the
|
63
|
-
# short version of the roles. So `Learner` and `urn:lti:role:ims/lis/Learner`
|
64
|
-
# are both valid.
|
65
|
-
|
66
|
-
# Convenience method for checking if the user has 'learner' role in the current launch context
|
67
|
-
def context_student?
|
68
|
-
has_exact_role?('Learner') || has_exact_role?('urn:lti:role:ims/lis/Learner')
|
69
|
-
end
|
70
|
-
|
71
|
-
# Convenience method for checking if the user has 'instructor' role in the current launch context
|
72
|
-
def context_instructor?
|
73
|
-
has_exact_role?('instructor') || has_exact_role?('urn:lti:role:ims/lis/Instructor')
|
74
|
-
end
|
75
|
-
|
76
|
-
# Convenience method for checking if the user has 'contentdeveloper' role in the current launch context
|
77
|
-
def context_content_developer?
|
78
|
-
has_exact_role?('ContentDeveloper') || has_exact_role?('urn:lti:role:ims/lis/ContentDeveloper')
|
79
|
-
end
|
80
|
-
|
81
|
-
# Convenience method for checking if the user has 'Mentor' role in the current launch context
|
82
|
-
def context_mentor?
|
83
|
-
has_exact_role?('Mentor') || has_exact_role?('urn:lti:role:ims/lis/Mentor')
|
84
|
-
end
|
85
|
-
|
86
|
-
# Convenience method for checking if the user has 'administrator' role in the current launch context
|
87
|
-
def context_admin?
|
88
|
-
has_exact_role?('Administrator') || has_exact_role?('urn:lti:role:ims/lis/Administrator')
|
89
|
-
end
|
90
|
-
|
91
|
-
# Convenience method for checking if the user has 'TeachingAssistant' role in the current launch context
|
92
|
-
def context_ta?
|
93
|
-
has_exact_role?('TeachingAssistant') || has_exact_role?('urn:lti:role:ims/lis/TeachingAssistant')
|
94
|
-
end
|
95
|
-
|
96
|
-
# Convenience method for checking if the user has 'Observer' role in the current launch context
|
97
|
-
def context_observer?
|
98
|
-
has_exact_role?('Observer') || has_exact_role?('urn:lti:instrole:ims/lis/Observer')
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
data/lib/ims/lti/tool_base.rb
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
module IMS::LTI
|
2
|
-
class ToolBase
|
3
|
-
include IMS::LTI::Extensions::Base
|
4
|
-
include IMS::LTI::LaunchParams
|
5
|
-
include IMS::LTI::RequestValidator
|
6
|
-
|
7
|
-
# OAuth credentials
|
8
|
-
attr_accessor :consumer_key, :consumer_secret
|
9
|
-
|
10
|
-
def initialize(consumer_key, consumer_secret, params={})
|
11
|
-
@consumer_key = consumer_key
|
12
|
-
@consumer_secret = consumer_secret
|
13
|
-
@custom_params = {}
|
14
|
-
@ext_params = {}
|
15
|
-
@non_spec_params = {}
|
16
|
-
process_params(params)
|
17
|
-
end
|
18
|
-
|
19
|
-
# Convenience method for doing oauth signed requests to services that
|
20
|
-
# aren't supported by this library
|
21
|
-
def post_service_request(url, content_type, body)
|
22
|
-
IMS::LTI::post_service_request(@consumer_key,
|
23
|
-
@consumer_secret,
|
24
|
-
url,
|
25
|
-
content_type,
|
26
|
-
body)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
data/lib/ims/lti/tool_config.rb
DELETED
@@ -1,231 +0,0 @@
|
|
1
|
-
module IMS::LTI
|
2
|
-
# Class used to represent an LTI configuration
|
3
|
-
#
|
4
|
-
# It can create and read the Common Cartridge XML representation of LTI links
|
5
|
-
# as described here: http://www.imsglobal.org/LTI/v1p1pd/ltiIMGv1p1pd.html#_Toc309649689
|
6
|
-
#
|
7
|
-
# == Usage
|
8
|
-
# To generate an XML configuration:
|
9
|
-
#
|
10
|
-
# # Create a config object and set some options
|
11
|
-
# tc = IMS::LTI::ToolConfig.new(:title => "Example Sinatra Tool Provider", :launch_url => url)
|
12
|
-
# tc.description = "This example LTI Tool Provider supports LIS Outcome pass-back."
|
13
|
-
#
|
14
|
-
# # generate the XML
|
15
|
-
# tc.to_xml
|
16
|
-
#
|
17
|
-
# Or to create a config object from an XML String:
|
18
|
-
#
|
19
|
-
# tc = IMS::LTI::ToolConfig.create_from_xml(xml)
|
20
|
-
class ToolConfig
|
21
|
-
attr_reader :custom_params, :extensions
|
22
|
-
|
23
|
-
attr_accessor :title, :description, :launch_url, :secure_launch_url,
|
24
|
-
:icon, :secure_icon, :cartridge_bundle, :cartridge_icon,
|
25
|
-
:vendor_code, :vendor_name, :vendor_description, :vendor_url,
|
26
|
-
:vendor_contact_email, :vendor_contact_name
|
27
|
-
|
28
|
-
# Create a new ToolConfig with the given options
|
29
|
-
#
|
30
|
-
# @param opts [Hash] The initial options for the ToolConfig
|
31
|
-
def initialize(opts={})
|
32
|
-
@custom_params = opts.delete("custom_params") || {}
|
33
|
-
@extensions = opts.delete("extensions") || {}
|
34
|
-
|
35
|
-
opts.each_pair do |key, val|
|
36
|
-
self.send("#{key}=", val) if self.respond_to?("#{key}=")
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
# Create a ToolConfig from the given XML
|
41
|
-
#
|
42
|
-
# @param xml [String]
|
43
|
-
def self.create_from_xml(xml)
|
44
|
-
tc = ToolConfig.new
|
45
|
-
tc.process_xml(xml)
|
46
|
-
|
47
|
-
tc
|
48
|
-
end
|
49
|
-
|
50
|
-
def set_custom_param(key, val)
|
51
|
-
@custom_params[key] = val
|
52
|
-
end
|
53
|
-
|
54
|
-
def get_custom_param(key)
|
55
|
-
@custom_params[key]
|
56
|
-
end
|
57
|
-
|
58
|
-
# Set the extension parameters for a specific vendor
|
59
|
-
#
|
60
|
-
# @param ext_key [String] The identifier for the vendor-specific parameters
|
61
|
-
# @param ext_params [Hash] The parameters, this is allowed to be two-levels deep
|
62
|
-
def set_ext_params(ext_key, ext_params)
|
63
|
-
raise ArgumentError unless ext_params.is_a?(Hash)
|
64
|
-
@extensions[ext_key] = ext_params
|
65
|
-
end
|
66
|
-
|
67
|
-
def get_ext_params(ext_key)
|
68
|
-
@extensions[ext_key]
|
69
|
-
end
|
70
|
-
|
71
|
-
def set_ext_param(ext_key, param_key, val)
|
72
|
-
@extensions[ext_key] ||= {}
|
73
|
-
@extensions[ext_key][param_key] = val
|
74
|
-
end
|
75
|
-
|
76
|
-
def get_ext_param(ext_key, param_key)
|
77
|
-
@extensions[ext_key] && @extensions[ext_key][param_key]
|
78
|
-
end
|
79
|
-
|
80
|
-
# Namespaces used for parsing configuration XML
|
81
|
-
LTI_NAMESPACES = {
|
82
|
-
"xmlns" => 'http://www.imsglobal.org/xsd/imslticc_v1p0',
|
83
|
-
"blti" => 'http://www.imsglobal.org/xsd/imsbasiclti_v1p0',
|
84
|
-
"lticm" => 'http://www.imsglobal.org/xsd/imslticm_v1p0',
|
85
|
-
"lticp" => 'http://www.imsglobal.org/xsd/imslticp_v1p0',
|
86
|
-
}
|
87
|
-
|
88
|
-
# Parse tool configuration data out of the Common Cartridge LTI link XML
|
89
|
-
def process_xml(xml)
|
90
|
-
doc = REXML::Document.new xml
|
91
|
-
if root = REXML::XPath.first(doc, 'xmlns:cartridge_basiclti_link')
|
92
|
-
@title = get_node_text(root, 'blti:title')
|
93
|
-
@description = get_node_text(root, 'blti:description')
|
94
|
-
@launch_url = get_node_text(root, 'blti:launch_url')
|
95
|
-
@secure_launch_url = get_node_text(root, 'blti:secure_launch_url')
|
96
|
-
@icon = get_node_text(root, 'blti:icon')
|
97
|
-
@secure_icon = get_node_text(root, 'blti:secure_icon')
|
98
|
-
@cartridge_bundle = get_node_att(root, 'xmlns:cartridge_bundle', 'identifierref')
|
99
|
-
@cartridge_icon = get_node_att(root, 'xmlns:cartridge_icon', 'identifierref')
|
100
|
-
|
101
|
-
if vendor = REXML::XPath.first(root, 'blti:vendor')
|
102
|
-
@vendor_code = get_node_text(vendor, 'lticp:code')
|
103
|
-
@vendor_description = get_node_text(vendor, 'lticp:description')
|
104
|
-
@vendor_name = get_node_text(vendor, 'lticp:name')
|
105
|
-
@vendor_url = get_node_text(vendor, 'lticp:url')
|
106
|
-
@vendor_contact_email = get_node_text(vendor, '//lticp:contact/lticp:email')
|
107
|
-
@vendor_contact_name = get_node_text(vendor, '//lticp:contact/lticp:name')
|
108
|
-
end
|
109
|
-
|
110
|
-
if custom = REXML::XPath.first(root, 'blti:custom', LTI_NAMESPACES)
|
111
|
-
set_properties(@custom_params, custom)
|
112
|
-
end
|
113
|
-
|
114
|
-
REXML::XPath.each(root, 'blti:extensions', LTI_NAMESPACES) do |vendor_ext_node|
|
115
|
-
platform = vendor_ext_node.attributes['platform']
|
116
|
-
properties = {}
|
117
|
-
set_properties(properties, vendor_ext_node)
|
118
|
-
set_options(properties, vendor_ext_node)
|
119
|
-
self.set_ext_params(platform, properties)
|
120
|
-
end
|
121
|
-
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
# Generate XML from the current settings
|
126
|
-
def to_xml(opts = {})
|
127
|
-
raise IMS::LTI::InvalidLTIConfigError, "A launch url is required for an LTI configuration." unless self.launch_url || self.secure_launch_url
|
128
|
-
|
129
|
-
builder = Builder::XmlMarkup.new(:indent => opts[:indent] || 0)
|
130
|
-
builder.instruct!
|
131
|
-
builder.cartridge_basiclti_link("xmlns" => "http://www.imsglobal.org/xsd/imslticc_v1p0",
|
132
|
-
"xmlns:blti" => 'http://www.imsglobal.org/xsd/imsbasiclti_v1p0',
|
133
|
-
"xmlns:lticm" => 'http://www.imsglobal.org/xsd/imslticm_v1p0',
|
134
|
-
"xmlns:lticp" => 'http://www.imsglobal.org/xsd/imslticp_v1p0',
|
135
|
-
"xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
|
136
|
-
"xsi:schemaLocation" => "http://www.imsglobal.org/xsd/imslticc_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticc_v1p0.xsd http://www.imsglobal.org/xsd/imsbasiclti_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imsbasiclti_v1p0p1.xsd http://www.imsglobal.org/xsd/imslticm_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticm_v1p0.xsd http://www.imsglobal.org/xsd/imslticp_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticp_v1p0.xsd"
|
137
|
-
) do |blti_node|
|
138
|
-
|
139
|
-
%w{title description launch_url secure_launch_url icon secure_icon}.each do |key|
|
140
|
-
blti_node.blti key.to_sym, self.send(key) if self.send(key)
|
141
|
-
end
|
142
|
-
|
143
|
-
vendor_keys = %w{name code description url}
|
144
|
-
if vendor_keys.any?{|k|self.send("vendor_#{k}")} || vendor_contact_email
|
145
|
-
blti_node.blti :vendor do |v_node|
|
146
|
-
vendor_keys.each do |key|
|
147
|
-
v_node.lticp key.to_sym, self.send("vendor_#{key}") if self.send("vendor_#{key}")
|
148
|
-
end
|
149
|
-
if vendor_contact_email
|
150
|
-
v_node.lticp :contact do |c_node|
|
151
|
-
c_node.lticp :name, vendor_contact_name
|
152
|
-
c_node.lticp :email, vendor_contact_email
|
153
|
-
end
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
if !@custom_params.empty?
|
159
|
-
blti_node.tag!("blti:custom") do |custom_node|
|
160
|
-
@custom_params.keys.sort.each do |key|
|
161
|
-
val = @custom_params[key]
|
162
|
-
custom_node.lticm :property, val, 'name' => key
|
163
|
-
end
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
if !@extensions.empty?
|
168
|
-
@extensions.keys.sort.each do |ext_platform|
|
169
|
-
ext_params = @extensions[ext_platform]
|
170
|
-
blti_node.blti(:extensions, :platform => ext_platform) do |ext_node|
|
171
|
-
ext_params.keys.sort.each do |key|
|
172
|
-
nest_xml(ext_node, key, ext_params[key])
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
blti_node.cartridge_bundle(:identifierref => @cartridge_bundle) if @cartridge_bundle
|
179
|
-
blti_node.cartridge_icon(:identifierref => @cartridge_icon) if @cartridge_icon
|
180
|
-
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
private
|
185
|
-
|
186
|
-
def nest_xml(ext_node, key, value)
|
187
|
-
if value.is_a?(Hash)
|
188
|
-
ext_node.lticm(:options, :name => key) do |type_node|
|
189
|
-
value.keys.sort.each do |sub_key|
|
190
|
-
nest_xml(type_node, sub_key, value[sub_key])
|
191
|
-
end
|
192
|
-
end
|
193
|
-
else
|
194
|
-
ext_node.lticm :property, value, 'name' => key
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
def get_node_text(node, path)
|
199
|
-
if val = REXML::XPath.first(node, path, LTI_NAMESPACES)
|
200
|
-
val.text
|
201
|
-
else
|
202
|
-
nil
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
|
-
def get_node_att(node, path, att)
|
207
|
-
if val = REXML::XPath.first(node, path, LTI_NAMESPACES)
|
208
|
-
val.attributes[att]
|
209
|
-
else
|
210
|
-
nil
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
def set_properties(hash, node)
|
215
|
-
REXML::XPath.each(node, 'lticm:property', LTI_NAMESPACES) do |prop|
|
216
|
-
hash[prop.attributes['name']] = prop.text
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
def set_options(hash, node)
|
221
|
-
REXML::XPath.each(node, 'lticm:options', LTI_NAMESPACES) do |options_node|
|
222
|
-
opt_name = options_node.attributes['name']
|
223
|
-
options = {}
|
224
|
-
set_properties(options, options_node)
|
225
|
-
set_options(options, options_node)
|
226
|
-
hash[opt_name] = options
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
end
|
231
|
-
end
|