# frozen_string_literal: true
require "decidim/core/engine"
require "decidim/core/api"
require "decidim/core/version"
# Decidim configuration.
module Decidim
autoload :Env, "decidim/env"
autoload :Deprecations, "decidim/deprecations"
autoload :ActsAsAuthor, "decidim/acts_as_author"
autoload :ActsAsTree, "decidim/acts_as_tree"
autoload :TranslatableAttributes, "decidim/translatable_attributes"
autoload :TranslatableResource, "decidim/translatable_resource"
autoload :JsonbAttributes, "decidim/jsonb_attributes"
autoload :FormBuilder, "decidim/form_builder"
autoload :AuthorizationFormBuilder, "decidim/authorization_form_builder"
autoload :FilterFormBuilder, "decidim/filter_form_builder"
autoload :ComponentManifest, "decidim/component_manifest"
autoload :NotificationSettingManifest, "decidim/notification_setting_manifest"
autoload :ParticipatorySpaceManifest, "decidim/participatory_space_manifest"
autoload :ResourceManifest, "decidim/resource_manifest"
autoload :Resourceable, "decidim/resourceable"
autoload :Traceable, "decidim/traceable"
autoload :Loggable, "decidim/loggable"
autoload :Reportable, "decidim/reportable"
autoload :UserReportable, "decidim/user_reportable"
autoload :Authorable, "decidim/authorable"
autoload :Coauthorable, "decidim/coauthorable"
autoload :Participable, "decidim/participable"
autoload :Publicable, "decidim/publicable"
autoload :Scopable, "decidim/scopable"
autoload :ScopableParticipatorySpace, "decidim/scopable_participatory_space"
autoload :ScopableComponent, "decidim/scopable_component"
autoload :ScopableResource, "decidim/scopable_resource"
autoload :ContentParsers, "decidim/content_parsers"
autoload :ContentRenderers, "decidim/content_renderers"
autoload :ContentProcessor, "decidim/content_processor"
autoload :Components, "decidim/components"
autoload :HasAttachmentCollections, "decidim/has_attachment_collections"
autoload :HasAttachments, "decidim/has_attachments"
autoload :ComponentValidator, "decidim/component_validator"
autoload :HasSettings, "decidim/has_settings"
autoload :HasComponent, "decidim/has_component"
autoload :HasCategory, "decidim/has_category"
autoload :Followable, "decidim/followable"
autoload :FriendlyDates, "decidim/friendly_dates"
autoload :Nicknamizable, "decidim/nicknamizable"
autoload :HasReference, "decidim/has_reference"
autoload :StatsRegistry, "decidim/stats_registry"
autoload :Exporters, "decidim/exporters"
autoload :FileZipper, "decidim/file_zipper"
autoload :Menu, "decidim/menu"
autoload :MenuItem, "decidim/menu_item"
autoload :MenuRegistry, "decidim/menu_registry"
autoload :ManifestRegistry, "decidim/manifest_registry"
autoload :AssetRouter, "decidim/asset_router"
autoload :EngineRouter, "decidim/engine_router"
autoload :UrlOptionResolver, "decidim/url_option_resolver"
autoload :Events, "decidim/events"
autoload :ViewHooks, "decidim/view_hooks"
autoload :ContentBlockRegistry, "decidim/content_block_registry"
autoload :ContentBlockManifest, "decidim/content_block_manifest"
autoload :MetricRegistry, "decidim/metric_registry"
autoload :MetricManifest, "decidim/metric_manifest"
autoload :MetricOperation, "decidim/metric_operation"
autoload :MetricOperationManifest, "decidim/metric_operation_manifest"
autoload :AttributeEncryptor, "decidim/attribute_encryptor"
autoload :NewsletterEncryptor, "decidim/newsletter_encryptor"
autoload :NewsletterParticipant, "decidim/newsletter_participant"
autoload :Searchable, "decidim/searchable"
autoload :FilterableResource, "decidim/filterable_resource"
autoload :SearchResourceFieldsMapper, "decidim/search_resource_fields_mapper"
autoload :QueryExtensions, "decidim/query_extensions"
autoload :ParticipatorySpaceResourceable, "decidim/participatory_space_resourceable"
autoload :HasPrivateUsers, "decidim/has_private_users"
autoload :ViewModel, "decidim/view_model"
autoload :FingerprintCalculator, "decidim/fingerprint_calculator"
autoload :Fingerprintable, "decidim/fingerprintable"
autoload :DownloadYourData, "decidim/download_your_data"
autoload :DownloadYourDataSerializers, "decidim/download_your_data_serializers"
autoload :DownloadYourDataExporter, "decidim/download_your_data_exporter"
autoload :Amendable, "decidim/amendable"
autoload :Gamification, "decidim/gamification"
autoload :Hashtag, "decidim/hashtag"
autoload :Etherpad, "decidim/etherpad"
autoload :Paddable, "decidim/paddable"
autoload :OpenDataExporter, "decidim/open_data_exporter"
autoload :IoEncoder, "decidim/io_encoder"
autoload :HasResourcePermission, "decidim/has_resource_permission"
autoload :PermissionsRegistry, "decidim/permissions_registry"
autoload :Randomable, "decidim/randomable"
autoload :Endorsable, "decidim/endorsable"
autoload :ActionAuthorization, "decidim/action_authorization"
autoload :Map, "decidim/map"
autoload :Geocodable, "decidim/geocodable"
autoload :Snippets, "decidim/snippets"
autoload :OrganizationSettings, "decidim/organization_settings"
autoload :HasUploadValidations, "decidim/has_upload_validations"
autoload :FileValidatorHumanizer, "decidim/file_validator_humanizer"
autoload :ShareableWithToken, "decidim/shareable_with_token"
autoload :RecordEncryptor, "decidim/record_encryptor"
autoload :AttachmentAttributes, "decidim/attachment_attributes"
autoload :CarrierWaveMigratorService, "decidim/carrier_wave_migrator_service"
autoload :ReminderRegistry, "decidim/reminder_registry"
autoload :ReminderManifest, "decidim/reminder_manifest"
autoload :ManifestMessages, "decidim/manifest_messages"
autoload :CommonPasswords, "decidim/common_passwords"
autoload :HasArea, "decidim/has_area"
autoload :AttributeObject, "decidim/attribute_object"
autoload :Query, "decidim/query"
autoload :Command, "decidim/command"
autoload :EventRecorder, "decidim/event_recorder"
autoload :ControllerHelpers, "decidim/controller_helpers"
autoload :ProcessesFileLocally, "decidim/processes_file_locally"
autoload :DependencyResolver, "decidim/dependency_resolver"
include ActiveSupport::Configurable
# Loads seeds from all engines.
def self.seed!
# After running the migrations, some records may have loaded their column
# caches at different stages of the migration process, so in order to
# prevent any "undefined method" errors if these tasks are run
# consecutively, reset the column cache before the migrations.
reset_all_column_information
# Faker needs to have the `:en` locale in order to work properly, so we
# must enforce it during the seeds.
original_locale = I18n.available_locales
I18n.available_locales = original_locale + [:en] unless original_locale.include?(:en)
Rails.application.railties.to_a.uniq.each do |railtie|
next unless railtie.respond_to?(:load_seed) && railtie.class.name.include?("Decidim::")
railtie.load_seed
end
participatory_space_manifests.each do |manifest|
manifest.seed!
Organization.all.each do |organization|
ContextualHelpSection.set_content(
organization,
manifest.name,
Decidim::Faker::Localized.wrapped("
", "
") do
Decidim::Faker::Localized.sentence(word_count: 15)
end
)
end
end
Gamification.badges.each do |badge|
puts "Setting random values for the \"#{badge.name}\" badge..."
User.all.find_each do |user|
Gamification::BadgeScore.find_or_create_by!(
user: user,
badge_name: badge.name,
value: Random.rand(0...20)
)
end
end
I18n.available_locales = original_locale
end
# Finds all currently loaded Decidim ActiveRecord classes and resets their
# column information.
def self.reset_all_column_information
ActiveRecord::Base.descendants.each do |cls|
next if cls.name.nil? # abstract classes registered during tests
next if cls.abstract_class? || !cls.name.match?(/^Decidim::/)
cls.reset_column_information
end
end
# Exposes a configuration option: The application name String.
config_accessor :application_name
# Exposes a configuration option: The email String to use as sender in all
# the mails.
config_accessor :mailer_sender
# Whether SSL should be forced or not.
config_accessor :force_ssl do
Rails.env.starts_with?("production") || Rails.env.starts_with?("staging")
end
# Having this on true will change the way the svg assets are being served.
config_accessor :cors_enabled do
false
end
# Exposes a configuration option: The application available locales.
config_accessor :available_locales do
%w(en bg ar ca cs da de el eo es es-MX es-PY et eu fi-pl fi fr fr-CA ga gl hr hu id is it ja ko lb lt lv mt nl no pl pt pt-BR ro ru sk sl sr sv tr uk vi zh-CN zh-TW)
end
# Exposes a configuration option: The application default locale.
config_accessor :default_locale do
:en
end
# Disable the redirection to the external host when performing redirect back
# For more details https://github.com/rails/rails/issues/39643
# Additional context: This has been revealed as an issue during a security audit on Future of Europe installation
config_accessor :allow_open_redirects do
false
end
# Exposes a configuration option: an array of symbols representing processors
# that will be automatically executed when a content is parsed or rendered.
#
# A content processor is a concept to refer to a set of two classes:
# the content parser class and the content renderer class.
# e.g. If we register a content processor named `user`:
#
# Decidim.content_processors += [:user]
#
# we must declare the following classes:
#
# Decidim::ContentParsers::UserParser < BaseParser
# Decidim::ContentRenderers::UserRenderer < BaseRenderer
config_accessor :content_processors do
[]
end
# Exposes a configuration option: an object to configure geocoder
config_accessor :geocoder
# Exposes a configuration option: an object to configure the mapping
# functionality. See Decidim::Map for more information.
config_accessor :maps
# Exposes a configuration option: a custom method to generate references.
# If overwritten, it should handle both component resources and participatory spaces.
# Default: Calculates a unique reference for the model in
# the following format:
#
# "BCN-PROP-2017-02-6589" which in this example translates to:
#
# BCN: A setting configured at the organization to be prepended to each reference.
# PROP: Unique name identifier for a resource: Decidim::Proposals::Proposal
# (MEET for meetings or PROJ for projects).
# 2017-02: Year-Month of the resource creation date
# 6589: ID of the resource
config_accessor :reference_generator do
lambda do |resource, component|
ref = ""
if resource.is_a?(Decidim::HasComponent) && component.present?
# It's a component resource
ref = component.participatory_space.organization.reference_prefix
elsif resource.is_a?(Decidim::Participable)
# It's a participatory space
ref = resource.organization.reference_prefix
end
class_identifier = resource.class.name.demodulize[0..3].upcase
year_month = (resource.created_at || Time.current).strftime("%Y-%m")
[ref, class_identifier, year_month, resource.id].join("-")
end
end
# Exposes a configuration option: the whitelist ips
config_accessor :system_accesslist_ips do
[]
end
# Exposes a configuration option: the currency unit
config_accessor :currency_unit do
"€"
end
# Exposes a configuration option: The image uploader quality.
config_accessor :image_uploader_quality do
80
end
# The number of reports which a resource can receive before hiding it
config_accessor :max_reports_before_hiding do
3
end
# Allow organization's administrators to inject custom HTML into the frontend
config_accessor :enable_html_header_snippets do
true
end
# Allow organization's administrators to track newsletter links
config_accessor :track_newsletter_links do
true
end
# Time that download your data files are available in server
config_accessor :download_your_data_expiry_time do
7.days
end
# Max requests in a time period to prevent DoS attacks. Only applied on production.
config_accessor :throttling_max_requests do
100
end
# Time window in which the throttling is applied.
config_accessor :throttling_period do
1.minute
end
# Time window were users can access the website even if their email is not confirmed.
config_accessor :unconfirmed_access_for do
0.days
end
# Allow machine translations
config_accessor :enable_machine_translations do
false
end
# How long can a user remained logged in before the session expires. Notice that
# this is also maximum time that user can idle before getting automatically signed out.
config_accessor :expire_session_after do
30.minutes
end
# If set to true, users have option to "remember me". Notice that expire_session_after won't take
# effect when the user wants to be remembered.
config_accessor :enable_remember_me do
true
end
# Defines how often session_timeouter.js checks time between current moment and last request
config_accessor :session_timeout_interval do
10.seconds
end
# Exposes a configuration option: an object to configure Etherpad
config_accessor :etherpad do
# {
# server: ,
# api_key: ,
# api_version:
# }
end
# A base path for the uploads. If set, make sure it ends in a slash.
# Uploads will be set to `/uploads/`. This can be useful if you
# want to use the same uploads place for both staging and production
# environments, but in different folders.
config_accessor :base_uploads_path do
nil
end
# The name of the class to deliver SMS codes to users.
#
# Check the example in `decidim-verifications`.
config_accessor :sms_gateway_service do
# "MyGatewayClass"
end
# The name of the class used to generate a timestamp from a document.
#
# Check the example in `decidim-initiatives`
config_accessor :timestamp_service do
# "MyTimestampService"
end
# The name of the class used to process a pdf and add a signature to the
# document.
#
# Check the example in `decidim-initiatives`
config_accessor :pdf_signature_service do
# "MyPDFSignatureService"
end
# The name of the class to translate user content.
#
config_accessor :machine_translation_service do
# "MyTranslationService"
end
# The Decidim::Exporters::CSV's default column separator
config_accessor :default_csv_col_sep do
";"
end
# Exposes a configuration option: HTTP_X_FORWADED_HOST header follow-up.
# If a caching system is in place, it can also allow cache and log poisoning attacks,
# allowing attackers to control the contents of caches and logs that could be used for other attacks.
config_accessor :follow_http_x_forwarded_host do
false
end
# The list of roles a user can have, not considering the space-specific roles.
config_accessor :user_roles do
%w(admin user_manager)
end
# The list of visibility options for amendments. An Array of Strings that
# serve both as locale keys and values to construct the input collection in
# Decidim::Amendment::VisibilityStepSetting::options.
#
# This collection is used in Decidim::Admin::SettingsHelper to generate a
# radio buttons collection input field form for a Decidim::Component
# step setting :amendments_visibility.
config_accessor :amendments_visibility_options do
%w(all participants)
end
# Exposes a configuration option: The maximum length for conversation
# messages.
config_accessor :maximum_conversation_message_length do
1_000
end
# Defines the name of the cookie used to check if the user has given consent
# to store local data in their browser.
config_accessor :consent_cookie_name do
"decidim-consent"
end
# Defines data consent categories. Note that when adding an item you need to
# add following i18n entries also (change 'foo' with the name of the data
# which can be a cookie for instance).
#
# layouts.decidim.data_consent.details.items.foo.service
# layouts.decidim.data_consent.details.items.foo.description
config_accessor :consent_categories do
[
{
slug: "essential",
mandatory: true,
items: [
{
type: "cookie",
name: "_session_id"
},
{
type: "cookie",
name: Decidim.consent_cookie_name
},
{
type: "local_storage",
name: "pwaInstallPromptSeen"
}
]
},
{
slug: "preferences",
mandatory: false
},
{
slug: "analytics",
mandatory: false
},
{
slug: "marketing",
mandatory: false
}
]
end
# Blacklisted passwords. Array may contain strings and regex entries.
config_accessor :password_blacklist do
[]
end
# Defines if admins are required to have stronger passwords than other users
config_accessor :admin_password_strong do
true
end
config_accessor :admin_password_expiration_days do
90
end
config_accessor :admin_password_min_length do
15
end
config_accessor :admin_password_repetition_times do
5
end
# This is an internal key that allow us to properly configure the caching key separator. This is useful for redis cache store
# as it creates some namespaces within the cached data.
# use `config.cache_key_separator = ":"` in your initializer to have namespaced data
config_accessor :cache_key_separator do
"/"
end
# Enable/Disable the service worker
config_accessor :service_worker_enabled do
Rails.env.exclude?("development")
end
# Public: Registers a global engine. This method is intended to be used
# by component engines that also offer unscoped functionality
#
# name - The name of the engine to register. Should be unique.
# engine - The engine to register.
# options - Options to pass to the engine.
# :at - The route to mount the engine to.
#
# Returns nothing.
def self.register_global_engine(name, engine, options = {})
return if global_engines.has_key?(name)
options[:at] ||= "/#{name}"
global_engines[name.to_sym] = {
at: options[:at],
engine: engine
}
end
# Semiprivate: Removes a global engine from the registry. Mostly used on testing,
# no real reason to use this on production.
#
# name - The name of the global engine to remove.
#
# Returns nothing.
def self.unregister_global_engine(name)
global_engines.delete(name.to_sym)
end
# Public: Finds all registered engines via the 'register_global_engine' method.
#
# Returns an Array[::Rails::Engine]
def self.global_engines
@global_engines ||= {}
end
# Public: Registers a component, usually held in an external library or in a
# separate folder in the main repository. Exposes a DSL defined by
# `Decidim::ComponentManifest`.
#
# Component manifests are held in a global registry and are used in all kinds of
# places to figure out what new components or functionalities the component provides.
#
# name - A Symbol with the component's unique name.
#
# Returns nothing.
def self.register_component(name, &block)
component_registry.register(name, &block)
end
# Public: Registers a participatory space, usually held in an external library
# or in a separate folder in the main repository. Exposes a DSL defined by
# `Decidim::ParticipatorySpaceManifest`.
#
# Participatory space manifests are held in a global registry and are used in
# all kinds of places to figure out what new components or functionalities the
# participatory space provides.
#
# name - A Symbol with the participatory space's unique name.
#
# Returns nothing.
def self.register_participatory_space(name, &block)
participatory_space_registry.register(name, &block)
end
# Public: Registers a resource.
#
# Returns nothing.
def self.register_resource(name, &block)
resource_registry.register(name, &block)
end
def self.notification_settings(name, &block)
notification_settings_registry.register(name, &block)
end
# Public: Finds all registered resource manifests via the `register_component`
# method.
#
# Returns an Array[ResourceManifest].
def self.resource_manifests
resource_registry.manifests
end
# Public: Finds all registered component manifest's via the `register_component`
# method.
#
# Returns an Array[ComponentManifest].
def self.component_manifests
component_registry.manifests.sort_by(&:name)
end
# Public: Finds all registered participatory space manifest's via the
# `register_participatory_space` method.
#
# Returns an Array[ParticipatorySpaceManifest].
def self.participatory_space_manifests
participatory_space_registry.manifests
end
# Public: Finds a component manifest by the component's name.
#
# name - The name of the ComponentManifest to find.
#
# Returns a ComponentManifest if found, nil otherwise.
def self.find_component_manifest(name)
component_registry.find(name.to_sym)
end
# Public: Finds a participatory space manifest by the participatory space's
# name.
#
# name - The name of the ParticipatorySpaceManifest to find.
#
# Returns a ParticipatorySpaceManifest if found, nil otherwise.
def self.find_participatory_space_manifest(name)
participatory_space_registry.find(name.to_sym)
end
# Public: Finds a resource manifest by the resource's name.
#
# resource_name_or_class - The String of the ResourceManifest name or the class of
# the ResourceManifest model_class to find.
#
# Returns a ResourceManifest if found, nil otherwise.
def self.find_resource_manifest(resource_name_or_klass)
resource_registry.find(resource_name_or_klass)
end
# Public: Stores the registry of components
def self.component_registry
@component_registry ||= ManifestRegistry.new(:components)
end
# Public: Stores the registry of participatory spaces
def self.participatory_space_registry
@participatory_space_registry ||= ManifestRegistry.new(:participatory_spaces)
end
def self.reminders_registry
@reminders_registry ||= ReminderRegistry.new
end
# Public: Stores the registry of resource spaces
def self.resource_registry
@resource_registry ||= ManifestRegistry.new(:resources)
end
def self.notification_settings_registry
@notification_settings_registry ||= ManifestRegistry.new(:notification_settings)
end
# Public: Stores the registry for user permissions
def self.permissions_registry
@permissions_registry ||= PermissionsRegistry.new
end
# Public: Stores an instance of StatsRegistry
def self.stats
@stats ||= StatsRegistry.new
end
# Public: Registers configuration for a new or existing menu
#
# name - A string or symbol with the name of the menu
# &block - A block using the DSL defined in `Decidim::MenuItem`
#
def self.menu(name, &block)
MenuRegistry.register(name.to_sym, &block)
end
# Public: Stores an instance of ViewHooks
def self.view_hooks
@view_hooks ||= ViewHooks.new
end
# Public: Stores an instance of ContentBlockRegistry
def self.content_blocks
@content_blocks ||= ContentBlockRegistry.new
end
# Public: Stores an instance of Traceability
def self.traceability
@traceability ||= Traceability.new
end
# Public: Stores an instance of MetricRegistry
def self.metrics_registry
@metrics_registry ||= MetricRegistry.new
end
# Public: Stores an instance of MetricOperation
def self.metrics_operation
@metrics_operation ||= MetricOperation.new
end
# Public: Returns the correct settings object for the given organization or
# the default settings object when the organization cannot be determined. The
# model to be passed to this method can be any model that responds to the
# `organization` method or the organization itself. If the given model is not
# an organization or does not respond to the organization method, returns the
# default organization settings.
#
# model - The target model for which to fetch the settings object, either an
# organization or a model responding to the `organization` method.
#
def self.organization_settings(model)
organization = if model.is_a?(Decidim::Organization)
model
elsif model.respond_to?(:organization) && model.organization.present?
model.organization
end
return Decidim::OrganizationSettings.defaults unless organization
Decidim::OrganizationSettings.for(organization)
end
# Defines the time after which the machine translation job should be enabled.
# In some cases, like when Workers is processing faster than ActiveRecord can commit to Database,
# it is required to have a delay, to prevent any discarding with
# Decidim::MachineTranslationResourceJob due to a ActiveJob::DeserializationError.
# In some Decidim Installations, ActiveJob can be configured to discard jobs failing with
# ActiveJob::DeserializationError
config_accessor :machine_translation_delay do
0.seconds
end
def self.machine_translation_service_klass
return unless Decidim.enable_machine_translations
Decidim.machine_translation_service.to_s.safe_constantize
end
def self.register_assets_path(path)
Rails.autoloaders.main.ignore(path) if Rails.configuration.autoloader == :zeitwerk
end
# Checks if a particular decidim gem is installed and needed by this
# particular instance. Preferrably this happens through bundler by inspecting
# the Gemfile of the instance but when Decidim is used without bundler, this
# will check:
# 1. If the gem is globally available or not in the loaded specs, i.e. the
# gems available in the gem install directory/directories.
# 2. If the gem has been required through `require "decidim/foo"`.
#
# Using bundler is suggested as it will provide more accurate results
# regarding what is actually needed. It will resolve all the gems listed in
# the Gemfile and also their dependencies which provides us accurate
# information whether a gem is needed by the instance or not.
#
# Note that using something like defined?(Decidim::Foo) will not work because
# the way the Decidim handles version definitions for each gem. After the gems
# are loaded, this would always return true because the version definition
# files of each module define that module which means it is available at
# runtime if the gem is installed in the gem load path. In some situations it
# can be installed there through other projects or through the command line
# even if the instance does not require that module or even through
# installing gems from git sources or from file paths.
#
# When a gem is reported as "needed" by the dependency resolver, this will
# also require that module ensuring its availability for the initialization
# code.
#
# @param mod [Symbol, String] The module name to check, e.g. `:proposals`.
# @return [Boolean] A boolean indicating whether the module is installed.
def self.module_installed?(mod)
return false unless Decidim::DependencyResolver.instance.needed?("decidim-#{mod}")
# The dependency may not be automatically loaded through the Gemfile if the
# user lists e.g. "decidim-core" and "decidim-budgets" in it. In this
# situation, "decidim-comments" is also needed because it is a dependency
# for "decidim-budgets".
require "decidim/#{mod}"
true
rescue LoadError
false
end
end