Skip to content

Commit

Permalink
Merge pull request bigbluebutton#100 from mconf/1979-feat-groups-v2
Browse files Browse the repository at this point in the history
[PRT-1979] Moodle Group Selection v2
  • Loading branch information
wpramio authored Mar 27, 2024
2 parents baa7832 + 12ceaaa commit a7892b1
Show file tree
Hide file tree
Showing 32 changed files with 529 additions and 81 deletions.
4 changes: 2 additions & 2 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,8 @@ def get_from_room_session(room, key)
end

def remove_from_room_session(room, key)
if session.dig(COOKIE_ROOMS_SCOPE, room.handler, key)
session[COOKIE_ROOMS_SCOPE][room.handler].delete(key)
if session.dig(COOKIE_ROOMS_SCOPE, key)
session[COOKIE_ROOMS_SCOPE].delete(key)
end
end

Expand Down
101 changes: 101 additions & 0 deletions app/controllers/rooms_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

class RoomsController < ApplicationController
include ApplicationHelper
include MeetingsHelper
include BbbApi
include BbbAppRooms

Expand All @@ -19,7 +20,9 @@ class RoomsController < ApplicationController
before_action :validate_room, except: %i[launch close]
before_action :find_user
before_action :find_app_launch, only: %i[launch]
before_action :set_user_groups_on_session, only: %i[launch]
before_action :set_room_title, only: :show
before_action :set_group_variables, only: %i[show meetings]

before_action only: %i[show launch close] do
authorize_user!(:show, @room)
Expand Down Expand Up @@ -56,6 +59,12 @@ def meetings_pagination
offset: offset,
includeRecordings: true
}
# with groups configured, non-moderators only see meetings that belong to the current
# selected group
if @room.moodle_group_select_enabled? && !@user.moderator?(Abilities.moderator_roles)
options['meta_bbb-moodle-group-id'] = get_from_room_session(@room, 'current_group_id')
end

meetings_and_recordings, all_meetings_loaded = get_all_meetings(@room, options)

args = { meetings_and_recordings: meetings_and_recordings,
Expand Down Expand Up @@ -211,6 +220,20 @@ def filesender_auth
redirect_to(filesender_path(@room, record_id: params['record_id']))
end

# POST /rooms/1/set_current_group_on_session
# expected params: [:group_id, :redir_url]
def set_current_group_on_session
if @room.moodle_group_select_enabled?
if params[:group_id].present?
add_to_room_session(@room, 'current_group_id', params[:group_id])
else
remove_from_room_session(@room, 'current_group_id')
end
end

redirect_to params[:redir_url]
end

helper_method :meetings, :recording_date, :recording_length

private
Expand Down Expand Up @@ -285,6 +308,84 @@ def set_launch_room
)
end

# Adds the user first group ID to the session if the grouping
# feature is enabled.
# Adds the formatted user groups to the session
# Example:
# current_group_id: 1
# user_groups: {'1': 'Grupo A', '2': 'Grupo B'}
def set_user_groups_on_session
if @room.moodle_group_select_enabled?
moodle_token = @room.moodle_token
Rails.logger.info "Moodle token #{moodle_token.token} found, group select is enabled"
# testing if the token is configured with the necessary functions
wsfunctions = [
'core_group_get_activity_groupmode',
'core_group_get_course_user_groups',
'core_course_get_course_module_by_instance',
'core_group_get_course_groups'
]

unless Moodle::API.check_token_functions(moodle_token, wsfunctions)
Rails.logger.error 'A function required for the groups feature is not configured in Moodle'
set_error('room', 'moodle_token_misconfigured', :forbidden)
respond_with_error(@error)
return
end

# the `resource_link_id` provided by Moodle is the `instance_id` of the activity.
# We use it to fetch the activity data, from where we get its `cmid` (course module id)
# to fetch the effective groupmode configured on the activity
activity_data = Moodle::API.get_activity_data(moodle_token, @app_launch.params['resource_link_id'])
if activity_data.nil?
Rails.logger.error "Could not find the necessary data for this activity (instance_id: #{@app_launch.params['resource_link_id']})"
set_error('room', 'moodle_token_misconfigured', :forbidden)
respond_with_error(@error)
return
end

groupmode = Moodle::API.get_groupmode(moodle_token, activity_data['id'])
# testing if the activity has its groupmode configured for separate groups (1)
# or visible groups (2)
if groupmode == 0 || groupmode.nil?
Rails.logger.error 'The Moodle activity has an invalid groupmode configured'
set_error('room', 'moodle_token_misconfigured', :forbidden)
respond_with_error(@error)
return
end

Rails.logger.info "Moodle groups are configured for this session (#{@app_launch.nonce})"

if @user.moderator?(Abilities.moderator_roles)
# Gets all course groups except the default 'All Participants' group (id 0)
groups = Moodle::API.get_course_groups(moodle_token, @app_launch.context_id)
.delete_if{ |element| element['id'] == "0" }
else
groups = Moodle::API.get_user_groups(moodle_token, @user.uid, @app_launch.context_id)
end

if groups.any?
groups_hash = groups.collect{ |g| g.slice('id', 'name').values }.to_h
current_group_id = groups.first['id']
else
groups_hash = {'no_groups': 'Você não pertence a nenhum grupo'}
current_group_id = 'no_groups'
end

add_to_room_session(@room, 'current_group_id', current_group_id)
add_to_room_session(@room, 'user_groups', groups_hash)
end
end

# Set the variables expected by the `group_select` partial
def set_group_variables
if @room.moodle_group_select_enabled?
@groups_hash = get_from_room_session(@room, 'user_groups')
@group_select = @groups_hash.invert
@current_group_id = get_from_room_session(@room, 'current_group_id')
end
end

def set_room_title
if @app_launch&.coc_launch?
@title = @room.name
Expand Down
102 changes: 90 additions & 12 deletions app/controllers/scheduled_meetings_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,21 @@ def join
# only way for a meeting to be created is through here
if @user.present?

opts = {}
if @room.moodle_group_select_enabled?
# coming from an external link
if params[:moodle_group_id].present?
opts = { moodle_group: { id: params[:moodle_group_id] } }
# coming from a 'play' button
else
groups = get_from_room_session(@room, 'user_groups')
group_id = get_from_room_session(@room, 'current_group_id').to_s
opts = { moodle_group: { name: groups[group_id], id: group_id } } unless group_id == 'no_groups'
end
end

# make user wait until moderator is in room
if wait_for_mod?(@scheduled_meeting, @user) && (!mod_in_room?(@scheduled_meeting) ||
if wait_for_mod?(@scheduled_meeting, @user) && (!mod_in_room?(@scheduled_meeting, opts) ||
(params[:no_auto_join] == 'true' && device_type? != 'desktop'))
redirect_to wait_room_scheduled_meeting_path(@room, @scheduled_meeting)
else
Expand All @@ -122,7 +135,7 @@ def join
end

# join as moderator (creates the meeting if not created yet)
res = join_api_url(@scheduled_meeting, @user)
res = join_api_url(@scheduled_meeting, @user, opts)
if res[:can_join?]
if params[:join_in_app] == 'true'
direct_join_url = 'br.rnp.conferenciawebmobile://direct-join/' + res[:join_api_url].gsub(/^https?:\/\//, '') + "&meetingName=#{@scheduled_meeting.name}"
Expand All @@ -145,15 +158,20 @@ def join
return
end

if !mod_in_room?(@scheduled_meeting)
opts = {}
if @room.moodle_group_select_enabled? && params[:moodle_group_id].present?
opts = { moodle_group: { id: params[:moodle_group_id] } }
end

if !mod_in_room?(@scheduled_meeting, opts)
redirect_to wait_room_scheduled_meeting_path(
@room, @scheduled_meeting,
first_name: params[:first_name], last_name: params[:last_name]
)
else
# join as guest
name = "#{params[:first_name]} #{params[:last_name]}"
res = external_join_api_url(@scheduled_meeting, name)
res = external_join_api_url(@scheduled_meeting, name, opts)
if res[:can_join?]
if params[:join_in_app] == 'true'
direct_join_url = 'br.rnp.conferenciawebmobile://direct-join/' + res[:join_api_url].gsub(/^https?:\/\//, '') + "&meetingName=#{@scheduled_meeting.name}"
Expand Down Expand Up @@ -196,7 +214,18 @@ def wait
first_name: params[:first_name], last_name: params[:last_name]
)
end
@is_running = mod_in_room?(@scheduled_meeting)
opts = {}
if @room.moodle_group_select_enabled?
# coming from an external link
if params[:moodle_group_id].present?
opts = { moodle_group: { id: params[:moodle_group_id] } }
# coming from a 'play' button
else
group_id = get_from_room_session(@room, 'current_group_id').to_s
opts = { moodle_group: { id: group_id } } unless group_id == 'no_groups'
end
end
@is_running = mod_in_room?(@scheduled_meeting, opts)
@can_join_or_create = join_or_create?
end

Expand All @@ -217,19 +246,57 @@ def external

@scheduled_meeting.update_to_next_recurring_date

@is_running = mod_in_room?(@scheduled_meeting)
@participants_count = get_participants_count(@scheduled_meeting)
@ended = !@scheduled_meeting.active? && !mod_in_room?(@scheduled_meeting)
@started_ago = get_current_duration(@scheduled_meeting)
opts = {}
if params[:moodle_group_id].present? && @room.moodle_group_select_enabled?
opts = { moodle_group: { id: params[:moodle_group_id] } }
# ??? testar se grupo existe aqui, pra valer tanto pra user logado quanto nao logado?
# fazer chamada pra api do moodle ou pegar de alguma coisa no db, tipo a @room ou @scheduled?

# if @user.present?
# Situações pra um user logado:
# grupo existe, faz parte
# se é moderador, pode abrir, senão fica no wait
# grupo existe, n faz parte
# fica no wait
# grupo n existe
# mostra erro 404

# testar se ele faz parte daquele grupo?
# ou deixa o join cuidar disso?
if @user.nil?
# meeting exists?
@is_running = mod_in_room?(@scheduled_meeting, opts)
unless @is_running
set_error('scheduled_meeting', 'meeting_not_found', :not_found)
respond_with_error(@error) and return
end
end
end

@is_running ||= mod_in_room?(@scheduled_meeting, opts)
@ended = !@scheduled_meeting.active? && !mod_in_room?(@scheduled_meeting, opts)
@participants_count = get_participants_count(@scheduled_meeting, opts)
@started_ago = get_current_duration(@scheduled_meeting, opts)
@disclaimer = config&.external_disclaimer
end

def running
opts = {}
if @room.moodle_group_select_enabled?
# coming from an external link
if params[:moodle_group_id].present?
opts = { moodle_group: { id: params[:moodle_group_id] } }
# coming from a 'play' button
else
group_id = get_from_room_session(@room, 'current_group_id').to_s
opts = { moodle_group: { id: group_id } } unless group_id == 'no_groups'
end
end
respond_to do |format|
format.json {
render json: {
status: :ok,
running: mod_in_room?(@scheduled_meeting),
running: mod_in_room?(@scheduled_meeting, opts),
interval: Rails.configuration.cable_polling_secs.to_i,
can_join_or_create: join_or_create?
}
Expand Down Expand Up @@ -280,8 +347,19 @@ def set_blank_repeat_as_nil
end

def join_or_create?
can_join = (@user.present? && !(wait_for_mod?(@scheduled_meeting, @user) && !mod_in_room?(@scheduled_meeting))) ||
(!@user.present? && mod_in_room?(@scheduled_meeting))
opts = {}
if @room.moodle_group_select_enabled?
# coming from an external link
if params[:moodle_group_id].present?
opts = { moodle_group: { id: params[:moodle_group_id] } }
# coming from a 'play' button
else
group_id = get_from_room_session(@room, 'current_group_id').to_s
opts = { moodle_group: { id: group_id } } unless group_id == 'no_groups'
end
end
can_join = (@user.present? && !(wait_for_mod?(@scheduled_meeting, @user) && !mod_in_room?(@scheduled_meeting, opts))) ||
(!@user.present? && mod_in_room?(@scheduled_meeting, opts))

can_join
end
Expand Down
10 changes: 9 additions & 1 deletion app/models/room.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def self.from_param(param)
def can_create_moodle_calendar_event
moodle_token = self.consumer_config&.moodle_token
if moodle_token
Moodle::API.check_token_functions(moodle_token, 'core_calendar_create_calendar_events')
Moodle::API.check_token_functions(moodle_token, ['core_calendar_create_calendar_events'])
else
false
end
Expand All @@ -31,6 +31,14 @@ def consumer_config
ConsumerConfig.find_by(key: self.consumer_key)
end

def moodle_token
consumer_config&.moodle_token
end

def moodle_group_select_enabled?
moodle_token&.group_select_enabled?
end

def default_values
self.handler ||= Digest::SHA1.hexdigest(SecureRandom.uuid)
self.moderator = random_password(8) if moderator.blank?
Expand Down
12 changes: 12 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,12 @@ en:
suggestion: "The token could not be validated"
explanation: "This problem can be caused by using an invalid key when validating the request through the LTI Provider."
status_code: "403"
moodle_token_misconfigured:
code: "AccessForbidden"
message: "Access denied"
suggestion: "The Moodle integration is misconfigured"
explanation: "The issue may be caused by incorrect configuration in the LTI integration with your Moodle environment."
status_code: "403"
external_context_parse_error:
code: "ParseError"
message: "Internal error fetching data"
Expand All @@ -239,6 +245,12 @@ en:
suggestion: "The scheduled meeting was not found"
explanation: "It might have been removed by the session creator."
status_code: "404"
meeting_not_found:
code: "NotFound"
message: "Meeting not found"
suggestion: "The meeting was not found"
explanation: "The group identifier is invalid."
status_code: "404"
meeting:
learning_dashboard_url_missing: "Error: Learning dashboard URL not configured"
bucket_api:
Expand Down
12 changes: 12 additions & 0 deletions config/locales/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,12 @@ es:
suggestion: "No se puede verificar el token"
explanation: "El problema puede deberse al uso de una clave no válida para validar la solicitud realizada por el proveedor de LTI."
status_code: "403"
moodle_token_misconfigured:
code: "AccessForbidden"
message: "Acceso denegado"
suggestion: "La integración con Moodle está mal configurada"
explanation: "El problema puede ser causado por una configuración incorrecta en la integración de LTI con su entorno Moodle."
status_code: "403"
external_context_parse_error:
code: "ParseError"
message: "Error interno al buscar datos"
Expand All @@ -207,6 +213,12 @@ es:
suggestion: "No se encontró la programación"
explanation: "Es posible que el creador de la sesión lo haya eliminado."
status_code: "404"
meeting_not_found:
code: "NotFound"
message: "Reunión no encontrada"
suggestion: "No se encontró la reunión"
explanation: "El identificador de grupo no es válido."
status_code: "404"
meeting:
learning_dashboard_url_missing: "Error: URL del panel de aprendizaje no está configurada"
bucket_api:
Expand Down
Loading

0 comments on commit a7892b1

Please sign in to comment.