wimba integration, fixes #2989

added wimba web conferencing support, moved dimdim config into plugin
settings, fixed a few web conferencing ui issues

Change-Id: I6b36b0e594a9f296d14cd35bec02186478bcbd13
Reviewed-on: https://gerrit.instructure.com/2343
Tested-by: Hudson <hudson@instructure.com>
Reviewed-by: Bracken Mosbacker <bracken@instructure.com>
This commit is contained in:
Jon Jensen 2011-02-17 17:45:55 -07:00
parent 820fa03212
commit 86b062bf61
24 changed files with 631 additions and 103 deletions

View File

@ -42,17 +42,7 @@ class ConferencesController < ApplicationController
if authorized_action(@context.web_conferences.new, @current_user, :create)
@conference = @context.web_conferences.build(params[:web_conference])
@conference.user = @current_user
@conference.duration += 30 if @conference.duration
members = [@current_user]
if params[:user] && params[:user][:all] != '1'
ids = []
params[:user].each do |id, val|
ids << id.to_i if val == '1'
end
members += @context.users.find_all_by_id(ids).to_a
else
members += @context.users.to_a
end
members = get_new_members
respond_to do |format|
if @conference.save
@conference.add_initiator(@current_user)
@ -74,8 +64,13 @@ class ConferencesController < ApplicationController
get_conference
if authorized_action(@conference, @current_user, :update)
@conference.user ||= @current_user
members = get_new_members
respond_to do |format|
if @conference.update_attributes(params[:web_conference])
if @conference.update_attributes(params[:web_conference].delete_if{|k,v| k.to_sym == :conference_type})
# TODO: ability to dis-invite people
members.uniq.each do |u|
@conference.add_invitee(u)
end
format.html { redirect_to named_context_url(@context, :context_conference_url, @conference.id) }
format.json { render :json => @conference.to_json(:permissions => {:user => @current_user, :session => session}) }
else
@ -100,7 +95,12 @@ class ConferencesController < ApplicationController
@conference.restart if @conference.ended_at && @conference.grants_right?(@current_user, session, :initiate)
log_asset_access(@conference, "conferences", "conferences", 'participate')
@conference.touch
redirect_to @conference.craft_url(@current_user, session, named_context_url(@context, :context_url, :include_host => true))
if url = @conference.craft_url(@current_user, session, named_context_url(@context, :context_url, :include_host => true))
redirect_to url
else
flash[:error] = "There was an error joining the conference"
redirect_to named_context_url(@context, :context_url)
end
else
flash[:notice] = "That conference is not currently active"
redirect_to named_context_url(@context, :context_url)
@ -128,7 +128,21 @@ class ConferencesController < ApplicationController
redirect_to named_context_url(@context, :context_url)
end
end
def get_new_members
members = [@current_user]
if params[:user] && params[:user][:all] != '1'
ids = []
params[:user].each do |id, val|
ids << id.to_i if val == '1'
end
members += @context.users.find_all_by_id(ids).to_a
else
members += @context.users.to_a
end
members - @conference.invitees
end
def get_conference
@conference = @context.web_conferences.find(params[:conference_id] || params[:id])
end

View File

@ -16,20 +16,6 @@
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class PluginsController < ApplicationController
before_filter :require_setting_site_admin

View File

@ -27,7 +27,7 @@ class DimDimConference < WebConference
require 'uri'
active = nil
begin
url = URI.parse("http://#{config['domain']}/dimdim/IsMeetingKeyInUse.action?key=#{self.conference_key}&roomName=#{self.conference_key}")
url = URI.parse("http://#{config[:domain]}/dimdim/IsMeetingKeyInUse.action?key=#{self.conference_key}&roomName=#{self.conference_key}")
req = Net::HTTP::Get.new(url.path + '?' + url.query)
res = Net::HTTP.start(url.host, url.port) {|http|
http.request(req)
@ -46,11 +46,11 @@ class DimDimConference < WebConference
end
def admin_join_url(user, return_to="http://www.instructure.com")
"http://#{config['domain']}/dimdim/html/envcheck/connect.action?action=host&email=#{CGI::escape(user.email)}&confKey=#{self.conference_key}&attendeePwd=#{self.attendee_key}&presenterPwd=#{self.presenter_key}&displayName=#{CGI::escape(user.name)}&meetingRoomName=#{self.conference_key}&confName=#{CGI::escape(self.title)}&presenterAV=av&collabUrl=#{CGI::escape("http://#{HostUrl.context_host(self.context)}/dimdim_welcome.html")}&returnUrl=#{CGI::escape(return_to)}"
"http://#{config[:domain]}/dimdim/html/envcheck/connect.action?action=host&email=#{CGI::escape(user.email)}&confKey=#{self.conference_key}&attendeePwd=#{self.attendee_key}&presenterPwd=#{self.presenter_key}&displayName=#{CGI::escape(user.name)}&meetingRoomName=#{self.conference_key}&confName=#{CGI::escape(self.title)}&presenterAV=av&collabUrl=#{CGI::escape("http://#{HostUrl.context_host(self.context)}/dimdim_welcome.html")}&returnUrl=#{CGI::escape(return_to)}"
end
def participant_join_url(user, return_to="http://www.instructure.com")
"http://#{config['domain']}/dimdim/html/envcheck/connect.action?action=join&email=#{CGI::escape(user.email)}&confKey=#{self.conference_key}&attendeePwd=#{self.attendee_key}&displayName=#{CGI::escape(user.name)}&meetingRoomName=#{self.conference_key}"
"http://#{config[:domain]}/dimdim/html/envcheck/connect.action?action=join&email=#{CGI::escape(user.email)}&confKey=#{self.conference_key}&attendeePwd=#{self.attendee_key}&displayName=#{CGI::escape(user.name)}&meetingRoomName=#{self.conference_key}"
end
end

View File

@ -31,6 +31,9 @@ class PluginSetting < ActiveRecord::Base
before_save :validate_posted_settings
serialize :settings
attr_accessor :posted_settings
attr_writer :plugin
before_save :encrypt_settings
def validate_uniqueness_of_name?
true
@ -42,10 +45,46 @@ class PluginSetting < ActiveRecord::Base
plugin.validate_settings(self, @posted_settings)
end
end
def plugin
@plugin ||= Canvas::Plugin.find(name.to_s)
end
# dummy value for encrypted fields so that you can still have something in the form (to indicate
# it's set) and be able to tell when it gets blanked out.
DUMMY_STRING = "~!?3NCRYPT3D?!~"
def after_initialize
if settings && self.plugin && self.plugin.encrypted_settings
self.plugin.encrypted_settings.each do |key|
if settings["#{key}_enc".to_sym]
settings["#{key}_dec".to_sym] = self.class.decrypt(settings["#{key}_enc"], settings["#{key}_salt".to_sym])
settings[key] = DUMMY_STRING
end
end
end
end
def encrypt_settings
if settings && self.plugin && self.plugin.encrypted_settings
self.plugin.encrypted_settings.each do |key|
unless settings[key].blank?
value = settings.delete(key)
settings.delete("#{key}_dec".to_sym)
if value == DUMMY_STRING # no change, use what was there previously
settings["#{key}_enc".to_sym] = settings_was["#{key}_enc".to_sym]
settings["#{key}_salt".to_sym] = settings_was["#{key}_salt".to_sym]
else
settings["#{key}_enc".to_sym], settings["#{key}_salt".to_sym] = self.class.encrypt(value)
end
end
end
end
end
def self.settings_for_plugin(name, plugin=nil)
if settings = PluginSetting.find_by_name(name.to_s)
settings = settings.settings
if plugin_setting = PluginSetting.find_by_name(name.to_s)
plugin_setting.plugin = plugin
settings = plugin_setting.settings
else
plugin ||= Canvas::Plugin.find(name.to_s)
raise Canvas::NoPluginError unless plugin
@ -55,4 +94,11 @@ class PluginSetting < ActiveRecord::Base
settings
end
def self.encrypt(text)
Canvas::Security.encrypt_password(text, 'instructure_plugin_setting')
end
def self.decrypt(text, salt)
Canvas::Security.decrypt_password(text, salt, 'instructure_plugin_setting')
end
end

View File

@ -27,10 +27,10 @@ class WebConference < ActiveRecord::Base
has_many :attendees, :through => :web_conference_participants, :source => :user, :conditions => ['web_conference_participants.participation_type = ?', 'attendee']
belongs_to :user
validates_length_of :description, :maximum => maximum_text_length, :allow_nil => true, :allow_blank => true
validates_presence_of :conference_type, :title
adheres_to_policy
before_save :infer_conference_details
before_validation :infer_conference_details
before_create :assign_uuid
after_save :touch_context
@ -92,11 +92,11 @@ class WebConference < ActiveRecord::Base
end
def conference_type=(val)
conf_type = WebConference.conference_types.detect{|t| t['name'] == val }
conf_type = WebConference.conference_types.detect{|t| t[:conference_type] == val }
if conf_type
write_attribute(:conference_type, conf_type['name'] )
write_attribute(:type, "#{conf_type['type'].classify}Conference" )
conf_type['name']
write_attribute(:conference_type, conf_type[:conference_type] )
write_attribute(:type, conf_type[:class_name] )
conf_type[:conference_type]
else
nil
end
@ -104,7 +104,7 @@ class WebConference < ActiveRecord::Base
def infer_conference_details
infer_conference_settings
self.conference_type ||= config && config['name']
self.conference_type ||= config && config[:conference_type]
self.context_code = "#{self.context_type.underscore}_#{self.context_id}" rescue nil
self.duration ||= 30
self.user_ids ||= (self.user_id || "").to_s
@ -204,11 +204,12 @@ class WebConference < ActiveRecord::Base
end
def initiate_conference
true
end
def craft_url(user=nil,session=nil,return_to="http://www.instructure.com")
user ||= self.user
initiate_conference
initiate_conference or return nil
if (user == self.user || self.grants_right?(user,session,:initiate)) && !active?(true)
admin_join_url(user, return_to)
else
@ -248,36 +249,46 @@ class WebConference < ActiveRecord::Base
end
def config
@config ||= WebConference.config(self.class.to_s, conference_type)
@config ||= WebConference.config(self.class.to_s)
end
def valid_config?
if !config
false
elsif conference_type
config['name'] == conference_type
else
"#{config['type'].classify}Conference" == self.class.to_s
config[:class_name] == self.class.to_s
end
end
named_scope :active, lambda {
}
def self.plugins
Canvas::Plugin.all_for_tag(:web_conferencing)
end
def self.conference_types
@configs ||= (YAML.load_file(RAILS_ROOT + "/config/web_conferences.yml")[RAILS_ENV] rescue nil) || []
plugins.select{ |plugin|
plugin.settings &&
!plugin.settings.values.all?(&:blank?) &&
(klass = (plugin.base || "#{plugin.id.classify}Conference").constantize rescue nil) &&
klass < self
}.
map{ |plugin|
plugin.settings.merge(
:conference_type => plugin.id.classify,
:class_name => (plugin.base || "#{plugin.id.classify}Conference")
)
}
end
def self.config(type=nil, name=nil)
configs ||= conference_types
if name
config = configs.detect{|c| c['name'] == name }
elsif type
config = configs.detect{|c| "#{c['type'].classify}Conference" == type }
def self.config(class_name=nil)
if class_name
conference_types.detect{ |c| c[:class_name] == class_name }
else
config = configs.first
conference_types.first
end
end
def self.serialization_excludes; [:uuid]; end
end

View File

@ -0,0 +1,183 @@
#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require 'net/http'
require 'uri'
class WimbaConference < WebConference
def server
config[:domain]
end
def craft_api_url(action, opts={})
url = "http://#{server}/admin/api/api.pl"
query_string = "function=#{action.to_s}"
opts.each do |key, val|
query_string += "&#{CGI::escape(key)}=#{CGI::escape(val.to_s)}"
end
url + "?" + query_string
end
def send_request(action, opts={})
headers = {}
if action.to_s == 'Init'
url = craft_api_url('NoOp', {
'AuthType' => 'AuthCookieHandler',
'AuthName' => 'Horizon',
'credential_0' => config[:user],
'credential_1' => config[:password_dec]
})
else
init_session or return nil
url = craft_api_url(action, opts)
headers['Cookie'] = @auth_cookie
end
uri = URI.parse(url)
res = nil
# TODO: rework this so that we reuse the same tcp conn (we may call
# send_request multiple times in the course of one browser request).
Net::HTTP.start(uri.host, uri.port) do |http|
http.read_timeout = 10
5.times do # follow redirects, but not forever
logger.debug "wimba api call: #{uri.path}?#{uri.query}"
res = http.request_get("#{uri.path}?#{uri.query}", headers)
if res['Set-Cookie'] && res['Set-Cookie'] =~ /AuthCookieHandler_Horizon=.*;/
@auth_cookie = headers['Cookie'] = res['Set-Cookie'].sub(/.*(AuthCookieHandler_Horizon=.*?);.*/, '\\1')
end
if res.is_a?(Net::HTTPRedirection)
url = res['location']
uri = URI.parse(url)
else
break
end
end
end
case res
when Net::HTTPSuccess
api_status = res.body.to_s.split("\n").first.split(" ", 2)
if api_status[0] == "100"
logger.debug "wimba api success: #{res.body}"
return res.body
end
# any other status indicates an error
logger.error "wimba api error #{api_status[1]} (#{api_status[0]})"
else
logger.error "wimba http error #{res}"
end
nil
rescue Timeout::Error
logger.error "wimba timeout error"
nil
rescue
logger.error "wimba unhandled exception #{$!}"
nil
end
def add_user_to_conference(user, role='participant')
names = user.last_name_first.split(/, /, 2)
(
send_request('modifyUser', {
'target' => wimba_id(user.uuid),
'password_type' => 'A',
'first_name' => names[1],
'last_name' => names[0]}) ||
send_request('createUser', {
'target' => wimba_id(user.uuid),
'password_type' => 'A',
'first_name' => names[1],
'last_name' => names[0]})
) &&
send_request('createRole', {
'target' => wimba_id,
'user_id' => wimba_id(user.uuid),
'role_id' => (role == 'participant' ? 'Student' : 'Instructor')
})
end
def remove_user_from_conference(user)
send_request('deleteRole', {
'target' => wimba_id,
'user_id' => wimba_id(user.uuid)
})
end
def join_url(user)
if (res = send_request('getAuthToken', {
'target' => wimba_id(user.uuid),
'nickname' => user.name.gsub(/[^a-zA-Z0-9]/, '')
})) && (token = res.split("\n").detect{|s| s.match(/^authToken/) })
"http://#{server}/launcher.cgi.pl?hzA=#{CGI::escape(token.split(/=/, 2).last.chomp)}&room=#{CGI::escape(wimba_id)}"
end
end
def admin_join_url(user, return_to="http://www.instructure.com")
add_user_to_conference(user, :admin) &&
join_url(user)
end
def participant_join_url(user, return_to="http://www.instructure.com")
add_user_to_conference(user) &&
join_url(user)
end
def initiate_conference
return conference_key if conference_key
self.conference_key = uuid
send_request('createClass', {
'target' => wimba_id,
'longname' => title[0,50],
'preview' => '0' # we want the room open by default
}) or return nil
save
conference_key
end
def init_session
if !@auth_cookie
send_request('Init') or return false
end
true
end
def conference_status
active = nil
if res = send_request('statusClass', {'target' => wimba_id})
res.split(/\r?\n/).each do |str|
key, value = str.strip.split(/=/, 2)
if key == 'num_users'
return :closed unless value.to_i > 0
active = true
end
if key == 'roomlock'
return :closed if value.to_i == 1
active = true
end
end
end
active ? :active : :closed
end
def wimba_id(id = uuid)
# wimba ids are limited to 34 chars. assuming we are using uuids, we can put an "IN" prefix,
# which makes distinguishing these users easier.
"IN" + id.delete("-")[0,32]
end
end

View File

@ -40,11 +40,11 @@
<div class="content">
<table class="formtable" style="width: 100%;">
<tr>
<td class="nobr" style="width: 5px;"><%= f.label :title, "Name:", :value => "#{@context.name} Conference" %></td>
<td class="nobr" style="width: 5px;"><%= f.label :title, "Name:" %></td>
<td><%= f.text_field :title, :style => "width: 200px;", :value => "#{@context.name} Conference" %></td>
</tr><tr style="<%= hidden unless WebConference.conference_types.length > 1 %>">
<td class="nobr" style="width: 5px;"><%= f.label :conference_type, "Type:" %></td>
<td><%= f.select :conference_type, WebConference.conference_types.map{|c| [c['name'], c['name']] } %></td>
<td><%= f.select :conference_type, WebConference.conference_types.map{|c| [c[:conference_type], c[:conference_type]] } %></td>
</tr><tr>
<td class="nobr" style="width: 5px;"><%= f.label :duration, "Duration:" %></td>
<td><%= f.text_field :duration, :style => "width: 25px;", :value => "60" %> minutes</td>
@ -112,8 +112,10 @@
beforeSubmit: function(data) {
var $conference = $(this).prev(".conference");
if($conference.length == 0) {
$conference = $("#conference_blank").clone(true);
$conference = $("#conference_blank").clone(true).attr('id', '');
$("#conferences").prepend($conference.show());
} else {
$conference.show();
}
$("#add_conference_form").hide();
$conference.loadingImage();
@ -137,22 +139,36 @@
$conference.find(".join_conference_link").show();
}
}
},
error: function(data, $conference) {
$conference.loadingImage('remove');
$conference.remove();
$(this).show();
}
});
$(".edit_conference_link, .add_conference_link").click(function(event) {
event.preventDefault();
var $form = $("#add_conference_form");
if ($form.is(":visible")) {
if (confirm('It looks like you are already editing another conference. Do you wish to continue? Any unsaved changes will be lost.')) {
$form.prev(".conference").show();
} else {
return;
}
}
var edit = $(this).hasClass('edit_conference_link');
var $conference = $(this).parents(".conference")
if(!edit) {
$conference = $("#conference_blank");
}
var $form = $("#add_conference_form");
var data = $conference.getTemplateData({
textValues: ['title', 'duration', 'description', 'user_ids']
textValues: ['title', 'duration', 'description', 'user_ids', 'conference_type']
});
if(edit) {
$form.find("h3").text("Edit Conference Details");
$form.find("span.title").text("Edit Conference Details");
$form.find("button[type=submit]").text("Update Conference");
$form.attr('method', 'PUT').attr('action', $(this).attr('href'));
$form.find('select').attr("disabled", true);
$conference.after($form);
$form.find("#members_list").show().find(":checkbox").attr('checked', false).end().end()
.find(".all_users_checkbox").attr('checked', false);
@ -162,10 +178,12 @@
$form.find("#members_list .member.user_" + id).find(":checkbox").attr('checked', true);
}
} else {
$form.find("h3").text("Start a New Conference");
$form.find("span.title").text("Start a New Conference");
$form.find("button[type=submit]").text("Create Conference");
$form.attr('method', 'POST').attr('action', $(".add_conference_url").attr('href'));
$form.find("all_users_checkbox").attr('checked', true).end()
.find("#members_list").hide().attr('checked', true).find(":checkbox").attr('checked', false);
$form.find('select').attr("disabled", false);
$form.find(".all_users_checkbox").attr('checked', true).end()
.find("#members_list").hide().find(":checkbox").attr('checked', false);
$("#conferences").before($form);
}
$form.fillFormData(data, {object_name: 'web_conference'});

View File

@ -0,0 +1,17 @@
<% fields_for :settings, OpenObject.new(settings) do |f| %>
<table style="width: 500px;" class="formtable">
<tr>
<td colspan="2">
<p>
You will need access to a Dim Dim instance.
</p>
</td>
</tr>
<tr>
<td><%= f.label :domain, "Domain:" %></td>
<td>
<%= f.text_field :domain %>
</td>
</tr>
</table>
<% end %>

View File

@ -0,0 +1,29 @@
<% fields_for :settings, OpenObject.new(settings) do |f| %>
<table style="width: 500px;" class="formtable">
<tr>
<td colspan="2">
<p>
You will need access to a <a href="http://wimba.com">Wimba</a> account.
</p>
</td>
</tr>
<tr>
<td><%= f.label :domain, "Domain:" %></td>
<td>
<%= f.text_field :domain %>
</td>
</tr>
<tr>
<td><%= f.label :user, "Username:" %></td>
<td>
<%= f.text_field :user %>
</td>
</tr>
<tr>
<td><%= f.label :password, "Password:" %></td>
<td>
<%= f.password_field :password, :autocomplete => false %>
</td>
</tr>
</table>
<% end %>

View File

@ -26,6 +26,7 @@
<% end %>
<div style="display: none;" class="duration"><%= conference.duration.to_i rescue "60" %></div>
<div style="display: none;" class="user_ids"><%= Array(conference.user_ids).join(",") rescue nbsp %></div>
<div style="display: none;" class="conference_type"><%= conference && conference.conference_type %></div>
</div>
<div class="description">
<%= conference.description rescue "" %>

View File

@ -60,7 +60,6 @@ Rails::Initializer.run do |config|
config.middleware.use("RequestContextGenerator")
config.to_prepare do
require_dependency 'canvas/plugins/default_plugins'
require_dependency 'canvas/plugins/validators/kaltura_validator'
end
end

View File

@ -1,14 +0,0 @@
production:
- name: DimDim
type: dim_dim
domain: dimdim.com
development:
- name: DimDim
type: dim_dim
domain: dimdim.com
test:
- name: DimDim
type: dim_dim
domain: dimdim.com

View File

@ -1,3 +1,21 @@
#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require 'canvas'
module Canvas
@ -21,7 +39,9 @@ module Canvas
:author_website=>nil,
:version=>nil,
:settings_partial=>nil,
:settings=>nil
:settings=>nil,
:encrypted_settings=>nil,
:base=>nil
}.with_indifferent_access
end
@ -42,7 +62,11 @@ module Canvas
# cache this properly across all web servers
saved_settings
end
def encrypted_settings
@meta[:encrypted_settings]
end
def description
@meta[:description]
end
@ -75,6 +99,11 @@ module Canvas
!meta[:settings_partial].blank?
end
# base class/module for this plugin
def base
@meta[:base]
end
# Let the plugin do any validations necessary.
# If the plugin has defined a validator, call
# the :validate method on that validator. If it

View File

@ -1,9 +1,31 @@
Dir.glob('lib/canvas/plugins/validators/*').each do |file|
require_dependency file
end
Canvas::Plugin.register('kaltura', nil, {
:description => 'Kaltura video/audio recording and playback',
:website => 'http://www.instructure.com',
:website => 'http://corp.kaltura.com',
:author => 'instructure',
:author_website => 'http://www.instructure.com',
:version => 1.0,
:settings_partial => 'plugins/kaltura_settings',
:validator => 'KalturaValidator'
})
})
Canvas::Plugin.register('dim_dim', :web_conferencing, {
:description => 'DimDim web conferencing support',
:website => 'http://www.dimdim.com',
:author => 'instructure',
:author_website => 'http://www.instructure.com',
:version => 1.0,
:settings_partial => 'plugins/dim_dim_settings'
})
Canvas::Plugin.register('wimba', :web_conferencing, {
:description => 'Wimba web conferencing support',
:website => 'http://www.wimba.com',
:author => 'instructure',
:author_website => 'http://www.instructure.com',
:version => 1.0,
:settings_partial => 'plugins/wimba_settings',
:validator => 'WimbaValidator',
:encrypted_settings => [:password]
})

View File

@ -1,3 +1,21 @@
#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
module Canvas::Plugins::Validators::KalturaValidator
def self.validate(settings, plugin_setting)
if settings.map(&:last).all?(&:blank?)

View File

@ -0,0 +1,32 @@
#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
module Canvas::Plugins::Validators::WimbaValidator
def self.validate(settings, plugin_setting)
if settings.map(&:last).all?(&:blank?)
{}
else
if settings.size != 3 || settings.map(&:last).any?(&:blank?)
plugin_setting.errors.add_to_base('All fields are required')
false
else
settings.slice(:domain, :user, :password)
end
end
end
end

3
spec/fixtures/plugin_settings.yml vendored Normal file
View File

@ -0,0 +1,3 @@
dim_dim_plugin:
name: dim_dim
settings: "--- \n:domain: dimdim.instructure.com\n"

View File

@ -22,7 +22,7 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper')
describe 'web_conference_invitation.email' do
it "should render" do
course_model(:reusable => true)
@object = @course.web_conferences.create!
@object = @course.web_conferences.create!(:conference_type => 'DimDim')
generate_message(:web_conference_invitation, :email, @object)
end
end

View File

@ -22,7 +22,7 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper')
describe 'web_conference_invitation.facebook' do
it "should render" do
course_model(:reusable => true)
@object = @course.web_conferences.create!
@object = @course.web_conferences.create!(:conference_type => 'DimDim')
generate_message(:web_conference_invitation, :facebook, @object)
end
end

View File

@ -22,7 +22,7 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper')
describe 'web_conference_invitation.sms' do
it "should render" do
course_model(:reusable => true)
@object = @course.web_conferences.create!
@object = @course.web_conferences.create!(:conference_type => 'DimDim')
generate_message(:web_conference_invitation, :sms, @object)
end
end

View File

@ -22,7 +22,7 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper')
describe 'web_conference_invitation.summary' do
it "should render" do
course_model(:reusable => true)
@object = @course.web_conferences.create!
@object = @course.web_conferences.create!(:conference_type => 'DimDim')
generate_message(:web_conference_invitation, :summary, @object)
end
end

View File

@ -20,19 +20,41 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
describe WebConference do
before(:all) do
WebConference.instance_variable_set('@configs', [{
'type' => 'dim_dim',
'name' => 'dimdim',
'domain' => 'dimdim.instructure.com'
}])
WebConference.instance_eval do
def plugins
[OpenObject.new(:id => "dim_dim", :settings => {:domain => "dimdim.instructure.com"}),
OpenObject.new(:id => "broken_plugin", :settings => {:foo => :bar})]
end
end
end
after(:all) do
WebConference.instance_eval do
def plugins; Canvas::Plugin.all_for_tag(:web_conferencing); end
end
end
context "broken_plugin" do
it "should return false on valid_config? if no matching config" do
WebConference.new.valid_config?.should be_false
conf = WebConference.new
conf.conference_type = 'bad_type'
conf.valid_config?.should be_false
end
it "should return false on valid_config? if plugin subclass is broken/missing" do
conf = WebConference.new
conf.conference_type = "broken_plugin"
conf.valid_config?.should be_false
end
end
context "dim_dim" do
it "should correctly retrieve a config hash" do
conference = DimDimConference.new
config = conference.config
config.should_not be_nil
config['type'].should eql('dim_dim')
config['name'].should eql('dimdim')
config[:conference_type].should eql('DimDim')
config[:class_name].should eql('DimDimConference')
end
it "should correctly generate join urls" do
@ -47,14 +69,7 @@ describe WebConference do
it "should confirm valid config" do
DimDimConference.new.valid_config?.should be_true
DimDimConference.new(:conference_type => "dimdim").valid_config?.should be_true
end
it "should return false on valid_config? if no matching config" do
WebConference.new.valid_config?.should be_false
conf = DimDimConference.new
conf.write_attribute(:conference_type, 'bad_type')
conf.valid_config?.should be_false
DimDimConference.new(:conference_type => "DimDim").valid_config?.should be_true
end
end

View File

@ -0,0 +1,118 @@
#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
describe WimbaConference do
before(:all) do
WebConference.instance_eval do
def plugins
[OpenObject.new(:id => "wimba", :settings => {:domain => "wimba.test"})]
end
end
WimbaConference.class_eval do
# set up a simple mock that mimics basic API functionality
def send_request(action, opts={})
@mocked_users ||= {:added => [], :admins => [], :joined => []}
extra = ''
if action == 'Init'
@auth_cookie = 'authcookie=secret'
else
return nil unless init_session
return nil unless @auth_cookie == 'authcookie=secret'
case action
when 'modifyUser'
return nil unless @mocked_users[:added].include?(opts['target'])
when 'createUser'
@mocked_users[:added] << opts['target']
when 'createRole'
@mocked_users[:admins] << opts['user_id'] if opts['role_id'] == 'Instructor'
when 'getAuthToken'
return nil unless @mocked_users[:added].include?(opts['target'])
return nil if @mocked_users[:joined].empty? && !@mocked_users[:admins].include?(opts['target'])
@mocked_users[:joined] << opts['target']
extra = "\nauthToken=s3kr1tfor#{opts['target']}\nuser_id=#{opts['target']}\n=END RECORD"
when 'statusClass'
extra = "\nnum_users=#{@mocked_users[:joined].size}\nroomlock=\n=END RECORD"
end
end
"100 OK#{extra}"
end
end
end
after(:all) do
WebConference.instance_eval do
def plugins; Canvas::Plugin.all_for_tag(:web_conferencing); end
end
end
before(:each) do
user_model
email = "email@email.com"
@user.stub!(:email).and_return(email)
end
context "wimba" do
it "should correctly retrieve a config hash" do
conference = WimbaConference.new
config = conference.config
config.should_not be_nil
config[:conference_type].should eql('Wimba')
config[:class_name].should eql('WimbaConference')
end
it "should confirm valid config" do
WimbaConference.new.valid_config?.should be_true
WimbaConference.new(:conference_type => "Wimba").valid_config?.should be_true
end
it "should be active if an admin has joined" do
conference = WimbaConference.create!(:title => "my conference", :user => @user)
# this makes it active
conference.initiate_conference
conference.admin_join_url(@user).should_not be_nil
conference.conference_status.should eql(:active)
conference.participant_join_url(@user).should_not be_nil
end
it "should be closed if it has not been initiated" do
conference = WimbaConference.create!(:title => "my conference", :user => @user)
conference.conference_status.should eql(:closed)
conference.participant_join_url(@user).should be_nil
end
it "should be closed if no admins have joined" do
conference = WimbaConference.create!(:title => "my conference", :user => @user)
conference.initiate_conference
conference.conference_status.should eql(:closed)
conference.participant_join_url(@user).should be_nil
end
it "should correctly generate join urls" do
conference = WimbaConference.create!(:title => "my conference", :user => @user)
conference.initiate_conference
# join urls for admins and participants look the same (though token will vary by user), since
# someone's admin/participant-ness is negotiated beforehand through api calls and isn't
# reflected in the token/url
join_url = "http://wimba.test/launcher.cgi.pl?hzA=s3kr1tfor#{conference.wimba_id(@user.uuid)}&room=#{conference.wimba_id}"
conference.admin_join_url(@user).should eql(join_url)
conference.participant_join_url(@user).should eql(join_url)
end
end
end

View File

@ -48,6 +48,7 @@ Spec::Runner.configure do |config|
config.use_transactional_fixtures = true
config.use_instantiated_fixtures = false
config.fixture_path = RAILS_ROOT + '/spec/fixtures/'
config.global_fixtures = :plugin_settings
config.include Webrat::Matchers, :type => :views