jk-mil-ims-lti 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Changelog ADDED
@@ -0,0 +1,34 @@
1
+ 2013-04-24 Version 1.1.3
2
+
3
+ * Corrected lti_version launch parameter
4
+
5
+ 2012-09-05 Version 1.1.2
6
+
7
+ * Added better role checking and convenience methods
8
+
9
+ 2012-09-04 Version 1.1.1
10
+
11
+ * Added cdata value for outcome data extension
12
+
13
+ 2012-08-14 Version 1.1.0
14
+
15
+ * Added framework for LTI extensions
16
+ * Added LTI outcome data extension
17
+ * Fix tests reliant on random ordering
18
+ * Add rails 3 note to readme install docs
19
+ * Create Changelog
20
+
21
+
22
+ 2012-03-14 Version 1.0.2
23
+
24
+ * Refactor OAuth validation into its own module
25
+
26
+
27
+ 2012-03-13 Version 1.0.1
28
+
29
+ * Add gem dependencies to gemspec
30
+
31
+
32
+ 2012-03-11 Version 1.0.0
33
+
34
+ * Publish fully functional and operational IMS LTI library
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2012 Instructure
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to use,
6
+ copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
7
+ Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
17
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # IMS LTI
2
+
3
+ This ruby library is to help create Tool Providers and Tool Consumers for the
4
+ [IMS LTI standard](http://www.imsglobal.org/lti/index.html).
5
+
6
+ ## Installation
7
+ This is packaged as the `ims-lti` rubygem, so you can just add the dependency to
8
+ your Gemfile or install the gem on your system:
9
+
10
+ gem install ims-lti
11
+
12
+ To require the library in your project:
13
+
14
+ require 'ims/lti'
15
+
16
+ To validate the OAuth signatures you need to require the appropriate request
17
+ proxy for your application. For example:
18
+
19
+ # For a Sinatra or a Rails 3 app:
20
+ require 'oauth/request_proxy/rack_request'
21
+ # You also need to explicitly enable OAuth 1 support in the environment.rb or an initializer:
22
+ OAUTH_10_SUPPORT = true
23
+
24
+ # For a Rails 2.3 app:
25
+ require 'oauth/request_proxy/action_controller_request'
26
+
27
+ For further information see the [oauth-ruby](https://github.com/oauth/oauth-ruby) project.
28
+
29
+ ## Usage
30
+ This readme won't cover the LTI standard, just how to use the library. It will be
31
+ very helpful to read the [LTI documentation](http://www.imsglobal.org/lti/index.html)
32
+
33
+ In LTI there are Tool Providers (TP) and Tool Consumers (TC), this library is
34
+ useful for implementing both. Here is an overview of the communication process:
35
+ [LTI 1.1 Introduction](http://www.imsglobal.org/lti/v1p1pd/ltiIMGv1p1pd.html#_Toc309649680)
36
+
37
+ This library doesn't help you manage the consumer keys and secrets. The POST
38
+ headers/parameters will contain the `oauth_consumer_key` and your app can use
39
+ that to look up the appropriate `oauth_consumer_secret`.
40
+
41
+ Your app will also need to manage the OAuth nonce to make sure the same nonce
42
+ isn't used twice with the same timestamp. [Read the LTI documentation on OAuth](http://www.imsglobal.org/LTI/v1p1pd/ltiIMGv1p1pd.html#_Toc309649687).
43
+
44
+ ### Tool Provider
45
+ As a TP your app will receive a POST request with a bunch of
46
+ [LTI launch data](http://www.imsglobal.org/lti/v1p1pd/ltiIMGv1p1pd.html#_Toc309649684)
47
+ and it will be signed with OAuth using a key/secret that both the TP and TC share.
48
+ This is covered in the [LTI security model](http://www.imsglobal.org/lti/v1p1pd/ltiIMGv1p1pd.html#_Toc309649685)
49
+
50
+ Here is an example of a simple TP Sinatra app using this gem:
51
+ [LTI Tool Provider](https://github.com/instructure/lti_tool_provider_example)
52
+
53
+ Once you find the `oauth_consumer_secret` based on the `oauth_consumer_key` in
54
+ the request, you can initialize a `ToolProvider` object with them and the post parameters:
55
+
56
+ ```ruby
57
+ # Initialize TP object with OAuth creds and post parameters
58
+ provider = IMS::LTI::ToolProvider.new(consumer_key, consumer_secret, params)
59
+
60
+ # Verify OAuth signature by passing the request object
61
+ if provider.valid_request?(request)
62
+ # success
63
+ else
64
+ # handle invalid OAuth
65
+ end
66
+ ```
67
+
68
+ Once your TP object is initialized and verified you can load your tool. All of the
69
+ [launch data](http://www.imsglobal.org/lti/v1p1pd/ltiIMGv1p1pd.html#_Toc309649684)
70
+ is available in the TP object along with some convenience methods like `provider.username`
71
+ which will try to find the name from the 3 potential name launch data attributes.
72
+
73
+ #### Returning Results of a Quiz/Assignment
74
+ If your TP provides some kind of assessment service you can write grades back to
75
+ the TC. This is documented in the LTI docs [here](http://www.imsglobal.org/lti/v1p1pd/ltiIMGv1p1pd.html#_Toc309649690).
76
+
77
+ You can check whether the TC is expecting a grade write-back:
78
+
79
+ ```ruby
80
+ if provider.outcome_service?
81
+ # ready for grade write-back
82
+ else
83
+ # normal tool launch without grade write-back
84
+ end
85
+ ```
86
+
87
+ To write the grade back to the TC your tool will do a POST directly back to the
88
+ URL the TC passed in the launch data. You can use the TP object to do that for you:
89
+
90
+ ```ruby
91
+ # post the score to the TC, score should be a float >= 0.0 and <= 1.0
92
+ # this returns an OutcomeResponse object
93
+ response = provider.post_replace_result!(score)
94
+ if response.success?
95
+ # grade write worked
96
+ elsif response.processing?
97
+ elsif response.unsupported?
98
+ else
99
+ # failed
100
+ end
101
+ ```
102
+
103
+ You can see the error code documentation
104
+ [here](http://www.imsglobal.org/gws/gwsv1p0/imsgws_baseProfv1p0.html#1639667).
105
+
106
+ ### Tool Consumer
107
+ As a Tool Consumer your app will POST an OAuth-signed launch requests to TPs with the necessary
108
+ [LTI launch data](http://www.imsglobal.org/lti/v1p1pd/ltiIMGv1p1pd.html#_Toc309649684).
109
+ This is covered in the [LTI security model](http://www.imsglobal.org/lti/v1p1pd/ltiIMGv1p1pd.html#_Toc309649685)
110
+
111
+ Here is an example of a simple TC Sinatra app using this gem:
112
+ [LTI Tool Consumer](https://github.com/instructure/lti_tool_consumer_example)
@@ -0,0 +1,109 @@
1
+ module IMS::LTI
2
+ module Extensions
3
+ # Module that adds Canvas specific LTI extensions
4
+ #
5
+ # It adds convenience methods for generating common canvas use case LTI configurations
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
+ # # Extend the Canvas Tool config and add canvas related extensions
15
+ # tc.extend IMS::LTI::Extensions::Canvas::ToolConfig
16
+ # tc.homework_submission! 'http://someplace.com/homework', 'Find Homework'
17
+ #
18
+ # # generate the XML
19
+ # tc.to_xml
20
+ #
21
+ # Or to create a config object from an XML String:
22
+ #
23
+ # tc = IMS::LTI::ToolConfig.create_from_xml(xml)
24
+
25
+ module Canvas
26
+ module ToolConfig
27
+ PLATFORM = 'canvas.instructure.com'
28
+
29
+ # Canvas extension defaults
30
+ # These properties will cascade down to any options that are configured
31
+
32
+ def canvas_privacy_public!()
33
+ set_ext_param(PLATFORM, :privacy_level, 'public')
34
+ end
35
+
36
+ def canvas_privacy_name_only!()
37
+ set_ext_param(PLATFORM, :privacy_level, 'name_only')
38
+ end
39
+
40
+ def canvas_privacy_anonymous!()
41
+ set_ext_param(PLATFORM, :privacy_level, 'anonymous')
42
+ end
43
+
44
+ def canvas_domain!(domain)
45
+ set_ext_param(PLATFORM, :domain, domain)
46
+ end
47
+
48
+ def canvas_text!(text)
49
+ set_ext_param(PLATFORM, :text, text)
50
+ end
51
+
52
+ def canvas_icon_url!(icon_url)
53
+ set_ext_param(PLATFORM, :icon_url, icon_url)
54
+ end
55
+
56
+ def canvas_selector_dimensions!(width, height)
57
+ set_ext_param(PLATFORM, :selection_width, width)
58
+ set_ext_param(PLATFORM, :selection_height, height)
59
+ end
60
+
61
+ # Canvas options
62
+ # These configure canvas to expose the tool in various locations. Any properties that are set
63
+ # at this level will override the defaults for this launch of the tool
64
+
65
+ # Enables homework submissions via the tool
66
+ # Valid properties are url, text, selection_width, selection_height, enabled
67
+ def canvas_homework_submission!(params = {})
68
+ set_ext_param(PLATFORM, :homework_submission, params)
69
+ end
70
+
71
+ # Adds the tool to canvas' rich text editor
72
+ # Valid properties are url, icon_url, text, selection_width, selection_height, enabled
73
+ def canvas_editor_button!(params = {})
74
+ set_ext_param(PLATFORM, :editor_button, params)
75
+ end
76
+
77
+ # Adds the tool to canvas' rich text editor
78
+ # Valid properties are url, icon_url, text, selection_width, selection_height, enabled
79
+ def canvas_resource_selection!(params = {})
80
+ set_ext_param(PLATFORM, :resource_selection, params)
81
+ end
82
+
83
+ # Adds the tool to account level navigation in canvas
84
+ # Valid properties are url, text, enabled
85
+ def canvas_account_navigation!(params = {})
86
+ set_ext_param(PLATFORM, :account_navigation, params)
87
+ end
88
+
89
+ # Adds the tool to course level navigation in canvas
90
+ # Valid properties are url, text, visibility, default, enabled
91
+ # Visibility describes who will see the navigation element. Possible values are "admins", "members", and nil
92
+ # Default determines if it is on or off by default. Possible values are "admins", "members", and nil
93
+ def canvas_course_navigation!(params = {})
94
+ set_ext_param(PLATFORM, :course_navigation, params)
95
+ end
96
+
97
+ # Adds the tool to user level navigation in canvas
98
+ # Valid properties are url, text, enabled
99
+ def canvas_user_navigation!(params = {})
100
+ set_ext_param(PLATFORM, :user_navigation, params)
101
+ end
102
+
103
+ def get_canvas_param(param_key)
104
+ get_ext_param PLATFORM, param_key
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,209 @@
1
+ module IMS::LTI
2
+ module Extensions
3
+
4
+ # An LTI extension that adds support for content back to the consumer
5
+ #
6
+ # # Initialize TP object with OAuth creds and post parameters
7
+ # provider = IMS::LTI::ToolProvider.new(consumer_key, consumer_secret, params)
8
+ # # add extension
9
+ # provider.extend IMS::LTI::Extensions::Content::ToolProvider
10
+ #
11
+ # If the tool was launched as an content request and it supports the content extension
12
+ # you can redirect the user to the tool consumer using the return url helper methods.
13
+ # The tool consumer is then responsible for consuming the content.
14
+ #
15
+ # #Check if a certain response type is available
16
+ # if provider.accepts_url? do
17
+ # #Generate the URL for the user
18
+ # redirect provider.url_content_return_url(url)
19
+ # end
20
+ #
21
+ module Content
22
+ module ToolProvider
23
+ include IMS::LTI::Extensions::ExtensionBase
24
+ include Base
25
+
26
+ # a list of the supported outcome data types
27
+ def accepted_content_types
28
+ return @content_types if @content_types
29
+ @content_types = []
30
+ if val = @ext_params["content_return_types"]
31
+ @content_types = val.split(',').map {|i| i.to_sym}
32
+ end
33
+
34
+ @content_types
35
+ end
36
+
37
+ def accepted_file_extensions
38
+ return @file_extensions if @file_extensions
39
+ @file_extensions = []
40
+ if val = @ext_params["content_file_extensions"]
41
+ @file_extensions = val.split(',').map {|i| i.downcase.strip}
42
+ end
43
+
44
+ @file_extensions
45
+ end
46
+
47
+ def accepts_file?(file_name = nil)
48
+ accepted_content_types.include?(:file) &&
49
+ ( file_name.nil? ||
50
+ accepted_file_extensions.empty? ||
51
+ accepted_file_extensions.any?{|ext| file_name.downcase[/#{ext}$/]} )
52
+ end
53
+
54
+ def accepts_url?
55
+ accepted_content_types.include?(:url)
56
+ end
57
+
58
+ def accepts_lti_launch_url?
59
+ accepted_content_types.include?(:lti_launch_url)
60
+ end
61
+
62
+ def accepts_image_url?
63
+ accepted_content_types.include?(:image_url)
64
+ end
65
+
66
+ def accepts_iframe?
67
+ accepted_content_types.include?(:iframe)
68
+ end
69
+
70
+ def accepts_oembed?
71
+ accepted_content_types.include?(:oembed)
72
+ end
73
+
74
+ def content_intended_use
75
+ @ext_params["content_intended_use"].to_sym if @ext_params["content_intended_use"]
76
+ end
77
+
78
+ # check if the content extension is supported
79
+ def accepts_content?
80
+ !!@ext_params["content_return_types"]
81
+ end
82
+
83
+ # check if the consumer accepts a given type of content
84
+ def accepts_content_type?(content_type)
85
+ accepted_content_types.include? content_type.to_sym
86
+ end
87
+
88
+ #check the use of the content
89
+ def is_content_for? (intended_use)
90
+ content_intended_use == intended_use
91
+ end
92
+
93
+ def content_return_url
94
+ @ext_params["content_return_url"]
95
+ end
96
+
97
+ #generates the return url for file submissions
98
+ def file_content_return_url(url, text, content_type = nil)
99
+ url = CGI::escape(url)
100
+ text = CGI::escape(text)
101
+ content_type = CGI::escape(content_type) if content_type
102
+
103
+ return_url = "#{content_return_url}?return_type=file&url=#{url}&text=#{text}"
104
+ return_url = "#{return_url}&content_type=#{content_type}" if content_type
105
+
106
+ return return_url
107
+ end
108
+
109
+ #generates the return url for url submissions
110
+ def url_content_return_url(url, title = nil, text = 'link', target = '_blank')
111
+ url = CGI::escape(url)
112
+ text = CGI::escape(text)
113
+ target = CGI::escape(target)
114
+
115
+ return_url = "#{content_return_url}?return_type=url&url=#{url}&text=#{text}&target=#{target}"
116
+ return_url = "#{return_url}&title=#{CGI::escape(title)}" if title
117
+
118
+ return return_url
119
+ end
120
+
121
+ #generates the return url for lti launch submissions
122
+ def lti_launch_content_return_url(url, text='link', title=nil)
123
+ url = CGI::escape(url)
124
+ text = CGI::escape(text)
125
+
126
+ return_url = "#{content_return_url}?return_type=lti_launch_url&url=#{url}&text=#{text}"
127
+ return_url = "#{return_url}&title=#{CGI::escape(title)}" if title
128
+
129
+ return return_url
130
+ end
131
+
132
+ #generates the return url for image submissions
133
+ def image_content_return_url(url, width, height, alt = '')
134
+ url = CGI::escape(url)
135
+ width = CGI::escape(width.to_s)
136
+ height = CGI::escape(height.to_s)
137
+ alt = CGI::escape(alt)
138
+
139
+ "#{content_return_url}?return_type=image_url&url=#{url}&width=#{width}&height=#{height}&alt=#{alt}"
140
+ end
141
+
142
+ #generates the return url for iframe submissions
143
+ def iframe_content_return_url(url, width, height, title = nil)
144
+ url = CGI::escape(url)
145
+ width = CGI::escape(width.to_s)
146
+ height = CGI::escape(height.to_s)
147
+
148
+ return_url = "#{content_return_url}?return_type=iframe&url=#{url}&width=#{width}&height=#{height}"
149
+ return_url = "#{return_url}&title=#{CGI::escape(title)}" if title
150
+
151
+ return return_url
152
+ end
153
+
154
+ #generates the return url for oembed submissions
155
+ def oembed_content_return_url(url, endpoint)
156
+ url = CGI::escape(url)
157
+ endpoint = CGI::escape(endpoint)
158
+
159
+ "#{content_return_url}?return_type=oembed&url=#{url}&endpoint=#{endpoint}"
160
+ end
161
+ end
162
+
163
+ module ToolConsumer
164
+ include IMS::LTI::Extensions::ExtensionBase
165
+ include Base
166
+
167
+ # a list of the content types accepted
168
+ #
169
+ # tc.add_content_return_types=(['url', 'text'])
170
+ # tc.add_content_return_types=("url,text")
171
+ def content_return_types=(val)
172
+ val = val.join(',') if val.is_a? Array
173
+ set_ext_param('content_return_types', val)
174
+ end
175
+
176
+ # a comma-separated string of the supported outcome data types
177
+ def content_return_types
178
+ get_ext_param('content_return_types')
179
+ end
180
+
181
+ def content_intended_use=(val)
182
+ set_ext_param('content_intended_use', val)
183
+ end
184
+
185
+ def content_intended_use
186
+ get_ext_param('content_intended_use')
187
+ end
188
+
189
+ # convenience method for setting support for homework content
190
+ def support_homework_content!
191
+ self.content_intended_use = 'homework'
192
+ self.content_return_types = 'file,url'
193
+ end
194
+
195
+ # convenience method for setting support for embed content
196
+ def support_embed_content!
197
+ self.content_intended_use = 'embed'
198
+ self.content_return_types = 'oembed,lti_launch_url,url,image_url,iframe'
199
+ end
200
+
201
+ # convenience method for setting support for navigation content
202
+ def support_navigation_content!
203
+ self.content_intended_use = 'navigation'
204
+ self.content_return_types = 'lti_launch_url'
205
+ end
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,199 @@
1
+ module IMS::LTI
2
+ module Extensions
3
+
4
+ # An LTI extension that adds support for sending data back to the consumer
5
+ # in addition to the score.
6
+ #
7
+ # # Initialize TP object with OAuth creds and post parameters
8
+ # provider = IMS::LTI::ToolProvider.new(consumer_key, consumer_secret, params)
9
+ # # add extension
10
+ # provider.extend IMS::LTI::Extensions::OutcomeData::ToolProvider
11
+ #
12
+ # If the tool was launch as an outcome service and it supports the data extension
13
+ # you can POST a score to the TC.
14
+ # The POST calls all return an OutcomeResponse object which can be used to
15
+ # handle the response appropriately.
16
+ #
17
+ # # post the score to the TC, score should be a float >= 0.0 and <= 1.0
18
+ # # this returns an OutcomeResponse object
19
+ # if provider.accepts_outcome_text?
20
+ # response = provider.post_replace_result_with_data!(score, "text" => "submission text")
21
+ # else
22
+ # response = provider.post_replace_result!(score)
23
+ # end
24
+ # if response.success?
25
+ # # grade write worked
26
+ # elsif response.processing?
27
+ # elsif response.unsupported?
28
+ # else
29
+ # # failed
30
+ # end
31
+ #
32
+ # Needs Grading outcome +outcome_needs_grading+ is a flag to specify whether the submission should be
33
+ # needs_grading by the teacher It expects to be present or 'true' or 'false' value needs_grading by
34
+ # teacher or not, should set 'needs grading' in LMS if true or graded if false.
35
+ #
36
+ # provider.post_replace_result_with_data!(score,'needs_grading' => 'true','url' => outcome_url)
37
+ #
38
+ # Can also be used in conjunction with outcome_url to show url to a students state for grading
39
+ #
40
+ module OutcomeData
41
+
42
+ #IMS::LTI::Extensions::OutcomeData::ToolProvider
43
+ module Base
44
+ def outcome_request_extensions
45
+ super + [IMS::LTI::Extensions::OutcomeData::OutcomeRequest]
46
+ end
47
+ end
48
+
49
+ module ToolProvider
50
+ include IMS::LTI::Extensions::ExtensionBase
51
+ include Base
52
+
53
+ # a list of the supported outcome data types
54
+ def accepted_outcome_types
55
+ return @outcome_types if @outcome_types
56
+ @outcome_types = []
57
+ if val = @ext_params['outcome_data_values_accepted']
58
+ @outcome_types = val.split(',')
59
+ end
60
+
61
+ @outcome_types
62
+ end
63
+
64
+ # check if the outcome data extension is supported
65
+ def accepts_outcome_data?
66
+ !!@ext_params['outcome_data_values_accepted']
67
+ end
68
+
69
+ # check if the consumer accepts text as outcome data
70
+ def accepts_outcome_text?
71
+ accepted_outcome_types.member?('text')
72
+ end
73
+
74
+ # check if the consumer accepts a url as outcome data
75
+ def accepts_outcome_url?
76
+ accepted_outcome_types.member?('url')
77
+ end
78
+
79
+ # check if the consumer accepts a needs_grading as outcome data
80
+ def accepts_outcome_needs_grading?
81
+ accepted_outcome_types.member?('needs_grading')
82
+ end
83
+
84
+ # check if the consumer accepts a date as outcome data
85
+ #
86
+ # currently only supported by BrainHoney
87
+ def accepts_outcome_date?
88
+ accepted_outcome_types.member?('date')
89
+ end
90
+
91
+ # check if the consumer accepts a statusOfResult as outcome data
92
+ #
93
+ # currently only supported by BrainHoney
94
+ #
95
+ # Setting a Needs-Grading Status
96
+ #
97
+ # Tools that wish to indicate that the student's work needs grading in the tool may include in the XML the LIS-defined statusofResult element with the value tobemoderated:
98
+ # the value of this element should be set to tobemoderated
99
+ def accepts_outcome_status_of_result?
100
+ accepted_outcome_types.member?('statusofResult')
101
+ end
102
+
103
+ # POSTs the given score to the Tool Consumer with a replaceResult and
104
+ # adds the specified data. The data hash can have the keys "text", "cdata_text",
105
+ # "url" or "needs_grading" (needs_grading expects a true/false value)
106
+ #
107
+ # If both cdata_text and text are sent, cdata_text will be used
108
+ #
109
+ # Creates a new OutcomeRequest object and stores it in @outcome_requests
110
+ #
111
+ # @return [OutcomeResponse] the response from the Tool Consumer
112
+ def post_replace_result_with_data!(score, data={})
113
+ req = new_request
114
+ if data['cdata_text']
115
+ req.outcome_cdata_text = data['cdata_text']
116
+ elsif data['text']
117
+ req.outcome_text = data['text']
118
+ end
119
+ req.outcome_url = data['url'] if data['url']
120
+ req.outcome_needs_grading = data['needs_grading'] if data['needs_grading']
121
+ req.date = data['date'] if data['date']
122
+ req.status_of_result = data['statusofResult'] if data['statusofResult']
123
+ req.post_replace_result!(score)
124
+ end
125
+
126
+ end
127
+
128
+ module ToolConsumer
129
+ include IMS::LTI::Extensions::ExtensionBase
130
+ include Base
131
+
132
+ OUTCOME_DATA_TYPES = %w{text url needs_grading date status_of_result}
133
+
134
+ # a list of the outcome data types accepted, currently only 'url',
135
+ # 'text' and 'needs_grading' are valid
136
+ #
137
+ # tc.outcome_data_values_accepted(['url', 'text'])
138
+ # tc.outcome_data_valued_accepted("url,text")
139
+ def outcome_data_values_accepted=(val)
140
+ if val.is_a? Array
141
+ val = val.join(',')
142
+ end
143
+
144
+ set_ext_param('outcome_data_values_accepted', val)
145
+ end
146
+
147
+ # a comma-separated string of the supported outcome data types
148
+ def outcome_data_values_accepted
149
+ get_ext_param('outcome_data_values_accepted')
150
+ end
151
+
152
+ # convenience method for setting support for all current outcome data types
153
+ def support_outcome_data!
154
+ self.outcome_data_values_accepted = OUTCOME_DATA_TYPES
155
+ end
156
+ end
157
+
158
+ module OutcomeRequest
159
+ include IMS::LTI::Extensions::ExtensionBase
160
+ include Base
161
+
162
+ attr_accessor :outcome_text, :outcome_url, :outcome_needs_grading, :outcome_cdata_text
163
+
164
+ def result_values(node)
165
+ super
166
+ if @outcome_text || @outcome_url || @outcome_needs_grading || @outcome_cdata_text || @outcome_status_of_result || @outcome_date
167
+ node.resultData do |res_data|
168
+ if @outcome_cdata_text
169
+ res_data.text {
170
+ res_data.cdata! @outcome_cdata_text
171
+ }
172
+ elsif @outcome_text
173
+ res_data.text @outcome_text
174
+ end
175
+ res_data.url @outcome_url if @outcome_url
176
+ res_data.needs_grading @outcome_needs_grading if @outcome_needs_grading
177
+ res_data.status_of_result @outcome_status_of_result if @outcome_status_of_result
178
+ res_data.date @outcome_date if @outcome_date
179
+ end
180
+ end
181
+ end
182
+
183
+ def has_result_data?
184
+ !!@outcome_text || !!@outcome_url || !!@outcome_needs_grading || @outcome_status_of_result || @outcome_date || super
185
+ end
186
+
187
+ def extention_process_xml(doc)
188
+ super
189
+ @outcome_text = doc.get_text('//resultRecord/result/resultData/text')
190
+ @outcome_url = doc.get_text('//resultRecord/result/resultData/url')
191
+ @outcome_needs_grading = doc.get_text('//resultRecord/result/resultData/needs_grading')
192
+ @outcome_date = doc.get_text('//resultRecord/result/resultData/date')
193
+ @outcome_status_of_result = doc.get_text('//resultRecord/result/resultData/status_of_result')
194
+ end
195
+ end
196
+
197
+ end
198
+ end
199
+ end