Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • v3-modify-mail
  • snyk-fix-207483a1e839c807f95a55077e86527d
  • master
  • translations_3b5aa4f3c755059914cfa23d7d2edcde_ru
  • translations_6e4a5e377a3e50f17e6402264fdbfcc6_ru
  • translations_3b5aa4f3c755059914cfa23d7d2edcde_fa_IR
  • translations_en-yml--master_fa_IR
  • snyk-fix-7d634f2eb65555f41bf06d6af930e812
  • translations_en-yml--master_ar
  • translations_3b5aa4f3c755059914cfa23d7d2edcde_el
  • jfederico-patch-1
  • v2
  • v3
  • v1
  • release-2.0.0
  • release-2.0.1
  • release-2.0.2
  • release-2.0.3
  • release-2.0.4
  • release-2.0.5
  • release-2.0.6
  • release-2.0.7
  • release-2.0.8
  • release-2.0.9
  • release-2.1.0
  • release-2.1.1
  • release-2.1.2
  • release-2.1.3
  • release-2.10.0
  • release-2.10.0.1
  • release-2.10.0.1-beta.1
  • release-2.10.0.2
  • release-2.10.0.3
  • release-2.11.0
  • release-2.11.1
  • release-2.11.2
  • release-2.12.0
  • release-2.12.1
  • release-2.12.1.1
  • release-2.12.1.1-beta.1
  • release-2.12.2
  • release-2.12.2.1
  • release-2.12.3
  • release-2.12.4
  • release-2.12.5
  • release-2.12.6
  • release-2.12.6-beta.1
  • release-2.13.0
  • release-2.14.0
  • release-2.14.1
  • release-2.14.2
  • release-2.14.3
  • release-2.14.4
  • release-2.14.5
  • release-2.14.6
  • release-2.14.7
  • release-2.14.8
  • release-2.14.8.1
  • release-2.14.8.2
  • release-2.14.8.3
  • release-2.14.8.4
  • release-2.2.0
  • release-2.2.1
  • release-2.2.2
  • release-2.2.3
  • release-2.2.4
  • release-2.3.0
  • release-2.3.1
  • release-2.3.2
  • release-2.3.3
  • release-2.3.4
  • release-2.4
  • release-2.4-b1
  • release-2.4-b2
  • release-2.4-b3
  • release-2.4-rc1
  • release-2.4.1
  • release-2.4.2
  • release-2.4.2-rc.1
  • release-2.5
  • release-2.5-rc.1
  • release-2.5.1
  • release-2.5.2
  • release-2.5.3
  • release-2.5.4
  • release-2.5.5
  • release-2.5.6
  • release-2.6
  • release-2.6.1
  • release-2.6.2
  • release-2.6.3
  • release-2.6.4
  • release-2.6.5
  • release-2.7
  • release-2.7.1
  • release-2.7.10
  • release-2.7.11
  • release-2.7.12
  • release-2.7.13
  • release-2.7.14
  • release-2.7.15
  • release-2.7.15.1
  • release-2.7.16
  • release-2.7.17
  • release-2.7.18
  • release-2.7.19
  • release-2.7.2
  • release-2.7.20
  • release-2.7.3
  • release-2.7.4
  • release-2.7.5
  • release-2.7.6
  • release-2.7.7
  • release-2.7.8
114 results

Target

Select target project
  • Arbeitsgruppe Hardwarenahe IT-Systeme / Greenlight
1 result
Select Git revision
  • v3-modify-mail
  • snyk-fix-207483a1e839c807f95a55077e86527d
  • master
  • translations_3b5aa4f3c755059914cfa23d7d2edcde_ru
  • translations_6e4a5e377a3e50f17e6402264fdbfcc6_ru
  • translations_3b5aa4f3c755059914cfa23d7d2edcde_fa_IR
  • translations_en-yml--master_fa_IR
  • snyk-fix-7d634f2eb65555f41bf06d6af930e812
  • translations_en-yml--master_ar
  • translations_3b5aa4f3c755059914cfa23d7d2edcde_el
  • jfederico-patch-1
  • v2
  • v3
  • v1
  • release-2.0.0
  • release-2.0.1
  • release-2.0.2
  • release-2.0.3
  • release-2.0.4
  • release-2.0.5
  • release-2.0.6
  • release-2.0.7
  • release-2.0.8
  • release-2.0.9
  • release-2.1.0
  • release-2.1.1
  • release-2.1.2
  • release-2.1.3
  • release-2.10.0
  • release-2.10.0.1
  • release-2.10.0.1-beta.1
  • release-2.10.0.2
  • release-2.10.0.3
  • release-2.11.0
  • release-2.11.1
  • release-2.11.2
  • release-2.12.0
  • release-2.12.1
  • release-2.12.1.1
  • release-2.12.1.1-beta.1
  • release-2.12.2
  • release-2.12.2.1
  • release-2.12.3
  • release-2.12.4
  • release-2.12.5
  • release-2.12.6
  • release-2.12.6-beta.1
  • release-2.13.0
  • release-2.14.0
  • release-2.14.1
  • release-2.14.2
  • release-2.14.3
  • release-2.14.4
  • release-2.14.5
  • release-2.14.6
  • release-2.14.7
  • release-2.14.8
  • release-2.14.8.1
  • release-2.14.8.2
  • release-2.14.8.3
  • release-2.14.8.4
  • release-2.2.0
  • release-2.2.1
  • release-2.2.2
  • release-2.2.3
  • release-2.2.4
  • release-2.3.0
  • release-2.3.1
  • release-2.3.2
  • release-2.3.3
  • release-2.3.4
  • release-2.4
  • release-2.4-b1
  • release-2.4-b2
  • release-2.4-b3
  • release-2.4-rc1
  • release-2.4.1
  • release-2.4.2
  • release-2.4.2-rc.1
  • release-2.5
  • release-2.5-rc.1
  • release-2.5.1
  • release-2.5.2
  • release-2.5.3
  • release-2.5.4
  • release-2.5.5
  • release-2.5.6
  • release-2.6
  • release-2.6.1
  • release-2.6.2
  • release-2.6.3
  • release-2.6.4
  • release-2.6.5
  • release-2.7
  • release-2.7.1
  • release-2.7.10
  • release-2.7.11
  • release-2.7.12
  • release-2.7.13
  • release-2.7.14
  • release-2.7.15
  • release-2.7.15.1
  • release-2.7.16
  • release-2.7.17
  • release-2.7.18
  • release-2.7.19
  • release-2.7.2
  • release-2.7.20
  • release-2.7.3
  • release-2.7.4
  • release-2.7.5
  • release-2.7.6
  • release-2.7.7
  • release-2.7.8
114 results
Show changes

Commits on Source 105

5 additional commits have been omitted to prevent performance issues.
112 files
+ 8739
10700
Compare changes
  • Side-by-side
  • Inline

Files

+1 −0
Original line number Diff line number Diff line
@@ -17,3 +17,4 @@ rules:
  max-len:
    - 'error'
    - code: 150
  jsx-a11y/control-has-associated-label: "off"
+5 −3
Original line number Diff line number Diff line
@@ -2,6 +2,8 @@ require:
  - rubocop-performance
  - rubocop-rails
  - rubocop-rspec
  - rubocop-factory_bot
  - rubocop-capybara

AllCops:
  Exclude:
@@ -70,7 +72,7 @@ Metrics/ClassLength:
# A calculated magnitude based on number of assignments,
# branches, and conditions.
Metrics/AbcSize:
  Max: 65
  Max: 80

Metrics/ParameterLists:
  CountKeywordArgs: false
@@ -79,10 +81,10 @@ RSpec/AnyInstance:
  Enabled: false

Metrics/CyclomaticComplexity:
  Max: 16
  Max: 17

Metrics/PerceivedComplexity:
  Max: 15
  Max: 17

Rails/Exit:
  Exclude:
+10 −6
Original line number Diff line number Diff line
@@ -6,11 +6,12 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby '>= 3.0'

gem 'active_model_serializers', '>= 0.10.14'
gem 'active_storage_validations', '>= 1.0.4'
gem 'active_storage_validations', '>= 1.1.0'
gem 'aws-sdk-s3', require: false
gem 'bcrypt', '~> 3.1.7'
gem 'bigbluebutton-api-ruby', '1.9.1'
gem 'bootsnap', require: false
gem 'clamby', '~> 1.6.10'
gem 'cssbundling-rails', '>= 1.3.3'
gem 'data_migrate', '>= 9.2.0'
gem 'dotenv-rails'
@@ -20,16 +21,16 @@ gem 'hiredis', '~> 0.6.0'
gem 'i18n-language-mapping'
gem 'image_processing', '~> 1.2'
gem 'jbuilder'
gem 'jsbundling-rails', '>= 1.2.1'
gem 'jsbundling-rails', '>= 1.2.2'
gem 'jwt'
gem 'mini_magick', '>= 4.9.5'
gem 'omniauth', '~> 2.1.0'
gem 'omniauth', '~> 2.1.2'
gem 'omniauth_openid_connect', '>= 0.6.1'
gem 'omniauth-rails_csrf_protection', '~> 1.0.1'
gem 'pagy', '~> 6.0', '>= 6.0.0'
gem 'pg'
gem 'puma', '~> 5.6'
gem 'rails', '~> 7.1.0'
gem 'rails', '~> 7.1.1'
gem 'redis', '~> 4.0'
gem 'sprockets-rails'
gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby]
@@ -40,6 +41,8 @@ end

group :development do
  gem 'rubocop', '~> 1.26', require: false
  gem 'rubocop-capybara', '~> 2.19.0', require: false
  gem 'rubocop-factory_bot', '~> 2.24.0', require: false
  gem 'rubocop-performance', '~> 1.13', require: false
  gem 'rubocop-rails', '~> 2.18', '>= 2.18.0', require: false
  gem 'rubocop-rspec', '~> 2.9.0', require: false
@@ -48,9 +51,10 @@ end

group :test do
  gem 'capybara'
  gem 'factory_bot_rails'
  gem 'factory_bot', '>= 6.4.1'
  gem 'factory_bot_rails', '>= 6.4.3'
  gem 'faker'
  gem 'rspec-rails', '>= 6.0.2'
  gem 'rspec-rails', '>= 6.0.4'
  gem 'selenium-webdriver'
  gem 'shoulda-matchers', '~> 5.0'
  gem 'webdrivers'
+101 −85
Original line number Diff line number Diff line
GEM
  remote: https://rubygems.org/
  specs:
    actioncable (7.1.0)
      actionpack (= 7.1.0)
      activesupport (= 7.1.0)
    actioncable (7.1.1)
      actionpack (= 7.1.1)
      activesupport (= 7.1.1)
      nio4r (~> 2.0)
      websocket-driver (>= 0.6.1)
      zeitwerk (~> 2.6)
    actionmailbox (7.1.0)
      actionpack (= 7.1.0)
      activejob (= 7.1.0)
      activerecord (= 7.1.0)
      activestorage (= 7.1.0)
      activesupport (= 7.1.0)
    actionmailbox (7.1.1)
      actionpack (= 7.1.1)
      activejob (= 7.1.1)
      activerecord (= 7.1.1)
      activestorage (= 7.1.1)
      activesupport (= 7.1.1)
      mail (>= 2.7.1)
      net-imap
      net-pop
      net-smtp
    actionmailer (7.1.0)
      actionpack (= 7.1.0)
      actionview (= 7.1.0)
      activejob (= 7.1.0)
      activesupport (= 7.1.0)
    actionmailer (7.1.1)
      actionpack (= 7.1.1)
      actionview (= 7.1.1)
      activejob (= 7.1.1)
      activesupport (= 7.1.1)
      mail (~> 2.5, >= 2.5.4)
      net-imap
      net-pop
      net-smtp
      rails-dom-testing (~> 2.2)
    actionpack (7.1.0)
      actionview (= 7.1.0)
      activesupport (= 7.1.0)
    actionpack (7.1.1)
      actionview (= 7.1.1)
      activesupport (= 7.1.1)
      nokogiri (>= 1.8.5)
      rack (>= 2.2.4)
      rack-session (>= 1.0.1)
      rack-test (>= 0.6.3)
      rails-dom-testing (~> 2.2)
      rails-html-sanitizer (~> 1.6)
    actiontext (7.1.0)
      actionpack (= 7.1.0)
      activerecord (= 7.1.0)
      activestorage (= 7.1.0)
      activesupport (= 7.1.0)
    actiontext (7.1.1)
      actionpack (= 7.1.1)
      activerecord (= 7.1.1)
      activestorage (= 7.1.1)
      activesupport (= 7.1.1)
      globalid (>= 0.6.0)
      nokogiri (>= 1.8.5)
    actionview (7.1.0)
      activesupport (= 7.1.0)
    actionview (7.1.1)
      activesupport (= 7.1.1)
      builder (~> 3.1)
      erubi (~> 1.11)
      rails-dom-testing (~> 2.2)
@@ -54,27 +54,27 @@ GEM
      activemodel (>= 4.1)
      case_transform (>= 0.2)
      jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
    active_storage_validations (1.0.4)
    active_storage_validations (1.1.0)
      activejob (>= 5.2.0)
      activemodel (>= 5.2.0)
      activestorage (>= 5.2.0)
      activesupport (>= 5.2.0)
    activejob (7.1.0)
      activesupport (= 7.1.0)
    activejob (7.1.1)
      activesupport (= 7.1.1)
      globalid (>= 0.3.6)
    activemodel (7.1.0)
      activesupport (= 7.1.0)
    activerecord (7.1.0)
      activemodel (= 7.1.0)
      activesupport (= 7.1.0)
    activemodel (7.1.1)
      activesupport (= 7.1.1)
    activerecord (7.1.1)
      activemodel (= 7.1.1)
      activesupport (= 7.1.1)
      timeout (>= 0.4.0)
    activestorage (7.1.0)
      actionpack (= 7.1.0)
      activejob (= 7.1.0)
      activerecord (= 7.1.0)
      activesupport (= 7.1.0)
    activestorage (7.1.1)
      actionpack (= 7.1.1)
      activejob (= 7.1.1)
      activerecord (= 7.1.1)
      activesupport (= 7.1.1)
      marcel (~> 1.0)
    activesupport (7.1.0)
    activesupport (7.1.1)
      base64
      bigdecimal
      concurrent-ruby (~> 1.0, >= 1.0.2)
@@ -115,7 +115,7 @@ GEM
      rack (>= 1.6.11)
      rubyzip (>= 1.3.0)
      xml-simple (~> 1.1)
    bigdecimal (3.1.4)
    bigdecimal (3.1.5)
    bindata (2.4.15)
    bindex (0.8.1)
    bootsnap (1.16.0)
@@ -133,6 +133,7 @@ GEM
    case_transform (0.2)
      activesupport
    childprocess (4.1.0)
    clamby (1.6.10)
    concurrent-ruby (1.2.2)
    connection_pool (2.4.1)
    crack (0.4.5)
@@ -155,13 +156,13 @@ GEM
    dotenv-rails (2.8.1)
      dotenv (= 2.8.1)
      railties (>= 3.2)
    drb (2.1.1)
    drb (2.2.0)
      ruby2_keywords
    erubi (1.12.0)
    factory_bot (6.2.1)
    factory_bot (6.4.5)
      activesupport (>= 5.0.0)
    factory_bot_rails (6.2.0)
      factory_bot (~> 6.2.0)
    factory_bot_rails (6.4.3)
      factory_bot (~> 6.4)
      railties (>= 5.0.0)
    faker (3.1.1)
      i18n (>= 1.8.11, < 2)
@@ -220,14 +221,15 @@ GEM
    image_processing (1.12.2)
      mini_magick (>= 4.9.5, < 5)
      ruby-vips (>= 2.0.17, < 3)
    io-console (0.6.0)
    irb (1.6.2)
      reline (>= 0.3.0)
    io-console (0.7.1)
    irb (1.11.0)
      rdoc
      reline (>= 0.3.8)
    jbuilder (2.11.5)
      actionview (>= 5.0.0)
      activesupport (>= 5.0.0)
    jmespath (1.6.2)
    jsbundling-rails (1.2.1)
    jsbundling-rails (1.2.2)
      railties (>= 6.0.0)
    json (2.6.3)
    json-jwt (1.16.3)
@@ -244,7 +246,7 @@ GEM
      activesupport (>= 4)
      railties (>= 4)
      request_store (~> 1.0)
    loofah (2.21.4)
    loofah (2.22.0)
      crass (~> 1.0.2)
      nokogiri (>= 1.12.0)
    mail (2.8.1)
@@ -257,11 +259,11 @@ GEM
    memoist (0.16.2)
    mini_magick (4.12.0)
    mini_mime (1.1.5)
    mini_portile2 (2.8.4)
    mini_portile2 (2.8.5)
    minitest (5.20.0)
    msgpack (1.6.0)
    multi_json (1.15.0)
    mutex_m (0.1.2)
    mutex_m (0.2.0)
    net-imap (0.4.1)
      date
      net-protocol
@@ -271,13 +273,13 @@ GEM
      timeout
    net-smtp (0.4.0)
      net-protocol
    nio4r (2.5.9)
    nokogiri (1.15.4)
    nio4r (2.7.0)
    nokogiri (1.16.0)
      mini_portile2 (~> 2.8.2)
      racc (~> 1.4)
    nokogiri (1.15.4-x86_64-linux)
    nokogiri (1.16.0-x86_64-linux)
      racc (~> 1.4)
    omniauth (2.1.1)
    omniauth (2.1.2)
      hashie (>= 3.4.6)
      rack (>= 2.2.3)
      rack-protection
@@ -307,10 +309,12 @@ GEM
      ast (~> 2.4.1)
      racc
    pg (1.4.5)
    psych (5.1.2)
      stringio
    public_suffix (5.0.3)
    puma (5.6.7)
    puma (5.6.8)
      nio4r (~> 2.0)
    racc (1.7.1)
    racc (1.7.3)
    rack (2.2.8)
    rack-oauth2 (2.2.0)
      activesupport
@@ -319,29 +323,30 @@ GEM
      faraday-follow_redirects
      json-jwt (>= 1.11.0)
      rack (>= 2.1.0)
    rack-protection (3.1.0)
    rack-protection (3.2.0)
      base64 (>= 0.1.0)
      rack (~> 2.2, >= 2.2.4)
    rack-session (1.0.1)
    rack-session (1.0.2)
      rack (< 3)
    rack-test (2.1.0)
      rack (>= 1.3)
    rackup (1.0.0)
      rack (< 3)
      webrick
    rails (7.1.0)
      actioncable (= 7.1.0)
      actionmailbox (= 7.1.0)
      actionmailer (= 7.1.0)
      actionpack (= 7.1.0)
      actiontext (= 7.1.0)
      actionview (= 7.1.0)
      activejob (= 7.1.0)
      activemodel (= 7.1.0)
      activerecord (= 7.1.0)
      activestorage (= 7.1.0)
      activesupport (= 7.1.0)
    rails (7.1.1)
      actioncable (= 7.1.1)
      actionmailbox (= 7.1.1)
      actionmailer (= 7.1.1)
      actionpack (= 7.1.1)
      actiontext (= 7.1.1)
      actionview (= 7.1.1)
      activejob (= 7.1.1)
      activemodel (= 7.1.1)
      activerecord (= 7.1.1)
      activestorage (= 7.1.1)
      activesupport (= 7.1.1)
      bundler (>= 1.15.0)
      railties (= 7.1.0)
      railties (= 7.1.1)
    rails-dom-testing (2.2.0)
      activesupport (>= 5.0.0)
      minitest
@@ -349,19 +354,21 @@ GEM
    rails-html-sanitizer (1.6.0)
      loofah (~> 2.21)
      nokogiri (~> 1.14)
    railties (7.1.0)
      actionpack (= 7.1.0)
      activesupport (= 7.1.0)
    railties (7.1.1)
      actionpack (= 7.1.1)
      activesupport (= 7.1.1)
      irb
      rackup (>= 1.0.0)
      rake (>= 12.2)
      thor (~> 1.0, >= 1.2.2)
      zeitwerk (~> 2.6)
    rainbow (3.1.1)
    rake (13.0.6)
    rake (13.1.0)
    rdoc (6.6.2)
      psych (>= 4.0.0)
    redis (4.8.0)
    regexp_parser (2.8.1)
    reline (0.3.2)
    reline (0.4.2)
      io-console (~> 0.5)
    remote_syslog_logger (1.0.4)
      syslog_protocol
@@ -378,10 +385,10 @@ GEM
    rspec-expectations (3.12.3)
      diff-lcs (>= 1.2.0, < 2.0)
      rspec-support (~> 3.12.0)
    rspec-mocks (3.12.5)
    rspec-mocks (3.12.6)
      diff-lcs (>= 1.2.0, < 2.0)
      rspec-support (~> 3.12.0)
    rspec-rails (6.0.3)
    rspec-rails (6.1.0)
      actionpack (>= 6.1)
      activesupport (>= 6.1)
      railties (>= 6.1)
@@ -404,6 +411,10 @@ GEM
      unicode-display_width (>= 2.4.0, < 3.0)
    rubocop-ast (1.29.0)
      parser (>= 3.2.1.0)
    rubocop-capybara (2.19.0)
      rubocop (~> 1.41)
    rubocop-factory_bot (2.24.0)
      rubocop (~> 1.33)
    rubocop-performance (1.16.0)
      rubocop (>= 1.7.0, < 2.0)
      rubocop-ast (>= 0.4.0)
@@ -436,13 +447,14 @@ GEM
      actionpack (>= 5.2)
      activesupport (>= 5.2)
      sprockets (>= 3.0.0)
    stringio (3.1.0)
    swd (2.0.2)
      activesupport (>= 3)
      attr_required (>= 0.0.5)
      faraday (~> 2.0)
      faraday-follow_redirects
    syslog_protocol (0.9.2)
    thor (1.2.2)
    thor (1.3.0)
    timeout (0.4.0)
    trailblazer-option (0.1.2)
    tzinfo (2.0.6)
@@ -489,17 +501,19 @@ PLATFORMS

DEPENDENCIES
  active_model_serializers (>= 0.10.14)
  active_storage_validations (>= 1.0.4)
  active_storage_validations (>= 1.1.0)
  aws-sdk-s3
  bcrypt (~> 3.1.7)
  bigbluebutton-api-ruby (= 1.9.1)
  bootsnap
  capybara
  clamby (~> 1.6.10)
  cssbundling-rails (>= 1.3.3)
  data_migrate (>= 9.2.0)
  debug
  dotenv-rails
  factory_bot_rails
  factory_bot (>= 6.4.1)
  factory_bot_rails (>= 6.4.3)
  faker
  google-cloud-storage (~> 1.44)
  hcaptcha
@@ -507,21 +521,23 @@ DEPENDENCIES
  i18n-language-mapping
  image_processing (~> 1.2)
  jbuilder
  jsbundling-rails (>= 1.2.1)
  jsbundling-rails (>= 1.2.2)
  jwt
  lograge (~> 0.14.0)
  mini_magick (>= 4.9.5)
  omniauth (~> 2.1.0)
  omniauth (~> 2.1.2)
  omniauth-rails_csrf_protection (~> 1.0.1)
  omniauth_openid_connect (>= 0.6.1)
  pagy (~> 6.0, >= 6.0.0)
  pg
  puma (~> 5.6)
  rails (~> 7.1.0)
  rails (~> 7.1.1)
  redis (~> 4.0)
  remote_syslog_logger
  rspec-rails (>= 6.0.2)
  rspec-rails (>= 6.0.4)
  rubocop (~> 1.26)
  rubocop-capybara (~> 2.19.0)
  rubocop-factory_bot (~> 2.24.0)
  rubocop-performance (~> 1.13)
  rubocop-rails (~> 2.18, >= 2.18.0)
  rubocop-rspec (~> 2.9.0)
+1 −1

File changed.

Contains only whitespace changes.

Original line number Diff line number Diff line
@@ -81,6 +81,7 @@
      "account_info": "Kontoinformation",
      "delete_account": "Konto löschen",
      "change_password": "Passwort ändern",
      "set_password": "Setzen Sie Ihr neues Passwort",
      "reset_password": "Passwort zurücksetzen",
      "update_account_info": "Konto aktualisieren",
      "current_password": "Aktuelles Passwort",
@@ -129,6 +130,7 @@
      "click_to_upload": "Klicken zum Hochladen",
      "drag_and_drop": " oder Datei per Drag & Drop hier ablegen.",
      "upload_description": "Ein Office-Dokument oder eine PDF-Datei hochladen (nicht größer als {{size}}). Abhängig von der Größe kann das eine gewisse Zeit dauern.",
      "delete_presentation": "Präsentation löschen",
      "are_you_sure_delete_presentation": "Diese Präsentation wirklich löschen?"
    },
    "shared_access": {
@@ -165,6 +167,7 @@
  "recording": {
    "recording": "Aufzeichnung",
    "recordings": "Aufzeichnungen",
    "processing": "Aufzeichnungen werden erstellt...",
    "name": "Name",
    "length": "Länge",
    "users": "Nutzer:innen",
@@ -347,7 +350,8 @@
        "manage_site_settings": "Nutzer:innen mit dieser Rolle erlauben, die Webseiteneinstellungen zu verwalten",
        "manage_roles": "Nutzer:innen mit dieser Rolle erlauben, andere Rollen zu bearbeiten",
        "shared_list": "Nutzer:innen mit dieser Rolle in die Auswahlliste für die gemeinsame Nutzung von Räumen aufnehmen",
        "room_limit": "Raumlimit"
        "room_limit": "Raumlimit",
        "email_on_signup": "Bei Anmeldung neuer Nutzer E-Mail erhalten"
      }
    }
  },
@@ -358,6 +362,7 @@
        "user_updated": "Nutzer:in aktualisiert.",
        "user_deleted": "Nutzer:in gelöscht.",
        "avatar_updated": "Avatar aktualisiert.",
        "password_changed": "Passwort erfolgreich aktualisiert. Bitte erneut anmelden.",
        "password_updated": "Passwort aktualisiert.",
        "account_activated": "Konto erfolgreich aktiviert. Bitte melden Sie sich in Ihrem Konto an.",
        "activation_email_sent": "E-Mail zur Aktivierung wurde gesendet",
Original line number Diff line number Diff line
@@ -81,6 +81,7 @@
      "account_info": "Πληροφορίες λογαριασμού",
      "delete_account": "Διαγραφή λογαριασμού",
      "change_password": "Αλλαγή κωδικού πρόσβασης",
      "set_password": "Ορίστε τον νέο κωδικό πρόσβασής σας",
      "reset_password": "Επαναφορά κωδικού πρόσβασης",
      "update_account_info": "Ενημέρωση πληροφοριών λογαριασμού",
      "current_password": "Τρέχων κωδικός πρόσβασης",
@@ -129,6 +130,7 @@
      "click_to_upload": "Κάντε κλικ για μεταφόρτωση ",
      "drag_and_drop": "ή σύρετε και αποθέστε το αρχείο",
      "upload_description": "Μεταφόρτωση εγγράφου ή αρχείου PDF (έως {{size}}). Ανάλογα με το μέγεθος του αρχείου, απαιτείται χρόνος για τη μεταφόρτωση πριν τη χρήση του",
      "delete_presentation": "Διαγραφή παρουσίασης",
      "are_you_sure_delete_presentation": "Θέλετε σίγουρα να διαγράψετε αυτή την παρουσίαση;"
    },
    "shared_access": {
@@ -165,6 +167,7 @@
  "recording": {
    "recording": "Καταγραφή",
    "recordings": "Καταγραφές",
    "processing": "Επεξεργασία καταγραφών... ",
    "name": "Όνομα",
    "length": "Μήκος",
    "users": "Χρήστες",
@@ -271,6 +274,8 @@
        "privacy_policy": "Πολιτική Απορρήτου",
        "change_term_links": "Αλλαγή των συνδέσμων για τους όρους χρήσης που εμφανίζονται στο κάτω μέρος της σελίδας",
        "change_privacy_link": "Αλλαγή του συνδέσμου για το απόρρητο που εμφανίζεται στο κάτω μέρος της σελίδας",
        "helpcenter": "Κέντρο Βοήθειας",
        "change_helpcenter_link": "Αλλαγή του συνδέσμου για το Κέντρο βοήθειας που εμφανίζεται κάτω από το προφίλ. ",
        "change_url": "Αλλαγή URL",
        "enter_link": "Εισαγάγετε εδώ το σύνδεσμο"
      },
@@ -279,7 +284,9 @@
        "allow_users_to_share_rooms": "Να επιτρέπεται οι χρήστες να κοινοποιούν τις αίθουσες διασκέψεων",
        "allow_users_to_share_rooms_description": "Η ρύθμιση σε «απενεργοποιημένο» θα καταργήσει το κουμπί από το αναπτυσσόμενο μενού επιλογών της αίθουσας διάσκεψης, εμποδίζοντας τους χρήστες να διαμοιράζονται αίθουσες διασκέψεων",
        "allow_users_to_preupload_presentation": "Να επιτρέπεται στους χρήστες να μεταφορτώνουν παρουσιάσεις νωρίτερα",
        "allow_users_to_preupload_presentation_description": "Οι χρήστες μπορούν να μεταφορτώνουν νωρίτερα μια παρουσίαση για χρήση όπως την προεπιλεγμένη για συγκεκριμένη αίθουσα διασκέψεων"
        "allow_users_to_preupload_presentation_description": "Οι χρήστες μπορούν να μεταφορτώνουν νωρίτερα μια παρουσίαση για χρήση όπως την προεπιλεγμένη για συγκεκριμένη αίθουσα διασκέψεων",
        "default_visibility": "Προεπιλεγμένη ορατότητα καταγραφής",
        "default_visibility_description": "Όλες οι καταγραφές που δημιουργήθηκαν πρόσφατα θα έχουν αυτήν την ορατότητα από προεπιλογή"
      },
      "registration": {
        "registration": "Εγγραφή",
@@ -347,7 +354,9 @@
        "manage_site_settings": "Να επιτρέπεται στους χρήστες με αυτόν τον ρόλο να διαχειρίζονται τις ρυθμίσεις του ιστοτόπου",
        "manage_roles": "Να επιτρέπεται στους χρήστες με αυτόν τον ρόλο να επεξεργάζονται άλλους ρόλους",
        "shared_list": "Να συμπεριλαμβάνονται χρήστες με αυτόν τον ρόλο στο αναπτυσσόμενο μενού για κοινή χρήση αιθουσών",
        "room_limit": "Όριο αίθουσας"
        "room_limit": "Όριο αίθουσας",
        "email_on_signup": "Λάβετε ένα email όταν εγγραφεί ένας νέος χρήστης",
        "allowed_recording_visibility": "Επιτρεπόμενες ορατότητες καταγραφής"
      }
    }
  },
@@ -358,6 +367,7 @@
        "user_updated": "Ο χρήστης ενημερώθηκε.",
        "user_deleted": "Ο χρήστης διαγράφηκε.",
        "avatar_updated": "Το άβαταρ ενημερώθηκε.",
        "password_changed": "Επιτυχής ενημέρωση του κωδικού πρόσβασής σας. Παρακαλούμε συνδεθείτε ξανά.",
        "password_updated": "Ο κωδικός πρόσβασης ενημερώθηκε.",
        "account_activated": "Ο λογαριασμός ενεργοποιήθηκε επιτυχώς. Παρακαλούμε συνδεθείτε στον λογαριασμό σας.",
        "activation_email_sent": "Το email επιβεβαίωσης στάλθηκε.",
@@ -390,6 +400,7 @@
        "brand_image_updated": "Η εικόνα επωνυμίας ενημερώθηκε.",
        "brand_image_deleted": "Η εικόνα επωνυμίας διαγράφηκε.",
        "privacy_policy_updated": "Η πολιτική ιδιωτικότητας ενημερώθηκε.",
        "helpcenter_updated": "Ο σύνδεσμος για το Κέντρο βοήθειας ενημερώθηκε. ",
        "terms_of_service_updated": "Οι όριο χρήσης ενημερώθηκαν."
      },
      "recording": {
@@ -414,6 +425,7 @@
      "file_size_too_large": "Το μέγεθος αρχείου είναι πολύ μεγάλο.",
      "file_upload_error": "Το αρχείο δεν μπορεί να μεταφορτωθεί.",
      "signin_required": "Πρέπει να συνδεθείτε για πρόσβαση σε αυτή τη σελίδα",
      "malware_detected": "Εντοπίστηκε malware! Το αρχείο που ανεβάσατε ίσως περιέχει malware. Παρακαλούμε ελέγξτε το αρχείο σας και δοκιμάστε ξανά.",
      "roles": {
        "role_assigned": "Αυτός ο ρόλος δεν μπορεί να διαγραφεί καθώς έχει εκχωρηθεί σε τουλάχιστον έναν χρήστη."
      },
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
  "close": "Close",
  "delete": "Delete",
  "copy": "Copy Join Link",
  "copy_viewer_code": "Copy Viewer Code",
  "copy_moderator_code": "Copy Moderator Code",
  "or": "Or",
  "online": "Online",
  "help_center": "Help Center",
@@ -130,6 +132,7 @@
      "click_to_upload": "Click to Upload",
      "drag_and_drop": " or drag and drop",
      "upload_description": "Upload any office document or PDF file (not larger than {{size}}). Depending on the size of the file, it may require additional time to upload before it can be used",
      "delete_presentation": "Delete Presentation",
      "are_you_sure_delete_presentation": "Are you sure you want to delete this presentation?"
    },
    "shared_access": {
@@ -273,6 +276,8 @@
        "privacy_policy": "Privacy Policy",
        "change_term_links": "Change the terms links that appears at the bottom of the page",
        "change_privacy_link": "Change the privacy link that appears at the bottom of the page",
        "helpcenter": "Help Center",
        "change_helpcenter_link": "Change the help center link that appears under the profile dropdown",
        "change_url": "Change URL",
        "enter_link": "Enter link here"
      },
@@ -281,7 +286,9 @@
        "allow_users_to_share_rooms": "Allow Users to Share Rooms",
        "allow_users_to_share_rooms_description": "Setting to disabled will remove the button from the room options dropdown, preventing users from sharing rooms",
        "allow_users_to_preupload_presentation": "Allow Users to Preupload Presentations",
        "allow_users_to_preupload_presentation_description": "Users can preupload a presentation to be used as the default presentation for that specific room"
        "allow_users_to_preupload_presentation_description": "Users can preupload a presentation to be used as the default presentation for that specific room",
        "default_visibility": "Default Recording Visibility",
        "default_visibility_description": "All newly created recordings will have this visibility by default"
      },
      "registration": {
        "registration": "Registration",
@@ -349,7 +356,9 @@
        "manage_site_settings": "Allow users with this role to manage site settings",
        "manage_roles": "Allow users with this role to edit other roles",
        "shared_list": "Include users with this role in the dropdown for sharing rooms",
        "room_limit": "Room Limit"
        "room_limit": "Room Limit",
        "email_on_signup": "Receive an email when a new user signs up",
        "allowed_recording_visibility": "Allowed recording visibilities"
      }
    }
  },
@@ -385,7 +394,9 @@
        "access_code_copied": "The access code has been copied.",
        "access_code_generated": "A new access code has been generated.",
        "access_code_deleted": "The access code has been deleted.",
        "copied_meeting_url": "The meeting URL has been copied. The link can be used to join the meeting."
        "copied_meeting_url": "The meeting URL has been copied. The link can be used to join the meeting.",
        "copied_viewer_code": "The viewer access code has been copied.",
        "copied_moderator_code": "The moderator access code has been copied."
      },
      "site_settings": {
        "site_setting_updated": "The site setting has been updated.",
@@ -393,6 +404,7 @@
        "brand_image_updated": "The brand image has been updated.",
        "brand_image_deleted": "The brand image has been deleted.",
        "privacy_policy_updated": "The privacy policy has been updated.",
        "helpcenter_updated": "The help center link has been updated.",
        "terms_of_service_updated": "The terms of service have been updated."
      },
      "recording": {
@@ -417,6 +429,7 @@
      "file_size_too_large": "The file size is too large.",
      "file_upload_error": "The file can't be uploaded.",
      "signin_required": "You must be signed in to access this page.",
      "malware_detected": "Malware Detected! The file you uploaded may contain malware. Please check your file and try again.",
      "roles": {
        "role_assigned": "This role can't be deleted because it is assigned to at least one user."
      },
Original line number Diff line number Diff line
@@ -29,7 +29,7 @@
  "action_permanent": "Cette action ne peut être annulée",
  "homepage": {
    "welcome_bbb": "Bienvenue sur BigBlueButton.",
    "bigbluebutton_description": "BigBluButton est un système de webconférence dont le code source ouvert et qui est destinée à l'enseignement en ligne. La plateforme permet de profiter au mieux du temps dédié à la formation en favorisant la collaboration et une supervision en temps réel. ",
    "bigbluebutton_description": "BigBlueButton est un système de webconférence dont le code source ouvert et qui est destinée à l'enseignement en ligne. La plateforme permet de profiter au mieux du temps dédié à la formation en favorisant la collaboration et une supervision en temps réel. ",
    "greenlight_description": "Créez vos salles et sessions ou rejoignez celles de autres grâce à un lien court et pratique.",
    "learn_more": "En apprendre plus sur BigBlueButton",
    "explore_features": "Découvrez les fonctionnalités",
@@ -81,6 +81,7 @@
      "account_info": "Informations sur le compte",
      "delete_account": "Supprimer le compte",
      "change_password": "Changer de mot de passe",
      "set_password": "Définissez votre nouveau mot de passe",
      "reset_password": "Réinitialiser le mot de passe",
      "update_account_info": "Mettre à jour les informations du compte",
      "current_password": "Mot de passe actuel",
@@ -110,7 +111,7 @@
    "delete_room": "Supprimer une salle",
    "create_new_room": "Créer une nouvelle salle",
    "enter_room_name": "Saisissez le nom de la salle",
    "shared_by": "Partagé par",
    "shared_by": "partagé par",
    "last_session": "Dernière session : {{ localizedTime }}",
    "no_last_session": "Il n'y a aucune session précédente",
    "search_not_found": "Aucune salle trouvée",
@@ -129,6 +130,7 @@
      "click_to_upload": "Cliquez pour téléverser",
      "drag_and_drop": "ou glissez-déposez",
      "upload_description": "Téléverser tout document Office ou fichier PDF (d'un volume inférieur à {{size}}). Le délai avant de pouvoir utiliser le fichier varie en fonction de son volume.",
      "delete_presentation": "Effacer la présentation",
      "are_you_sure_delete_presentation": "Etes-vous sûr de vouloir supprimer cet enregistrement ?"
    },
    "shared_access": {
@@ -165,6 +167,7 @@
  "recording": {
    "recording": "Enregistrement",
    "recordings": "Enregistrements",
    "processing": "Enregistrements en cours de traitement...",
    "name": "Nom",
    "length": "Durée",
    "users": "Utilisateurs",
@@ -347,7 +350,8 @@
        "manage_site_settings": "Permettre aux utilisateurs avec ce rôle de gérer les paramètres du site",
        "manage_roles": "Permettre aux utilisateurs avec ce rôle de modifier les autres rôles",
        "shared_list": "Inclure les utilisateurs avec ce rôle dans le menu déroulant du partage des salles",
        "room_limit": "Limite pour la salle"
        "room_limit": "Limite pour la salle",
        "email_on_signup": "Recevoir un email quand un nouvel utilisateur s'inscrit"
      }
    }
  },
@@ -358,6 +362,7 @@
        "user_updated": "L'utilisateur a été mis à jour.",
        "user_deleted": "L'utilisateur a été supprimé.",
        "avatar_updated": "L'avatar a été mis à jour.",
        "password_changed": "Votre mot de passe a été mis à jour. Veuillez vous identifier à nouveau.",
        "password_updated": "Le mot de passe a été changé.",
        "account_activated": "Le compte a été activé avec succès. Veuillez vous connecter à votre compte.",
        "activation_email_sent": "Un courriel d'activation a été envoyé.",
Original line number Diff line number Diff line
@@ -81,6 +81,7 @@
      "account_info": "Información da conta",
      "delete_account": "Eliminar a conta",
      "change_password": "Cambiar o contrasinal",
      "set_password": "Defina o seu novo contrasinal",
      "reset_password": "Restabelecer o contrasinal",
      "update_account_info": "Actualizar a información da conta",
      "current_password": "Contrasinal actual",
@@ -129,6 +130,7 @@
      "click_to_upload": "Prema para enviar",
      "drag_and_drop": " ou arrastre e solte",
      "upload_description": "Envíe calquera documento de oficina ou ficheiro PDF (non maior que {{size}}). Dependendo do tamaño do ficheiro, pode requirir un tempo adicional para envialo antes de poder utilizalo",
      "delete_presentation": "Eliminar presentación",
      "are_you_sure_delete_presentation": "Confirma que quere eliminar esta presentación?"
    },
    "shared_access": {
@@ -165,6 +167,7 @@
  "recording": {
    "recording": "Gravación",
    "recordings": "Gravacións",
    "processing": "Procesando as gravacións…",
    "name": "Nome",
    "length": "Duración",
    "users": "Usuarios",
@@ -347,7 +350,8 @@
        "manage_site_settings": "Permitir que os usuarios con este rol xestionen os axustes do sitio",
        "manage_roles": "Permitir que os usuarios con este rol editen outros roles",
        "shared_list": "Incluir os usuarios con este rol no menú despregábel para compartir salas",
        "room_limit": "Límite de salas"
        "room_limit": "Límite de salas",
        "email_on_signup": "Recibir un correo-e cando se rexistre un novo usuario"
      }
    }
  },
@@ -358,6 +362,7 @@
        "user_updated": "O usuario foi actualizado.",
        "user_deleted": "O usuario foi eliminado.",
        "avatar_updated": "O avatar foi actualizado.",
        "password_changed": "O seu contrasinal foi actualizado correctamente. Acceda de novo.",
        "password_updated": "O contrasinal foi actualizado.",
        "account_activated": "A súa conta foi activada.",
        "activation_email_sent": "Enviouse un correo que contén as instrucións para activar a súa conta.",
Original line number Diff line number Diff line
@@ -81,6 +81,7 @@
      "account_info": "アカウント情報",
      "delete_account": "アカウント削除",
      "change_password": "パスワード変更",
      "set_password": "新しいパスワードを入力",
      "reset_password": "パスワード再設定",
      "update_account_info": "アカウント情報の更新",
      "current_password": "現在のパスワード",
@@ -129,6 +130,7 @@
      "click_to_upload": "クリックしてアップロード",
      "drag_and_drop": "またはドラッグ&ドロップ",
      "upload_description": "OfficeドキュメントかPDFファイル(サイズ{{size}}以下)をアップロードしてください。ファイルの大きさによっては、使用可能になるまでに少し時間がかかるかもしれません。",
      "delete_presentation": "プレゼンファイルを削除",
      "are_you_sure_delete_presentation": "本当にこのプレゼンファイルを削除しますか?"
    },
    "shared_access": {
@@ -165,6 +167,7 @@
  "recording": {
    "recording": "録画",
    "recordings": "録画",
    "processing": "録画を処理しています...",
    "name": "名前",
    "length": "長さ",
    "users": "参加者",
@@ -271,6 +274,8 @@
        "privacy_policy": "プライバシーポリシー",
        "change_term_links": "ページ下方の利用規約へのリンクを変更する",
        "change_privacy_link": "ページ下方のプライバシーポリシーへのリンクを変更する",
        "helpcenter": "ヘルプセンター",
        "change_helpcenter_link": "プロフィールのドロップダウンメニュー内に表示されるヘルプセンターのリンクを変更",
        "change_url": "URL変更",
        "enter_link": "リンクをここに入力"
      },
@@ -279,12 +284,14 @@
        "allow_users_to_share_rooms": "ユーザーに会議室の共有を許可",
        "allow_users_to_share_rooms_description": "無効に設定した場合、各会議室のドロップダウンからボタンが削除され、ユーザーが会議室を共有できなくなります",
        "allow_users_to_preupload_presentation": "ユーザーにプレゼンファイルの事前アップロードを許可",
        "allow_users_to_preupload_presentation_description": "ユーザーがプレゼンファイルを事前にアップロードし、この会議室専用の標準プレゼンとして使用できるようになります"
        "allow_users_to_preupload_presentation_description": "ユーザーがプレゼンファイルを事前にアップロードし、この会議室専用の標準プレゼンとして使用できるようになります",
        "default_visibility": "デフォルトの録画公開度",
        "default_visibility_description": "新しい録画は、まずは全てこの公開度となります。"
      },
      "registration": {
        "registration": "ユーザー登録",
        "role_mapping_by_email": "メールアドレスによる役割の割り当て",
        "role_mapping_by_email_description": "メールアドレスを使ってユーザーに役割を割り当てます。役割1=メール1, 役割2=メール2、の形式にしてください。",
        "role_mapping_by_email_description": "メールアドレスによって、割り当てられる役割を固定します。役割1=メール1, 役割2=メール2、の形式にしてください。",
        "enter_role_mapping_rule": "役割割り当てルールを入力",
        "resync_on_login": "サインインごとにユーザーのデーターを再同期",
        "resync_on_login_description": "サインインのたびにユーザーの情報を再同期し、外部の承認提供者の情報とGreenlightの情報とを常に一致させます",
@@ -347,7 +354,9 @@
        "manage_site_settings": "この役割のユーザーに、サイト全体の設定を許可する",
        "manage_roles": "この役割のユーザーに、他の役割の内容を変更することを許可する",
        "shared_list": "この役割のユーザーを、会議室を共有できるユーザーのリストに含める",
        "room_limit": "作成できる会議室数の制限"
        "room_limit": "作成できる会議室数の制限",
        "email_on_signup": "新しいユーザーが登録したときメールを受け取る",
        "allowed_recording_visibility": "許可する公開度"
      }
    }
  },
@@ -358,6 +367,7 @@
        "user_updated": "ユーザーが更新されました。",
        "user_deleted": "ユーザーが削除されました。",
        "avatar_updated": "アバターが更新されました。",
        "password_changed": "パスワードが更新されました。もう一度サインインしてください。",
        "password_updated": "パスワードが更新されました。",
        "account_activated": "アカウントが有効化されました。サインインしてみてください。",
        "activation_email_sent": "アクチベーションメールが送信されました",
@@ -390,6 +400,7 @@
        "brand_image_updated": "ブランド画像がアップデートされました。",
        "brand_image_deleted": "ブランド画像が消去されました。",
        "privacy_policy_updated": "プライバシーポリシーがアップデートされました。",
        "helpcenter_updated": "ヘルプセンターのリンクが更新されました。",
        "terms_of_service_updated": "利用規約がアップデートされました。"
      },
      "recording": {
@@ -414,6 +425,7 @@
      "file_size_too_large": "ファイルが大きすぎます。",
      "file_upload_error": "ファイルがアップロードできません。",
      "signin_required": "このページに接続するにはサインインが必要です",
      "malware_detected": "マルウェアが検出されました!アップロードしたファイルにはマルウェアが含まれている可能性があります。ファイルを確認して、もう一度試してください。",
      "roles": {
        "role_assigned": "この役割はすでにユーザーに割り当てられているため、削除できません。"
      },
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@
  "are_you_sure": "Вы уверены?",
  "return_home": "Вернуться Домой",
  "created_at": "Создано",
  "view_recordings": "Посмотреть записи",
  "join_session": "Присоединиться к вебинару",
  "no_result_search_input": "Не удалось найти никаких результатов для \"{{ searchInput }}\"",
  "action_permanent": "Это действие не может быть отменено.",
  "homepage": {
@@ -79,6 +81,7 @@
      "account_info": "Информация об аккаунте",
      "delete_account": "Удалить аккаунт",
      "change_password": "Сменить пароль",
      "set_password": "Установить новый пароль",
      "reset_password": "Сбросить пароль",
      "update_account_info": "Обновить информацию об аккаунте",
      "current_password": "Текущий пароль",
@@ -108,7 +111,7 @@
    "delete_room": "Удалить комнату",
    "create_new_room": "Создать новую комнату",
    "enter_room_name": "Введите название комнаты",
    "shared_by": "доступ предоставлен ",
    "shared_by": "опубликовано",
    "last_session": "Последний сеанс: {{ localizedTime }}",
    "no_last_session": "Предыдущие сеансы отсутствуют",
    "search_not_found": "Комнаты не найдены",
@@ -127,6 +130,7 @@
      "click_to_upload": "Нажмите, чтобы загрузить ",
      "drag_and_drop": "или перетащите и отпустите",
      "upload_description": "Загрузите любой офисный документ или PDF (размером не более {{size}}). В зависимости от размера файла, для загрузки может потребоваться дополнительное время, прежде чем его можно будет использовать",
      "delete_presentation": "Удалить презентацию",
      "are_you_sure_delete_presentation": "Вы уверены, что хотите удалить эту презентацию?"
    },
    "shared_access": {
@@ -163,6 +167,7 @@
  "recording": {
    "recording": "Запись",
    "recordings": "Записи",
    "processing": "Записи обрабатываются...",
    "name": "Имя",
    "length": "Длина",
    "users": "Пользователи",
@@ -171,11 +176,15 @@
    "published": "Опубликована",
    "unpublished": "Не опубликована",
    "protected": "Защищенные",
    "public": "Публичная",
    "public_protected": "Публичная/Защищенная",
    "length_in_minutes": "{{recording.length}} мин.",
    "processing_recording": "Обработка записи, это может занять несколько минут...",
    "copy_recording_urls": "Скопировать ссылку на запись",
    "recordings_list_empty": "У вас еще нет записей!",
    "public_recordings_list_empty": "Публичных записей нет!",
    "recordings_list_empty_description": "Записи появятся здесь после того, как вы начнете встречу и запишете ее.",
    "public_recordings_list_empty_description": "Записи будут появляться здесь по мере поступления.",
    "delete_recording": "Удалить запись",
    "are_you_sure_delete_recording": "Вы уверены, что хотите удалить эту запись?",
    "search_not_found": "Записи не найдены"
@@ -265,6 +274,8 @@
        "privacy_policy": "Политика конфиденциальности",
        "change_term_links": "Изменить ссылку на правила, которая появляется внизу страницы",
        "change_privacy_link": "Изменить ссылку на политику конфиденциальности, которая появляется внизу страницы",
        "helpcenter": "Справочный центр",
        "change_helpcenter_link": "Измените ссылку Справочного центра, которая появляется под раскрывающимся списком профиля",
        "change_url": "Изменить URL",
        "enter_link": "Введите ссылку здесь"
      },
@@ -273,7 +284,9 @@
        "allow_users_to_share_rooms": "Разрешить пользователям совместное использование комнат",
        "allow_users_to_share_rooms_description": "Отключение опции удалит кнопку из выпадающего меню опций комнаты и не позволит пользователям совместно использовать комнаты",
        "allow_users_to_preupload_presentation": "Разрешить пользователям предварительно загружать презентации",
        "allow_users_to_preupload_presentation_description": "Пользователи могут предварительно загрузить презентацию, которая будет использоваться в качестве презентации по умолчанию для этой комнаты"
        "allow_users_to_preupload_presentation_description": "Пользователи могут предварительно загрузить презентацию, которая будет использоваться в качестве презентации по умолчанию для этой комнаты",
        "default_visibility": "Статус видимости записи по умолчанию",
        "default_visibility_description": "Все вновь созданные записи будут иметь этот статус видимости по умолчанию."
      },
      "registration": {
        "registration": "Регистрация",
@@ -341,7 +354,9 @@
        "manage_site_settings": "Разрешить пользователям с этой ролью управлять настройками сайта",
        "manage_roles": "Разрешить пользователям с этой ролью изменять другие роли",
        "shared_list": "Включите пользователей с этой ролью в раскрывающийся список для совместного использования комнат.",
        "room_limit": "Ограничение комнаты"
        "room_limit": "Ограничение комнаты",
        "email_on_signup": "Отправлять email, когда новый пользователь зарегистрируется",
        "allowed_recording_visibility": "Разрешённый статус видимости записи"
      }
    }
  },
@@ -352,6 +367,7 @@
        "user_updated": "Пользователь обновлен.",
        "user_deleted": "Пользователь удален.",
        "avatar_updated": "Аватар обновлен.",
        "password_changed": "Пароль успешно обновлен. Пожалуйста, войдите в систему еще раз.",
        "password_updated": "Пароль обновлен.",
        "account_activated": "Учетная запись успешно активирована. Пожалуйста, войдите в ваш аккаунт.",
        "activation_email_sent": "Письмо активации было выслано",
@@ -384,6 +400,7 @@
        "brand_image_updated": "Фирменное изображение обновлено.",
        "brand_image_deleted": "Фирменное изображение удалено.",
        "privacy_policy_updated": "Политика конфиденциальности обновлена.",
        "helpcenter_updated": "Ссылка на Справочный центр обновлена",
        "terms_of_service_updated": "Условия использования обновлены."
      },
      "recording": {
Original line number Diff line number Diff line
@@ -81,6 +81,7 @@
      "account_info": "Hesap bilgileri",
      "delete_account": "Hesabı sil",
      "change_password": "Parolayı değiştir",
      "set_password": "Yeni parolanızı ayarlayın",
      "reset_password": "Parolayı sıfırla",
      "update_account_info": "Hesap bilgilerinizi güncelleyin",
      "current_password": "Geçerli parola",
@@ -129,6 +130,7 @@
      "click_to_upload": "Yüklemek için tıklayın",
      "drag_and_drop": "ya da sürükleyip bırakın",
      "upload_description": "Herhangi bir Office belgesi ya da PDF dosyası ({{size}} boyutundan büyük olmayan) yükleyin. Dosyanın boyutuna bağlı olarak, kullanılmadan önce karşıya yüklenmesi biraz sürebilir.",
      "delete_presentation": "Sunumu sil",
      "are_you_sure_delete_presentation": "Bu sunumu silmek istediğinize emin misiniz?"
    },
    "shared_access": {
@@ -165,6 +167,7 @@
  "recording": {
    "recording": "Kaydediliyor",
    "recordings": "Kayıtlar",
    "processing": "Kayıtlar işleniyor...",
    "name": "Ad",
    "length": "Süre",
    "users": "Kullanıcılar",
@@ -271,6 +274,8 @@
        "privacy_policy": "Gizlilik ilkesi",
        "change_term_links": "Sayfanın altında görüntülenecek koşullar bağlantısını değiştir",
        "change_privacy_link": "Sayfanın altında görüntülenecek gizlilik bağlantısını değiştir",
        "helpcenter": "Yardım merkezi",
        "change_helpcenter_link": "Profil açılır menüsünde görüntülenecek yardım merkezi bağlantısını değiştirin",
        "change_url": "Adresi değiştir",
        "enter_link": "Bağlantıyı buraya yazın"
      },
@@ -279,7 +284,9 @@
        "allow_users_to_share_rooms": "Kullanıcılar odayı paylaşabilsin",
        "allow_users_to_share_rooms_description": "Bu seçenek devre dışı olarak ayarlandığında, düğme oda seçenekleri açılır menüsünden kaldırılır ve kullanıcıların odaları paylaşması engellenir.",
        "allow_users_to_preupload_presentation": "Kullanıcılar önceden sunum yükleyebilsin",
        "allow_users_to_preupload_presentation_description": "Kullanıcılar, bu odada varsayılan olarak kullanılacak bir sunumu önceden yükleyebilir"
        "allow_users_to_preupload_presentation_description": "Kullanıcılar, bu odada varsayılan olarak kullanılacak bir sunumu önceden yükleyebilir",
        "default_visibility": "Varsayılan kaydetme görünürlüğü",
        "default_visibility_description": "Yeni oluşturulan tüm kayıtların varsayılan görünürlüğünü belirler"
      },
      "registration": {
        "registration": "Hesap açma",
@@ -347,7 +354,9 @@
        "manage_site_settings": "Bu roldeki kullanıcılar site ayarlarını yönetebilsin",
        "manage_roles": "Bu roldeki kullanıcılar diğer kullanıcı rollerini düzenleyebilsin",
        "shared_list": "Bu roldeki kullanıcılar oda paylaşma açılan kutularında görüntülensin",
        "room_limit": "Oda sayısı sınırı"
        "room_limit": "Oda sayısı sınırı",
        "email_on_signup": "Yeni bir kullanıcı hesap açtığında e-posta alın",
        "allowed_recording_visibility": "İzin verilen kaydetme görünürlükleri"
      }
    }
  },
@@ -358,6 +367,7 @@
        "user_updated": "Kullanıcı güncellendi.",
        "user_deleted": "Kullanıcı silindi.",
        "avatar_updated": "Avatar güncellendi.",
        "password_changed": "Parolanız güncellendi. Lütfen yeniden oturum açın.",
        "password_updated": "Parola güncellendi.",
        "account_activated": "Hesap etkinleştirildi. Lütfen hesabınızla oturum açın.",
        "activation_email_sent": "Etkinleştirme e-postası gönderildi.",
@@ -390,6 +400,7 @@
        "brand_image_updated": "Marka görseli güncellendi.",
        "brand_image_deleted": "Marka görseli silindi.",
        "privacy_policy_updated": "Gizlilik ilkesi güncellendi.",
        "helpcenter_updated": "Yardım merkezi bağlantısı güncellendi.",
        "terms_of_service_updated": "Hizmet koşulları güncellendi."
      },
      "recording": {
@@ -414,6 +425,7 @@
      "file_size_too_large": "Dosya boyutu çok büyük.",
      "file_upload_error": "Dosya yüklenemedi.",
      "signin_required": "Bu sayfaya erişebilmek için oturum açmalısınız",
      "malware_detected": "Zararlı yazılım algılandı! Yüklediğiniz dosyada zararlı yazılım bulunuyor. Lütfen dosyayı denetleyip yeniden deneyin.",
      "roles": {
        "role_assigned": "Bu rol, en az bir kullanıcıya atanmış olduğundan silinemez."
      },
Original line number Diff line number Diff line
@@ -81,6 +81,7 @@
      "account_info": "Інформація про обліковий запис",
      "delete_account": "Видалити обліковий запис",
      "change_password": "Змінити пароль",
      "set_password": "Встановіть свій новий пароль",
      "reset_password": "Скинути пароль",
      "update_account_info": "Оновити дані облікового запису",
      "current_password": "Чинний пароль",
@@ -110,7 +111,7 @@
    "delete_room": "Видалити кімнату",
    "create_new_room": "Створити нову кімнату",
    "enter_room_name": "Введіть назву кімнати",
    "shared_by": "надається",
    "shared_by": "поділився",
    "last_session": "Останній сеанс: {{ localizedTime }}",
    "no_last_session": "Попередніх сесій не створено",
    "search_not_found": "Кімнати не знайдено",
@@ -129,6 +130,7 @@
      "click_to_upload": "Натисніть, щоб додати",
      "drag_and_drop": ", або перетягніть сюди файл",
      "upload_description": "Завантажте документ у офісному форматі, чи PDF файл (обсягом не більше, ніж {{size}}). Залежно від розміру файлу, потрібен певний час для його завантаження, перш ніж він буде доступним для використання",
      "delete_presentation": "Видалити презентацію",
      "are_you_sure_delete_presentation": "Ви певні, що слід видалити цю презентацію?"
    },
    "shared_access": {
@@ -165,6 +167,7 @@
  "recording": {
    "recording": "Запис",
    "recordings": "Записи",
    "processing": "Обробка запису...",
    "name": "Назва",
    "length": "Тривалість",
    "users": "Користувачі",
@@ -211,7 +214,7 @@
      "create_account": "Створити обліковий запис",
      "create_room": "Створити кімнату",
      "create_new_room": "Створити нову кімнату",
      "user_created_at": "Створено: {{user.created_at}}",
      "user_created_at": "Створено: {{localizedTime}}",
      "are_you_sure_delete_account": "Ви певні, що слід видалити обліковий запис {{user.name}}?",
      "delete_account_warning": "Якщо ви оберете видалення облікового запису, ця дія буде незворотна.",
      "empty_active_users": "Немає наразі активних користувачів на цьому сервері!",
@@ -271,6 +274,8 @@
        "privacy_policy": "Політика приватності",
        "change_term_links": "Змінити посилання на умови використання внизу сторінки",
        "change_privacy_link": "Змінити посилання на політику приватності внизу сторінки",
        "helpcenter": "Центр допомоги",
        "change_helpcenter_link": "Змініть посилання на центр довідки, яке з'являється під розкривним списком профілю.",
        "change_url": "Змінити URL",
        "enter_link": "Введіть посилання тут"
      },
@@ -279,7 +284,9 @@
        "allow_users_to_share_rooms": "Добволити спільне використання кімнат",
        "allow_users_to_share_rooms_description": "Якщо вимкнено, у меню налаштувань кімнати буде вилучено кнопку для надання спільного доступу до кімнати",
        "allow_users_to_preupload_presentation": "Дозволити користувачам попереднє завантаження презентацій",
        "allow_users_to_preupload_presentation_description": "Користувач може заздалегідь завантажити презентацію, яка буде використовуватись як стандартна для даної кімнати"
        "allow_users_to_preupload_presentation_description": "Користувач може заздалегідь завантажити презентацію, яка буде використовуватись як стандартна для даної кімнати",
        "default_visibility": "За замовчуванням видимість запису",
        "default_visibility_description": "Усі новостворені записи матимуть цю видимість за замовчуванням."
      },
      "registration": {
        "registration": "Реєстрація",
@@ -347,7 +354,9 @@
        "manage_site_settings": "Дозволити користувачам з цією роллю керувати налаштуваннями сайту",
        "manage_roles": "Дозволити користувачам з цією роллю редагувати інші ролі",
        "shared_list": "Дозволити спільне використання кімнат з користувачами з цією роллю",
        "room_limit": "Обмеження на кількість кімнат"
        "room_limit": "Обмеження на кількість кімнат",
        "email_on_signup": "Отримувати електронного листа, коли новий користувач реєструється",
        "allowed_recording_visibility": "Видимість дозволених записів"
      }
    }
  },
@@ -358,6 +367,7 @@
        "user_updated": "Дані користувача оновлено.",
        "user_deleted": "Користувача видалено.",
        "avatar_updated": "Аватар оновлено.",
        "password_changed": "Успішно оновлено ваш пароль. Будь ласка, увійдіть знову.",
        "password_updated": "Пароль оновлено.",
        "account_activated": "Обліковий запис активовано.",
        "activation_email_sent": "Вам надіслано електронного листа з вказівками щодо активації вашого облікового запису.",
@@ -390,6 +400,7 @@
        "brand_image_updated": "Зобрадення логотипу оновлено.",
        "brand_image_deleted": "Колір оформлення видалено.",
        "privacy_policy_updated": "Політику приватності оновлено.",
        "helpcenter_updated": "Посилання на центр допомоги було оновлено.",
        "terms_of_service_updated": "Умови використання оновлено."
      },
      "recording": {
@@ -414,6 +425,7 @@
      "file_size_too_large": "Файл надто великий.",
      "file_upload_error": "Файл не може бути завантажено.",
      "signin_required": "Для доступу до цієї сторінки слід увійти в систему.",
      "malware_detected": "Виявлено шкідливе програмне забезпечення! Файл, який ви завантажили, може містити шкідливе програмне забезпечення. Будь ласка, перевірте свій файл і спробуйте ще раз.",
      "roles": {
        "role_assigned": "Цю роль неможливо видалити, бо її надано принаймні одному користувачу."
      },
Original line number Diff line number Diff line
@@ -284,6 +284,21 @@ input.search-bar {
  }
}

.custom-select {
  .select-brand-control {
    border-color: var(--brand-color) !important;
    box-shadow: 0 0 0 1px var(--brand-color) !important;
  }

  .select-brand-option {
    background-color: whitesmoke;
    color: var(--brand-color) !important;
    &:active {
      background-color: var(--brand-color-light) !important;
    }
  }
}

//Brand
:root {
  --brand-color: '';
Original line number Diff line number Diff line
@@ -66,3 +66,9 @@
    display: none;
  }
}

#updateUserFormLanguage .dropdown-menu {
  max-height: 20vh;
  overflow-y: auto;
  background-clip: border-box;
}
Original line number Diff line number Diff line
@@ -130,3 +130,7 @@
    padding-top: 2rem;
  }
}

#dropdown-toggle {
  min-width: 2px;
}
Original line number Diff line number Diff line
@@ -50,7 +50,7 @@ module Api
        private

        def role_params
          params.require(:role).permit(:role_id, :name, :value)
          params.require(:role).permit(:role_id, :name, :value, value: [])
        end

        def create_default_room
Original line number Diff line number Diff line
@@ -35,7 +35,7 @@ module Api
          render_data data: site_settings, status: :ok
        end

        # GET /api/v1/admin/site_settings/:name.json
        # PATCH /api/v1/admin/site_settings/:name.json
        # Updates the value of the specified Site Setting
        def update
          site_setting = SiteSetting.joins(:setting)
@@ -51,10 +51,7 @@ module Api
                     site_setting.update(value: params[:site_setting][:value].to_s)
                   end

          unless update
            return render_error status: :bad_request,
                                errors: Rails.configuration.custom_error_msgs[:record_invalid]
          end
          return render_error status: :bad_request, errors: site_setting.errors.to_a unless update

          render_data status: :ok
        end
Original line number Diff line number Diff line
@@ -16,8 +16,6 @@

# frozen_string_literal: true

# rubocop:disable Metrics/PerceivedComplexity

module Api
  module V1
    module Migrations
@@ -77,7 +75,7 @@ module Api
        end

        # POST /api/v1/migrations/users.json
        # Expects: { user: { :name, :email, :external_id, :language, :role } }
        # Expects: { user: { :name, :email, :password_digest, :provider, :external_id, :language, :role } }
        # Returns: { data: Array[serializable objects] , errors: Array[String] }
        # Does: Creates a user.
        def create_user
@@ -105,10 +103,8 @@ module Api

          return render_error(status: :bad_request, errors: user&.errors&.to_a) unless user.save

          if user_hash[:provider] != 'greenlight'
            user.password_digest = nil
          user.password_digest = user_hash[:provider] == 'greenlight' ? user_hash[:password_digest] : nil
          user.save(validations: false)
          end

          render_data status: :created
        end
@@ -119,7 +115,6 @@ module Api
        #                    shared_users_emails: [ <list of shared users emails> ] }}
        # Returns: { data: Array[serializable objects] , errors: Array[String] }
        # Does: Creates a Room and its RoomMeetingOptions.
        # rubocop:disable Metrics/CyclomaticComplexity
        def create_room
          room_hash = room_params.to_h

@@ -174,7 +169,6 @@ module Api

          render_data status: :created
        end
        # rubocop:enable Metrics/CyclomaticComplexity

        # POST /api/v1/migrations/site_settings.json
        # Expects: { settings: { site_settings: { :PrimaryColor, :PrimaryColorLight, :PrimaryColorDark,
@@ -230,7 +224,7 @@ module Api
        end

        def user_params
          decrypted_params.require(:user).permit(:name, :email, :provider, :external_id, :language, :role, :created_at)
          decrypted_params.require(:user).permit(:name, :email, :password_digest, :provider, :external_id, :language, :role, :created_at)
        end

        def room_params
@@ -276,4 +270,3 @@ module Api
    end
  end
end
# rubocop:enable Metrics/PerceivedComplexity
Original line number Diff line number Diff line
@@ -28,9 +28,6 @@ module Api
      before_action only: %i[update update_visibility recording_url] do
        ensure_authorized(%w[ManageRecordings SharedRoom PublicRecordings], record_id: params[:id])
      end
      before_action only: %i[index recordings_count] do
        ensure_authorized('CreateRoom')
      end

      # GET /api/v1/recordings.json
      # Returns all of the current_user's recordings
@@ -66,14 +63,13 @@ module Api
      def update_visibility
        new_visibility = params[:visibility].to_s

        new_visibility_params = visibility_params_of(new_visibility)

        return render_error status: :bad_request if new_visibility_params.nil?
        allowed_visibilities = JSON.parse(RolePermission.joins(:permission)
                                            .find_by(role_id: current_user.role_id, permission: { name: 'AccessToVisibilities' })
                                                        .value)

        bbb_api = BigBlueButtonApi.new(provider: current_provider)
        return render_error status: :forbidden unless allowed_visibilities.include?(new_visibility)

        bbb_api.publish_recordings(record_ids: @recording.record_id, publish: new_visibility_params[:publish])
        bbb_api.update_recordings(record_id: @recording.record_id, meta_hash: new_visibility_params[:meta_hash])
        BigBlueButtonApi.new(provider: current_provider).update_recording_visibility(record_id: @recording.record_id, visibility: new_visibility)

        @recording.update!(visibility: new_visibility)

@@ -125,18 +121,6 @@ module Api
      def find_recording
        @recording = Recording.find_by! record_id: params[:id]
      end

      def visibility_params_of(visibility)
        params_of = {
          Recording::VISIBILITIES[:unpublished] => { publish: false, meta_hash: { protect: false, 'meta_gl-listed': false } },
          Recording::VISIBILITIES[:published] => { publish: true, meta_hash: { protect: false, 'meta_gl-listed': false } },
          Recording::VISIBILITIES[:public] => { publish: true, meta_hash: { protect: false, 'meta_gl-listed': true } },
          Recording::VISIBILITIES[:protected] => { publish: true, meta_hash: { protect: true, 'meta_gl-listed': false } },
          Recording::VISIBILITIES[:public_protected] => { publish: true, meta_hash: { protect: true, 'meta_gl-listed': true } }
        }

        params_of[visibility.to_s]
      end
    end
  end
end
Original line number Diff line number Diff line
@@ -19,9 +19,10 @@
module Api
  module V1
    class RoomsConfigurationsController < ApiController
      before_action only: %i[index show] do
      before_action only: %i[index] do
        ensure_authorized(%w[CreateRoom ManageSiteSettings ManageRoles ManageRooms], friendly_id: params[:friendly_id])
      end
      skip_before_action :ensure_authenticated, only: %i[show]

      # GET /api/v1/rooms_configurations.json
      # Fetches and returns all rooms configurations.
Original line number Diff line number Diff line
@@ -23,7 +23,7 @@ module Api

      before_action :find_room, only: %i[show update destroy recordings recordings_processing purge_presentation public_show public_recordings]

      before_action only: %i[create index] do
      before_action only: %i[create] do
        ensure_authorized('CreateRoom')
      end
      before_action only: %i[create] do
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ module Api
  module V1
    class SessionsController < ApiController
      skip_before_action :ensure_authenticated, only: %i[index create]
      before_action :ensure_unauthenticated, only: :create

      # GET /api/v1/sessions
      # Returns the current_user
@@ -43,7 +44,7 @@ module Api
        return render_error if user.blank?

        # Will return an error if the user is NOT from the current provider and if the user is NOT a super admin
        return render_error if user.provider != current_provider && !user.super_admin?
        return render_error status: :forbidden if !user.super_admin? && (user.provider != current_provider || external_auth?)

        # Password is not set (local user migrated from v2)
        if user.external_id.blank? && user.password_digest.blank?
Original line number Diff line number Diff line
@@ -76,7 +76,7 @@ module Api
                              .with_provider(current_provider)
                              .where.not(id: [@room.shared_users.pluck(:id) << @room.user_id])
                              .where(role_id: [role_ids])
                              .name_search(params[:search])
                              .shared_access_search(params[:search])
        render_data data: shareable_users, serializer: SharedAccessSerializer, status: :ok
      end

Original line number Diff line number Diff line
@@ -76,6 +76,8 @@ module Api
            UserMailer.with(user:,
                            activation_url: activate_account_url(token), base_url: request.base_url,
                            provider: current_provider).activate_account_email.deliver_later

            UserMailer.with(user:, admin_panel_url:, base_url: request.base_url, provider: current_provider).new_user_signup_email.deliver_later
          end

          create_default_room(user)
@@ -108,7 +110,7 @@ module Api
          create_default_room(user)
          render_data  status: :ok
        else
          render_error errors: Rails.configuration.custom_error_msgs[:record_invalid]
          render_error errors: user.errors.to_a
        end
      end

Original line number Diff line number Diff line
@@ -29,9 +29,8 @@ module Api
      def create
        token = @user.generate_activation_token!

        UserMailer.with(user: @user,
                        activation_url: activate_account_url(token), base_url: request.base_url,
                        provider: current_provider).activate_account_email.deliver_later
        UserMailer.with(user: @user, activation_url: activate_account_url(token),
                        base_url: request.base_url, provider: current_provider).activate_account_email.deliver_later

        render_data status: :ok
      end
Original line number Diff line number Diff line
@@ -29,6 +29,11 @@ module Authorizable
    render_error status: :unauthorized unless current_user
  end

  # Ensures that the user is NOT logged in
  def ensure_unauthenticated
    render_error status: :unauthorized if current_user
  end

  # PermissionsChecker service will return a true or false depending on whether the current_user's role has the provided permission_name
  def ensure_authorized(permission_names, user_id: nil, friendly_id: nil, record_id: nil)
    render_error status: :forbidden unless PermissionsChecker.new(
Original line number Diff line number Diff line
@@ -33,4 +33,8 @@ module ClientRoutable
  def pending_path
    "#{root_path}pending"
  end

  def admin_panel_url
    "#{root_url}admin/users"
  end
end
Original line number Diff line number Diff line
@@ -30,7 +30,15 @@ class ExternalController < ApplicationController

    user_info = build_user_info(credentials)

    user = User.find_by(external_id: credentials['uid'], provider:) || User.find_by(email: credentials['info']['email'], provider:)
    user = User.find_by(external_id: credentials['uid'], provider:)

    # Fallback mechanism to search by email
    if user.blank?
      user = User.find_by(email: credentials['info']['email'], provider:)
      # Update the user's external id to the latest value to avoid using the fallback
      user.update(external_id: credentials['uid']) if user.present? && credentials['uid'].present?
    end

    new_user = user.blank?

    registration_method = SettingGetter.new(setting_name: 'RegistrationMethod', provider: current_provider).call
@@ -45,6 +53,12 @@ class ExternalController < ApplicationController
      user = UserCreator.new(user_params: user_info, provider: current_provider, role: default_role).call
      user.save!
      create_default_room(user)

      # Send admins an email if smtp is enabled
      if ENV['SMTP_SERVER'].present?
        UserMailer.with(user:, admin_panel_url:, base_url: request.base_url,
                        provider: current_provider).new_user_signup_email.deliver_later
      end
    end

    if SettingGetter.new(setting_name: 'ResyncOnLogin', provider:).call
@@ -90,7 +104,7 @@ class ExternalController < ApplicationController
      @room.update(recordings_processing: @room.recordings_processing - 1) unless @room.recordings_processing.zero?
    end

    RecordingCreator.new(recording:).call
    RecordingCreator.new(recording:, first_creation: true).call

    render json: {}, status: :ok
  end
Original line number Diff line number Diff line
@@ -39,7 +39,7 @@ class HealthChecksController < ApplicationController

  def check_database
    raise 'Unable to connect to Database' unless ActiveRecord::Base.connection.active?
    raise 'Unable to connect to Database - pending migrations' unless ActiveRecord::Migration.check_pending!.nil?
    raise 'Unable to connect to Database - pending migrations' unless ActiveRecord::Migration.check_all_pending!.nil?
  rescue StandardError => e
    raise "Unable to connect to Database - #{e}"
  end
@@ -69,8 +69,8 @@ class HealthChecksController < ApplicationController
  end

  def check_big_blue_button
    checksum = Digest::SHA1.hexdigest("getMeetings#{Rails.configuration.bigbluebutton_secret}")
    uri = URI("#{Rails.configuration.bigbluebutton_endpoint}getMeetings?checksum=#{checksum}")
    checksum = Digest::SHA1.hexdigest("isMeetingRunningmeetingID=0#{Rails.configuration.bigbluebutton_secret}")
    uri = URI("#{Rails.configuration.bigbluebutton_endpoint}isMeetingRunning?meetingID=0&checksum=#{checksum}")
    res = Net::HTTP.get(uri)
    doc = Nokogiri::XML(res)

Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Button, Stack } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import Select from 'react-select';
import Form from '../../../shared_components/forms/Form';
import FormControl from '../../../shared_components/forms/FormControl';
import useUpdateRole from '../../../../hooks/mutations/admin/roles/useUpdateRole';
@@ -44,6 +45,14 @@ export default function EditRoleForm({ role }) {

  const { methods: methodsName, fields: fieldsName } = useEditRoleNameForm({ defaultValues: { name: role?.name } });

  const visibilityOptions = [
    { value: 'Published', label: 'Published' },
    { value: 'Unpublished', label: 'Unpublished' },
    { value: 'Protected', label: 'Protected' },
    { value: 'Public', label: 'Public' },
    { value: 'Public/Protected', label: 'Public/Protected' },
  ];

  const {
    methods: methodsLimit,
    fields: fieldsLimit,
@@ -130,6 +139,38 @@ export default function EditRoleForm({ role }) {
                defaultValue={rolePermissions?.SharedList === 'true'}
              />

              <RolePermissionRow
                permissionName="EmailOnSignup"
                description={t('admin.roles.edit.email_on_signup')}
                roleId={role?.id}
                defaultValue={rolePermissions?.EmailOnSignup === 'true'}
              />

              <Form className="pb-3">
                <Stack direction="horizontal">
                  <div className="text-muted me-auto">
                    {t('admin.roles.edit.allowed_recording_visibility')}
                  </div>
                  <div>
                    <Select
                      className="custom-select float-end"
                      isMulti
                      isClearable={false}
                      isSearchable={false}
                      defaultValue={visibilityOptions?.filter((vis) => JSON.parse(rolePermissions?.AccessToVisibilities)?.includes(vis.value))}
                      options={visibilityOptions}
                      onChange={(value) => {
                        updatePermissionAPI.mutate({ role_id: role?.id, name: 'AccessToVisibilities', value: value.map((v) => v.value) });
                      }}
                      classNames={{
                        control: (state) => (state.isFocused ? 'select-brand-control' : ''),
                        option: (state) => (state.isFocused ? 'select-brand-option' : ''),
                      }}
                    />
                  </div>
                </Stack>
              </Form>

              <Form methods={methodsLimit} onBlur={methodsLimit.handleSubmit(updatePermissionAPI.mutate)}>
                <Stack direction="horizontal">
                  <div className="text-muted me-auto">
Original line number Diff line number Diff line
@@ -23,11 +23,11 @@ import useSiteSettings from '../../../../hooks/queries/admin/site_settings/useSi

export default function Administration() {
  const { t } = useTranslation();
  const { data: siteSettings } = useSiteSettings(['Terms', 'PrivacyPolicy']);
  const { data: siteSettings } = useSiteSettings(['Terms', 'PrivacyPolicy', 'HelpCenter']);

  return (
    <>
      <Row className="mb-4">
      <Row>
        <h6> { t('admin.site_settings.administration.terms') } </h6>
        <p className="text-muted"> { t('admin.site_settings.administration.change_term_links') } </p>
        <LinksForm
@@ -45,6 +45,15 @@ export default function Administration() {
          value={siteSettings?.PrivacyPolicy}
        />
      </Row>
      <Row>
        <h6> { t('admin.site_settings.administration.helpcenter') } </h6>
        <p className="text-muted"> { t('admin.site_settings.administration.change_helpcenter_link') } </p>
        <LinksForm
          id="helpForm"
          mutation={() => useUpdateSiteSetting('HelpCenter')}
          value={siteSettings?.HelpCenter}
        />
      </Row>
    </>
  );
}
Original line number Diff line number Diff line
@@ -16,12 +16,16 @@

import React from 'react';
import { useTranslation } from 'react-i18next';
import { Dropdown } from 'react-bootstrap';
import useSiteSettings from '../../../../hooks/queries/admin/site_settings/useSiteSettings';
import SettingsRow from '../SettingsRow';
import SettingSelect from './SettingSelect';
import useUpdateSiteSetting from '../../../../hooks/mutations/admin/site_settings/useUpdateSiteSetting';

export default function Settings() {
  const { t } = useTranslation();
  const { data: siteSettings, isLoading } = useSiteSettings(['ShareRooms', 'PreuploadPresentation']);
  const { data: siteSettings, isLoading } = useSiteSettings(['ShareRooms', 'PreuploadPresentation', 'DefaultRecordingVisibility']);
  const updateDefaultRecordingVisibility = useUpdateSiteSetting('DefaultRecordingVisibility');

  if (isLoading) return null;

@@ -47,6 +51,32 @@ export default function Settings() {
      )}
        value={siteSettings?.PreuploadPresentation}
      />

      <SettingSelect
        defaultValue={siteSettings?.DefaultRecordingVisibility}
        title={t('admin.site_settings.settings.default_visibility')}
        description={t('admin.site_settings.settings.default_visibility_description')}
      >
        <Dropdown.Item
          key="Public/Protected"
          value="Public/Protected"
          onClick={() => updateDefaultRecordingVisibility.mutate({ value: 'Public/Protected' })}
        >
          {t('recording.public_protected')}
        </Dropdown.Item>
        <Dropdown.Item key="Public" value="Public" onClick={() => updateDefaultRecordingVisibility.mutate({ value: 'Public' })}>
          {t('recording.public')}
        </Dropdown.Item>
        <Dropdown.Item key="Protected" value="Protected" onClick={() => updateDefaultRecordingVisibility.mutate({ value: 'Protected' })}>
          {t('recording.protected')}
        </Dropdown.Item>
        <Dropdown.Item key="Published" value="Published" onClick={() => updateDefaultRecordingVisibility.mutate({ value: 'Published' })}>
          {t('recording.published')}
        </Dropdown.Item>
        <Dropdown.Item key="Unpublished" value="Unpublished" onClick={() => updateDefaultRecordingVisibility.mutate({ value: 'Unpublished' })}>
          {t('recording.unpublished')}
        </Dropdown.Item>
      </SettingSelect>
    </>
  );
}
Original line number Diff line number Diff line
@@ -46,7 +46,6 @@ export default function AuthButtons({ direction }) {
        <input type="hidden" name="authenticity_token" value={document.querySelector('meta[name="csrf-token"]').content} />
        <input type="hidden" name="current_provider" value={env?.CURRENT_PROVIDER} />
        <Stack direction={direction} gap={2}>
          <Button variant="brand-outline-color" className="btn" type="submit">{t('authentication.sign_up')}</Button>
          <Button variant="brand" className="btn" type="submit">{t('authentication.sign_in')}</Button>
        </Stack>
      </Form>
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import {
import { toast } from 'react-toastify';
import { useAuth } from '../../contexts/auth/AuthProvider';
import HomepageFeatureCard from './HomepageFeatureCard';
import useRoomConfigValue from '../../hooks/queries/rooms/useRoomConfigValue';

export default function HomePage() {
  const { t } = useTranslation();
@@ -33,6 +34,7 @@ export default function HomePage() {
  const navigate = useNavigate();
  const [searchParams, setSearchParams] = useSearchParams();
  const error = searchParams.get('error');
  const { data: recordValue } = useRoomConfigValue('record');

  // Redirects the user to the proper page based on signed in status and CreateRoom permission
  useEffect(
@@ -88,6 +90,7 @@ export default function HomePage() {
            icon={<ComputerDesktopIcon className="hi-s text-white" />}
          />
        </Col>
        { (recordValue !== 'false') && (
          <Col className="mb-3">
            <HomepageFeatureCard
              title={t('homepage.recording_title')}
@@ -95,6 +98,7 @@ export default function HomePage() {
              icon={<VideoCameraIcon className="hi-s text-white" />}
            />
          </Col>
        )}
        <Col className="mb-3">
          <HomepageFeatureCard
            title={t('homepage.settings_title')}
Original line number Diff line number Diff line
@@ -25,10 +25,12 @@ import PropTypes from 'prop-types';
import { ChevronDownIcon } from '@heroicons/react/20/solid';
import useDeleteSession from '../../hooks/mutations/sessions/useDeleteSession';
import Avatar from '../users/user/Avatar';
import useSiteSetting from '../../hooks/queries/site_settings/useSiteSetting';

export default function NavbarSignedIn({ currentUser }) {
  const { t } = useTranslation();
  const deleteSession = useDeleteSession({ showToast: true });
  const { data: helpCenter } = useSiteSetting('HelpCenter');

  const adminAccess = () => {
    const { permissions } = currentUser;
@@ -61,10 +63,15 @@ export default function NavbarSignedIn({ currentUser }) {
            <IdentificationIcon className="hi-s me-3" />
            {t('user.profile.profile')}
          </Nav.Link>
          <Nav.Link eventKey={2} href="https://docs.bigbluebutton.org/greenlight/v3/install">
          {
            helpCenter
            && (
              <Nav.Link eventKey={2} href={helpCenter} target="_blank">
                <QuestionMarkCircleIcon className="hi-s me-3" />
                {t('help_center')}
              </Nav.Link>
            )
          }
          {
            adminAccess()
            && (
@@ -103,10 +110,15 @@ export default function NavbarSignedIn({ currentUser }) {
            <IdentificationIcon className="hi-s me-3" />
            { t('user.profile.profile') }
          </NavDropdown.Item>
          <NavDropdown.Item href="https://docs.bigbluebutton.org/greenlight/v3/install">
          {
            helpCenter
            && (
              <NavDropdown.Item href={helpCenter} target="_blank">
                <QuestionMarkCircleIcon className="hi-s me-3" />
                {t('help_center')}
              </NavDropdown.Item>
            )
          }
          {
            adminAccess()
            && (
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ export default function RecordingRow({
  const currentUser = useAuth();
  const redirectRecordingUrl = useRedirectRecordingUrl();
  const copyRecordingUrl = useCopyRecordingUrl();
  const allowedVisibilities = JSON.parse(currentUser.permissions?.AccessToVisibilities);

  const localizedTime = localizeDateTimeString(recording?.recorded_at, currentUser?.language);
  const formats = recording.formats.sort(
@@ -106,6 +107,7 @@ export default function RecordingRow({
          defaultValue={recording.visibility}
          dropUp={dropUp}
        >
          { (allowedVisibilities.includes('Public/Protected') || recording.visibility === 'Public/Protected') && (
            <Dropdown.Item
              key="Public/Protected"
              value="Public/Protected"
@@ -113,6 +115,9 @@ export default function RecordingRow({
            >
              {t('recording.public_protected')}
            </Dropdown.Item>
          )}

          { (allowedVisibilities.includes('Public') || recording.visibility === 'Public') && (
            <Dropdown.Item
              key="Public"
              value="Public"
@@ -120,6 +125,9 @@ export default function RecordingRow({
            >
              {t('recording.public')}
            </Dropdown.Item>
          )}

          { (allowedVisibilities.includes('Protected') || recording.visibility === 'Protected') && (
            <Dropdown.Item
              key="Protected"
              value="Protected"
@@ -127,6 +135,9 @@ export default function RecordingRow({
            >
              {t('recording.protected')}
            </Dropdown.Item>
          )}

          { (allowedVisibilities.includes('Published') || recording.visibility === 'Published') && (
            <Dropdown.Item
              key="Published"
              value="Published"
@@ -134,6 +145,9 @@ export default function RecordingRow({
            >
              {t('recording.published')}
            </Dropdown.Item>
          )}

          { (allowedVisibilities.includes('Unpublished') || recording.visibility === 'Unpublished') && (
            <Dropdown.Item
              key="Unpublished"
              value="Unpublished"
@@ -141,6 +155,7 @@ export default function RecordingRow({
            >
              {t('recording.unpublished')}
            </Dropdown.Item>
          )}
        </SimpleSelect>
      </td>
      <td className="border-0">
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import UserBoardIcon from './UserBoardIcon';
export default function EmptyRoomsList() {
  const { t } = useTranslation();
  const currentUser = useAuth();
  const canCreate = currentUser?.permissions.CreateRoom;
  const mutationWrapper = (args) => useCreateRoom({ userId: currentUser.id, ...args });

  return (
@@ -39,11 +40,13 @@ export default function EmptyRoomsList() {
          <Card.Text>
            { t('room.rooms_list_empty_create_room') }
          </Card.Text>
          { (canCreate === 'true') && (
            <Modal
              modalButton={<Button variant="brand" className="ms-auto me-xxl-1">{ t('room.add_new_room') }</Button>}
              title={t('room.create_new_room')}
              body={<CreateRoomForm mutation={mutationWrapper} userId={currentUser.id} />}
            />
          )}
        </Card.Body>
      </Card>
    </div>
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ export default function RoomsList() {
  const [searchInput, setSearchInput] = useState('');
  const { isLoading, data: rooms } = useRooms(searchInput);
  const currentUser = useAuth();
  const canCreate = currentUser?.permissions.CreateRoom;
  const mutationWrapper = (args) => useCreateRoom({ userId: currentUser.id, ...args });

  if (!isLoading && rooms?.length === 0 && !searchInput) {
@@ -47,6 +48,7 @@ export default function RoomsList() {
        <div>
          <SearchBar searchInput={searchInput} id="rooms-search" setSearchInput={setSearchInput} />
        </div>
        { (canCreate === 'true') && (
          <Modal
            modalButton={(
              <Button
@@ -58,6 +60,7 @@ export default function RoomsList() {
            title={t('room.create_new_room')}
            body={<CreateRoomForm mutation={mutationWrapper} userId={currentUser.id} />}
          />
        )}
      </Stack>
      <Row className="g-4 mt-4">
        {
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@

import React from 'react';
import {
  Stack, Button, Col, Row,
  Stack, Button, Col, Row, Dropdown,
} from 'react-bootstrap';
import { Link, useParams } from 'react-router-dom';
import { HomeIcon, Square2StackIcon } from '@heroicons/react/24/outline';
@@ -32,6 +32,7 @@ import MeetingBadges from '../MeetingBadges';
import SharedBadge from './SharedBadge';
import RoomNamePlaceHolder from './RoomNamePlaceHolder';
import Title from '../../shared_components/utilities/Title';
import useRoomSettings from '../../../hooks/queries/rooms/useRoomSettings';

export default function Room() {
  const { t } = useTranslation();
@@ -42,11 +43,20 @@ export default function Room() {
  const startMeeting = useStartMeeting(friendlyId);
  const currentUser = useAuth();
  const localizedTime = localizeDayDateTimeString(room?.last_session, currentUser?.language);
  const roomSettings = useRoomSettings(friendlyId);

  function copyInvite() {
  function copyInvite(role) {
    if (role === 'viewer') {
      navigator.clipboard.writeText(roomSettings?.data?.glViewerAccessCode);
      toast.success(t('toast.success.room.copied_viewer_code'));
    } else if (role === 'moderator') {
      navigator.clipboard.writeText(roomSettings?.data?.glModeratorAccessCode);
      toast.success(t('toast.success.room.copied_moderator_code'));
    } else {
      navigator.clipboard.writeText(`${window.location}/join`);
      toast.success(t('toast.success.room.copied_meeting_url'));
    }
  }

  return (
    <>
@@ -60,7 +70,7 @@ export default function Room() {
          </Col>
        </Row>
        <Row className="py-5">
          <Col className="col-xxl-8">
          <Col className="col-4">
            {
                isRoomLoading
                  ? (
@@ -85,7 +95,14 @@ export default function Room() {
              }
          </Col>
          <Col>
            <Button variant="brand" className="start-meeting-btn mt-1 mx-2 float-end" onClick={startMeeting.mutate} disabled={startMeeting.isLoading}>
            <Row>
              <Col className="col-12">
                <Button
                  variant="brand"
                  className="start-meeting-btn mt-1 mx-2 float-end"
                  onClick={startMeeting.mutate}
                  disabled={startMeeting.isLoading}
                >
                  {startMeeting.isLoading && <Spinner className="me-2" />}
                  { room?.online ? (
                    t('room.meeting.join_meeting')
@@ -93,10 +110,35 @@ export default function Room() {
                    t('room.meeting.start_meeting')
                  )}
                </Button>
            <Button variant="brand-outline" className="mt-1 mx-2 float-end" onClick={() => copyInvite()}>

                <Dropdown className="btn-group mt-1 mx-2 float-end pb-5">
                  <Button variant="brand-outline" type="button" className="btn dropdown-main" onClick={() => copyInvite()}>
                    <Square2StackIcon className="hi-s me-1" />
                    { t('copy') }
                  </Button>
                  { (roomSettings?.data?.glModeratorAccessCode || roomSettings?.data?.glViewerAccessCode) && (
                    <Dropdown.Toggle
                      variant="brand-outline"
                      className="btn dropdown-toggle dropdown-toggle-split"
                      id="dropdown-toggle"
                    />
                  )}

                  <Dropdown.Menu className="dropdown-menu">
                    { roomSettings?.data?.glModeratorAccessCode && (
                      <Dropdown.Item onClick={() => copyInvite('moderator')}>
                        { t('copy_moderator_code') }
                      </Dropdown.Item>
                    )}
                    { roomSettings?.data?.glViewerAccessCode && (
                      <Dropdown.Item onClick={() => copyInvite('viewer')}>
                        { t('copy_viewer_code') }
                      </Dropdown.Item>
                    )}
                  </Dropdown.Menu>
                </Dropdown>
              </Col>
            </Row>
          </Col>
        </Row>
      </div>
Original line number Diff line number Diff line
@@ -18,7 +18,7 @@
import React, { useState, useEffect } from 'react';
import Card from 'react-bootstrap/Card';
import {
  Navigate, Link, useParams,
  Navigate, Link, useParams, useLocation,
} from 'react-router-dom';
import {
  Button, Col, Row, Stack, Form as RegularForm,
@@ -42,6 +42,7 @@ import RoomJoinPlaceholder from './RoomJoinPlaceholder';
import useRoomJoinForm from '../../../../hooks/forms/rooms/useRoomJoinForm';
import ButtonLink from '../../../shared_components/utilities/ButtonLink';
import Title from '../../../shared_components/utilities/Title';
import useRoomConfigValue from '../../../../hooks/queries/rooms/useRoomConfigValue';

export default function JoinCard() {
  const { t } = useTranslation();
@@ -54,11 +55,18 @@ export default function JoinCard() {
  const roomStatusAPI = useRoomStatus(friendlyId, joinInterval);

  const { data: env } = useEnv();
  const { data: recordValue } = useRoomConfigValue('record');

  const { methods, fields } = useRoomJoinForm();

  const path = encodeURIComponent(document.location.pathname);

  // get queryParams for JoinFormName
  const location = useLocation();
  const queryParams = new URLSearchParams(location.search);
  const joinFormName = queryParams.get('joinFormName');
  const viewerCode = queryParams.get('viewerCode');

  useEffect(() => { // set cookie to return to if needed
    const date = new Date();
    date.setTime(date.getTime() + (60 * 1000)); // expire the cookie in 1min
@@ -84,10 +92,19 @@ export default function JoinCard() {

  useEffect(() => {
    // Default Join name to authenticated user full name.
    if (currentUser?.name) {
    if (joinFormName) {
      methods.setValue('name', joinFormName);
    } else if (currentUser?.name) {
      methods.setValue('name', currentUser.name);
    }
  }, [currentUser?.name]);
  }, [joinFormName, currentUser?.name]);

  useEffect(() => {
    // Default viewerCode if passed as query param
    if (viewerCode) {
      methods.setValue('access_code', viewerCode);
    }
  }, [viewerCode]);

  useEffect(() => {
    // Room channel subscription:
@@ -205,6 +222,7 @@ export default function JoinCard() {
            <h1 className="mt-2">
              {publicRoom?.data.name}
            </h1>
            { (recordValue !== 'false') && (
              <ButtonLink
                variant="brand-outline"
                className="mt-3 mb-0 cursor-pointer"
@@ -212,6 +230,7 @@ export default function JoinCard() {
              >
                <span> <VideoCameraIcon className="hi-s text-brand" /> {t('view_recordings')} </span>
              </ButtonLink>
            )}
          </Col>
          <Col>
            <Stack direction="vertical" gap={3}>
Original line number Diff line number Diff line
@@ -36,7 +36,6 @@ export default function RequireAuthentication({ path }) {
          env?.EXTERNAL_AUTH ? (
            <Form action={process.env.OMNIAUTH_PATH} method="POST" data-turbo="false">
              <input type="hidden" name="authenticity_token" value={document.querySelector('meta[name="csrf-token"]').content} />
              <Button variant="brand-outline-color" className="btn btn-lg m-2" type="submit">{t('authentication.sign_up')}</Button>
              <Button variant="brand" className="btn btn-lg m-2" type="submit">{t('authentication.sign_in')}</Button>
            </Form>
          ) : (
Original line number Diff line number Diff line
@@ -37,7 +37,7 @@ export default function DeletePresentationForm({ handleClose }) {
      <Stack direction="horizontal" className="mb-3">
        <ExclamationTriangleIcon className="text-danger hi-xl" />
        <Stack direction="vertical" className="ps-3">
          <h3> { t('recording.delete_recording') } </h3>
          <h3> { t('room.presentation.delete_presentation') } </h3>
          <p className="mb-0"> { t('room.presentation.are_you_sure_delete_presentation') } </p>
          <p className="mt-0"><strong> { t('action_permanent') } </strong></p>
        </Stack>
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import useDeleteSharedAccess from '../../../../hooks/mutations/shared_accesses/u
import useSharedUsers from '../../../../hooks/queries/shared_accesses/useSharedUsers';
import SharedAccessEmpty from './SharedAccessEmpty';
import useRoom from '../../../../hooks/queries/rooms/useRoom';
import { useAuth } from '../../../../contexts/auth/AuthProvider';

export default function SharedAccess() {
  const { t } = useTranslation();
@@ -37,6 +38,8 @@ export default function SharedAccess() {
  const { data: sharedUsers } = useSharedUsers(friendlyId, searchInput);
  const deleteSharedAccess = useDeleteSharedAccess(friendlyId);
  const { data: room } = useRoom(friendlyId);
  const currentUser = useAuth();
  const isAdmin = currentUser?.role.name === 'Administrator';

  if (sharedUsers?.length || searchInput) {
    return (
@@ -45,7 +48,7 @@ export default function SharedAccess() {
          <div>
            <SearchBar searchInput={searchInput} setSearchInput={setSearchInput} />
          </div>
          { !room.shared && (
          { (!room.shared || isAdmin) && (
            <Modal
              modalButton={(
                <Button
@@ -82,7 +85,7 @@ export default function SharedAccess() {
                          </Stack>
                        </td>
                        <td>
                          {!room.shared && (
                          { (!room.shared || isAdmin) && (
                          <Button
                            variant="icon"
                            className="float-end pe-2"
Original line number Diff line number Diff line
@@ -21,7 +21,7 @@ import { ChevronDownIcon } from '@heroicons/react/20/solid';

export default function SimpleSelect({ defaultValue, dropUp, children }) {
  // Get the currently selected option and set the dropdown toggle to that value
  const defaultString = children?.filter((item) => item.props.value === defaultValue)[0];
  const defaultString = children?.filter(Boolean)?.filter((item) => item.props.value === defaultValue)[0];

  return (
    <Dropdown className="simple-select" drop={dropUp ? 'up' : undefined}>
Original line number Diff line number Diff line
@@ -25,7 +25,7 @@ export function useEditRoleLimitFormValidation() {
    value: yup.number().required('forms.validations.role.limit.required')
      .typeError('forms.validations.role.type.error')
      .min(0, 'forms.validations.role.limit.min')
      .max(100, 'forms.validations.role.limit.max'),
      .max(10000, 'forms.validations.role.limit.max'),
  })), []);
}

Original line number Diff line number Diff line
@@ -26,16 +26,18 @@ export default function useUpdateSiteSetting(name) {

  const uploadPresentation = (data) => {
    let settings;
    let headers = { 'Content-Type': 'application/json' };

    if (name === 'BrandingImage') {
      fileValidation(data, 'image');
      settings = new FormData();
      settings.append('site_setting[value]', data);
      headers = { 'Content-Type': 'multipart/form-data' };
    } else {
      settings = data;
    }

    return axios.patch(`/admin/site_settings/${name}.json`, settings);
    return axios.patch(`/admin/site_settings/${name}.json`, settings, { headers });
  };

  const handleSuccess = () => {
@@ -52,6 +54,9 @@ export default function useUpdateSiteSetting(name) {
      case 'PrivacyPolicy':
        toast.success(t('toast.success.site_settings.privacy_policy_updated'));
        break;
      case 'HelpCenter':
        toast.success(t('toast.success.site_settings.helpcenter_updated'));
        break;
      case 'TermsOfService':
        toast.success(t('toast.success.site_settings.terms_of_service_updated'));
        break;
@@ -69,7 +74,11 @@ export default function useUpdateSiteSetting(name) {
        handleSuccess();
      },
      onError: (error) => {
        if (error.response.data.errors.includes('Image MalwareDetected')) {
          toast.error(t('toast.error.malware_detected'));
        } else {
          handleError(error, t, toast);
        }
      },
    },
  );
Original line number Diff line number Diff line
@@ -28,7 +28,11 @@ export default function useUploadPresentation(friendlyId) {
    fileValidation(presentation, 'presentation');
    const formData = new FormData();
    formData.append('room[presentation]', presentation);
    return axios.patch(`/rooms/${friendlyId}.json`, formData);
    return axios.patch(`/rooms/${friendlyId}.json`, formData, {
      headers: {
        'Content-Type': 'multipart/form-data',
      },
    });
  };

  const mutation = useMutation(uploadPresentation, {
@@ -37,7 +41,11 @@ export default function useUploadPresentation(friendlyId) {
      toast.success(t('toast.success.room.presentation_updated'));
    },
    onError: (error) => {
      if (error.response.data.errors.includes('Presentation MalwareDetected')) {
        toast.error(t('toast.error.malware_detected'));
      } else {
        handleError(error, t, toast);
      }
    },
  });

Original line number Diff line number Diff line
@@ -30,7 +30,11 @@ export default function useCreateAvatar(currentUser) {
    });
    const formData = new FormData();
    formData.append('user[avatar]', avatarBlob);
    return axios.patch(`/users/${currentUser.id}.json`, formData);
    return axios.patch(`/users/${currentUser.id}.json`, formData, {
      headers: {
        'Content-Type': 'multipart/form-data',
      },
    });
  }

  const mutation = useMutation(
@@ -41,8 +45,12 @@ export default function useCreateAvatar(currentUser) {
        queryClient.invalidateQueries('getUser');
        toast.success(t('toast.success.user.avatar_updated'));
      },
      onError: () => {
      onError: (error) => {
        if (error.response.data.errors.includes('Avatar MalwareDetected')) {
          toast.error(t('toast.error.malware_detected'));
        } else {
          toast.error(t('toast.error.problem_completing_action'));
        }
      },
    },
  );
Original line number Diff line number Diff line
@@ -15,9 +15,7 @@
// with Greenlight; if not, see <http://www.gnu.org/licenses/>.

import React from 'react';
import {
  Navigate, Outlet, useLocation, useMatch,
} from 'react-router-dom';
import { Navigate, Outlet, useMatch } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { toast } from 'react-toastify';
import { useAuth } from '../contexts/auth/AuthProvider';
@@ -26,7 +24,6 @@ import useDeleteSession from '../hooks/mutations/sessions/useDeleteSession';
export default function AuthenticatedOnly() {
  const { t } = useTranslation();
  const currentUser = useAuth();
  const location = useLocation();
  const roomsMatch = useMatch('/rooms/:friendlyId');
  const superAdminMatch = useMatch('/admin/*');
  const deleteSession = useDeleteSession({ showToast: false });
@@ -46,7 +43,7 @@ export default function AuthenticatedOnly() {

  // Custom logic to redirect from Rooms page to join page if the user isn't signed in
  if (!currentUser.signed_in && roomsMatch) {
    return <Navigate to={`${location.pathname}/join`} />;
    return <Navigate to={`${roomsMatch.pathnameBase}/join`} />;
  }

  if (currentUser.signed_in && currentUser.isSuperAdmin && !superAdminMatch) {
Original line number Diff line number Diff line
@@ -42,11 +42,20 @@ class UserMailer < ApplicationMailer
    @email = params[:email]
    @name = params[:name]
    @signup_url = params[:signup_url]
    @email = params[:email]

    mail(to: @email, subject: t('email.invitation.invitation_to_join'))
  end

  def new_user_signup_email
    @user = params[:user]
    @admin_panel_url = params[:admin_panel_url]
    emails = admin_emails

    return if emails.blank? # Dont send anything if no-one has EmailOnSignup enabled

    mail(to: emails, subject: t('email.new_user_signup.new_user'))
  end

  private

  def preset
@@ -59,4 +68,13 @@ class UserMailer < ApplicationMailer
    @brand_image = ActionController::Base.helpers.image_url(branding_hash['BrandingImage'], host: @base_url)
    @brand_color = branding_hash['PrimaryColor']
  end

  def admin_emails
    # Find all the roles that have EmailOnSignup enabled
    role_ids = Role.joins(role_permissions: :permission).with_provider(@provider).where(role_permissions: { value: 'true' },
                                                                                        permission: { name: 'EmailOnSignup' })
                   .pluck(:id)

    User.where(role_id: role_ids).pluck(:email)
  end
end
Original line number Diff line number Diff line
@@ -19,4 +19,8 @@
class ApplicationRecord < ActiveRecord::Base
  primary_abstract_class
  self.implicit_order_column = 'created_at'

  def virus_scan?
    ENV.fetch('CLAMAV_SCANNING', 'false') == 'true'
  end
end
Original line number Diff line number Diff line
@@ -47,6 +47,8 @@ class Role < ApplicationRecord
                'true'
              when 'RoomLimit'
                '100'
              when 'AccessToVisibilities'
                Recording::VISIBILITIES.values
              else
                'false'
              end
Original line number Diff line number Diff line
@@ -38,6 +38,8 @@ class Room < ApplicationRecord
  validates :recordings_processing, numericality: { only_integer: true, greater_than_or_equal_to: 0 }

  before_validation :set_friendly_id, :set_meeting_id, on: :create
  before_save :scan_presentation_for_virus

  after_create :create_meeting_options

  attr_accessor :shared, :active, :participants
@@ -99,4 +101,15 @@ class Room < ApplicationRecord
  rescue StandardError
    retry
  end

  def scan_presentation_for_virus
    return if !virus_scan? || !attachment_changes['presentation']

    path = attachment_changes['presentation']&.attachable&.tempfile&.path

    return true if Clamby.safe?(path)

    errors.add(:presentation, 'MalwareDetected')
    throw :abort
  end
end
Original line number Diff line number Diff line
@@ -31,4 +31,19 @@ class SiteSetting < ApplicationRecord
  validates :image,
            content_type: Rails.configuration.uploads[:images][:formats],
            size: { less_than: Rails.configuration.uploads[:images][:max_size] }

  before_save :scan_image_for_virus

  private

  def scan_image_for_virus
    return if !virus_scan? || !attachment_changes['image']

    path = attachment_changes['image']&.attachable&.tempfile&.path

    return true if Clamby.safe?(path)

    errors.add(:image, 'MalwareDetected')
    throw :abort
  end
end
Original line number Diff line number Diff line
@@ -71,6 +71,7 @@ class User < ApplicationRecord
  validate :check_user_role_provider, if: :role_changed?

  before_validation :set_session_token, on: :create
  before_save :scan_avatar_for_virus

  scope :with_provider, ->(current_provider) { where(provider: current_provider) }

@@ -80,8 +81,8 @@ class User < ApplicationRecord
    all
  end

  def self.name_search(input)
    return where('users.name ILIKE :input', input: "%#{input}%") if input
  def self.shared_access_search(input)
    return where('users.name ILIKE :input OR users.email ILIKE :input', input: "%#{input}%") if input

    all
  end
@@ -214,4 +215,17 @@ class User < ApplicationRecord

    errors.add(:user_provider, 'has to be the same as the Role provider') if provider != role.provider
  end

  private

  def scan_avatar_for_virus
    return if !virus_scan? || !attachment_changes['avatar']

    path = attachment_changes['avatar']&.attachable&.tempfile&.path

    return true if Clamby.safe?(path)

    errors.add(:avatar, 'MalwareDetected')
    throw :abort
  end
end
Original line number Diff line number Diff line
@@ -89,6 +89,12 @@ class BigBlueButtonApi
    bbb_server.publish_recordings(record_ids, publish)
  end

  def update_recording_visibility(record_id:, visibility:)
    new_visibility_params = visibility_params_of(visibility)
    publish_recordings(record_ids: record_id, publish: new_visibility_params[:publish])
    update_recordings(record_id:, meta_hash: new_visibility_params[:meta_hash])
  end

  def update_recordings(record_id:, meta_hash:)
    bbb_server.update_recordings(record_id, {}, meta_hash)
  end
@@ -107,4 +113,16 @@ class BigBlueButtonApi
      ProviderCredentials.new(provider: @provider).call
    end
  end

  def visibility_params_of(visibility)
    params_of = {
      Recording::VISIBILITIES[:unpublished] => { publish: false, meta_hash: { protect: false, 'meta_gl-listed': false } },
      Recording::VISIBILITIES[:published] => { publish: true, meta_hash: { protect: false, 'meta_gl-listed': false } },
      Recording::VISIBILITIES[:public] => { publish: true, meta_hash: { protect: false, 'meta_gl-listed': true } },
      Recording::VISIBILITIES[:protected] => { publish: true, meta_hash: { protect: true, 'meta_gl-listed': false } },
      Recording::VISIBILITIES[:public_protected] => { publish: true, meta_hash: { protect: true, 'meta_gl-listed': true } }
    }

    params_of[visibility.to_s]
  end
end
Original line number Diff line number Diff line
@@ -57,8 +57,8 @@ class MeetingStarter

  def computed_options(access_code:)
    room_url = "#{root_url(host: @base_url)}rooms/#{@room.friendly_id}/join"
    moderator_message = "#{I18n.t('meeting.moderator_message')}<br>#{room_url}"
    moderator_message += "<br>#{I18n.t('meeting.access_code', code: access_code)}" if access_code.present?
    moderator_message = "#{I18n.t('meeting.moderator_message', locale: @current_user&.language&.to_sym)}<br>#{room_url}"
    moderator_message += "<br>#{I18n.t('meeting.access_code', code: access_code, locale: @current_user&.language&.to_sym)}" if access_code.present?
    {
      moderatorOnlyMessage: moderator_message,
      logoutURL: room_url,
Original line number Diff line number Diff line
@@ -17,25 +17,32 @@
# frozen_string_literal: true

class RecordingCreator
  def initialize(recording:)
  def initialize(recording:, first_creation: false)
    @recording = recording
    @first_creation = first_creation
  end

  def call
    meeting_id = @recording[:metadata][:meetingId] || @recording[:meetingID]
    room_id = Room.find_by(meeting_id:)&.id
    room = Room.find_by(meeting_id:)
    raise ActiveRecord::RecordNotFound if room.nil?

    raise ActiveRecord::RecordNotFound if room_id.nil?
    @provider = room.user.provider

    visibility = get_recording_visibility(recording: @recording)
    # If the recording is being created for the first time, get the visibility from the site setting
    visibility = if @first_creation
                   default_recording_visibility
                 else
                   recording_visibility
                 end

    # Get length of presentation format(s)
    length = get_recording_length(recording: @recording)
    length = recording_length

    new_name = @recording[:metadata][:name] || @recording[:name]

    new_recording = Recording.find_or_initialize_by(record_id: @recording[:recordID])
    new_recording.room_id = room_id
    new_recording.room_id = room.id
    new_recording.name = new_name
    new_recording.visibility = visibility
    new_recording.participants = @recording[:participants]
@@ -45,16 +52,25 @@ class RecordingCreator
    new_recording.save!

    # Create format(s)
    create_formats(recording: @recording, new_recording:)
    create_formats(new_recording:)

    return unless @first_creation

    BigBlueButtonApi.new(provider: @provider).update_recording_visibility(record_id: new_recording.record_id, visibility:)
  end

  private

  # Returns the default recording visibility for a recording that is being created for the first time
  def default_recording_visibility
    SettingGetter.new(setting_name: 'DefaultRecordingVisibility', provider: @provider).call || 'Published'
  end

  # Returns the visibility of the recording (published, unpublished or protected)
  def get_recording_visibility(recording:)
    list = recording[:metadata][:'gl-listed'].to_s == 'true'
    protect = recording[:protected].to_s == 'true'
    publish = recording[:published].to_s == 'true'
  def recording_visibility
    list = @recording[:metadata][:'gl-listed'].to_s == 'true'
    protect = @recording[:protected].to_s == 'true'
    publish = @recording[:published].to_s == 'true'

    visibility = visibility_for(publish:, protect:, list:)

@@ -64,27 +80,27 @@ class RecordingCreator
  end

  # Returns the length of presentation recording for the recording given
  def get_recording_length(recording:)
  def recording_length
    length = 0
    if recording[:playback][:format].is_a?(Array)
      recording[:playback][:format].each do |formats|
    if @recording[:playback][:format].is_a?(Array)
      @recording[:playback][:format].each do |formats|
        length = formats[:length] if formats[:type] == 'presentation' || formats[:type] == 'video'
      end
    else
      length = recording[:playback][:format][:length]
      length = @recording[:playback][:format][:length]
    end
    length
  end

  # Creates format(s) for given new_recording
  def create_formats(recording:, new_recording:)
    if recording[:playback][:format].is_a?(Array)
      recording[:playback][:format].each do |format|
  def create_formats(new_recording:)
    if @recording[:playback][:format].is_a?(Array)
      @recording[:playback][:format].each do |format|
        Format.find_or_create_by(recording_id: new_recording.id, recording_type: format[:type]).update(url: format[:url])
      end
    else
      Format.find_or_create_by(recording_id: new_recording.id,
                               recording_type: recording[:playback][:format][:type]).update(url: recording[:playback][:format][:url])
                               recording_type: @recording[:playback][:format][:type]).update(url: @recording[:playback][:format][:url])
    end
  end

Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ class TenantSetup
        provider: @provider },
      { setting: Setting.find_by(name: 'Terms'), value: '', provider: @provider },
      { setting: Setting.find_by(name: 'PrivacyPolicy'), value: '', provider: @provider },
      { setting: Setting.find_by(name: 'HelpCenter'), value: '', provider: @provider },
      { setting: Setting.find_by(name: 'RegistrationMethod'), value: SiteSetting::REGISTRATION_METHODS[:open],
        provider: @provider },
      { setting: Setting.find_by(name: 'ShareRooms'), value: 'true', provider: @provider },
@@ -83,6 +84,7 @@ class TenantSetup
    shared_list = Permission.find_by(name: 'SharedList')
    can_record = Permission.find_by(name: 'CanRecord')
    room_limit = Permission.find_by(name: 'RoomLimit')
    access_to_visbilities = Permission.find_by(name: 'AccessToVisibilities')

    RolePermission.create! [
      { role: admin, permission: create_room, value: 'true' },
@@ -94,6 +96,7 @@ class TenantSetup
      { role: admin, permission: shared_list, value: 'true' },
      { role: admin, permission: can_record, value: 'true' },
      { role: admin, permission: room_limit, value: '100' },
      { role: admin, permission: access_to_visbilities, value: Recording::VISIBILITIES.values },

      { role: user, permission: create_room, value: 'true' },
      { role: user, permission: manage_users, value: 'false' },
@@ -104,6 +107,7 @@ class TenantSetup
      { role: user, permission: shared_list, value: 'true' },
      { role: user, permission: can_record, value: 'true' },
      { role: user, permission: room_limit, value: '100' },
      { role: user, permission: access_to_visbilities, value: Recording::VISIBILITIES.values },

      { role: guest, permission: create_room, value: 'false' },
      { role: guest, permission: manage_users, value: 'false' },
@@ -113,7 +117,8 @@ class TenantSetup
      { role: guest, permission: manage_roles, value: 'false' },
      { role: guest, permission: shared_list, value: 'true' },
      { role: guest, permission: can_record, value: 'true' },
      { role: guest, permission: room_limit, value: '100' }
      { role: guest, permission: room_limit, value: '100' },
      { role: guest, permission: access_to_visbilities, value: Recording::VISIBILITIES.values }
    ]
  end
end
Original line number Diff line number Diff line
<!--BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.-->

<!--Copyright (c) 2022 BigBlueButton Inc. and by respective authors (see below).-->

<!--This program is free software; you can redistribute it and/or modify it under the-->
<!--terms of the GNU Lesser General Public License as published by the Free Software-->
<!--Foundation; either version 3.0 of the License, or (at your option) any later-->
<!--version.-->

<!--Greenlight 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 Lesser General Public License for more details.-->

<!--You should have received a copy of the GNU Lesser General Public License along-->
<!--with Greenlight; if not, see <http://www.gnu.org/licenses/>.-->

<div style="padding-left: 80px; padding-right: 80px;">
  <p style="font-size: 40px; margin-bottom: 20px; font-weight: 600;"><%= t('email.new_user_signup.new_user') %></p>

  <p style="font-size: 24px;"><%= t('email.new_user_signup.new_user_description') %></p>

  <p style="font-size: 20px;"><%= t('email.new_user_signup.name', name: @user.name) %></p>
  <p style="font-size: 20px;"><%= t('email.new_user_signup.email', email: @user.email) %></p>

  <p style="font-size: 24px;"><%= t('email.new_user_signup.take_action') %></p>

  <a href="<%= @admin_panel_url %>" target="_blank" style="background-color: <%= @brand_color %>; border-radius: 8px; border: none; color: white; padding: 15px 32px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; font-weight: 600; margin-top: 16px; margin-bottom: 64px;">
    <%= t('email.new_user_signup.admin_panel') %>
  </a>
</div>
Original line number Diff line number Diff line
<%#
  BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.

  Copyright (c) 2022 BigBlueButton Inc. and by respective authors (see below).

  This program is free software; you can redistribute it and/or modify it under the
  terms of the GNU Lesser General Public License as published by the Free Software
  Foundation; either version 3.0 of the License, or (at your option) any later
  version.

  Greenlight 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 Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License along
  with Greenlight; if not, see http://www.gnu.org/licenses/.
%>

---
<%= t('email.new_user_signup.new_user') %>
<%= t('email.new_user_signup.new_user_description') %>
<%= t('email.new_user_signup.name', name: @user.name) %>
<%= t('email.new_user_signup.email', email: @user.email) %>
<%= t('email.new_user_signup.take_action') %>
<%= @admin_panel_url %>
---
+1 −1
Original line number Diff line number Diff line
@@ -28,4 +28,4 @@ rails assets:precompile
rails db:create
rails db:migrate:with_data

rails s -b 0.0.0.0 -p $PORT
exec rails s -b 0.0.0.0 -p $PORT
Original line number Diff line number Diff line
@@ -84,5 +84,7 @@ module Greenlight
    config.relative_url_root = '/' if config.relative_url_root.blank?

    I18n.load_path += Dir[Rails.root.join('config/locales/*.{rb,yml}').to_s]
    config.i18n.fallbacks = %i[en]
    config.i18n.enforce_available_locales = false
  end
end
Original line number Diff line number Diff line
@@ -135,17 +135,13 @@ Rails.application.configure do
  # require "syslog/logger"
  # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name")

  if ENV['RAILS_LOG_TO_STDOUT'].present?
    $stdout.sync = true
    logger           = ActiveSupport::Logger.new($stdout)
    logger.formatter = config.log_formatter
    config.logger    = ActiveSupport::TaggedLogging.new(logger)
  end

  if ENV['RAILS_LOG_REMOTE_NAME'] && ENV['RAILS_LOG_REMOTE_PORT']
    require 'remote_syslog_logger'
    logger_program = ENV['RAILS_LOG_REMOTE_TAG'] || "greenlight-v3-#{ENV.fetch('RAILS_ENV', nil)}"
    logger = RemoteSyslogLogger.new(ENV['RAILS_LOG_REMOTE_NAME'], ENV['RAILS_LOG_REMOTE_PORT'], program: logger_program)
  else
    $stdout.sync = true
    logger = ActiveSupport::Logger.new($stdout)
  end

  logger.formatter = config.log_formatter
Original line number Diff line number Diff line
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
#
# Copyright (c) 2022 BigBlueButton Inc. and by respective authors (see below).
#
# This program is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free Software
# Foundation; either version 3.0 of the License, or (at your option) any later
# version.
#
# Greenlight 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with Greenlight; if not, see <http://www.gnu.org/licenses/>.

# frozen_string_literal: true

ActiveModelSerializers.logger = Logger.new(IO::NULL) if Rails.env.production?
+24 −0
Original line number Diff line number Diff line
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
#
# Copyright (c) 2022 BigBlueButton Inc. and by respective authors (see below).
#
# This program is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free Software
# Foundation; either version 3.0 of the License, or (at your option) any later
# version.
#
# Greenlight 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with Greenlight; if not, see <http://www.gnu.org/licenses/>.

# frozen_string_literal: true

Clamby.configure({
                   check: ENV.fetch('CLAMAV_SCANNING', 'false') == 'true',
                   daemonize: ENV.fetch('CLAMAV_DAEMONIZE', 'true') == 'true',
                   fdpass: ENV.fetch('CLAMAV_DAEMONIZE', 'true') == 'true',
                   config_file: ENV.fetch('CLAMAV_CONFIG', nil)
                 })
Original line number Diff line number Diff line
@@ -29,7 +29,7 @@ Rails.application.config.middleware.use OmniAuth::Builder do

      env['omniauth.strategy'].options[:issuer] = issuer_url
      env['omniauth.strategy'].options[:scope] = %i[openid email profile]
      env['omniauth.strategy'].options[:uid_field] = ENV.fetch('OPENID_CONNECT_UID_FIELD', 'preferred_username')
      env['omniauth.strategy'].options[:uid_field] = ENV.fetch('OPENID_CONNECT_UID_FIELD', 'sub')
      env['omniauth.strategy'].options[:discovery] = true
      env['omniauth.strategy'].options[:client_options].identifier = ENV.fetch('OPENID_CONNECT_CLIENT_ID')
      env['omniauth.strategy'].options[:client_options].secret = secret
@@ -46,7 +46,7 @@ Rails.application.config.middleware.use OmniAuth::Builder do
    provider :openid_connect,
             issuer:,
             scope: %i[openid email profile],
             uid_field: ENV.fetch('OPENID_CONNECT_UID_FIELD', 'preferred_username'),
             uid_field: ENV.fetch('OPENID_CONNECT_UID_FIELD', 'sub'),
             discovery: true,
             client_options: {
               identifier: ENV.fetch('OPENID_CONNECT_CLIENT_ID'),
Original line number Diff line number Diff line
@@ -17,7 +17,8 @@
# frozen_string_literal: true

if ENV['LOADBALANCER_ENDPOINT'].present?
  Rails.application.config.session_store :cookie_store, key: '_greenlight-3_0_session', domain: ENV.fetch('SESSION_DOMAIN_NAME', nil)
  Rails.application.config.session_store :cookie_store, key: '_greenlight-3_0_session', domain: ENV.fetch('SESSION_DOMAIN_NAME', nil),
                                                        path: ENV.fetch('RELATIVE_URL_ROOT', '/')
else
  Rails.application.config.session_store :cookie_store, key: '_greenlight-3_0_session'
  Rails.application.config.session_store :cookie_store, key: '_greenlight-3_0_session', path: ENV.fetch('RELATIVE_URL_ROOT', '/')
end
Original line number Diff line number Diff line
@@ -47,24 +47,31 @@

ar:
  opengraph:
    description: تعلم باستخدام بك بلو بتن ، الحل الموثوق به لعقد المؤتمرات عبر الويب مفتوح المصدر والذي يتيح التعاون الافتراضي السلس وتجارب التعلم عبر الإنترنت.
    description: تعلم باستخدام بك_بلو_بتن ، الحل الموثوق به لعقد المؤتمرات عبر الويب مفتوح المصدر والذي يتيح التعاون الافتراضي السلس وتجارب التعلم عبر الإنترنت.
  meeting:
    moderator_message: "لدعوة شخص ما إلى الاجتماع ، أرسل لهم هذا الرابط:"
    access_code: "رمز الوصول: %{code}"
  email:
    activation:
      account_activation: تفعيل الحساب
      welcome_to_bbb: مرحبًا بك في  بك بلو بتن!
      welcome_to_bbb: مرحبًا بك في  بك_بلو_بتن!
      get_started: للبدء ، يرجى تفعيل حسابك بالضغط على الزر أدناه.
      activate_account: تفعيل حساب
      link_expires: ستنتهي صلاحية الرابط في غضون 24 ساعة.
      if_link_expires: في حالة انتهاء صلاحية الرابط ، يرجى تسجيل الدخول وطلب بريد إلكتروني جديد للتنشيط.
    invitation:
      invitation_to_join: دعوة  بك بلو بتن
      you_have_been_invited: "لقد تمت دعوتك لإنشاء حساب  بك بلو بتن بواسطة %{name}."
      invitation_to_join: دعوة  بك_بلو_بتن
      you_have_been_invited: "لقد تمت دعوتك لإنشاء حساب  بك_بلو_بتن بواسطة %{name}."
      get_started: للتسجيل ، يرجى النقر فوق الزر أدناه واتباع الخطوات.
      valid_invitation: الدعوة صالحة لمدة 24 ساعة.
      sign_up: التسجيل
    new_user_signup:
      new_user: تسجيل مستخدم جديد في بك_بلو_بتن
      new_user_description: قام مستخدم جديد بالتسجيل لاستخدام بك_بلو_بتن.
      name: "الاسم: %{name}"
      email: "البريد: %{email}"
      admin_panel: "لوحة الإدارة"
      take_action: "لعرض المستخدم الجديد أو اتخاذ الإجراء اللازم، قم بزيارة لوحة الادارة"
    reset:
      password_reset: إعادة تعيين كلمة المرور
      password_reset_requested: "تم طلب إعادة تعيين كلمة المرور لـ %{email}."
Original line number Diff line number Diff line
@@ -65,6 +65,13 @@ de:
      get_started: "Um dich zu registrieren, klicken bitte auf diese Schaltfläche."
      valid_invitation: Die Einladung ist 24 Stunden gültig.
      sign_up: Registrieren
    new_user_signup:
      new_user: Anmeldung eines neuen BigBlueButton-Nutzers
      new_user_description: Ein neuer Nutzer hat sich angemeldet um BigBlueButton zu benutzen.
      name: "Name: %{name}"
      email: "E-Mail: %{email}"
      admin_panel: "Administrator-Panel"
      take_action: "Besuchen Sie das Administrator-Panel um den neuen Nutzer zu sehen und entsprechende Maßnahmen zu ergreifen"
    reset:
      password_reset: Passwort zurücksetzen
      password_reset_requested: "Eine Passwortrücksetzung wurde für %{email} beantragt."
Original line number Diff line number Diff line
@@ -47,30 +47,37 @@

el:
  opengraph:
    description: "Εκπαιδευτείτε με χρήση του BigBlueButton, την αξιόπιστη λύση διαδικτυακής διάσκεψης ανοιχτού κώδικα που επιτρέπει την απρόσκοπτη εικονική συνεργασία και τη διαδικτυακή εμπειρία εκπαίδευσης."
    description: "Μάθετε να χρησιμοποιείτε το BigBlueButton, την αξιόπιστη λύση διαδικτυακής διάσκεψης ανοιχτού κώδικα, που επιτρέπει την απρόσκοπτη εικονική συνεργασία και τη διαδικτυακή εμπειρία μάθησης."
  meeting:
    moderator_message: "Για να προσκαλέσετε κάποιον στη συνεδρίαση, στείλτε τον σύνδεσμο:"
    moderator_message: "Για αποστολή πρόσκλησης στη διάσκεψη, χρησιμοποιήστε τον σύνδεσμο:"
    access_code: "Κωδικός πρόσβασης: %{code}"
  email:
    activation:
      account_activation: Ενεργοποίηση λογαριασμού
      welcome_to_bbb: Καλώς ορίσατε στο BigBlueButton!
      get_started: "Για να ξεκινήσετε, παρακαλούμε ενεργοποιήστε τον λογαριασμό σας κάνοντας κλικ στο κουμπί παρακάτω."
      welcome_to_bbb: Καλώς ήρθατε στο BigBlueButton!
      get_started: "Για να ξεκινήσετε, παρακαλούμε ενεργοποιήστε τον λογαριασμό σας κάνοντας κλικ στο παρακάτω κουμπί."
      activate_account: Ενεργοποίηση λογαριασμού
      link_expires: Ο σύνδεσμος θα λήξει σε 24 ώρες.
      if_link_expires: "Εάν έληξε ο σύνδεσμος, συνδεθείτε και ζητήστε νέο email ενεργοποίησης."
      link_expires: Ο σύνδεσμος θα λήξη σε 24 ώρες.
      if_link_expires: "Εάν ο σύνδεσμος λήξει, συνδεθείτε και ζητήστε νέο email ενεργοποίησης."
    invitation:
      invitation_to_join: Πρόσκληση BigBlueButton
      you_have_been_invited: "Έχετε προσκληθεί από τον/την %{name}, να δημιουργήσετε έναν λογαριασμό BigBlueButton."
      get_started: "Για να εγγραφείτε, παρακαλούμε κάντε κλικ στο κουμπί παρακάτω και ακολουθήστε τα βήματα."
      invitation_to_join: Πρόσκληση στο BigBlueButton
      you_have_been_invited: "Έχετε προσκληθεί να δημιουργήσετε λογαριασμό στο BigBlueButton από τον/την %{name}."
      get_started: "Για εγγραφή, παρακαλούμε κάντε κλικ στο παρακάτω κουμπί και ακολουθήστε τα βήματα."
      valid_invitation: Η πρόσκληση ισχύει για 24 ώρες.
      sign_up: Εγγραφή
    new_user_signup:
      new_user: Εγγραφή νέου χρήστη BigBlueButton
      new_user_description: Ένας νέος χρήστης έχει εγγραφεί για να χρησιμοποιήσει το BigBlueButton.
      name: "Όνομα: %{name}"
      email: "Email: %{email}"
      admin_panel: "Πίνακας ελέγχου διαχειριστή"
      take_action: "Για να δείτε τον νέο χρήστη ή για να κάνετε τις απαραίτητες ενέργειες, επισκεφτείτε τον «Πίνακα ελέγχου» του διαχειριστή."
    reset:
      password_reset: Επαναφορά κωδικού πρόσβασης
      password_reset_requested: "Αίτημα επαναφοράς κωδικού πρόσβασης για %{email}."
      password_reset_confirmation: "Για επαναφορά κωδικού πρόσβασης, παρακαλούμε κάντε κλικ στο παρακάτω κουμπί."
      password_reset: Αίτημα επαναφοράς κωδικού πρόσβασης
      password_reset_requested: "Ένα αίτημα επαναφοράς κωδικού πρόσβασης στάλθηκε για %{email}."
      password_reset_confirmation: "Για επαναφορά του κωδικού πρόσβασης σας, παρακαλούμε κάντε κλικ στο παρακάτω κουμπί."
      reset_password: Επαναφορά κωδικού πρόσβασης
      link_expires: Ο σύνδεσμος θα λήξει σε 1 ώρα.
      ignore_request: "Εάν δεν υποβάλατε αίτημα αλλαγής κωδικού πρόσβασης, παρακαλούμε αγνοήστε αυτό το email."
      link_expires: Ο σύνδεσμος θα λήξη σε 1 ώρα.
      ignore_request: "Εάν δε ζητήσατε εσείς την αλλαγή του κωδικού πρόσβασης σας, παρακαλούμε αγνοείστε αυτό το email."
  room:
    new_room_name: "Αίθουσα του %{username}"
Original line number Diff line number Diff line
@@ -65,6 +65,13 @@ en:
      get_started: To sign up, please click the button below and follow the steps.
      valid_invitation: The invitation is valid for 24 hours.
      sign_up: Sign Up
    new_user_signup:
      new_user: New BigBlueButton User Signup
      new_user_description: A new user has signed up to use BigBlueButton.
      name: "Name: %{name}"
      email: "Email: %{email}"
      admin_panel: "Administrator Panel"
      take_action: "To view the new user or to take the necessary action, visit the Administrator Panel"
    reset:
      password_reset: Reset Password
      password_reset_requested: A password reset has been requested for %{email}.
Original line number Diff line number Diff line
@@ -47,30 +47,37 @@

fa_IR:
  opengraph:
    description: با استفاده از BigBlueButton بیاموزید؛ راه‌حل متن‌باز و قابل اعتماد کنفرانس تحت وب که همکاری مجازی یکپارچه و تجربیات یادگیری آنلاین را امکانپذیر می‌کند.
    description: یادگیری با استفاده از BigBlueButton، راه‌حل متن‌باز کنفرانس وب قابل اعتماد است که همکاری مجازی یکپارچه و تجربیات یادگیری آنلاین را امکان پذیر می‌کند.
  meeting:
    moderator_message: "برای دعوت از افراد دیگر به این جلسه، این پیوند را برای آنها ارسال کنید:"
    moderator_message: "برای دعوت از افراد دیگر به این جلسه، این پیوند را برای آنها ارسال کنید:"
    access_code: "کد دسترسی: %{code}"
  email:
    activation:
      account_activation: فعال‌سازی حساب کاربری
      welcome_to_bbb: به BigBlueButton خوش آمدید.
      get_started: برای شروع، لطفا با کلیک بر روی دکمهٔ زیر حساب کاربری خود را فعال کنید.
      activate_account: فعالکردن حساب کاربری
      get_started: برای شروع، لطفا با کلیک بر روی دکمه زیر حساب کاربری خود را فعال کنید.
      activate_account: فعال کردن حساب کاربری
      link_expires: این پیوند ۲۴ ساعت دیگر منقضی خواهد شد.
      if_link_expires: در صورت منقضیشدن پیوند، لطفا وارد شوید و درخواست یک رایانامهٔ فعالسازی جدید کنید.
      if_link_expires: در صورت منقضی شدن پیوند، لطفا در سیستم وارد شوید و درخواست فعال سازی ایمیل، جدید کنید.
    invitation:
      invitation_to_join: دعوت‌نامهٔ BigBlueButton
      invitation_to_join: دعوت به پیوستن به BigBlueButton
      you_have_been_invited: "شما توسط %{name} برای ایجاد حساب کاربری در BigBlueButton دعوت شده‌اید."
      get_started: برای ثبت‌نام، لطفا روی دکمهٔ زیر کلیک کنید و گام‌ها را دنبال کنید.
      valid_invitation: دعوت‌نامه فقط ۴۸ ساعت اعتبار دارد.
      sign_up: ثبت‌نام
      get_started: برای ثبت نام لطفا روی دکمه زیر کلیک کنید و مراحل را دنبال کنید.
      valid_invitation: The invitation is valid for 24 hours.
      sign_up: ثبت نام
    new_user_signup:
      new_user: ثبت نام کاربر جدید BigBlueButton
      new_user_description: یک کاربر جدید برای استفاده از BigBlueButton ثبت نام کرده است.
      name: "نام: %{name}"
      email: "ایمیل: %{email}"
      admin_panel: "پنل مدیریت"
      take_action: "برای مشاهده کاربر جدید یا انجام اقدامات لازم، به پنل مدیریت مراجعه کنید"
    reset:
      password_reset: بازنشانی گذرواژه
      password_reset_requested: "بازنشانی گذرواژه برای %{email} درخواست شدهاست."
      password_reset_confirmation: برای بازنشانی گذرواژه، لطفا روی دکمهٔ زیر کلیک کنید.
      reset_password: بازنشانی گذرواژه
      password_reset: بازنشانی رمز عبور
      password_reset_requested: "بازنشانی رمز عبور برای %{email} درخواست شده است."
      password_reset_confirmation: برای بازنشانی رمز عبور، لطفا روی دکمه زیر کلیک کنید.
      reset_password: بازنشانی رمز عبور
      link_expires: این پیوند ۱ ساعت دیگر منقضی خواهد شد.
      ignore_request: اگر درخواستی برای تغییر گذرواژه خود نکرده‌اید، لطفا این رایانامه را نادیده بگیرید.
      ignore_request: اگر درخواستی برای تغییر رمز عبور خود نکرده‌اید، لطفا این ایمیل را نادیده بگیرید.
  room:
    new_room_name: "اتاق %{username}"
Original line number Diff line number Diff line
@@ -50,7 +50,7 @@ fr:
    description: "Apprenez à utiliser BigBlueButton, la solution de webconférence open-source, fiable et qui offre une expérience adaptée aux collaborations comme aux apprentissages en ligne."
  meeting:
    moderator_message: "Pour inviter quelqu'un à la réunion, envoyez ce lien:"
    access_code: "Code d'accès: 1%{code}"
    access_code: "Code d'accès: %{code}"
  email:
    activation:
      account_activation: Activation du compte
@@ -61,14 +61,21 @@ fr:
      if_link_expires: "Si le lien a expiré, veuillez vous connecter et demander un nouveau courriel d'activation"
    invitation:
      invitation_to_join: Invitation à rejoindre BigBlueButton
      you_have_been_invited: "Vous avez été invité à créer un compte dans BigBlueButton par 1%{name}."
      you_have_been_invited: "Vous avez été invité à créer un compte dans BigBlueButton par %{name}."
      get_started: "Pour vous inscrire, veuillez cliquer sur le bouton ci-dessous et suivre les étapes proposées"
      valid_invitation: L'invitation est valable pendant 24 heures.
      sign_up: inscription
      sign_up: Inscription
    new_user_signup:
      new_user: Nouvelle inscription d'un utilisateur dans BigBlueButton
      new_user_description: Un nouvel utilisateur s'est inscrit pour utiliser BigBlueButton.
      name: "Nom: %{name}"
      email: "Courriel: %{email}"
      admin_panel: "Panneau Administrateur"
      take_action: "Pour afficher le nouvel utilisateur ou pour toute action nécessaire, consultez le panneau d'administration."
    reset:
      password_reset: Demande de réinitialisation du mot de passe
      password_reset_requested: "Une demande de réinitialisation de mot de passe est demandée par 1%{email}"
      password_reset_confirmation: "Pour réinitialiser votre mot de passe, veuillez cliquer sur la bouton ci-dessous\""
      password_reset_requested: "Une demande de réinitialisation de mot de passe est demandée par %{email}"
      password_reset_confirmation: "Pour réinitialiser votre mot de passe, veuillez cliquer sur le bouton ci-dessous."
      reset_password: Réinitialisation du mot de passe
      link_expires: Le lien expirera dans 1 heure.
      ignore_request: "Si vous n'avez pas demandé à changer de mot de passe, ignorez ce message."
Original line number Diff line number Diff line
@@ -65,6 +65,13 @@ gl:
      get_started: "Para rexistrarse, prema no seguinte botón e siga os pasos."
      valid_invitation: O convite é válido durante 24 horas.
      sign_up: Rexistrarse
    new_user_signup:
      new_user: Novo rexistro de usuario de BigBlueButton
      new_user_description: Rexistrouse un novo usuario para usar BigBlueButton.
      name: "Nome: %{name}"
      email: "Correo-e: %{email}"
      admin_panel: "Panel de administración"
      take_action: "Para ver o novo usuario ou realizar a acción necesaria, visite o Panel de administración"
    reset:
      password_reset: Restabelecer o contrasinal
      password_reset_requested: "Solicitouse un restabelecemento de contrasinal para %{email}."
Original line number Diff line number Diff line
@@ -65,6 +65,13 @@ ja:
      get_started: 登録するには下のボタンをクリックし、手順にしたがってください。
      valid_invitation: この招待は24時間有効です。
      sign_up: 登録
    new_user_signup:
      new_user: 新しいBigBlueButtonユーザーの登録
      new_user_description: 新しいBigBlueButtonユーザーの登録が行われました。
      name: "名前: %{name}"
      email: "メール: %{email}"
      admin_panel: "管理者パネル"
      take_action: "新しいユーザーをチェックしたり、必要な操作を行ったりするには、管理者パネルを使用してください"
    reset:
      password_reset: パスワード再設定
      password_reset_requested: "%{email}に対してパスワードの再設定が要請されました。"
Original line number Diff line number Diff line
@@ -65,6 +65,13 @@ ru:
      get_started: "Чтобы зарегистрироваться, нажмите кнопку ниже и следуйте инструкциям."
      valid_invitation: Приглашение действительно в течение 24 часов.
      sign_up: Зарегистрироваться
    new_user_signup:
      new_user: Новая регистрация пользователя
      new_user_description: Зарегистрирован новый пользователь.
      name: "Имя: %{name}"
      email: "Email: %{email}"
      admin_panel: "Панель администратора"
      take_action: "Чтобы просмотреть нового пользователя, откройте панель администратора"
    reset:
      password_reset: Сброс пароля
      password_reset_requested: "Запрошен сброс пароля для %{email}."
@@ -72,3 +79,5 @@ ru:
      reset_password: Сбросить пароль
      link_expires: Срок действия ссылки истекает через 1 час.
      ignore_request: "Если вы не отправляли запрос на смену пароля, проигнорируйте это письмо."
  room:
    new_room_name: "Комната для встреч пользователя %{username}"
Original line number Diff line number Diff line
@@ -47,30 +47,37 @@

tr:
  opengraph:
    description: "Sorunsuz sanal işbirliği ve çevrim içi öğrenme deneyimleri sağlayan, güvenilir ve açık kaynaklı internet üzerinden görüşme çözümü BigBlueButton uygulamasını kullanarak öğrenin."
    description: "Sorunsuz sanal işbirliği ve çevrim içi öğrenme deneyimi sağlayan, güvenilir açık kaynaklı internet konferansı çözümü BigBlueButton uygulamasını kullanarak öğrenin."
  meeting:
    moderator_message: "Toplantıya katılmasını istediğiniz kişilere bu bağlantıyı gönderin: "
    moderator_message: "Toplantıya çağırmak istediğiniz kişilere bu bağlantıyı gönderin:"
    access_code: "Erişim kodu: %{code}"
  email:
    activation:
      account_activation: Hesap etkinleştirme
      welcome_to_bbb: BigBlueButton uygulamasına hoş geldiniz!
      get_started: "Başlamak için, lütfen aşağıdaki düğmeye tıklayarak hesabınızı etkinleştirin."
      get_started: Lütfen başlamak için aşağıdaki düğmeye tıklayarak hesabınızı etkinleştirin.
      activate_account: Hesabı etkinleştir
      link_expires: Bağlantı 24 saat sonra geçersiz olacak.
      if_link_expires: Bağlantının süresi geçerse oturum açarak yeni bir etkinleştirme e-postası alın.
      link_expires: Bu bağlantı 24 saat sonra geçersiz olacak.
      if_link_expires: "Bağlantının süresi geçerse, yeni bir etkinleştirme e-postası almak için oturum ın."
    invitation:
      invitation_to_join: BigBlueButton çağrısı
      you_have_been_invited: "%{name} tarafından bir BigBlueButton hesabı açmaya çağrıldınız."
      get_started: Hesap açmak için aşağıdaki düğmeye tıklayın ve yönergeleri izleyin.
      valid_invitation: Bu çağrı 24 saat süreyle geçerlidir.
      valid_invitation: Çağrı 24 saat boyunca geçerlidir.
      sign_up: Hesap aç
    new_user_signup:
      new_user: Yeni BigBlueButton kullanıcı hesabı açılışı
      new_user_description: Yeni bir kullanıcı BigBlueButton kullanmak için hesap açtı.
      name: "Adı: %{name}"
      email: "E-posta adresi: %{email}"
      admin_panel: "Yönetim panosu"
      take_action: "Yeni kullanıcıyı görüntülemek ya da gerekli işlemi yapmak için yönetim panosuna gidin"
    reset:
      password_reset: Parolayı sıfırla
      password_reset_requested: "%{email} için bir parola sıfırlama isteğinde bulunuldu."
      password_reset_requested: "%{email} e-posta adresi için parola sıfırlama isteğinde bulunuldu."
      password_reset_confirmation: Parolanızı sıfırlamak için aşağıdaki düğmeye tıklayın.
      reset_password: Parolayı sıfırla
      link_expires: Bağlantı 1 saat sonra geçersiz olacak.
      ignore_request: Parola değiştirme isteğinde bulunmadıysanız bu e-postayı yok sayabilirsiniz.
      ignore_request: Parolanızı değiştirme isteğinde bulunmadıysanız bu e-postayı yok sayabilirsiniz.
  room:
    new_room_name: "%{username} kullanıcısının odası"
Original line number Diff line number Diff line
@@ -65,6 +65,13 @@ uk:
      get_started: "Щоб зареєструватись, натисніть кнопку знизу та слідуйте подальшим крокам."
      valid_invitation: Запрошення дійсне протягом 24 годин.
      sign_up: Реєстрація
    new_user_signup:
      new_user: Нова реєстрація користувача BigBlueButton
      new_user_description: Новий користувач зареєструвався для використання BigBlueButton.
      name: "Ім'я: %{name}"
      email: "Електронна пошта: %{email}"
      admin_panel: "Панель адміністратора"
      take_action: "Для перегляду нового користувача та необхідних дій відвідайте панель адміністратора."
    reset:
      password_reset: Запит на новий пароль
      password_reset_requested: "Повідомлення на зміну паролю надіслане на пошту %{email}"
Original line number Diff line number Diff line
@@ -16,43 +16,24 @@

# frozen_string_literal: true

require_relative 'task_helpers'

namespace :migration do
  batch_size = 500

  desc 'Send a reset password email to users'
  task :reset_password_email, %i[base_url provider] => :environment do |_task, args|
    args.with_defaults(provider: 'greenlight')

    root_url = "#{args[:base_url]}/"

    if ENV['SMTP_SERVER'].blank?
      err 'SMTP Server not set. Skipping sending reset password emails.'
      exit 0
class AddEmailOnSignUpPermission < ActiveRecord::Migration[7.1]
  def up
    email_permission = Permission.create!(name: 'EmailOnSignup')
    admin = Role.where(name: 'Administrator')

    values = []
    admin.each do |adm|
      values << { role: adm, permission: email_permission, value: 'true' }
    end

    info 'Sending reset password emails...'
    User.where(external_id: nil, provider: args[:provider])
        .find_each(batch_size:) do |user|
      token = user.generate_reset_token!

      UserMailer.with(user:,
                      reset_url: reset_password_url(root_url, token),
                      base_url: args[:base_url],
                      provider: args[:provider]).reset_password_email.deliver_now

      success 'Successfully sent reset password email to:'
      info    "  name: #{user.name}"
      info    "  email: #{user.email}"
    rescue StandardError => e
      err "Unable to send reset password email to:\n  name: #{user.name} \n  email: #{user.email} \n  error: #{e}"
    end
    Role.where.not(name: 'Administrator').each do |role|
      values.push({ role:, permission: email_permission, value: 'false' })
    end

  private
    RolePermission.create! values
  end

  def reset_password_url(root_url, token)
    "#{root_url}reset_password/#{token}"
  def down
    raise ActiveRecord::IrreversibleMigration
  end
end
Original line number Diff line number Diff line
# frozen_string_literal: true

class AddVisibilityToRolePermissions < ActiveRecord::Migration[7.1]
  def up
    visibility_permission = Permission.create!(name: 'AccessToVisibilities')

    Role.all.each do |role|
      RolePermission.create!(role:, permission: visibility_permission, value: Recording::VISIBILITIES.values)
    end
  end

  def down
    raise ActiveRecord::IrreversibleMigration
  end
end
Original line number Diff line number Diff line
# frozen_string_literal: true

class AddDefaultRecordingVisibilityToSettings < ActiveRecord::Migration[7.1]
  def up
    setting = Setting.create!(name: 'DefaultRecordingVisibility')
    SiteSetting.create!(setting:, value: 'Published', provider: 'greenlight')
  end

  def down
    raise ActiveRecord::IrreversibleMigration
  end
end
Original line number Diff line number Diff line
# frozen_string_literal: true

class FixAccessToVisibilitiesValue < ActiveRecord::Migration[7.1]
  def up
    # rubocop:disable Rails/SkipsModelValidations
    Permission.find_by(name: 'AccessToVisibilities').role_permissions.where(value: 'false').update_all(value: Recording::VISIBILITIES.values)
    # rubocop:enable Rails/SkipsModelValidations
  end

  def down
    raise ActiveRecord::IrreversibleMigration
  end
end
Original line number Diff line number Diff line
DataMigrate::Data.define(version: 20231030185844)
DataMigrate::Data.define(version: 20231213203353)
Original line number Diff line number Diff line
# frozen_string_literal: true

class AddHelpCenterSetting < ActiveRecord::Migration[7.1]
  def up
    Setting.create!(name: 'HelpCenter') unless Setting.exists?(name: 'HelpCenter')

    return if SiteSetting.exists?(setting: Setting.find_by(name: 'HelpCenter'))

    SiteSetting.create!(
      setting: Setting.find_by(name: 'HelpCenter'),
      value: '',
      provider: 'greenlight'
    )
  end

  def down
    Setting.find_by(name: 'HelpCenter')&.destroy
    SiteSetting.find_by(setting: Setting.find_by(name: 'HelpCenter')).destroy
  end
end
+1 −1
Original line number Diff line number Diff line
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.1].define(version: 2023_07_05_183747) do
ActiveRecord::Schema[7.1].define(version: 2023_12_18_154727) do
  # These are extensions that must be enabled in order to support this database
  enable_extension "pgcrypto"
  enable_extension "plpgsql"
+16 −10
Original line number Diff line number Diff line
@@ -3,7 +3,7 @@ import * as esbuild from 'esbuild';
// Fetch 'RELATIVE_URL_ROOT' ENV variable value while removing any trailing slashes.
const relativeUrlRoot = (process.env.RELATIVE_URL_ROOT || '').replace(/\/*$/, '');

await esbuild.build({
esbuild.context({
  entryPoints: ['app/javascript/main.jsx'],
  bundle: true,
  sourcemap: true,
@@ -12,16 +12,22 @@ await esbuild.build({
    '.png': 'dataurl',
    '.svg': 'text',
  },
  watch: {
    onRebuild: (error, result) => {
      if (error) console.error('watch build failed:', error);
      else console.log('watch build succeeded:', result);
    },
  },
  define: {
    'process.env.RELATIVE_URL_ROOT': `"${relativeUrlRoot}"`,
    'process.env.OMNIAUTH_PATH': `"${relativeUrlRoot}/auth/openid_connect"`, // currently, only OIDC is implemented
  },
});

console.log('watch build started');
}).then(context => {
  if (process.argv.includes("--watch")) {
    // Enable watch mode
    context.watch()
  } else {
    // Build once and exit if not in watch mode
    context.rebuild().then(result => {
      context.dispose()
    })
  }
  console.log('build succeeded');
}).catch((e) => {
  console.error('build failed:', e);
  process.exit(1)
})
 No newline at end of file
Original line number Diff line number Diff line
@@ -26,6 +26,8 @@ task :server_recordings_sync, %i[provider] => :environment do |_task, args|

    recordings = BigBlueButtonApi.new(provider: args[:provider]).get_recordings(meeting_ids:)

    next if recordings[:recordings].blank?

    # Skip the entire batch if the first and last recordings exist
    if Recording.exists?(record_id: recordings[:recordings][0][:recordID]) && Recording.exists?(record_id: recordings[:recordings][-1][:recordID])
      next
Original line number Diff line number Diff line
@@ -43,6 +43,27 @@ namespace :user do
    exit 0
  end

  desc 'Set user role to Administrator'
  task :set_admin_role, %i[email] => :environment do |_task, args|
    email = args[:email]

    # return err if no user email provided
    err 'Please provide an email address of the user you wish to set to Administrator role.' if email.blank?

    user = User.find_by(email:, provider: 'greenlight')

    # return err if user not found
    err "User with email: #{email} not found" if user.blank?

    role = Role.find_by(name: 'Administrator', provider: 'greenlight')

    # return err if Administrator role not found
    err "Role 'Administrator' not found for provider 'greenlight'" if role.blank?

    user.update(role:)
    success "User role set to Administrator for email: #{email}"
  end

  private

  def check_role!(user:)
+4825 −8584

File changed.

Preview size limit exceeded, changes collapsed.

+5 −4
Original line number Diff line number Diff line
@@ -9,10 +9,10 @@
    "@hookform/resolvers": "^2.8.8",
    "@popperjs/core": "^2.11.5",
    "@rails/actioncable": "^7.0.2",
    "axios": "^0.26.1",
    "bootstrap": "5.1.3",
    "axios": "^1.6.0",
    "bootstrap": "5.3.2",
    "bootstrap-icons": "^1.8.3",
    "esbuild": "^0.14.42",
    "esbuild": "^0.19.9",
    "esbuild-plugin-import-glob": "^0.1.1",
    "esbuild-sass-plugin": "^2.2.6",
    "i18next": "^21.9.1",
@@ -29,6 +29,7 @@
    "react-i18next": "^11.18.5",
    "react-query": "^3.34.16",
    "react-router-dom": "^6.4.3",
    "react-select": "^5.8.0",
    "react-test-renderer": "^17.0.2",
    "react-toastify": "^9.1.1",
    "sass": "^1.52.1",
@@ -40,7 +41,7 @@
  "scripts": {
    "build": "node esbuild.mjs",
    "build:css": "sass ./app/assets/stylesheets/application.bootstrap.scss ./app/assets/builds/application.css --no-source-map --load-path=node_modules --style compressed",
    "build:development": "node esbuild.dev.mjs",
    "build:development": "node esbuild.dev.mjs --watch",
    "build:development:css": "sass ./app/assets/stylesheets/application.bootstrap.scss ./app/assets/builds/application.css --no-source-map --load-path=node_modules"
  },
  "devDependencies": {
+14 −1
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ REDIS_URL=
### SMTP CONFIGURATION
# Emails are required for the basic features of Greenlight to function.
# Please refer to your SMTP provider to get the values for the variables below
# More information: https://docs.bigbluebutton.org/greenlight/v3/install/#email-setup
#SMTP_SENDER_EMAIL=
#SMTP_SENDER_NAME=
#SMTP_SERVER=
@@ -39,13 +40,15 @@ REDIS_URL=
#SMTP_SSL_VERIFY=true

### EXTERNAL AUTHENTICATION METHODS
#
# More information: https://docs.bigbluebutton.org/greenlight/v3/install/#openid-connect-setup
#OPENID_CONNECT_CLIENT_ID=
#OPENID_CONNECT_CLIENT_SECRET=
#OPENID_CONNECT_ISSUER=
#OPENID_CONNECT_REDIRECT=
#OPENID_CONNECT_UID_FIELD=sub

# To enable hCaptcha on the user sign up and sign in, define these 2 keys
# More information: https://docs.bigbluebutton.org/greenlight/v3/install/#hcaptcha-setup
#HCAPTCHA_SITE_KEY=
#HCAPTCHA_SECRET_KEY=

@@ -70,9 +73,12 @@ REDIS_URL=

# Define the default locale language code (i.e. 'en' for English) from the following list:
#  [en, ar, fr, es, fa_IR]
# The DEFAULT_LOCALE setting specifies the default language, overriding the browser language which is always set.
# More information: https://docs.bigbluebutton.org/greenlight/v3/install/#default-locale-setup
#DEFAULT_LOCALE=en

# Set this if you like to deploy Greenlight on a relative root path other than /
# More information: https://docs.bigbluebutton.org/greenlight/v3/install/#relative-url-root-path-subdirectory-setup
#RELATIVE_URL_ROOT=/gl

## Define log level in production.
@@ -84,3 +90,10 @@ LOG_LEVEL=info
# RAILS_LOG_REMOTE_NAME=xxx.papertrailapp.com
# RAILS_LOG_REMOTE_PORT=99999
# RAILS_LOG_REMOTE_TAG=greenlight-v3

## ClamAV Virus Scanning
# If you have ClamAV installed on the same machine as your Greenlight deployment, you can enable automatic virus scanning
# for presentations, avatars, and BrandingImage. If a malicious file is detected, the user will be informed and asked
# to check their file.
#CLAMAV_SCANNING=true
#CLAMAV_DAEMONIZE=true
Original line number Diff line number Diff line
@@ -136,6 +136,7 @@ RSpec.describe Api::V1::Admin::TenantsController, type: :controller do
    Setting.find_or_create_by(name: 'BrandingImage')
    Setting.find_or_create_by(name: 'Terms')
    Setting.find_or_create_by(name: 'PrivacyPolicy')
    Setting.find_or_create_by(name: 'HelpCenter')
    Setting.find_or_create_by(name: 'RegistrationMethod')
    Setting.find_or_create_by(name: 'ShareRooms')
    Setting.find_or_create_by(name: 'PreuploadPresentation')
Original line number Diff line number Diff line
@@ -374,7 +374,7 @@ RSpec.describe ExternalController, type: :controller do
    end

    it 'calls RecordingCreator with the right values' do
      expect(RecordingCreator).to receive(:new).with(recording: sample_recording).and_call_original
      expect(RecordingCreator).to receive(:new).with(recording: sample_recording, first_creation: true).and_call_original

      post :recording_ready
    end
Original line number Diff line number Diff line
@@ -150,6 +150,7 @@ RSpec.describe Api::V1::Migrations::ExternalController, type: :controller do
        name: 'user',
        email: 'user@users.com',
        provider: 'greenlight',
        password_digest: 'fake_password_digest',
        language: 'language',
        role: valid_user_role.name
      }
@@ -185,8 +186,8 @@ RSpec.describe Api::V1::Migrations::ExternalController, type: :controller do
            expect(user.role).to eq(valid_user_role)
            expect(user.session_token).to be_present
            expect(user.provider).to eq('greenlight')
            expect(user.password_digest).to eq(valid_user_params[:password_digest])
            expect(response).to have_http_status(:created)
            expect(user.password_digest).to be_present
          end

          it 'creates the user without a password if provider is not greenlight' do
@@ -251,8 +252,8 @@ RSpec.describe Api::V1::Migrations::ExternalController, type: :controller do
            expect(user.role).to eq(valid_user_role)
            expect(user.session_token).to be_present
            expect(user.provider).to eq('greenlight')
            expect(user.password_digest).to eq(valid_user_params[:password_digest])
            expect(response).to have_http_status(:created)
            expect(user.password_digest).to be_blank
          end
        end

@@ -296,14 +297,17 @@ RSpec.describe Api::V1::Migrations::ExternalController, type: :controller do
          end
        end

        context 'when providing a :provider or a :password' do
          before { valid_user_params.merge!(password: 'Password1!') }
        context 'when providing a valid :password_digest' do
          before do
            temp_user = create(:user, password: 'Password1!')
            valid_user_params.merge!(password_digest: temp_user.password_digest)
          end

          it 'returns :created and creates a user while ignoring the extra values' do
            encrypted_params = encrypt_params({ user: valid_user_params }, expires_in: 10.seconds)
            expect { post :create_user, params: { v2: { encrypted_params: } } }.to change(User, :count).from(0).to(1)
            expect { post :create_user, params: { v2: { encrypted_params: } } }.to change(User, :count).by(1)

            user = User.take
            user = User.find_by(email: valid_user_params[:email])
            expect(user.name).to eq(valid_user_params[:name])
            expect(user.email).to eq(valid_user_params[:email])
            expect(user.language).to eq(valid_user_params[:language])
@@ -311,7 +315,7 @@ RSpec.describe Api::V1::Migrations::ExternalController, type: :controller do
            expect(user.session_token).to be_present
            expect(user.provider).to eq(valid_user_params[:provider])
            expect(response).to have_http_status(:created)
            expect(user.authenticate('Password1!')).to be_falsy
            expect(user.authenticate('Password1!')).to be_truthy
          end
        end

Original line number Diff line number Diff line
@@ -196,49 +196,43 @@ RSpec.describe Api::V1::RecordingsController, type: :controller do
    let(:room) { create(:room, user:) }
    let(:recording) { create(:recording, room:) }

    def expect_to_update_recording_props_to(publish:, protect:, list:, visibility:)
      expect_any_instance_of(BigBlueButtonApi).to receive(:publish_recordings).with(record_ids: recording.record_id, publish:)
      expect_any_instance_of(BigBlueButtonApi).to receive(:update_recordings).with(record_id: recording.record_id,
                                                                                   meta_hash: {
                                                                                     protect:, 'meta_gl-listed': list
                                                                                   })
    it 'updates a recordings visibility' do
      expect_any_instance_of(BigBlueButtonApi)
        .to receive(:update_recording_visibility)
        .with(record_id: recording.record_id, visibility: Recording::VISIBILITIES[:published])

      post :update_visibility, params: { visibility:, id: recording.record_id }
      expect do
        post :update_visibility, params: { visibility: Recording::VISIBILITIES[:published], id: recording.record_id }
      end.to(change { recording.reload.visibility })

      expect(recording.reload.visibility).to eq(visibility)
      expect(response).to have_http_status(:ok)
    end

    it 'changes the recording visibility to "Published"' do
      expect_to_update_recording_props_to(publish: true, protect: false, list: false, visibility: Recording::VISIBILITIES[:published])
    context 'AccessToVisibilities permission' do
      before do
        RolePermission.find_by(role: user.role, permission: Permission.find_by(name: 'AccessToVisibilities')).update(value: ['Published'])
      end

    it 'changes the recording visibility to "Unpublished"' do
      expect_to_update_recording_props_to(publish: false, protect: false, list: false, visibility: Recording::VISIBILITIES[:unpublished])
    end
      it 'returns forbidden if the user is not permitted to use that format' do
        expect_any_instance_of(BigBlueButtonApi).not_to receive(:update_recording_visibility)

    it 'changes the recording visibility to "Protected"' do
      expect_to_update_recording_props_to(publish: true, protect: true, list: false, visibility: Recording::VISIBILITIES[:protected])
    end
        expect do
          post :update_visibility, params: { visibility: 'Unpublished', id: recording.record_id }
        end.not_to(change { recording.reload.visibility })

    it 'changes the recording visibility to "Public"' do
      expect_to_update_recording_props_to(publish: true, protect: false, list: true, visibility: Recording::VISIBILITIES[:public])
        expect(response).to have_http_status(:forbidden)
      end

    it 'changes the recording visibility to "Public/Protected"' do
      expect_to_update_recording_props_to(publish: true, protect: true, list: true, visibility: Recording::VISIBILITIES[:public_protected])
    end

    context 'Unkown visibility' do
      it 'returns :bad_request and does not update the recording' do
        expect_any_instance_of(BigBlueButtonApi).not_to receive(:publish_recordings)
        expect_any_instance_of(BigBlueButtonApi).not_to receive(:update_recordings)
    context 'Unknown visibility' do
      it 'returns :forbidden and does not update the recording' do
        expect_any_instance_of(BigBlueButtonApi).not_to receive(:update_recording_visibility)

        expect do
          post :update_visibility, params: { visibility: '404', id: recording.record_id }
        end.not_to(change { recording.reload.visibility })

        expect(response).to have_http_status(:bad_request)
        expect(response).to have_http_status(:forbidden)
      end
    end

@@ -253,11 +247,9 @@ RSpec.describe Api::V1::RecordingsController, type: :controller do
      it 'allows a shared user to update a recording visibility' do
        create(:shared_access, user_id: signed_in_user.id, room_id: room.id)

        expect_any_instance_of(BigBlueButtonApi).to receive(:publish_recordings).with(record_ids: recording.record_id, publish: false)
        expect_any_instance_of(BigBlueButtonApi).to receive(:update_recordings).with(record_id: recording.record_id,
                                                                                     meta_hash: {
                                                                                       protect: false, 'meta_gl-listed': false
                                                                                     })
        expect_any_instance_of(BigBlueButtonApi)
          .to receive(:update_recording_visibility)
          .with(record_id: recording.record_id, visibility: Recording::VISIBILITIES[:unpublished])

        expect do
          post :update_visibility, params: { visibility: Recording::VISIBILITIES[:unpublished], id: recording.record_id }
@@ -267,8 +259,7 @@ RSpec.describe Api::V1::RecordingsController, type: :controller do
      end

      it 'disallows a none shared user to update a recording visibility' do
        expect_any_instance_of(BigBlueButtonApi).not_to receive(:publish_recordings)
        expect_any_instance_of(BigBlueButtonApi).not_to receive(:update_recordings)
        expect_any_instance_of(BigBlueButtonApi).not_to receive(:update_recording_visibility)

        expect do
          post :update_visibility, params: { visibility: Recording::VISIBILITIES[:unpublished], id: recording.record_id }

File changed.

Preview size limit exceeded, changes collapsed.

+2280 −1463

File changed.

Preview size limit exceeded, changes collapsed.