diff --git a/README.md b/README.md index 1fc0207a8..85ca8fa94 100644 --- a/README.md +++ b/README.md @@ -71,9 +71,8 @@ The deal is fair, you share your projects and as reward you benefits from projec If you want to try it, you can visit [this Fab-manager](https://fablab.lacasemate.fr/#!/projects) and see projects from different Fab-managers. To start using this awesome feature, there are a few steps: -- send a mail to **contact@fab-manager.com** asking for your Open Projects client's credentials and giving them the name of your Fab-manager, they will give you an `OPENLAB_APP_ID` and an `OPENLAB_APP_SECRET` -- fill in the value of the keys in your environment file -- start your Fab-manager app +- send a mail to **contact@fab-manager.com** asking for your Open Projects client's credentials and giving them the name and the URL of your Fab-manager, they will give you an `App ID` and a `secret` +- fill in the value of the keys in Admin > Projects > Settings > Projects sharing - export your projects to open-projects (if you already have projects created on your Fab-manager, unless you can skip that part) executing this command: `bundle exec rake fablab:openlab:bulk_export` **IMPORTANT: please run your server in production mode.** diff --git a/app/assets/javascripts/controllers/projects.js.erb b/app/assets/javascripts/controllers/projects.js.erb index 99cbfd47a..5125835e1 100644 --- a/app/assets/javascripts/controllers/projects.js.erb +++ b/app/assets/javascripts/controllers/projects.js.erb @@ -87,7 +87,7 @@ class ProjectsController { $scope.totalSteps = $scope.project.project_steps_attributes.length; // List of extensions allowed for CAD attachements upload - $scope.allowedExtensions = allowedExtensions; + $scope.allowedExtensions = allowedExtensions.setting.value.split(' '); /** * For use with ngUpload (https://github.com/twilson63/ngUpload). @@ -266,8 +266,8 @@ class ProjectsController { /** * Controller used on projects listing page */ -Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'Project', 'machinesPromise', 'themesPromise', 'componentsPromise', 'paginationService', 'OpenlabProject', '$window', 'growl', '_t', '$location', '$timeout', - function ($scope, $state, Project, machinesPromise, themesPromise, componentsPromise, paginationService, OpenlabProject, $window, growl, _t, $location, $timeout) { +Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'Project', 'machinesPromise', 'themesPromise', 'componentsPromise', 'paginationService', 'OpenlabProject', '$window', 'growl', '_t', '$location', '$timeout', 'settingsPromise', 'openLabActive', + function ($scope, $state, Project, machinesPromise, themesPromise, componentsPromise, paginationService, OpenlabProject, $window, growl, _t, $location, $timeout, settingsPromise, openLabActive) { /* PRIVATE STATIC CONSTANTS */ // Number of projects added to the page when the user clicks on 'load more projects' @@ -277,11 +277,11 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P /* PUBLIC SCOPE */ // Fab-manager's instance ID in the openLab network - $scope.openlabAppId = Fablab.openlabAppId; + $scope.openlabAppId = settingsPromise.openlab_app_id // Is openLab enabled on the instance? $scope.openlab = { - projectsActive: Fablab.openlabProjectsActive, + projectsActive: openLabActive.isPresent, searchOverWholeNetwork: false }; diff --git a/app/assets/javascripts/router.js.erb b/app/assets/javascripts/router.js.erb index 8e6979fe6..7d6f20b37 100644 --- a/app/assets/javascripts/router.js.erb +++ b/app/assets/javascripts/router.js.erb @@ -255,7 +255,9 @@ angular.module('application.router', ['ui.router']) resolve: { themesPromise: ['Theme', function (Theme) { return Theme.query().$promise; }], componentsPromise: ['Component', function (Component) { return Component.query().$promise; }], - machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }] + machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }], + settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['openlab_app_id']" }).$promise; }], + openLabActive: ['Setting', function (Setting) { return Setting.isPresent({ name: 'openlab_app_secret' }).$promise; }], } }) .state('app.logged.projects_new', { @@ -267,7 +269,7 @@ angular.module('application.router', ['ui.router']) } }, resolve: { - allowedExtensions: ['Project', function (Project) { return Project.allowedExtensions().$promise; }] + allowedExtensions: ['Setting', function (Setting) { return Setting.get({ name: 'allowed_cad_extensions' }).$promise; }] } }) .state('app.public.projects_show', { @@ -293,7 +295,7 @@ angular.module('application.router', ['ui.router']) }, resolve: { projectPromise: ['$stateParams', 'Project', function ($stateParams, Project) { return Project.get({ id: $stateParams.id }).$promise; }], - allowedExtensions: ['Project', function (Project) { return Project.allowedExtensions().$promise; }] + allowedExtensions: ['Setting', function (Setting) { return Setting.get({ name: 'allowed_cad_extensions' }).$promise; }] } }) @@ -629,7 +631,7 @@ angular.module('application.router', ['ui.router']) themesPromise: ['Theme', function (Theme) { return Theme.query().$promise; }], settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['feature_tour_display', 'disqus_shortname', 'allowed_cad_extensions', \ - 'allowed_cad_mime_types', 'open_lab_app_id', 'open_lab_app_secret']" }).$promise; + 'allowed_cad_mime_types', 'openlab_app_id', 'openlab_app_secret']" }).$promise; }] } }) diff --git a/app/assets/javascripts/services/project.js b/app/assets/javascripts/services/project.js index 564562f9f..2b14b36c8 100644 --- a/app/assets/javascripts/services/project.js +++ b/app/assets/javascripts/services/project.js @@ -12,11 +12,6 @@ Application.Services.factory('Project', ['$resource', function ($resource) { method: 'GET', url: '/api/projects/search', isArray: false - }, - allowedExtensions: { - method: 'GET', - url: '/api/projects/allowed_extensions', - isArray: true } } ); diff --git a/app/assets/javascripts/services/setting.js b/app/assets/javascripts/services/setting.js index f97ef9af6..db240533a 100644 --- a/app/assets/javascripts/services/setting.js +++ b/app/assets/javascripts/services/setting.js @@ -20,6 +20,11 @@ Application.Services.factory('Setting', ['$resource', function ($resource) { url: '/api/settings/reset/:name', params: { name: '@name' }, method: 'PUT' + }, + isPresent: { + url: '/api/settings/is_present/:name', + params: { name: '@name' }, + method: 'GET' } } ); diff --git a/app/assets/templates/admin/projects/settings.html.erb b/app/assets/templates/admin/projects/settings.html.erb index b52c8cf76..d7cd3e214 100644 --- a/app/assets/templates/admin/projects/settings.html.erb +++ b/app/assets/templates/admin/projects/settings.html.erb @@ -74,7 +74,7 @@ + fa-icon="fa-info">
diff --git a/app/assets/templates/projects/_form.html.erb b/app/assets/templates/projects/_form.html.erb index 4f948c79f..cf5ec2cec 100644 --- a/app/assets/templates/projects/_form.html.erb +++ b/app/assets/templates/projects/_form.html.erb @@ -54,7 +54,7 @@
{{ 'app.shared.buttons.browse' }} {{ 'app.shared.buttons.change' }} - "> + diff --git a/app/controllers/api/projects_controller.rb b/app/controllers/api/projects_controller.rb index adf27a948..b83f44154 100644 --- a/app/controllers/api/projects_controller.rb +++ b/app/controllers/api/projects_controller.rb @@ -60,10 +60,6 @@ class API::ProjectsController < API::ApiController render :index end - def allowed_extensions - render json: ENV['ALLOWED_EXTENSIONS'].split(' '), status: :ok - end - private def set_project diff --git a/app/controllers/api/settings_controller.rb b/app/controllers/api/settings_controller.rb index 5b1604157..33e268cbd 100644 --- a/app/controllers/api/settings_controller.rb +++ b/app/controllers/api/settings_controller.rb @@ -5,7 +5,7 @@ class API::SettingsController < API::ApiController before_action :authenticate_user!, only: %i[update bulk_update reset] def index - @settings = Setting.where(name: names_as_string_to_array) + @settings = policy_scope(Setting.where(name: names_as_string_to_array)) end def update @@ -35,10 +35,18 @@ class API::SettingsController < API::ApiController end def show + authorize SettingContext.new(params[:name]) + @setting = Setting.find_or_create_by(name: params[:name]) @show_history = params[:history] == 'true' && current_user.admin? end + def test_present + authorize SettingContext.new(params[:name]) + + @setting = Setting.get(params[:name]) + end + def reset authorize Setting diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb index e737e8aac..7f6714930 100644 --- a/app/mailers/base_mailer.rb +++ b/app/mailers/base_mailer.rb @@ -2,7 +2,7 @@ # Mailer configuration class BaseMailer < ActionMailer::Base - default from: ->(*) { Setting.get('mail_from') } + default from: ->(*) { Setting.get('email_from') } layout 'notifications_mailer' helper :application diff --git a/app/mailers/notifications_mailer.rb b/app/mailers/notifications_mailer.rb index d5f153ddf..837a91395 100644 --- a/app/mailers/notifications_mailer.rb +++ b/app/mailers/notifications_mailer.rb @@ -2,7 +2,7 @@ # Handle most of the emails sent by the platform. Triggered by notifications class NotificationsMailer < NotifyWith::NotificationsMailer - default from: ->(*) { Setting.get('mail_from') } + default from: ->(*) { Setting.get('email_from') } layout 'notifications_mailer' helper :application diff --git a/app/mailers/overwritten_devise_mailer.rb b/app/mailers/overwritten_devise_mailer.rb index a23b11a8a..f9ed4c8c7 100644 --- a/app/mailers/overwritten_devise_mailer.rb +++ b/app/mailers/overwritten_devise_mailer.rb @@ -5,5 +5,5 @@ class OverwrittenDeviseMailer < Devise::Mailer helper :application include Devise::Controllers::UrlHelpers default template_path: 'devise/mailer' - default from: ->(*) { Setting.get('mail_from') } + default from: ->(*) { Setting.get('email_from') } end diff --git a/app/models/project/openlab_sync.rb b/app/models/project/openlab_sync.rb index 3dffcde7b..66ee04370 100644 --- a/app/models/project/openlab_sync.rb +++ b/app/models/project/openlab_sync.rb @@ -59,7 +59,7 @@ module Project::OpenlabSync class_methods do def openlab_sync_active? - Rails.application.secrets.openlab_app_secret.present? + Setting.get('openlab_app_secret').present? end end end diff --git a/app/policies/setting_context.rb b/app/policies/setting_context.rb new file mode 100644 index 000000000..8b183f253 --- /dev/null +++ b/app/policies/setting_context.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# Pundit Additional context to authorize getting a parameter +class SettingContext + attr_reader :name + + def initialize(name) + @name = name + end + + def policy_class + SettingPolicy + end +end diff --git a/app/policies/setting_policy.rb b/app/policies/setting_policy.rb index a2c47fc45..c9ce0e8ac 100644 --- a/app/policies/setting_policy.rb +++ b/app/policies/setting_policy.rb @@ -2,9 +2,50 @@ # Check the access policies for API::SettingsController class SettingPolicy < ApplicationPolicy + # Defines the scope of the settings index, depending on the role of the current user + class Scope < Scope + def resolve + if user.nil? || (user && !user.admin?) + scope.where.not(name: SettingPolicy.public_blacklist) + else + scope + end + end + end + %w[update bulk_update reset].each do |action| define_method "#{action}?" do user.admin? end end + + def show? + user&.admin? || SettingPolicy.public_whitelist.include?(record.name) + end + + def test_present? + user&.admin? || SettingPolicy.public_whitelist.push('openlab_app_secret').include?(record.name) + end + + ## + # Every settings that anyone can read. The other settings are restricted for admins. + # This list must be manually updated if a new setting should be world-readable + ## + def self.public_whitelist + %w[about_title about_body about_contacts privacy_body privacy_dpo twitter_name home_blogpost machine_explications_alert + training_explications_alert training_information_message subscription_explications_alert booking_window_start + booking_window_end booking_slot_duration booking_move_enable booking_move_delay booking_cancel_enable booking_cancel_delay + fablab_name name_genre event_explications_alert space_explications_alert link_name home_content phone_required + tracking_id book_overlapping_slots slot_duration events_in_calendar spaces_module plans_module invoicing_module + recaptcha_site_key feature_tour_display disqus_shortname allowed_cad_extensions openlab_app_id] + end + + ## + # Every settings that only admins can read. + # This blacklist is automatically generated from the public_whitelist above. + ## + def self.public_blacklist + Setting.validators.detect { |v| v.class == ActiveModel::Validations::InclusionValidator && v.attributes.include?(:name) } + .options[:in] - SettingPolicy.public_whitelist + end end diff --git a/app/services/health_service.rb b/app/services/health_service.rb index cf5f19a5d..8ea25aaee 100644 --- a/app/services/health_service.rb +++ b/app/services/health_service.rb @@ -43,7 +43,7 @@ class HealthService spaces: Setting.get('spaces_module'), online_payment: !Rails.application.secrets.fablab_without_online_payments, invoices: Setting.get('invoicing_module'), - openlab: Rails.application.secrets.openlab_app_secret.present? + openlab: Setting.get('openlab_app_secret').present? } end diff --git a/app/views/api/settings/test_present.json.jbuilder b/app/views/api/settings/test_present.json.jbuilder new file mode 100644 index 000000000..661b3b928 --- /dev/null +++ b/app/views/api/settings/test_present.json.jbuilder @@ -0,0 +1 @@ +json.isPresent @setting.present? diff --git a/app/views/application/index.html.erb b/app/views/application/index.html.erb index c922aaf4f..534872a1c 100644 --- a/app/views/application/index.html.erb +++ b/app/views/application/index.html.erb @@ -48,11 +48,7 @@ Fablab.weekStartingDay = <%= Date.parse(Rails.application.secrets.week_starting_day).strftime('%w') %>; Fablab.d3DateFormat = "<%= Rails.application.secrets.d3_date_format %>"; Fablab.uibDateFormat = "<%= Rails.application.secrets.uib_date_format %>"; - Fablab.openlabProjectsActive = <%= Rails.application.secrets.openlab_app_secret.present? %>; Fablab.openlabDefault = ('<%= Rails.application.secrets.openlab_default %>' !== 'false'); - <% if Rails.application.secrets.openlab_app_id.present? %> - Fablab.openlabAppId = "<%= Rails.application.secrets.openlab_app_id %>"; - <% end %> Fablab.userConfirmationNeededToSignIn = ('<%= Rails.application.secrets.user_confirmation_needed_to_sign_in %>' === 'true'); // feature tour (used when feature_tour_display = session) diff --git a/config/initializers/openlab_ruby.rb b/config/initializers/openlab_ruby.rb index ca44a4780..e0c1ffd9e 100644 --- a/config/initializers/openlab_ruby.rb +++ b/config/initializers/openlab_ruby.rb @@ -1,6 +1,3 @@ Openlab.configure do |config| - config.app_secret = Rails.application.secrets.openlab_app_secret - if !Rails.env.production? - config.base_uri = Rails.application.secrets.openlab_base_uri - end + config.base_uri = Rails.application.secrets.openlab_base_uri unless Rails.env.production? end diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index beadcdec9..778f82e25 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -158,7 +158,7 @@ en: file_is_TYPE: "MIME type of this file is {TYPE}" projects_sharing: "Projects sharing" open_lab_projects: "OpenLab Projects" - open_lab_info_html: "Share your projects with other Fab Labs and display a gallery of shared projects. Please send an email to contact@fab-manager.com to get your access credentials for free." + open_lab_info_html: "Enable OpenLab to share your projects with other Fab Labs and display a gallery of shared projects. Please send an email to contact@fab-manager.com to get your access credentials for free." open_lab_app_id: "ID" open_lab_app_secret: "Secret" #track and monitor the trainings @@ -1077,8 +1077,8 @@ en: disqus_shortname: "Disqus shortname" COUNT_items_removed: "{COUNT, plural, =1{One item} other{{COUNT} items}} removed" item_added: "One item added" - openlab_app_id: "OpenLab-Projects ID" - openlab_app_secret: "OpenLab-Projects secret" + openlab_app_id: "OpenLab ID" + openlab_app_secret: "OpenLab secret" general: general: "General" title: "Title" diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index e0d21a6c5..4a4c58c72 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -157,9 +157,9 @@ fr: set_a_file: "Sélectionner un fichier" file_is_TYPE: "Le type MIME de ce fichier est {TYPE}" projects_sharing: "Partage de projets" - open_lab_projects: "OpenLab Projects" - open_lab_info_html: "Partagez vos projets avec d'autres Fab Labs et affichez une galerie de projets partagés. Veuillez envoyer un courriel à contact@fab-manager.com pour obtenir gratuitement vos identifiants d'accès." - open_lab_app_id: "ID" + open_lab_projects: "Projets OpenLab" + open_lab_info_html: "Activez OpenLab pout partager vos projets avec d'autres Fab Labs et afficher une galerie de projets partagés. Veuillez envoyer un courriel à contact@fab-manager.com pour obtenir gratuitement vos identifiants d'accès." + open_lab_app_id: "Identifiant" open_lab_app_secret: "Secret" #track and monitor the trainings trainings: @@ -1077,8 +1077,8 @@ fr: disqus_shortname: "nom court Disqus" COUNT_items_removed: "{COUNT, plural, =1{Un élément retiré} other{{COUNT} éléments retirés}}" item_added: "Un élément ajouté" - openlab_app_id: "l'ID OpenLab-Projects" - openlab_app_secret: "secret OpenLab-Projects" + openlab_app_id: "l'identifiant OpenLab" + openlab_app_secret: "secret OpenLab" general: general: "Général" title: "Titre" diff --git a/config/routes.rb b/config/routes.rb index 6ba60aab1..3525a2e56 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -32,7 +32,6 @@ Rails.application.routes.draw do collection do get :last_published get :search - get :allowed_extensions end end resources :openlab_projects, only: :index @@ -44,6 +43,7 @@ Rails.application.routes.draw do resources :settings, only: %i[show update index], param: :name do patch '/bulk_update', action: 'bulk_update', on: :collection put '/reset/:name', action: 'reset', on: :collection + get '/is_present/:name', action: 'test_present', on: :collection end resources :users, only: %i[index create destroy] resources :members, only: %i[index show create update destroy] do diff --git a/config/secrets.yml b/config/secrets.yml index bb2f52d77..a37bddfa3 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -32,8 +32,6 @@ development: angular_locale: <%= ENV["ANGULAR_LOCALE"] %> fullcalendar_locale: <%= ENV["FULLCALENDAR_LOCALE"] %> elasticsearch_language_analyzer: <%= ENV["ELASTICSEARCH_LANGUAGE_ANALYZER"] %> - openlab_app_secret: <%= ENV["OPENLAB_APP_SECRET"] %> - openlab_app_id: <%= ENV["OPENLAB_APP_ID"] %> openlab_default: <%= ENV["OPENLAB_DEFAULT"] %> openlab_base_uri: <%= ENV["OPENLAB_BASE_URI"] %> navinum_api_login: <%= ENV["NAVINUM_API_LOGIN"] %> @@ -68,8 +66,6 @@ test: angular_locale: en-us fullcalendar_locale: en elasticsearch_language_analyzer: french - openlab_app_secret: - openlab_app_id: openlab_default: openlab_base_uri: navinum_api_login: @@ -112,8 +108,6 @@ staging: angular_locale: <%= ENV["ANGULAR_LOCALE"] %> fullcalendar_locale: <%= ENV["FULLCALENDAR_LOCALE"] %> elasticsearch_language_analyzer: <%= ENV["ELASTICSEARCH_LANGUAGE_ANALYZER"] %> - openlab_app_secret: <%= ENV["OPENLAB_APP_SECRET"] %> - openlab_app_id: <%= ENV["OPENLAB_APP_ID"] %> openlab_default: <%= ENV["OPENLAB_DEFAULT"] %> openlab_base_uri: <%= ENV["OPENLAB_BASE_URI"] %> navinum_api_login: <%= ENV["NAVINUM_API_LOGIN"] %> @@ -159,8 +153,6 @@ production: angular_locale: <%= ENV["ANGULAR_LOCALE"] %> fullcalendar_locale: <%= ENV["FULLCALENDAR_LOCALE"] %> elasticsearch_language_analyzer: <%= ENV["ELASTICSEARCH_LANGUAGE_ANALYZER"] %> - openlab_app_secret: <%= ENV["OPENLAB_APP_SECRET"] %> - openlab_app_id: <%= ENV["OPENLAB_APP_ID"] %> openlab_default: <%= ENV["OPENLAB_DEFAULT"] %> openlab_base_uri: <%= ENV["OPENLAB_BASE_URI"] %> navinum_api_login: <%= ENV["NAVINUM_API_LOGIN"] %> diff --git a/doc/environment.md b/doc/environment.md index a9a32fc50..5b698bb2d 100644 --- a/doc/environment.md +++ b/doc/environment.md @@ -280,13 +280,6 @@ See [Crowdin documentation](https://support.crowdin.com/in-context-localization/ Accordingly, `RAILS_LOCALE` and `APP_LOCALE` must be configured to `zu`. ## OpenLab settings - - - OPENLAB_APP_ID, OPENLAB_APP_SECRET - -This configuration is optional and can only work in production mode. -It allows you to display a shared projects gallery and to share your projects with other fablabs. -Send an email to **contact@fab-manager.com** to get your OpenLab client's credentials. OPENLAB_DEFAULT diff --git a/env.example b/env.example index e5e9e2aa3..60306e0c7 100644 --- a/env.example +++ b/env.example @@ -56,8 +56,6 @@ UIB_DATE_FORMAT=dd/MM/yyyy EXCEL_DATE_FORMAT=dd/mm/yyyy # OpenLab Projects -OPENLAB_APP_SECRET= -OPENLAB_APP_ID= OPENLAB_DEFAULT=true # do not change this URL OPENLAB_BASE_URI=https://openprojects.fab-manager.com diff --git a/lib/tasks/fablab/openlab.rake b/lib/tasks/fablab/openlab.rake index 053042430..41bcb7d1c 100644 --- a/lib/tasks/fablab/openlab.rake +++ b/lib/tasks/fablab/openlab.rake @@ -5,13 +5,13 @@ namespace :fablab do namespace :openlab do desc 'bulk and export projects to openlab' task bulk_export: :environment do - if Rails.application.secrets.openlab_app_secret.present? + if Setting.get('openlab_app_secret').present? Project.find_each do |project| project.openlab_create puts '-> Done' end else - warn "Rails.application.secrets.openlab_app_secret not present. Export can't be done." + warn "Openlab_app_secret was not configured. Export can't be done." end end end diff --git a/setup/env.example b/setup/env.example index 40064d5cd..571ac6e1a 100644 --- a/setup/env.example +++ b/setup/env.example @@ -44,8 +44,6 @@ D3_DATE_FORMAT=%d/%m/%y UIB_DATE_FORMAT=dd/MM/yyyy EXCEL_DATE_FORMAT=dd/mm/yyyy -OPENLAB_APP_SECRET= -OPENLAB_APP_ID= OPENLAB_DEFAULT='true' OPENLAB_BASE_URI=https://openprojects.fab-manager.com diff --git a/setup/setup.sh b/setup/setup.sh index d927b1068..79b0460fe 100755 --- a/setup/setup.sh +++ b/setup/setup.sh @@ -239,7 +239,7 @@ configure_env_file() SMTP_ENABLE_STARTTLS_AUTO SMTP_OPENSSL_VERIFY_MODE SMTP_TLS \ LOG_LEVEL MAX_IMAGE_SIZE MAX_CAO_SIZE MAX_IMPORT_SIZE DISK_SPACE_MB_ALERT \ SUPERADMIN_EMAIL APP_LOCALE RAILS_LOCALE MOMENT_LOCALE SUMMERNOTE_LOCALE ANGULAR_LOCALE FULLCALENDAR_LOCALE ELASTICSEARCH_LANGUAGE_ANALYZER TIME_ZONE \ - WEEK_STARTING_DAY D3_DATE_FORMAT UIB_DATE_FORMAT EXCEL_DATE_FORMAT OPENLAB_APP_ID OPENLAB_APP_SECRET OPENLAB_DEFAULT) + WEEK_STARTING_DAY D3_DATE_FORMAT UIB_DATE_FORMAT EXCEL_DATE_FORMAT OPENLAB_DEFAULT) for variable in "${variables[@]}"; do local var_doc current var_doc=$(get_md_anchor "$doc" "$variable")