diff --git a/.gitignore b/.gitignore index 7761dba2a..0413aaa36 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ public/sitemap.xml public/system/ +/public/ckeditor_assets/ diff --git a/.rubocop.yml b/.rubocop.yml index f697dab0f..93a1a6ce9 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -25,6 +25,12 @@ Layout/IndentationConsistency: Layout/EndOfLine: EnforcedStyle: lf +Layout/TrailingBlankLines: + Enabled: true + +Layout/TrailingWhitespace: + Enabled: true + Bundler/DuplicatedGem: Enabled: true @@ -420,3 +426,6 @@ Security/MarshalLoad: Security/YAMLLoad: Enabled: true + +Style/PercentLiteralDelimiters: + Enabled: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 218c9ebb6..5a6639f0b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,21 +34,12 @@ If you want to contribute code to solve an issue: * Fork the project. * Create a topic branch based on master. * Commit there your code to solve the issue. -* Make sure all test are passing (and add specs to test any new feature if needed). +* Make sure all test are passing (and add specs to test any new feature you've added). * Follow these [best practices](https://github.com/styleguide/ruby) * Open a *pull request* to the main repository describing what issue you are addressing. **Working on your first Pull Request?** You can learn how from this *free* series [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github) -## Cleaning up - -In the rush of time sometimes things get messy, you can help us cleaning things up: - -* implement [pending specs](https://travis-ci.org/consul/consul) -* increase [code coverage](https://coveralls.io/github/consul/consul?branch=master) -* improve [code quality](https://codeclimate.com/github/consul/consul) -* make [code consistent](https://github.com/bbatsov/rubocop) - ## Other ways of contributing without coding * If you think there's a feature missing, or find a bug, create an issue (make sure it has not already been reported). diff --git a/Dockerfile b/Dockerfile index 880d17e43..5b863d78d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,11 @@ +# Use Ruby 2.3.6 as base image FROM ruby:2.3.6 +ENV DEBIAN_FRONTEND noninteractive + # Install essential Linux packages -RUN apt-get update -qq && apt-get install -y build-essential libpq-dev postgresql-client nodejs imagemagick sudo +RUN apt-get update -qq +RUN apt-get install -y build-essential libpq-dev postgresql-client nodejs imagemagick sudo libxss1 libappindicator1 libindicator7 unzip memcached # Files created inside the container repect the ownership RUN adduser --shell /bin/bash --disabled-password --gecos "" consul \ @@ -34,13 +38,22 @@ COPY Gemfile_custom Gemfile_custom # Prevent bundler warnings; ensure that the bundler version executed is >= that which created Gemfile.lock RUN gem install bundler -# Finish establishing our Ruby enviornment +# Finish establishing our Ruby environment RUN bundle install --full-index +# Install Chromium and ChromeDriver for E2E integration tests +RUN apt-get update -qq && apt-get install -y chromium +RUN wget -N http://chromedriver.storage.googleapis.com/2.38/chromedriver_linux64.zip +RUN unzip chromedriver_linux64.zip +RUN chmod +x chromedriver +RUN mv -f chromedriver /usr/local/share/chromedriver +RUN ln -s /usr/local/share/chromedriver /usr/local/bin/chromedriver +RUN ln -s /usr/local/share/chromedriver /usr/bin/chromedriver + # Copy the Rails application into place COPY . . # Define the script we want run once the container boots # Use the "exec" form of CMD so our script shuts down gracefully on SIGTERM (i.e. `docker stop`) -#CMD [ "config/containers/app_cmd.sh" ] +# CMD [ "config/containers/app_cmd.sh" ] CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"] diff --git a/Gemfile b/Gemfile index 6bc87f934..1ee905d34 100644 --- a/Gemfile +++ b/Gemfile @@ -5,17 +5,17 @@ gem 'rails', '4.2.10' gem 'acts-as-taggable-on', '~> 5.0.0' gem 'acts_as_votable', '~> 0.11.1' gem 'ahoy_matey', '~> 1.6.0' -gem 'ancestry', '~> 3.0.1' -gem 'autoprefixer-rails', '~> 8.2.0' +gem 'ancestry', '~> 3.0.2' +gem 'autoprefixer-rails', '~> 9.1.4' gem 'best_in_place', '~> 3.0.1' -gem 'browser', '~> 2.5.2' +gem 'browser', '~> 2.5.3' gem 'cancancan', '~> 2.1.2' gem 'ckeditor', '~> 4.2.3' gem 'cocoon', '~> 1.2.9' -gem 'coffee-rails', '~> 4.2.1' +gem 'coffee-rails', '~> 4.2.2' gem 'daemons', '~> 1.2.4' gem 'dalli', '~> 2.7.6' -gem 'delayed_job_active_record', '~> 4.1.0' +gem 'delayed_job_active_record', '~> 4.1.3' gem 'devise', '~> 3.5.7' gem 'devise-async', '~> 0.10.2' gem 'devise_security_extension', '~> 0.10.0' @@ -28,7 +28,7 @@ gem 'groupdate', '~> 3.2.0' gem 'initialjs-rails', '~> 0.2.0.5' gem 'invisible_captcha', '~> 0.10.0' gem 'jquery-fileupload-rails' -gem 'jquery-rails', '~> 4.3.1' +gem 'jquery-rails', '~> 4.3.3' gem 'jquery-ui-rails', '~> 6.0.1' gem 'kaminari', '~> 1.1.1' gem 'newrelic_rpm', '~> 4.1.0.333' @@ -37,7 +37,7 @@ gem 'omniauth-facebook', '~> 4.0.0' gem 'omniauth-google-oauth2', '~> 0.4.0' gem 'omniauth-twitter', '~> 1.4.0' gem 'paperclip', '~> 5.2.1' -gem 'paranoia', '~> 2.4.0' +gem 'paranoia', '~> 2.4.1' gem 'pg', '~> 0.21.0' gem 'pg_search', '~> 2.0.1' gem 'redcarpet', '~> 3.4.0' @@ -45,14 +45,14 @@ gem 'responders', '~> 2.4.0' gem 'rinku', '~> 2.0.2', require: 'rails_rinku' gem 'rollbar', '~> 2.15.5' gem 'sass-rails', '~> 5.0', '>= 5.0.4' -gem 'savon', '~> 2.11.1' +gem 'savon', '~> 2.12.0' gem 'sitemap_generator', '~> 6.0.1' gem 'social-share-button', '~> 1.1' gem 'sprockets', '~> 3.7.2' gem 'turbolinks', '~> 2.5.3' gem 'turnout', '~> 2.4.0' gem 'uglifier', '~> 4.1.2' -gem 'unicorn', '~> 5.4.0' +gem 'unicorn', '~> 5.4.1' gem 'whenever', '~> 0.10.0', require: false gem 'globalize', '~> 5.0.0' gem 'globalize-accessors', '~> 0.2.1' @@ -68,7 +68,8 @@ group :development, :test do gem 'bullet', '~> 5.7.0' gem 'byebug', '~> 10.0.0' gem 'factory_bot_rails', '~> 4.8.2' - gem 'i18n-tasks', '~> 0.9.20' + gem 'faker', '~> 1.8.7' + gem 'i18n-tasks', '~> 0.9.25' gem 'knapsack_pro', '~> 0.53.0' gem 'launchy', '~> 2.4.3' gem 'letter_opener_web', '~> 1.3.2' @@ -79,7 +80,7 @@ end group :test do gem 'capybara', '~> 2.17.0' - gem 'coveralls', '~> 0.8.21', require: false + gem 'coveralls', '~> 0.8.22', require: false gem 'database_cleaner', '~> 1.6.1' gem 'email_spec', '~> 2.1.0' gem 'rspec-rails', '~> 3.6' @@ -89,13 +90,13 @@ end group :development do gem 'capistrano', '~> 3.10.1', require: false gem 'capistrano-bundler', '~> 1.2', require: false - gem 'capistrano-rails', '~> 1.3.1', require: false + gem 'capistrano-rails', '~> 1.4.0', require: false gem 'capistrano3-delayed-job', '~> 1.7.3' - gem 'mdl', '~> 0.4.0', require: false + gem 'mdl', '~> 0.5.0', require: false gem 'rubocop', '~> 0.54.0', require: false - gem 'rubocop-rspec', '~> 1.24.0', require: false + gem 'rubocop-rspec', '~> 1.26.0', require: false gem 'rvm1-capistrano3', '~> 1.4.0', require: false - gem 'scss_lint', '~> 0.54.0', require: false + gem 'scss_lint', '~> 0.55.0', require: false gem 'web-console', '~> 3.3.0' end diff --git a/Gemfile.lock b/Gemfile.lock index ecdea0a6a..e85a03d81 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -57,11 +57,11 @@ GEM akami (1.3.1) gyoku (>= 0.4.0) nokogiri - ancestry (3.0.1) + ancestry (3.0.2) activerecord (>= 3.2.0) arel (6.0.4) ast (2.4.0) - autoprefixer-rails (8.2.0) + autoprefixer-rails (9.1.4) execjs babel-source (5.8.35) babel-transpiler (0.7.0) @@ -71,7 +71,7 @@ GEM best_in_place (3.0.3) actionpack (>= 3.2) railties (>= 3.2) - browser (2.5.2) + browser (2.5.3) builder (3.2.3) bullet (5.7.1) activesupport (>= 3.0.0) @@ -86,7 +86,7 @@ GEM capistrano-bundler (1.3.0) capistrano (~> 3.1) sshkit (~> 1.2) - capistrano-rails (1.3.1) + capistrano-rails (1.4.0) capistrano (~> 3.1) capistrano-bundler (~> 1.1) capistrano3-delayed-job (1.7.5) @@ -117,21 +117,21 @@ GEM execjs coffee-script-source (1.12.2) concurrent-ruby (1.0.5) - coveralls (0.8.21) + coveralls (0.8.22) json (>= 1.8, < 3) - simplecov (~> 0.14.1) + simplecov (~> 0.16.1) term-ansicolor (~> 1.3) thor (~> 0.19.4) tins (~> 1.6) - crass (1.0.3) + crass (1.0.4) daemons (1.2.6) dalli (2.7.6) database_cleaner (1.6.2) debug_inspector (0.0.3) - delayed_job (4.1.3) - activesupport (>= 3.0, < 5.2) - delayed_job_active_record (4.1.2) - activerecord (>= 3.0, < 5.2) + delayed_job (4.1.5) + activesupport (>= 3.0, < 5.3) + delayed_job_active_record (4.1.3) + activerecord (>= 3.0, < 5.3) delayed_job (>= 3.0, < 5) devise (3.5.10) bcrypt (~> 3.0) @@ -146,16 +146,13 @@ GEM devise (>= 3.0.0, < 4.0) railties (>= 3.2.6, < 5.0) diff-lcs (1.3) - docile (1.1.5) - easy_translate (0.5.1) - thread - thread_safe + docile (1.3.1) email_spec (2.1.1) htmlentities (~> 4.3.3) launchy (~> 2.1) mail (~> 2.6) errbase (0.0.3) - erubi (1.7.0) + erubi (1.7.1) erubis (2.7.0) execjs (2.7.0) factory_bot (4.8.2) @@ -167,7 +164,7 @@ GEM i18n (>= 0.7) faraday (0.12.1) multipart-post (>= 1.2, < 3) - ffi (1.9.23) + ffi (1.9.25) foundation-rails (6.4.3.0) railties (>= 3.1.0) sass (>= 3.3.0, < 3.5) @@ -194,22 +191,21 @@ GEM gyoku (1.3.1) builder (>= 2.1.2) hashie (3.5.7) - highline (1.7.10) + highline (2.0.0) htmlentities (4.3.4) - httpi (2.4.2) + httpi (2.4.4) rack socksify i18n (0.9.5) concurrent-ruby (~> 1.0) - i18n-tasks (0.9.20) + i18n-tasks (0.9.25) activesupport (>= 4.0.2) ast (>= 2.1.0) - easy_translate (>= 0.5.1) erubi - highline (>= 1.7.3) + highline (>= 2.0.0) i18n parser (>= 2.2.3.0) - rainbow (~> 2.2) + rainbow (>= 2.2.2, < 4.0) terminal-table (>= 1.5.1) initialjs-rails (0.2.0.5) railties (>= 3.1, < 6.0) @@ -219,7 +215,7 @@ GEM actionpack (>= 3.1) railties (>= 3.1) sass (>= 3.2) - jquery-rails (4.3.1) + jquery-rails (4.3.3) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) @@ -239,10 +235,10 @@ GEM activerecord kaminari-core (= 1.1.1) kaminari-core (1.1.1) - kgio (2.11.1) + kgio (2.11.2) knapsack_pro (0.53.0) rake - kramdown (1.14.0) + kramdown (1.17.0) launchy (2.4.3) addressable (~> 2.3) letter_opener (1.6.0) @@ -256,7 +252,7 @@ GEM nokogiri (>= 1.5.9) mail (2.7.0) mini_mime (>= 0.1.1) - mdl (0.4.0) + mdl (0.5.0) kramdown (~> 1.12, >= 1.12.0) mixlib-cli (~> 1.7, >= 1.7.0) mixlib-config (~> 2.2, >= 2.2.1) @@ -268,15 +264,16 @@ GEM mini_portile2 (2.3.0) minitest (5.11.3) mixlib-cli (1.7.0) - mixlib-config (2.2.4) + mixlib-config (2.2.13) + tomlrb multi_json (1.12.2) multi_xml (0.6.0) multipart-post (2.0.0) net-scp (1.2.1) net-ssh (>= 2.6.5) - net-ssh (4.2.0) + net-ssh (5.0.2) newrelic_rpm (4.1.0.333) - nokogiri (1.8.2) + nokogiri (1.8.4) mini_portile2 (~> 2.3.0) nori (2.6.0) oauth (0.5.3) @@ -313,16 +310,16 @@ GEM mime-types mimemagic (~> 0.3.0) parallel (1.12.1) - paranoia (2.4.0) - activerecord (>= 4.0, < 5.2) - parser (2.5.0.5) + paranoia (2.4.1) + activerecord (>= 4.0, < 5.3) + parser (2.5.1.2) ast (~> 2.4.0) pg (0.21.0) pg_search (2.0.1) activerecord (>= 4.2) activesupport (>= 4.2) arel (>= 6) - powerpack (0.1.1) + powerpack (0.1.2) public_suffix (3.0.1) quiet_assets (1.1.0) railties (>= 3.1, < 5.0) @@ -359,8 +356,7 @@ GEM activesupport (= 4.2.10) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rainbow (2.2.2) - rake + rainbow (3.0.0) raindrops (0.19.0) rake (12.3.1) redcarpet (3.4.0) @@ -396,10 +392,10 @@ GEM rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) - rubocop-rspec (1.24.0) + rubocop-rspec (1.26.0) rubocop (>= 0.53.0) - ruby-progressbar (1.9.0) - rubyzip (1.2.1) + ruby-progressbar (1.10.0) + rubyzip (1.2.2) rvm1-capistrano3 (1.4.0) capistrano (~> 3.0) sshkit (>= 1.2) @@ -412,25 +408,25 @@ GEM sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) - savon (2.11.2) + savon (2.12.0) akami (~> 1.2) builder (>= 2.1.2) gyoku (~> 1.2) httpi (~> 2.3) - nokogiri (>= 1.4.0) + nokogiri (>= 1.8.1) nori (~> 2.4) wasabi (~> 3.4) - scss_lint (0.54.0) + scss_lint (0.55.0) rake (>= 0.9, < 13) sass (~> 3.4.20) selenium-webdriver (3.10.0) childprocess (~> 0.5) rubyzip (~> 1.2) - simplecov (0.14.1) - docile (~> 1.1.0) + simplecov (0.16.1) + docile (~> 1.1) json (>= 1.8, < 3) simplecov-html (~> 0.10.0) - simplecov-html (0.10.1) + simplecov-html (0.10.2) sitemap_generator (6.0.1) builder (~> 3.0) social-share-button (1.1.0) @@ -451,7 +447,7 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) - sshkit (1.15.1) + sshkit (1.17.0) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) term-ansicolor (1.6.0) @@ -459,10 +455,10 @@ GEM terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) thor (0.19.4) - thread (0.2.2) thread_safe (0.3.6) tilt (2.0.8) - tins (1.15.0) + tins (1.16.3) + tomlrb (1.2.7) turbolinks (2.5.3) coffee-rails turnout (2.4.0) @@ -474,8 +470,8 @@ GEM thread_safe (~> 0.1) uglifier (4.1.3) execjs (>= 0.3.0, < 3) - unicode-display_width (1.3.0) - unicorn (5.4.0) + unicode-display_width (1.4.0) + unicorn (5.4.1) kgio (~> 2.6) raindrops (~> 0.7) uniform_notifier (1.11.0) @@ -504,26 +500,26 @@ DEPENDENCIES acts-as-taggable-on (~> 5.0.0) acts_as_votable (~> 0.11.1) ahoy_matey (~> 1.6.0) - ancestry (~> 3.0.1) - autoprefixer-rails (~> 8.2.0) + ancestry (~> 3.0.2) + autoprefixer-rails (~> 9.1.4) best_in_place (~> 3.0.1) - browser (~> 2.5.2) + browser (~> 2.5.3) bullet (~> 5.7.0) byebug (~> 10.0.0) cancancan (~> 2.1.2) capistrano (~> 3.10.1) capistrano-bundler (~> 1.2) - capistrano-rails (~> 1.3.1) + capistrano-rails (~> 1.4.0) capistrano3-delayed-job (~> 1.7.3) capybara (~> 2.17.0) ckeditor (~> 4.2.3) cocoon (~> 1.2.9) - coffee-rails (~> 4.2.1) - coveralls (~> 0.8.21) + coffee-rails (~> 4.2.2) + coveralls (~> 0.8.22) daemons (~> 1.2.4) dalli (~> 2.7.6) database_cleaner (~> 1.6.1) - delayed_job_active_record (~> 4.1.0) + delayed_job_active_record (~> 4.1.3) devise (~> 3.5.7) devise-async (~> 0.10.2) devise_security_extension (~> 0.10.0) @@ -537,24 +533,24 @@ DEPENDENCIES graphiql-rails (~> 1.4.1) graphql (~> 1.7.8) groupdate (~> 3.2.0) - i18n-tasks (~> 0.9.20) + i18n-tasks (~> 0.9.25) initialjs-rails (~> 0.2.0.5) invisible_captcha (~> 0.10.0) jquery-fileupload-rails - jquery-rails (~> 4.3.1) + jquery-rails (~> 4.3.3) jquery-ui-rails (~> 6.0.1) kaminari (~> 1.1.1) knapsack_pro (~> 0.53.0) launchy (~> 2.4.3) letter_opener_web (~> 1.3.2) - mdl (~> 0.4.0) + mdl (~> 0.5.0) newrelic_rpm (~> 4.1.0.333) omniauth (~> 1.8.1) omniauth-facebook (~> 4.0.0) omniauth-google-oauth2 (~> 0.4.0) omniauth-twitter (~> 1.4.0) paperclip (~> 5.2.1) - paranoia (~> 2.4.0) + paranoia (~> 2.4.1) pg (~> 0.21.0) pg_search (~> 2.0.1) quiet_assets (~> 1.1.0) @@ -567,11 +563,11 @@ DEPENDENCIES rollbar (~> 2.15.5) rspec-rails (~> 3.6) rubocop (~> 0.54.0) - rubocop-rspec (~> 1.24.0) + rubocop-rspec (~> 1.26.0) rvm1-capistrano3 (~> 1.4.0) sass-rails (~> 5.0, >= 5.0.4) - savon (~> 2.11.1) - scss_lint (~> 0.54.0) + savon (~> 2.12.0) + scss_lint (~> 0.55.0) selenium-webdriver (~> 3.10) sitemap_generator (~> 6.0.1) social-share-button (~> 1.1) @@ -581,7 +577,7 @@ DEPENDENCIES turbolinks (~> 2.5.3) turnout (~> 2.4.0) uglifier (~> 4.1.2) - unicorn (~> 5.4.0) + unicorn (~> 5.4.1) web-console (~> 3.3.0) whenever (~> 0.10.0) wicked_pdf (~> 1.1.0) diff --git a/app/assets/fonts/custom/.keep b/app/assets/fonts/custom/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index e1fda6bb3..dd02ee6ee 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -34,6 +34,7 @@ //= require moderator_comment //= require moderator_debates //= require moderator_proposals +//= require moderator_budget_investments //= require moderator_proposal_notifications //= require prevent_double_submission //= require gettext diff --git a/app/assets/javascripts/ckeditor/config.js b/app/assets/javascripts/ckeditor/config.js index ede6210b3..dc8ae5149 100644 --- a/app/assets/javascripts/ckeditor/config.js +++ b/app/assets/javascripts/ckeditor/config.js @@ -5,35 +5,19 @@ For licensing, see LICENSE.html or http://ckeditor.com/license CKEDITOR.editorConfig = function( config ) { - // Define changes to default configuration here. For example: - // config.language = 'fr'; - // config.uiColor = '#AADC6E'; - + config.forcePasteAsPlainText = true; - /* Filebrowser routes */ - // The location of an external file browser, that should be launched when "Browse Server" button is pressed. config.filebrowserBrowseUrl = "/ckeditor/attachment_files"; - - // The location of an external file browser, that should be launched when "Browse Server" button is pressed in the Flash dialog. config.filebrowserFlashBrowseUrl = "/ckeditor/attachment_files"; - - // The location of a script that handles file uploads in the Flash dialog. config.filebrowserFlashUploadUrl = "/ckeditor/attachment_files"; - - // The location of an external file browser, that should be launched when "Browse Server" button is pressed in the Link tab of Image dialog. config.filebrowserImageBrowseLinkUrl = "/ckeditor/pictures"; - - // The location of an external file browser, that should be launched when "Browse Server" button is pressed in the Image dialog. config.filebrowserImageBrowseUrl = "/ckeditor/pictures"; - - // The location of a script that handles file uploads in the Image dialog. config.filebrowserImageUploadUrl = "/ckeditor/pictures"; - - // The location of a script that handles file uploads. config.filebrowserUploadUrl = "/ckeditor/attachment_files"; config.allowedContent = true; + config.format_tags = "p;h2;h3"; // Rails CSRF token config.filebrowserParams = function(){ @@ -111,7 +95,14 @@ CKEDITOR.editorConfig = function( config ) config.toolbar_mini = [ { name: 'paragraph', groups: [ 'list' ], items: [ 'NumberedList', 'BulletedList' ] }, + { name: 'links', items: [ 'Link', 'Unlink' ] }, + { name: 'styles', items: [ 'Format' ] }, { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ], items: [ 'Bold', 'Italic', 'Underline', 'Strike' ] } ]; + + config.toolbar_admin = config.toolbar_mini.concat([ + { name: 'insert', items: [ 'Image' ] } + ]); + config.toolbar = "mini"; }; diff --git a/app/assets/javascripts/custom/.keep b/app/assets/javascripts/custom/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/app/assets/javascripts/globalize.js.coffee b/app/assets/javascripts/globalize.js.coffee index 0de799318..374d2b299 100644 --- a/app/assets/javascripts/globalize.js.coffee +++ b/app/assets/javascripts/globalize.js.coffee @@ -1,6 +1,7 @@ App.Globalize = display_locale: (locale) -> + App.Globalize.enable_locale(locale) $(".js-globalize-locale-link").each -> if $(this).data("locale") == locale $(this).show() @@ -22,12 +23,25 @@ App.Globalize = element.addClass('is-active'); remove_language: (locale) -> - $(".js-globalize-attribute[data-locale=" + locale + "]").val('').hide() + $(".js-globalize-attribute[data-locale=" + locale + "]").each -> + $(this).val('').hide() + if CKEDITOR.instances[$(this).attr('id')] + CKEDITOR.instances[$(this).attr('id')].setData('') $(".js-globalize-locale-link[data-locale=" + locale + "]").hide() next = $(".js-globalize-locale-link:visible").first() App.Globalize.highlight_locale(next) App.Globalize.display_translations(next.data("locale")) - $("#delete_translations_" + locale).val(1) + App.Globalize.disable_locale(locale) + + enable_locale: (locale) -> + $("#enabled_translations_" + locale).val(1) + + disable_locale: (locale) -> + $("#enabled_translations_" + locale).val(0) + + refresh_visible_translations: -> + locale = $('.js-globalize-locale-link.is-active').data("locale") + App.Globalize.display_translations(locale) initialize: -> $('.js-globalize-locale').on 'change', -> diff --git a/app/assets/javascripts/legislation_admin.js.coffee b/app/assets/javascripts/legislation_admin.js.coffee index fc1d7cab4..f7f9ea17a 100644 --- a/app/assets/javascripts/legislation_admin.js.coffee +++ b/app/assets/javascripts/legislation_admin.js.coffee @@ -12,3 +12,5 @@ App.LegislationAdmin = else $(this).val("") + $("#nested-question-options").on "cocoon:after-insert", -> + App.Globalize.refresh_visible_translations() diff --git a/app/assets/javascripts/markdown_editor.js.coffee b/app/assets/javascripts/markdown_editor.js.coffee index 510bf83ec..29e74e51c 100644 --- a/app/assets/javascripts/markdown_editor.js.coffee +++ b/app/assets/javascripts/markdown_editor.js.coffee @@ -1,10 +1,15 @@ App.MarkdownEditor = refresh_preview: (element, md) -> - textarea_content = element.find('textarea').val() + textarea_content = App.MarkdownEditor.find_textarea(element).val() result = md.render(textarea_content) element.find('#markdown-preview').html(result) + # Multi-locale (translatable) form fields work by hiding inputs of locales + # which are not "active". + find_textarea: (editor) -> + editor.find('textarea:visible') + initialize: -> $('.markdown-editor').each -> md = window.markdownit({ @@ -13,18 +18,18 @@ App.MarkdownEditor = typographer: true, }) - App.MarkdownEditor.refresh_preview($(this), md) + editor = $(this) - $(this).on 'change input paste keyup', -> + editor.on 'input', -> App.MarkdownEditor.refresh_preview($(this), md) $('.legislation-draft-versions-edit .warning').show() return - $(this).find('textarea').on 'scroll', -> + editor.find('textarea').on 'scroll', -> $('#markdown-preview').scrollTop($(this).scrollTop()) - $(this).find('.fullscreen-toggle').on 'click', -> - $('.markdown-editor').toggleClass('fullscreen') + editor.find('.fullscreen-toggle').on 'click', -> + editor.toggleClass('fullscreen') $('.fullscreen-container').toggleClass('medium-8', 'medium-12') span = $(this).find('span') current_html = span.html() @@ -33,7 +38,8 @@ App.MarkdownEditor = else span.html(span.data('open-text')) - if $('.markdown-editor').hasClass('fullscreen') - $('.markdown-editor textarea').height($(window).height() - 100) + if editor.hasClass('fullscreen') + App.MarkdownEditor.find_textarea(editor).height($(window).height() - 100) + App.MarkdownEditor.refresh_preview(editor, md) else - $('.markdown-editor textarea').height("10em") + App.MarkdownEditor.find_textarea(editor).height("10em") diff --git a/app/assets/javascripts/moderator_budget_investments.js.coffee b/app/assets/javascripts/moderator_budget_investments.js.coffee new file mode 100644 index 000000000..612a058a4 --- /dev/null +++ b/app/assets/javascripts/moderator_budget_investments.js.coffee @@ -0,0 +1,8 @@ +App.ModeratorBudgetInvestments = + + add_class_faded: (id) -> + $("##{id}").addClass("faded") + $("#comments").addClass("faded") + + hide_moderator_actions: (id) -> + $("##{id} .js-moderator-investment-actions:first").hide() diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss index a8d81a12d..7b74b2697 100644 --- a/app/assets/stylesheets/admin.scss +++ b/app/assets/stylesheets/admin.scss @@ -46,6 +46,10 @@ $sidebar-active: #f4fcd0; .top-links { background: #000; + + a { + line-height: rem-calc($line-height * 1.5); + } } .admin-top-bar { @@ -190,6 +194,11 @@ $sidebar-active: #f4fcd0; &.with-button { line-height: $line-height * 2; + + .button { + background: #fff; + color: $brand; + } } } @@ -204,6 +213,19 @@ $sidebar-active: #f4fcd0; table { + thead { + color: #fff; + } + + th { + background: $brand; + color: #fff; + + label { + color: #fff; + } + } + .break { word-break: break-word; } @@ -220,7 +242,6 @@ $sidebar-active: #f4fcd0; @include breakpoint(medium) { margin-left: $line-height / 2; - margin-right: $line-height / 2; margin-top: 0; } } @@ -335,6 +356,14 @@ $sidebar-active: #f4fcd0; margin-bottom: 0 !important; } +.enabled { + color: $color-success; +} + +.disabled { + color: $text-medium; +} + // 02. Sidebar // ----------- diff --git a/app/assets/stylesheets/layout.scss b/app/assets/stylesheets/layout.scss index a140fca4c..8172d13ab 100644 --- a/app/assets/stylesheets/layout.scss +++ b/app/assets/stylesheets/layout.scss @@ -878,11 +878,6 @@ footer { h1 { margin-top: $line-height; - img { - height: rem-calc(80); - width: rem-calc(80); - } - a { color: #fff; display: block; diff --git a/app/assets/stylesheets/pages.scss b/app/assets/stylesheets/pages.scss index d3db7e9b2..efde88d07 100644 --- a/app/assets/stylesheets/pages.scss +++ b/app/assets/stylesheets/pages.scss @@ -10,14 +10,24 @@ // ---------------------- .jumbo { + background: $highlight; margin-bottom: $line-height; margin-top: rem-calc(-24); padding-bottom: $line-height; padding-top: $line-height; + @include breakpoint(medium) { + padding: rem-calc(24) 0; + } + &.light { background: #ecf0f1; } + + h1, + p { + color: $text; + } } .lead { diff --git a/app/assets/stylesheets/participation.scss b/app/assets/stylesheets/participation.scss index f0d194301..bb600ff83 100644 --- a/app/assets/stylesheets/participation.scss +++ b/app/assets/stylesheets/participation.scss @@ -9,7 +9,6 @@ // 07. Proposals successful // 08. Polls // 09. Polls results and stats -// 10. Guides // // 01. Votes and supports @@ -685,7 +684,8 @@ } .budget-investments-list .budget-investment, -.proposals-list .proposal { +.proposals-list .proposal, +.legislation-proposals .proposal { @include breakpoint(medium) { @@ -886,6 +886,13 @@ } } +.legislation-proposals { + + .votes { + min-height: $line-height * 8; + } +} + .proposal-show .votes, .debate-show .votes { border: 0; @@ -2087,94 +2094,3 @@ line-height: rem-calc(60); } } - -// 10. Guides -// ---------------------- - -.guides { - - h2 { - margin: $line-height 0 $line-height * 2; - } - - .guide-budget, - .guide-proposal { - border-radius: rem-calc(3); - margin: $line-height 0; - padding: $line-height; - position: relative; - - .button { - color: $text; - font-weight: bold; - } - - &::before { - border-radius: 100%; - color: #fff; - font-family: "icons" !important; - height: rem-calc(80); - left: 50%; - line-height: rem-calc(80); - margin-left: rem-calc(-40); - position: absolute; - text-align: center; - top: -40px; - width: rem-calc(80); - z-index: 99; - } - } - - .guide-budget { - border: 1px solid $budget; - - &::before { - background: $budget; - content: '\53'; - font-size: rem-calc(40); - } - - .button { - background: #f8f5f9; - border: 1px solid $budget; - } - } - - .guide-proposal { - border: 1px solid $proposals; - - &::before { - background: $proposals; - content: '\68'; - font-size: rem-calc(40); - } - - .button { - background: #fffaf4; - border: 1px solid $proposals; - } - } - - ul { - - @include breakpoint(medium) { - min-height: $line-height * 14; - } - - li { - margin-bottom: $line-height; - padding-left: $line-height; - position: relative; - - &::before { - color: #37af65; - content: '\56'; - font-family: "icons" !important; - font-size: $small-font-size; - left: 0; - position: absolute; - top: 1px; - } - } - } -} diff --git a/app/controllers/admin/activity_controller.rb b/app/controllers/admin/activity_controller.rb index 1d763c09d..ce9c04183 100644 --- a/app/controllers/admin/activity_controller.rb +++ b/app/controllers/admin/activity_controller.rb @@ -1,5 +1,5 @@ class Admin::ActivityController < Admin::BaseController - has_filters %w{all on_users on_proposals on_debates on_comments} + has_filters %w{all on_users on_proposals on_debates on_comments on_system_emails} def show @activity = Activity.for_render.send(@current_filter) diff --git a/app/controllers/admin/admin_notifications_controller.rb b/app/controllers/admin/admin_notifications_controller.rb index 1dd03038a..33e99f9ed 100644 --- a/app/controllers/admin/admin_notifications_controller.rb +++ b/app/controllers/admin/admin_notifications_controller.rb @@ -1,4 +1,5 @@ class Admin::AdminNotificationsController < Admin::BaseController + include Translatable def index @admin_notifications = AdminNotification.all @@ -62,6 +63,13 @@ class Admin::AdminNotificationsController < Admin::BaseController private def admin_notification_params - params.require(:admin_notification).permit(:title, :body, :link, :segment_recipient) + attributes = [:title, :body, :link, :segment_recipient, + *translation_params(AdminNotification)] + + params.require(:admin_notification).permit(attributes) + end + + def resource + AdminNotification.find(params[:id]) end end diff --git a/app/controllers/admin/banners_controller.rb b/app/controllers/admin/banners_controller.rb index 25038b80f..b17c17d1b 100644 --- a/app/controllers/admin/banners_controller.rb +++ b/app/controllers/admin/banners_controller.rb @@ -1,4 +1,5 @@ class Admin::BannersController < Admin::BaseController + include Translatable has_filters %w{all with_active with_inactive}, only: :index @@ -40,6 +41,7 @@ class Admin::BannersController < Admin::BaseController attributes = [:title, :description, :target_url, :post_started_at, :post_ended_at, :background_color, :font_color, + *translation_params(Banner), web_section_ids: []] params.require(:banner).permit(*attributes) end @@ -59,4 +61,9 @@ class Admin::BannersController < Admin::BaseController def banner_sections @banner_sections = WebSection.all end + + def resource + @banner = Banner.find(params[:id]) unless @banner + @banner + end end diff --git a/app/controllers/admin/budget_investment_milestones_controller.rb b/app/controllers/admin/budget_investment_milestones_controller.rb index d54dc0174..49f2df5bf 100644 --- a/app/controllers/admin/budget_investment_milestones_controller.rb +++ b/app/controllers/admin/budget_investment_milestones_controller.rb @@ -47,9 +47,10 @@ class Admin::BudgetInvestmentMilestonesController < Admin::BaseController image_attributes = [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy] documents_attributes = [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy] attributes = [:title, :description, :publication_date, :budget_investment_id, :status_id, + *translation_params(Budget::Investment::Milestone), image_attributes: image_attributes, documents_attributes: documents_attributes] - params.require(:budget_investment_milestone).permit(*attributes, translation_params(params[:budget_investment_milestone])) + params.require(:budget_investment_milestone).permit(*attributes) end def load_budget_investment @@ -64,10 +65,6 @@ class Admin::BudgetInvestmentMilestonesController < Admin::BaseController Budget::Investment::Milestone.find(params[:id]) end - def resource_model - Budget::Investment::Milestone - end - def resource get_milestone end diff --git a/app/controllers/admin/budget_investments_controller.rb b/app/controllers/admin/budget_investments_controller.rb index 0258c23a6..6797f63d0 100644 --- a/app/controllers/admin/budget_investments_controller.rb +++ b/app/controllers/admin/budget_investments_controller.rb @@ -75,25 +75,17 @@ class Admin::BudgetInvestmentsController < Admin::BaseController resource_model.parameterize('_') end - def sort_by(params) - if params.present? && Budget::Investment::SORTING_OPTIONS.include?(params) - "#{params == 'supports' ? 'cached_votes_up' : params} ASC" - else - "cached_votes_up DESC, created_at DESC" - end - end - def load_investments @investments = Budget::Investment.scoped_filter(params, @current_filter) - .order(sort_by(params[:sort_by])) + @investments = @investments.order_filter(params[:sort_by]) if params[:sort_by].present? @investments = @investments.page(params[:page]) unless request.format.csv? end def budget_investment_params params.require(:budget_investment) .permit(:title, :description, :external_url, :heading_id, :administrator_id, :tag_list, - :valuation_tag_list, :incompatible, :visible_to_valuators, :selected, valuator_ids: [], - valuator_group_ids: []) + :valuation_tag_list, :incompatible, :visible_to_valuators, :selected, + valuator_ids: [], valuator_group_ids: []) end def load_budget diff --git a/app/controllers/admin/hidden_budget_investments_controller.rb b/app/controllers/admin/hidden_budget_investments_controller.rb new file mode 100644 index 000000000..439f3c722 --- /dev/null +++ b/app/controllers/admin/hidden_budget_investments_controller.rb @@ -0,0 +1,34 @@ +class Admin::HiddenBudgetInvestmentsController < Admin::BaseController + include FeatureFlags + + has_filters %w{all with_confirmed_hide without_confirmed_hide}, only: :index + + feature_flag :budgets + + before_action :load_investment, only: [:confirm_hide, :restore] + + def index + @investments = Budget::Investment.only_hidden.send(@current_filter) + .order(hidden_at: :desc) + .page(params[:page]) + end + + def confirm_hide + @investment.confirm_hide + redirect_to request.query_parameters.merge(action: :index) + end + + def restore + @investment.restore + @investment.ignore_flag + Activity.log(current_user, :restore, @investment) + redirect_to request.query_parameters.merge(action: :index) + end + + private + + def load_investment + @investment = Budget::Investment.with_hidden.find(params[:id]) + end + +end diff --git a/app/controllers/admin/legislation/draft_versions_controller.rb b/app/controllers/admin/legislation/draft_versions_controller.rb index 4e7ae18a8..703d8a543 100644 --- a/app/controllers/admin/legislation/draft_versions_controller.rb +++ b/app/controllers/admin/legislation/draft_versions_controller.rb @@ -1,6 +1,8 @@ class Admin::Legislation::DraftVersionsController < Admin::Legislation::BaseController - load_and_authorize_resource :process, class: "Legislation::Process" - load_and_authorize_resource :draft_version, class: "Legislation::DraftVersion", through: :process + include Translatable + + load_and_authorize_resource :draft_version, class: "Legislation::DraftVersion", through: :process, prepend: true + load_and_authorize_resource :process, class: "Legislation::Process", prepend: true def index @draft_versions = @process.draft_versions @@ -44,7 +46,12 @@ class Admin::Legislation::DraftVersionsController < Admin::Legislation::BaseCont :status, :final_version, :body, - :body_html + :body_html, + *translation_params(Legislation::DraftVersion) ) end + + def resource + @draft_version + end end diff --git a/app/controllers/admin/legislation/processes_controller.rb b/app/controllers/admin/legislation/processes_controller.rb index 976318958..2cb20d0ba 100644 --- a/app/controllers/admin/legislation/processes_controller.rb +++ b/app/controllers/admin/legislation/processes_controller.rb @@ -1,4 +1,6 @@ class Admin::Legislation::ProcessesController < Admin::Legislation::BaseController + include Translatable + has_filters %w{open next past all}, only: :index load_and_authorize_resource :process, class: "Legislation::Process" @@ -61,6 +63,7 @@ class Admin::Legislation::ProcessesController < Admin::Legislation::BaseControll :result_publication_enabled, :published, :custom_list, + *translation_params(Legislation::Process), documents_attributes: [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy] ) end @@ -69,4 +72,8 @@ class Admin::Legislation::ProcessesController < Admin::Legislation::BaseControll @process.set_tag_list_on(:customs, process_params[:custom_list]) @process.save end + + def resource + @process || Legislation::Process.find(params[:id]) + end end diff --git a/app/controllers/admin/legislation/proposals_controller.rb b/app/controllers/admin/legislation/proposals_controller.rb index 7f4441bce..44a1dd192 100644 --- a/app/controllers/admin/legislation/proposals_controller.rb +++ b/app/controllers/admin/legislation/proposals_controller.rb @@ -1,7 +1,16 @@ class Admin::Legislation::ProposalsController < Admin::Legislation::BaseController + + has_orders %w[id title supports], only: :index + load_and_authorize_resource :process, class: "Legislation::Process" load_and_authorize_resource :proposal, class: "Legislation::Proposal", through: :process def index + @proposals = @proposals.send("sort_by_#{@current_order}").page(params[:page]) + end + + def toggle_selection + @proposal.toggle :selected + @proposal.save! end end diff --git a/app/controllers/admin/legislation/questions_controller.rb b/app/controllers/admin/legislation/questions_controller.rb index bac16295e..da73e905c 100644 --- a/app/controllers/admin/legislation/questions_controller.rb +++ b/app/controllers/admin/legislation/questions_controller.rb @@ -1,4 +1,6 @@ class Admin::Legislation::QuestionsController < Admin::Legislation::BaseController + include Translatable + load_and_authorize_resource :process, class: "Legislation::Process" load_and_authorize_resource :question, class: "Legislation::Question", through: :process @@ -46,7 +48,13 @@ class Admin::Legislation::QuestionsController < Admin::Legislation::BaseControll def question_params params.require(:legislation_question).permit( :title, - question_options_attributes: [:id, :value, :_destroy] + *translation_params(::Legislation::Question), + question_options_attributes: [:id, :value, + *translation_params(::Legislation::QuestionOption)] ) end + + def resource + @question || ::Legislation::Question.find(params[:id]) + end end diff --git a/app/controllers/admin/newsletters_controller.rb b/app/controllers/admin/newsletters_controller.rb index 966556082..8b1c2010f 100644 --- a/app/controllers/admin/newsletters_controller.rb +++ b/app/controllers/admin/newsletters_controller.rb @@ -48,10 +48,7 @@ class Admin::NewslettersController < Admin::BaseController @newsletter = Newsletter.find(params[:id]) if @newsletter.valid? - @newsletter.list_of_recipient_emails.each do |recipient_email| - Mailer.newsletter(@newsletter, recipient_email).deliver_later - end - + @newsletter.delay.deliver @newsletter.update(sent_at: Time.current) flash[:notice] = t("admin.newsletters.send_success") else diff --git a/app/controllers/admin/poll/polls_controller.rb b/app/controllers/admin/poll/polls_controller.rb index 7ed555275..e93a82d47 100644 --- a/app/controllers/admin/poll/polls_controller.rb +++ b/app/controllers/admin/poll/polls_controller.rb @@ -1,4 +1,5 @@ class Admin::Poll::PollsController < Admin::Poll::BaseController + include Translatable load_and_authorize_resource before_action :load_search, only: [:search_booths, :search_officers] @@ -63,7 +64,7 @@ class Admin::Poll::PollsController < Admin::Poll::BaseController attributes = [:name, :starts_at, :ends_at, :geozone_restricted, :summary, :description, :results_enabled, :stats_enabled, geozone_ids: [], image_attributes: image_attributes] - params.require(:poll).permit(*attributes) + params.require(:poll).permit(*attributes, *translation_params(Poll)) end def search_params @@ -74,4 +75,7 @@ class Admin::Poll::PollsController < Admin::Poll::BaseController @search = search_params[:search] end + def resource + @poll ||= Poll.find(params[:id]) + end end diff --git a/app/controllers/admin/poll/questions/answers_controller.rb b/app/controllers/admin/poll/questions/answers_controller.rb index 3833111b9..225665b94 100644 --- a/app/controllers/admin/poll/questions/answers_controller.rb +++ b/app/controllers/admin/poll/questions/answers_controller.rb @@ -1,4 +1,6 @@ class Admin::Poll::Questions::AnswersController < Admin::Poll::BaseController + include Translatable + before_action :load_answer, only: [:show, :edit, :update, :documents] load_and_authorize_resource :question, class: "::Poll::Question" @@ -49,11 +51,15 @@ class Admin::Poll::Questions::AnswersController < Admin::Poll::BaseController def answer_params documents_attributes = [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy] attributes = [:title, :description, :question_id, documents_attributes: documents_attributes] - params.require(:poll_question_answer).permit(*attributes) + params.require(:poll_question_answer).permit(*attributes, *translation_params(Poll::Question::Answer)) end def load_answer @answer = ::Poll::Question::Answer.find(params[:id] || params[:answer_id]) end + def resource + load_answer unless @answer + @answer + end end diff --git a/app/controllers/admin/poll/questions_controller.rb b/app/controllers/admin/poll/questions_controller.rb index 399c72788..913e6824a 100644 --- a/app/controllers/admin/poll/questions_controller.rb +++ b/app/controllers/admin/poll/questions_controller.rb @@ -1,5 +1,6 @@ class Admin::Poll::QuestionsController < Admin::Poll::BaseController include CommentableActions + include Translatable load_and_authorize_resource :poll load_and_authorize_resource :question, class: 'Poll::Question' @@ -55,11 +56,15 @@ class Admin::Poll::QuestionsController < Admin::Poll::BaseController private def question_params - params.require(:poll_question).permit(:poll_id, :title, :question, :proposal_id) + attributes = [:poll_id, :title, :question, :proposal_id] + params.require(:poll_question).permit(*attributes, *translation_params(Poll::Question)) end def search_params params.permit(:poll_id, :search) end + def resource + @poll_question ||= Poll::Question.find(params[:id]) + end end diff --git a/app/controllers/admin/proposal_notifications_controller.rb b/app/controllers/admin/proposal_notifications_controller.rb index 625e670fa..1fee9a055 100644 --- a/app/controllers/admin/proposal_notifications_controller.rb +++ b/app/controllers/admin/proposal_notifications_controller.rb @@ -5,8 +5,10 @@ class Admin::ProposalNotificationsController < Admin::BaseController before_action :load_proposal, only: [:confirm_hide, :restore] def index - @proposal_notifications = ProposalNotification.only_hidden.send(@current_filter).order(hidden_at: :desc) - .page(params[:page]) + @proposal_notifications = ProposalNotification.only_hidden + .send(@current_filter) + .order(hidden_at: :desc) + .page(params[:page]) end def confirm_hide diff --git a/app/controllers/admin/signature_sheets_controller.rb b/app/controllers/admin/signature_sheets_controller.rb index 60299c5a6..4a6777695 100644 --- a/app/controllers/admin/signature_sheets_controller.rb +++ b/app/controllers/admin/signature_sheets_controller.rb @@ -1,7 +1,7 @@ class Admin::SignatureSheetsController < Admin::BaseController def index - @signature_sheets = SignatureSheet.all + @signature_sheets = SignatureSheet.all.order(created_at: :desc) end def new @@ -29,4 +29,4 @@ class Admin::SignatureSheetsController < Admin::BaseController params.require(:signature_sheet).permit(:signable_type, :signable_id, :document_numbers) end -end \ No newline at end of file +end diff --git a/app/controllers/admin/site_customization/information_texts_controller.rb b/app/controllers/admin/site_customization/information_texts_controller.rb new file mode 100644 index 000000000..32483d5ed --- /dev/null +++ b/app/controllers/admin/site_customization/information_texts_controller.rb @@ -0,0 +1,82 @@ +class Admin::SiteCustomization::InformationTextsController < Admin::SiteCustomization::BaseController + include Translatable + + def index + fetch_existing_keys + append_or_create_keys + @content = @content[@tab.to_s] + end + + def update + content_params.each do |content| + values = content[:values].slice(*translation_params(I18nContent)) + + unless values.empty? + values.each do |key, value| + locale = key.split("_").last + + if value == t(content[:id], locale: locale) || value.match(/translation missing/) + next + else + text = I18nContent.find_or_create_by(key: content[:id]) + Globalize.locale = locale + text.update(value: value) + end + end + end + + end + + redirect_to admin_site_customization_information_texts_path, + notice: t('flash.actions.update.translation') + end + + private + + def resource + I18nContent.find(content_params[:id]) + end + + def content_params + params.require(:contents).values + end + + def delete_translations + languages_to_delete = params[:enabled_translations].select { |_, v| v == '0' } + .keys + languages_to_delete.each do |locale| + I18nContentTranslation.destroy_all(locale: locale) + end + end + + def fetch_existing_keys + @existing_keys = {} + @tab = params[:tab] || :debates + + I18nContent.begins_with_key(@tab) + .all + .map{ |content| @existing_keys[content.key] = content } + end + + def append_or_create_keys + @content = {} + I18n.backend.send(:init_translations) unless I18n.backend.initialized? + + locale = params[:locale] || I18n.locale + translations = I18n.backend.send(:translations)[locale.to_sym] + + translations.each do |k, v| + @content[k.to_s] = flat_hash(v).keys + .map { |s| @existing_keys["#{k.to_s}.#{s}"].nil? ? + I18nContent.new(key: "#{k.to_s}.#{s}") : + @existing_keys["#{k.to_s}.#{s}"] } + end + end + + def flat_hash(h, f = nil, g = {}) + return g.update({ f => h }) unless h.is_a? Hash + h.each { |k, r| flat_hash(r, [f,k].compact.join('.'), g) } + return g + end + +end diff --git a/app/controllers/admin/site_customization/pages_controller.rb b/app/controllers/admin/site_customization/pages_controller.rb index 68ede1da2..e4c6a8ed7 100644 --- a/app/controllers/admin/site_customization/pages_controller.rb +++ b/app/controllers/admin/site_customization/pages_controller.rb @@ -1,4 +1,5 @@ class Admin::SiteCustomization::PagesController < Admin::SiteCustomization::BaseController + include Translatable load_and_authorize_resource :page, class: "SiteCustomization::Page" def index @@ -34,15 +35,21 @@ class Admin::SiteCustomization::PagesController < Admin::SiteCustomization::Base private def page_params - params.require(:site_customization_page).permit( - :slug, + attributes = [:slug, :title, :subtitle, :content, :more_info_flag, :print_content_flag, :status, - :locale + :locale] + + params.require(:site_customization_page).permit(*attributes, + *translation_params(SiteCustomization::Page) ) end + + def resource + SiteCustomization::Page.find(params[:id]) + end end diff --git a/app/controllers/admin/stats_controller.rb b/app/controllers/admin/stats_controller.rb index 543f24a61..774254777 100644 --- a/app/controllers/admin/stats_controller.rb +++ b/app/controllers/admin/stats_controller.rb @@ -3,23 +3,28 @@ class Admin::StatsController < Admin::BaseController def show @event_types = Ahoy::Event.group(:name).count - @visits = Visit.count - @debates = Debate.with_hidden.count + @visits = Visit.count + @debates = Debate.with_hidden.count @proposals = Proposal.with_hidden.count - @comments = Comment.not_valuations.with_hidden.count + @comments = Comment.not_valuations.with_hidden.count - @debate_votes = Vote.where(votable_type: 'Debate').count + @debate_votes = Vote.where(votable_type: 'Debate').count @proposal_votes = Vote.where(votable_type: 'Proposal').count - @comment_votes = Vote.where(votable_type: 'Comment').count + @comment_votes = Vote.where(votable_type: 'Comment').count @votes = Vote.count - @user_level_two = User.active.level_two_verified.count + @user_level_two = User.active.level_two_verified.count @user_level_three = User.active.level_three_verified.count - @verified_users = User.active.level_two_or_three_verified.count + @verified_users = User.active.level_two_or_three_verified.count @unverified_users = User.active.unverified.count @users = User.active.count - @user_ids_who_voted_proposals = ActsAsVotable::Vote.where(votable_type: 'Proposal').distinct.count(:voter_id) + + @user_ids_who_voted_proposals = ActsAsVotable::Vote.where(votable_type: 'Proposal') + .distinct + .count(:voter_id) + @user_ids_who_didnt_vote_proposals = @verified_users - @user_ids_who_voted_proposals + @spending_proposals = SpendingProposal.count budgets_ids = Budget.where.not(phase: 'finished').pluck(:id) @budgets = budgets_ids.size diff --git a/app/controllers/admin/system_emails_controller.rb b/app/controllers/admin/system_emails_controller.rb new file mode 100644 index 000000000..d079c171b --- /dev/null +++ b/app/controllers/admin/system_emails_controller.rb @@ -0,0 +1,50 @@ +class Admin::SystemEmailsController < Admin::BaseController + + before_action :load_system_email, only: [:view, :preview_pending, :moderate_pending] + + def index + @system_emails = { + proposal_notification_digest: %w(view preview_pending) + } + end + + def view + case @system_email + when "proposal_notification_digest" + @notifications = Notification.where(notifiable_type: "ProposalNotification").limit(2) + @subject = t('mailers.proposal_notification_digest.title', org_name: Setting['org_name']) + end + end + + def preview_pending + case @system_email + when "proposal_notification_digest" + @previews = ProposalNotification.where(id: unsent_proposal_notifications_ids) + .page(params[:page]) + end + end + + def moderate_pending + ProposalNotification.find(params[:id]).moderate_system_email(current_user) + + redirect_to admin_system_email_preview_pending_path("proposal_notification_digest") + end + + def send_pending + Notification.delay.send_pending + + flash[:notice] = t("admin.system_emails.preview_pending.send_pending_notification") + redirect_to admin_system_emails_path + end + + private + + def load_system_email + @system_email = params[:system_email_id] + end + + def unsent_proposal_notifications_ids + Notification.where(notifiable_type: "ProposalNotification", emailed_at: nil) + .group(:notifiable_id).count.keys + end +end diff --git a/app/controllers/admin/verifications_controller.rb b/app/controllers/admin/verifications_controller.rb index 72685a199..8ff249772 100644 --- a/app/controllers/admin/verifications_controller.rb +++ b/app/controllers/admin/verifications_controller.rb @@ -5,8 +5,10 @@ class Admin::VerificationsController < Admin::BaseController end def search - @users = User.incomplete_verification.search(params[:name_or_email]).page(params[:page]).for_render + @users = User.incomplete_verification.search(params[:name_or_email]) + .page(params[:page]) + .for_render render :index end -end \ No newline at end of file +end diff --git a/app/controllers/admin/widget/cards_controller.rb b/app/controllers/admin/widget/cards_controller.rb index 1b452df87..f5dce76a1 100644 --- a/app/controllers/admin/widget/cards_controller.rb +++ b/app/controllers/admin/widget/cards_controller.rb @@ -1,4 +1,5 @@ class Admin::Widget::CardsController < Admin::BaseController + include Translatable def new @card = ::Widget::Card.new(header: header_card?) @@ -39,13 +40,21 @@ class Admin::Widget::CardsController < Admin::BaseController private def card_params - params.require(:widget_card).permit(:label, :title, :description, :link_text, :link_url, - :button_text, :button_url, :alignment, :header, - image_attributes: [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy]) + image_attributes = [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy] + + params.require(:widget_card).permit( + :label, :title, :description, :link_text, :link_url, + :button_text, :button_url, :alignment, :header, + *translation_params(Widget::Card), + image_attributes: image_attributes + ) end def header_card? params[:header_card].present? end -end \ No newline at end of file + def resource + Widget::Card.find(params[:id]) + end +end diff --git a/app/controllers/concerns/flag_actions.rb b/app/controllers/concerns/flag_actions.rb index 3f5d0b2c7..da637407a 100644 --- a/app/controllers/concerns/flag_actions.rb +++ b/app/controllers/concerns/flag_actions.rb @@ -3,18 +3,32 @@ module FlagActions def flag Flag.flag(current_user, flaggable) - respond_with flaggable, template: "#{controller_name}/_refresh_flag_actions" + + if controller_name == 'investments' + respond_with flaggable, template: "budgets/#{controller_name}/_refresh_flag_actions" + else + respond_with flaggable, template: "#{controller_name}/_refresh_flag_actions" + end end def unflag Flag.unflag(current_user, flaggable) - respond_with flaggable, template: "#{controller_name}/_refresh_flag_actions" + + if controller_name == 'investments' + respond_with flaggable, template: "budgets/#{controller_name}/_refresh_flag_actions" + else + respond_with flaggable, template: "#{controller_name}/_refresh_flag_actions" + end end private def flaggable - instance_variable_get("@#{resource_model.to_s.downcase}") + if resource_model.to_s == 'Budget::Investment' + instance_variable_get("@investment") + else + instance_variable_get("@#{resource_model.to_s.downcase}") + end end -end \ No newline at end of file +end diff --git a/app/controllers/concerns/polymorphic.rb b/app/controllers/concerns/polymorphic.rb index 8fd4ab312..0e25b4231 100644 --- a/app/controllers/concerns/polymorphic.rb +++ b/app/controllers/concerns/polymorphic.rb @@ -3,7 +3,11 @@ module Polymorphic private def resource - @resource ||= instance_variable_get("@#{resource_name}") + if resource_model.to_s == 'Budget::Investment' + @resource ||= instance_variable_get("@investment") + else + @resource ||= instance_variable_get("@#{resource_name}") + end end def resource_name diff --git a/app/controllers/concerns/translatable.rb b/app/controllers/concerns/translatable.rb index 5c3b1be62..0acc874d8 100644 --- a/app/controllers/concerns/translatable.rb +++ b/app/controllers/concerns/translatable.rb @@ -7,13 +7,18 @@ module Translatable private - def translation_params(params) - resource_model.globalize_attribute_names.select { |k, v| params.include?(k.to_sym) && params[k].present? } + def translation_params(resource_model) + return [] unless params[:enabled_translations] + + resource_model.translated_attribute_names.product(enabled_translations).map do |attr_name, loc| + resource_model.localized_attr_name_for(attr_name, loc) + end end def delete_translations - locales = resource_model.globalize_locales. - select { |k, v| params[:delete_translations].include?(k.to_sym) && params[:delete_translations][k] == "1" } + locales = resource.translated_locales + .select { |l| params.dig(:enabled_translations, l) == "0" } + locales.each do |l| Globalize.with_locale(l) do resource.translation.destroy @@ -21,4 +26,9 @@ module Translatable end end + def enabled_translations + params.fetch(:enabled_translations, {}) + .select { |_, v| v == '1' } + .keys + end end diff --git a/app/controllers/guides_controller.rb b/app/controllers/guides_controller.rb deleted file mode 100644 index 0a03955e9..000000000 --- a/app/controllers/guides_controller.rb +++ /dev/null @@ -1,8 +0,0 @@ -class GuidesController < ApplicationController - - skip_authorization_check - - def new - end - -end \ No newline at end of file diff --git a/app/controllers/legislation/processes_controller.rb b/app/controllers/legislation/processes_controller.rb index 7cde7b6d2..f05d2fdda 100644 --- a/app/controllers/legislation/processes_controller.rb +++ b/app/controllers/legislation/processes_controller.rb @@ -1,7 +1,11 @@ class Legislation::ProcessesController < Legislation::BaseController - has_filters %w{open next past}, only: :index + has_filters %w[open next past], only: :index + has_filters %w[random winners], only: :proposals + load_and_authorize_resource + before_action :set_random_seed, only: :proposals + def index @current_filter ||= 'open' @processes = ::Legislation::Process.send(@current_filter).published.page(params[:page]) @@ -25,7 +29,7 @@ class Legislation::ProcessesController < Legislation::BaseController set_process @phase = :debate_phase - if @process.debate_phase.started? + if @process.debate_phase.started? || (current_user && current_user.administrator?) render :debate else render :phase_not_open @@ -87,8 +91,14 @@ class Legislation::ProcessesController < Legislation::BaseController set_process @phase = :proposals_phase - if @process.proposals_phase.started? - legislation_proposal_votes(@process.proposals) + @proposals = ::Legislation::Proposal.where(process: @process) + @proposals = @proposals.search(params[:search]) if params[:search].present? + + @current_filter = "winners" if params[:filter].blank? && @proposals.winners.any? + @proposals = @proposals.send(@current_filter).page(params[:page]) + + if @process.proposals_phase.started? || (current_user && current_user.administrator?) + legislation_proposal_votes(@proposals) render :proposals else render :phase_not_open @@ -105,4 +115,15 @@ class Legislation::ProcessesController < Legislation::BaseController return if member_method? @process = ::Legislation::Process.find(params[:process_id]) end + + def set_random_seed + seed = begin + Float(params[:random_seed] || session[:random_seed] || (rand(99) / 100.0)) + rescue + 0 + end + session[:random_seed], params[:random_seed] = seed + seed = (-1..1).cover?(seed) ? seed : 1 + ::Legislation::Proposal.connection.execute "select setseed(#{seed})" + end end diff --git a/app/controllers/legislation/proposals_controller.rb b/app/controllers/legislation/proposals_controller.rb index 6b5e1c973..eb5ce4e2f 100644 --- a/app/controllers/legislation/proposals_controller.rb +++ b/app/controllers/legislation/proposals_controller.rb @@ -2,13 +2,13 @@ class Legislation::ProposalsController < Legislation::BaseController include CommentableActions include FlagActions - load_and_authorize_resource :process, class: "Legislation::Process" - load_and_authorize_resource :proposal, class: "Legislation::Proposal", through: :process - before_action :parse_tag_filter, only: :index before_action :load_categories, only: [:index, :new, :create, :edit, :map, :summary] before_action :load_geozones, only: [:edit, :map, :summary] + before_action :authenticate_user!, except: [:index, :show, :map, :summary] + load_and_authorize_resource :process, class: "Legislation::Process" + load_and_authorize_resource :proposal, class: "Legislation::Proposal", through: :process invisible_captcha only: [:create, :update], honeypot: :subtitle @@ -54,6 +54,7 @@ class Legislation::ProposalsController < Legislation::BaseController params.require(:legislation_proposal).permit(:legislation_process_id, :title, :question, :summary, :description, :video_url, :tag_list, :terms_of_service, :geozone_id, + image_attributes: [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy], documents_attributes: [:id, :title, :attachment, :cached_attachment, :user_id]) end diff --git a/app/controllers/management/base_controller.rb b/app/controllers/management/base_controller.rb index bcebfebaa..ba8ece9f2 100644 --- a/app/controllers/management/base_controller.rb +++ b/app/controllers/management/base_controller.rb @@ -6,6 +6,7 @@ class Management::BaseController < ActionController::Base helper_method :managed_user helper_method :current_user + helper_method :manager_logged_in private @@ -22,7 +23,10 @@ class Management::BaseController < ActionController::Base end def managed_user - @managed_user ||= Verification::Management::ManagedUser.find(session[:document_type], session[:document_number]) + @managed_user ||= Verification::Management::ManagedUser.find( + session[:document_type], + session[:document_number] + ) end def check_verified_user(alert_msg) @@ -49,4 +53,11 @@ class Management::BaseController < ActionController::Base def clear_password session[:new_password] = nil end + + def manager_logged_in + if current_manager + @manager_logged_in = User.find_by_manager_login(session[:manager]["login"]) + end + end + end diff --git a/app/controllers/moderation/budgets/investments_controller.rb b/app/controllers/moderation/budgets/investments_controller.rb new file mode 100644 index 000000000..7f33058e8 --- /dev/null +++ b/app/controllers/moderation/budgets/investments_controller.rb @@ -0,0 +1,24 @@ +class Moderation::Budgets::InvestmentsController < Moderation::BaseController + include FeatureFlags + include ModerateActions + + has_filters %w{pending_flag_review all with_ignored_flag}, only: :index + has_orders %w{flags created_at}, only: :index + + feature_flag :budgets + + before_action :load_resources, only: [:index, :moderate] + + load_and_authorize_resource class: 'Budget::Investment' + + private + + def resource_name + 'budget_investment' + end + + def resource_model + Budget::Investment + end + +end diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb index ee6878502..2c4fd3e9f 100644 --- a/app/controllers/notifications_controller.rb +++ b/app/controllers/notifications_controller.rb @@ -36,19 +36,11 @@ class NotificationsController < ApplicationController end private - def linkable_resource_path(notification) - case notification.linkable_resource.class.name - when "Budget::Investment" - budget_investment_path @notification.linkable_resource.budget, @notification.linkable_resource - when "Topic" - community_topic_path @notification.linkable_resource.community, @notification.linkable_resource + if notification.linkable_resource.is_a?(AdminNotification) + notification.linkable_resource.link || notifications_path else - if @notification.linkable_resource.is_a?(AdminNotification) - @notification.linkable_resource.link || notifications_path - else - url_for @notification.linkable_resource - end + polymorphic_hierarchy_path(notification.linkable_resource) end end diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index 5ea596f9a..489ea9dcf 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -1,6 +1,9 @@ class PagesController < ApplicationController + include FeatureFlags skip_authorization_check + feature_flag :help_page, if: lambda { params[:id] == "help/index" } + def show @custom_page = SiteCustomization::Page.published.find_by(slug: params[:id]) @banners = Banner.in_section('help_page').with_active diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index 7130e661d..7bb872257 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -14,6 +14,7 @@ class StatsController < ApplicationController @debate_votes = daily_cache('debate_votes') { Vote.where(votable_type: 'Debate').count } @proposal_votes = daily_cache('proposal_votes') { Vote.where(votable_type: 'Proposal').count } @comment_votes = daily_cache('comment_votes') { Vote.where(votable_type: 'Comment').count } + @investment_votes = daily_cache('budget_investment_votes') { Vote.where(votable_type: 'Budget::Investment').count } @votes = daily_cache('votes') { Vote.count } @verified_users = daily_cache('verified_users') { User.with_hidden.level_two_or_three_verified.count } diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb index 5605e3a00..d16f9c87e 100644 --- a/app/controllers/users/sessions_controller.rb +++ b/app/controllers/users/sessions_controller.rb @@ -11,7 +11,7 @@ class Users::SessionsController < Devise::SessionsController end def after_sign_out_path_for(resource) - request.referer.present? ? request.referer : super + request.referer.present? && !request.referer.match("management") ? request.referer : super end def verifying_via_email? diff --git a/app/helpers/admin_budget_investments_helper.rb b/app/helpers/admin_budget_investments_helper.rb index 742ff5bac..492225e35 100644 --- a/app/helpers/admin_budget_investments_helper.rb +++ b/app/helpers/admin_budget_investments_helper.rb @@ -1,7 +1,7 @@ module AdminBudgetInvestmentsHelper def advanced_menu_visibility - (params[:advanced_filters].empty? && params["max_per_heading"].blank?) ? 'hide' : '' + (params[:advanced_filters].empty? && params["min_total_supports"].blank?) ? 'hide' : '' end def init_advanced_menu diff --git a/app/helpers/admin_helper.rb b/app/helpers/admin_helper.rb index 038725e49..21bdb6519 100644 --- a/app/helpers/admin_helper.rb +++ b/app/helpers/admin_helper.rb @@ -1,19 +1,31 @@ module AdminHelper def side_menu - render "/#{namespace}/menu" + if namespace == 'moderation/budgets' + render "/moderation/menu" + else + render "/#{namespace}/menu" + end end def namespaced_root_path - "/#{namespace}" + if namespace == 'moderation/budgets' + "/moderation" + else + "/#{namespace}" + end end def namespaced_header_title - t("#{namespace}.header.title") + if namespace == 'moderation/budgets' + t("moderation.header.title") + else + t("#{namespace}.header.title") + end end def menu_moderated_content? - ["proposals", "debates", "comments", "hidden_users", "activity"].include?(controller_name) && controller.class.parent != Admin::Legislation + ["proposals", "debates", "comments", "hidden_users", "activity", "hidden_budget_investments"].include?(controller_name) && controller.class.parent != Admin::Legislation end def menu_budget? @@ -21,11 +33,11 @@ module AdminHelper end def menu_polls? - %w[polls questions answers].include?(controller_name) + %w[polls questions answers recounts results].include?(controller_name) end def menu_booths? - %w[officers booths officer_assignments booth_assignments recounts results shifts].include?(controller_name) + %w[officers booths shifts booth_assignments officer_assignments].include?(controller_name) end def menu_profiles? @@ -37,7 +49,7 @@ module AdminHelper end def menu_customization? - ["pages", "banners"].include?(controller_name) || menu_homepage? + ["pages", "banners", "information_texts"].include?(controller_name) || menu_homepage? end def menu_homepage? diff --git a/app/helpers/comments_helper.rb b/app/helpers/comments_helper.rb index e5060dad2..ab81c76c6 100644 --- a/app/helpers/comments_helper.rb +++ b/app/helpers/comments_helper.rb @@ -41,22 +41,7 @@ module CommentsHelper end def commentable_path(comment) - commentable = comment.commentable - - case comment.commentable_type - when "Budget::Investment" - budget_investment_path(commentable.budget_id, commentable) - when "Legislation::Question" - legislation_process_question_path(commentable.process, commentable) - when "Legislation::Annotation" - legislation_process_draft_version_annotation_path(commentable.draft_version.process, commentable.draft_version, commentable) - when "Topic" - community_topic_path(commentable.community, commentable) - when "Legislation::Proposal" - legislation_process_proposal_path(commentable.legislation_process_id, commentable) - else - commentable - end + polymorphic_hierarchy_path(comment.commentable) end def user_level_class(comment) diff --git a/app/helpers/globalize_helper.rb b/app/helpers/globalize_helper.rb index b64c1ff09..cff0b4ccd 100644 --- a/app/helpers/globalize_helper.rb +++ b/app/helpers/globalize_helper.rb @@ -6,16 +6,24 @@ module GlobalizeHelper def locale_options I18n.available_locales.map do |locale| - [name_for_locale(locale), neutral_locale(locale)] + [name_for_locale(locale), locale] end end def display_translation?(locale) - same_locale?(neutral_locale(I18n.locale), neutral_locale(locale)) ? "" : "display: none" + same_locale?(I18n.locale, locale) ? "" : "display: none;" + end + + def translation_enabled_tag(locale, enabled) + hidden_field_tag("enabled_translations[#{locale}]", (enabled ? 1 : 0)) end def css_to_display_translation?(resource, locale) - resource.translated_locales.include?(neutral_locale(locale)) || locale == I18n.locale ? "" : "display: none" + enable_locale?(resource, locale) ? "" : "display: none;" + end + + def enable_locale?(resource, locale) + resource.translated_locales.include?(locale) || locale == I18n.locale end def highlight_current?(locale) @@ -26,10 +34,6 @@ module GlobalizeHelper display_translation?(locale) end - def neutral_locale(locale) - locale.to_s.downcase.underscore.to_sym - end - def globalize(locale, &block) Globalize.with_locale(locale) do yield diff --git a/app/helpers/guides_helper.rb b/app/helpers/guides_helper.rb deleted file mode 100644 index dd504d7d6..000000000 --- a/app/helpers/guides_helper.rb +++ /dev/null @@ -1,19 +0,0 @@ -module GuidesHelper - - def new_proposal_guide - if feature?('guides') && Budget.current&.accepting? - new_guide_path - else - new_proposal_path - end - end - - def new_budget_investment_guide - if feature?('guides') - new_guide_path - else - new_budget_investment_path(current_budget) - end - end - -end diff --git a/app/helpers/legislation_helper.rb b/app/helpers/legislation_helper.rb index a93c2fa47..a737824ab 100644 --- a/app/helpers/legislation_helper.rb +++ b/app/helpers/legislation_helper.rb @@ -6,4 +6,24 @@ module LegislationHelper def format_date_for_calendar_form(date) l(date, format: "%d/%m/%Y") if date end + + def new_legislation_proposal_link_text(process) + t("proposals.index.start_proposal") + end + + def link_to_toggle_legislation_proposal_selection(proposal) + if proposal.selected? + button_text = t("admin.legislation.proposals.index.selected") + html_class = 'button expanded' + else + button_text = t("admin.legislation.proposals.index.select") + html_class = 'button hollow expanded' + end + + link_to button_text, + toggle_selection_admin_legislation_process_proposal_path(proposal.process, proposal), + remote: true, + method: :patch, + class: html_class + end end diff --git a/app/helpers/locales_helper.rb b/app/helpers/locales_helper.rb index 78ab25189..3ae6bb5ef 100644 --- a/app/helpers/locales_helper.rb +++ b/app/helpers/locales_helper.rb @@ -1,10 +1,7 @@ module LocalesHelper def name_for_locale(locale) - default = I18n.t("locale", locale: locale) - I18n.backend.translate(locale, "i18n.language.name", default: default) - rescue - nil + I18n.t("i18n.language.name", locale: locale, fallback: false, default: locale.to_s) end end diff --git a/app/helpers/polls_helper.rb b/app/helpers/polls_helper.rb index 0d5b0a605..0cb875dff 100644 --- a/app/helpers/polls_helper.rb +++ b/app/helpers/polls_helper.rb @@ -46,7 +46,7 @@ module PollsHelper end def voted_before_sign_in(question) - question.answers.where(author: current_user).any? { |vote| current_user.current_sign_in_at >= vote.updated_at } + question.answers.where(author: current_user).any? { |vote| current_user.current_sign_in_at > vote.updated_at } end end diff --git a/app/helpers/site_customization_helper.rb b/app/helpers/site_customization_helper.rb new file mode 100644 index 000000000..1b4968b6c --- /dev/null +++ b/app/helpers/site_customization_helper.rb @@ -0,0 +1,9 @@ +module SiteCustomizationHelper + def site_customization_enable_translation?(locale) + I18nContentTranslation.existing_languages.include?(locale) || locale == I18n.locale + end + + def site_customization_display_translation?(locale) + site_customization_enable_translation?(locale) ? "" : "display: none;" + end +end diff --git a/app/helpers/translatable_form_helper.rb b/app/helpers/translatable_form_helper.rb new file mode 100644 index 000000000..1e468c4ce --- /dev/null +++ b/app/helpers/translatable_form_helper.rb @@ -0,0 +1,67 @@ +module TranslatableFormHelper + def translatable_form_for(record_or_record_path, options = {}) + object = record_or_record_path.is_a?(Array) ? record_or_record_path.last : record_or_record_path + + form_for(record_or_record_path, options.merge(builder: TranslatableFormBuilder)) do |f| + + object.globalize_locales.each do |locale| + concat translation_enabled_tag(locale, enable_locale?(object, locale)) + end + + yield(f) + end + end + + def merge_translatable_field_options(options, locale) + options.merge( + class: "#{options[:class]} js-globalize-attribute".strip, + style: "#{options[:style]} #{display_translation?(locale)}".strip, + data: options.fetch(:data, {}).merge(locale: locale), + label_options: { + class: "#{options.dig(:label_options, :class)} js-globalize-attribute".strip, + style: "#{options.dig(:label_options, :style)} #{display_translation?(locale)}".strip, + data: (options.dig(:label_options, :data) || {}) .merge(locale: locale) + } + ) + end + + class TranslatableFormBuilder < FoundationRailsHelper::FormBuilder + + def translatable_text_field(method, options = {}) + translatable_field(:text_field, method, options) + end + + def translatable_text_area(method, options = {}) + translatable_field(:text_area, method, options) + end + + def translatable_cktext_area(method, options = {}) + translatable_field(:cktext_area, method, options) + end + + private + + def translatable_field(field_type, method, options = {}) + @template.capture do + @object.globalize_locales.each do |locale| + Globalize.with_locale(locale) do + localized_attr_name = @object.localized_attr_name_for(method, locale) + + label_without_locale = @object.class.human_attribute_name(method) + final_options = @template.merge_translatable_field_options(options, locale) + .reverse_merge(label: label_without_locale) + + if field_type == :cktext_area + @template.concat content_tag :div, send(field_type, localized_attr_name, final_options), + class: "js-globalize-attribute", + style: @template.display_translation?(locale), + data: { locale: locale } + else + @template.concat send(field_type, localized_attr_name, final_options) + end + end + end + end + end + end +end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 980dd1e39..4098b535e 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -52,8 +52,8 @@ module UsersHelper current_user && current_user.manager? end - def show_admin_menu? - current_administrator? || current_moderator? || current_valuator? || current_manager? + def show_admin_menu?(user = nil) + current_administrator? || current_moderator? || current_valuator? || current_manager? || (user && user.administrator?) end def interests_title_text(user) diff --git a/app/models/abilities/administrator.rb b/app/models/abilities/administrator.rb index b9f627b0e..264ad7783 100644 --- a/app/models/abilities/administrator.rb +++ b/app/models/abilities/administrator.rb @@ -14,9 +14,16 @@ module Abilities can :restore, Proposal cannot :restore, Proposal, hidden_at: nil + can :create, Legislation::Proposal + can :show, Legislation::Proposal + can :proposals, ::Legislation::Process + can :restore, Legislation::Proposal cannot :restore, Legislation::Proposal, hidden_at: nil + can :restore, Budget::Investment + cannot :restore, Budget::Investment, hidden_at: nil + can :restore, User cannot :restore, User, hidden_at: nil @@ -32,6 +39,9 @@ module Abilities can :confirm_hide, Legislation::Proposal cannot :confirm_hide, Legislation::Proposal, hidden_at: nil + can :confirm_hide, Budget::Investment + cannot :confirm_hide, Budget::Investment, hidden_at: nil + can :confirm_hide, User cannot :confirm_hide, User, hidden_at: nil @@ -51,8 +61,7 @@ module Abilities can :manage, Dashboard::Action can [:read, :update, :valuate, :destroy, :summary], SpendingProposal - - can [:index, :read, :new, :create, :update, :destroy, :calculate_winners, :read_results], Budget + can [:index, :read, :new, :create, :update, :destroy, :calculate_winners], Budget can [:read, :create, :update, :destroy], Budget::Group can [:read, :create, :update, :destroy], Budget::Heading can [:hide, :update, :toggle_selection], Budget::Investment @@ -75,6 +84,9 @@ module Abilities can :manage, SiteCustomization::Image can :manage, SiteCustomization::ContentBlock + can :access, :ckeditor + can :manage, Ckeditor::Picture + can [:manage], ::Legislation::Process can [:manage], ::Legislation::DraftVersion can [:manage], ::Legislation::Question diff --git a/app/models/abilities/common.rb b/app/models/abilities/common.rb index c5f947abf..af819145d 100644 --- a/app/models/abilities/common.rb +++ b/app/models/abilities/common.rb @@ -66,6 +66,9 @@ module Abilities can [:flag, :unflag], Legislation::Proposal cannot [:flag, :unflag], Legislation::Proposal, author_id: user.id + can [:flag, :unflag], Budget::Investment + cannot [:flag, :unflag], Budget::Investment, author_id: user.id + can [:create, :destroy], Follow can [:destroy], Document do |document| diff --git a/app/models/abilities/moderation.rb b/app/models/abilities/moderation.rb index 801e752ed..86f44f25e 100644 --- a/app/models/abilities/moderation.rb +++ b/app/models/abilities/moderation.rb @@ -63,6 +63,15 @@ module Abilities cannot :moderate, ProposalNotification, author_id: user.id can :index, ProposalNotification + + can :hide, Budget::Investment, hidden_at: nil + cannot :hide, Budget::Investment, author_id: user.id + + can :ignore_flag, Budget::Investment, ignored_flag_at: nil, hidden_at: nil + cannot :ignore_flag, Budget::Investment, author_id: user.id + + can :moderate, Budget::Investment + cannot :moderate, Budget::Investment, author_id: user.id end end end diff --git a/app/models/activity.rb b/app/models/activity.rb index 6c357d891..afe25bed3 100644 --- a/app/models/activity.rb +++ b/app/models/activity.rb @@ -2,7 +2,7 @@ class Activity < ActiveRecord::Base belongs_to :actionable, -> { with_hidden }, polymorphic: true belongs_to :user, -> { with_hidden } - VALID_ACTIONS = %w(hide block restore valuate) + VALID_ACTIONS = %w(hide block restore valuate email) validates :action, inclusion: {in: VALID_ACTIONS} @@ -11,6 +11,7 @@ class Activity < ActiveRecord::Base scope :on_users, -> { where(actionable_type: 'User') } scope :on_comments, -> { where(actionable_type: 'Comment') } scope :on_budget_investments, -> { where(actionable_type: 'Budget::Investment') } + scope :on_system_emails, -> { where(actionable_type: 'ProposalNotification') } scope :for_render, -> { includes(user: [:moderator, :administrator]).includes(:actionable) } def self.log(user, action, actionable) diff --git a/app/models/admin_notification.rb b/app/models/admin_notification.rb index eccd90910..4291206bc 100644 --- a/app/models/admin_notification.rb +++ b/app/models/admin_notification.rb @@ -1,6 +1,10 @@ class AdminNotification < ActiveRecord::Base include Notifiable + translates :title, touch: true + translates :body, touch: true + globalize_accessors + validates :title, presence: true validates :body, presence: true validates :segment_recipient, presence: true diff --git a/app/models/banner.rb b/app/models/banner.rb index 26d683e9c..37824d01f 100644 --- a/app/models/banner.rb +++ b/app/models/banner.rb @@ -3,6 +3,10 @@ class Banner < ActiveRecord::Base acts_as_paranoid column: :hidden_at include ActsAsParanoidAliases + translates :title, touch: true + translates :description, touch: true + globalize_accessors + validates :title, presence: true, length: { minimum: 2 } validates :description, presence: true diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb index ee3292fcd..e531612a8 100644 --- a/app/models/budget/investment.rb +++ b/app/models/budget/investment.rb @@ -23,6 +23,7 @@ class Budget include Relationable include Notifiable include Filterable + include Flaggable belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' belongs_to :heading @@ -57,6 +58,10 @@ class Budget scope :sort_by_price, -> { reorder(price: :desc, confidence_score: :desc, id: :desc) } scope :sort_by_random, ->(seed) { reorder("budget_investments.id % #{seed.to_f.nonzero? ? seed.to_f : 1}, budget_investments.id") } + scope :sort_by_id, -> { order("id DESC") } + scope :sort_by_title, -> { order("title ASC") } + scope :sort_by_supports, -> { order("cached_votes_up DESC") } + scope :valuation_open, -> { where(valuation_finished: false) } scope :without_admin, -> { valuation_open.where(administrator_id: nil) } scope :without_valuator, -> { valuation_open.where(valuator_assignments_count: 0) } @@ -77,6 +82,8 @@ class Budget scope :winners, -> { selected.compatible.where(winner: true) } scope :unselected, -> { not_unfeasible.where(selected: false) } scope :last_week, -> { where("created_at >= ?", 7.days.ago)} + scope :sort_by_flags, -> { order(flags_count: :desc, updated_at: :desc) } + scope :sort_by_created_at, -> { reorder(created_at: :desc) } scope :by_budget, ->(budget) { where(budget: budget) } scope :by_group, ->(group_id) { where(group_id: group_id) } @@ -109,7 +116,8 @@ class Budget budget = Budget.find_by(slug: params[:budget_id]) || Budget.find_by(id: params[:budget_id]) results = Investment.by_budget(budget) - results = limit_results(budget, params, results) if params[:max_per_heading].present? + results = results.where("cached_votes_up + physical_votes >= ?", + params[:min_total_supports]) if params[:min_total_supports].present? results = results.where(group_id: params[:group_id]) if params[:group_id].present? results = results.by_tag(params[:tag_name]) if params[:tag_name].present? results = results.by_heading(params[:heading_id]) if params[:heading_id].present? @@ -132,6 +140,12 @@ class Budget results.where("budget_investments.id IN (?)", ids) end + def self.order_filter(sorting_param) + if sorting_param.present? && SORTING_OPTIONS.include?(sorting_param) + send("sort_by_#{sorting_param}") + end + end + def self.limit_results(budget, params, results) max_per_heading = params[:max_per_heading].to_i return results if max_per_heading <= 0 diff --git a/app/models/budget/investment/milestone.rb b/app/models/budget/investment/milestone.rb index b00744138..1790f7323 100644 --- a/app/models/budget/investment/milestone.rb +++ b/app/models/budget/investment/milestone.rb @@ -8,7 +8,7 @@ class Budget accepted_content_types: [ "application/pdf" ] translates :title, :description, touch: true - globalize_accessors locales: [:en, :es, :fr, :nl, :val, :pt_br] + globalize_accessors belongs_to :investment belongs_to :status, class_name: 'Budget::Investment::Status' diff --git a/app/models/ckeditor/asset.rb b/app/models/ckeditor/asset.rb new file mode 100644 index 000000000..cf636ed19 --- /dev/null +++ b/app/models/ckeditor/asset.rb @@ -0,0 +1,4 @@ +class Ckeditor::Asset < ActiveRecord::Base + include Ckeditor::Orm::ActiveRecord::AssetBase + include Ckeditor::Backend::Paperclip +end diff --git a/app/models/ckeditor/picture.rb b/app/models/ckeditor/picture.rb new file mode 100644 index 000000000..445c2bbd9 --- /dev/null +++ b/app/models/ckeditor/picture.rb @@ -0,0 +1,14 @@ +class Ckeditor::Picture < Ckeditor::Asset + has_attached_file :data, + url: '/ckeditor_assets/pictures/:id/:style_:basename.:extension', + path: ':rails_root/public/ckeditor_assets/pictures/:id/:style_:basename.:extension', + styles: { content: '800>', thumb: '118x100#' } + + validates_attachment_presence :data + validates_attachment_size :data, less_than: 2.megabytes + validates_attachment_content_type :data, content_type: /\Aimage/ + + def url_content + url(:content) + end +end diff --git a/app/models/i18n_content.rb b/app/models/i18n_content.rb new file mode 100644 index 000000000..c291e696c --- /dev/null +++ b/app/models/i18n_content.rb @@ -0,0 +1,11 @@ +class I18nContent < ActiveRecord::Base + + scope :by_key, -> (key){ where(key: key) } + scope :begins_with_key, -> (key){ where("key ILIKE ?", "#{key}?%") } + + validates :key, uniqueness: true + + translates :value, touch: true + globalize_accessors locales: [:en, :es, :fr, :nl] + +end diff --git a/app/models/i18n_content_translation.rb b/app/models/i18n_content_translation.rb new file mode 100644 index 000000000..76447d83e --- /dev/null +++ b/app/models/i18n_content_translation.rb @@ -0,0 +1,5 @@ +class I18nContentTranslation < ActiveRecord::Base + def self.existing_languages + self.select(:locale).uniq.map{ |l| l.locale.to_sym }.to_a + end +end diff --git a/app/models/image.rb b/app/models/image.rb index 25cf449bd..8f62f94d8 100644 --- a/app/models/image.rb +++ b/app/models/image.rb @@ -2,7 +2,7 @@ class Image < ActiveRecord::Base include ImagesHelper include ImageablesHelper - TITLE_LEGHT_RANGE = 4..80 + TITLE_LENGTH_RANGE = 4..80 MIN_SIZE = 475 MAX_IMAGE_SIZE = 1.megabyte ACCEPTED_CONTENT_TYPE = %w(image/jpeg image/jpg).freeze @@ -23,7 +23,7 @@ class Image < ActiveRecord::Base validate :attachment_presence validate :validate_attachment_content_type, if: -> { attachment.present? } validate :validate_attachment_size, if: -> { attachment.present? } - validates :title, presence: true, length: { in: TITLE_LEGHT_RANGE } + validates :title, presence: true, length: { in: TITLE_LENGTH_RANGE } validates :user_id, presence: true validates :imageable_id, presence: true, if: -> { persisted? } validates :imageable_type, presence: true, if: -> { persisted? } diff --git a/app/models/legislation/draft_version.rb b/app/models/legislation/draft_version.rb index 7b9e6a0aa..18e4489b7 100644 --- a/app/models/legislation/draft_version.rb +++ b/app/models/legislation/draft_version.rb @@ -4,6 +4,13 @@ class Legislation::DraftVersion < ActiveRecord::Base acts_as_paranoid column: :hidden_at include ActsAsParanoidAliases + translates :title, touch: true + translates :changelog, touch: true + translates :body, touch: true + translates :body_html, touch: true + translates :toc_html, touch: true + globalize_accessors + belongs_to :process, class_name: 'Legislation::Process', foreign_key: 'legislation_process_id' has_many :annotations, class_name: 'Legislation::Annotation', foreign_key: 'legislation_draft_version_id', dependent: :destroy @@ -19,8 +26,17 @@ class Legislation::DraftVersion < ActiveRecord::Base renderer = Redcarpet::Render::HTML.new(with_toc_data: true) toc_renderer = Redcarpet::Render::HTML_TOC.new(with_toc_data: true) - self.body_html = Redcarpet::Markdown.new(renderer).render(body) - self.toc_html = Redcarpet::Markdown.new(toc_renderer).render(body) + if body_changed? + self.body_html = Redcarpet::Markdown.new(renderer).render(body) + self.toc_html = Redcarpet::Markdown.new(toc_renderer).render(body) + end + + translations.each do |translation| + if translation.body_changed? + translation.body_html = Redcarpet::Markdown.new(renderer).render(translation.body) + translation.toc_html = Redcarpet::Markdown.new(toc_renderer).render(translation.body) + end + end end def display_title diff --git a/app/models/legislation/process.rb b/app/models/legislation/process.rb index ff683aa00..bc3df6be4 100644 --- a/app/models/legislation/process.rb +++ b/app/models/legislation/process.rb @@ -9,6 +9,12 @@ class Legislation::Process < ActiveRecord::Base acts_as_paranoid column: :hidden_at acts_as_taggable_on :customs + translates :title, touch: true + translates :summary, touch: true + translates :description, touch: true + translates :additional_info, touch: true + globalize_accessors + PHASES_AND_PUBLICATIONS = %i(debate_phase allegations_phase proposals_phase draft_publication result_publication).freeze has_many :draft_versions, -> { order(:id) }, class_name: 'Legislation::DraftVersion', diff --git a/app/models/legislation/proposal.rb b/app/models/legislation/proposal.rb index 3fe6a9d96..43a5cf1e8 100644 --- a/app/models/legislation/proposal.rb +++ b/app/models/legislation/proposal.rb @@ -11,6 +11,7 @@ class Legislation::Proposal < ActiveRecord::Base include Communitable include Documentable include Notifiable + include Imageable documentable max_documents_allowed: 3, max_file_size: 3.megabytes, @@ -44,9 +45,15 @@ class Legislation::Proposal < ActiveRecord::Base scope :sort_by_confidence_score, -> { reorder(confidence_score: :desc) } scope :sort_by_created_at, -> { reorder(created_at: :desc) } scope :sort_by_most_commented, -> { reorder(comments_count: :desc) } + scope :sort_by_title, -> { reorder(title: :asc) } + scope :sort_by_id, -> { reorder(id: :asc) } + scope :sort_by_supports, -> { reorder(cached_votes_up: :desc) } scope :sort_by_random, -> { reorder("RANDOM()") } scope :sort_by_flags, -> { order(flags_count: :desc, updated_at: :desc) } scope :last_week, -> { where("proposals.created_at >= ?", 7.days.ago)} + scope :selected, -> { where(selected: true) } + scope :random, -> { sort_by_random } + scope :winners, -> { selected.sort_by_confidence_score } def to_param "#{id}-#{title}".parameterize diff --git a/app/models/legislation/question.rb b/app/models/legislation/question.rb index 2ca5fb39a..0c9a5703f 100644 --- a/app/models/legislation/question.rb +++ b/app/models/legislation/question.rb @@ -3,6 +3,9 @@ class Legislation::Question < ActiveRecord::Base include ActsAsParanoidAliases include Notifiable + translates :title, touch: true + globalize_accessors + belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' belongs_to :process, class_name: 'Legislation::Process', foreign_key: 'legislation_process_id' @@ -11,7 +14,7 @@ class Legislation::Question < ActiveRecord::Base has_many :answers, class_name: 'Legislation::Answer', foreign_key: 'legislation_question_id', dependent: :destroy, inverse_of: :question has_many :comments, as: :commentable, dependent: :destroy - accepts_nested_attributes_for :question_options, reject_if: proc { |attributes| attributes[:value].blank? }, allow_destroy: true + accepts_nested_attributes_for :question_options, reject_if: proc { |attributes| attributes.all? { |k, v| v.blank? } }, allow_destroy: true validates :process, presence: true validates :title, presence: true diff --git a/app/models/legislation/question_option.rb b/app/models/legislation/question_option.rb index f7927dd1a..1ee46ee90 100644 --- a/app/models/legislation/question_option.rb +++ b/app/models/legislation/question_option.rb @@ -2,6 +2,9 @@ class Legislation::QuestionOption < ActiveRecord::Base acts_as_paranoid column: :hidden_at include ActsAsParanoidAliases + translates :value, touch: true + globalize_accessors + belongs_to :question, class_name: 'Legislation::Question', foreign_key: 'legislation_question_id', inverse_of: :question_options has_many :answers, class_name: 'Legislation::Answer', foreign_key: 'legislation_question_id', dependent: :destroy, inverse_of: :question diff --git a/app/models/newsletter.rb b/app/models/newsletter.rb index 8c3a83cfa..b99e3e2ca 100644 --- a/app/models/newsletter.rb +++ b/app/models/newsletter.rb @@ -8,6 +8,9 @@ class Newsletter < ActiveRecord::Base validates_format_of :from, :with => /@/ + acts_as_paranoid column: :hidden_at + include ActsAsParanoidAliases + def list_of_recipient_emails UserSegments.user_segment_emails(segment_recipient) if valid_segment_recipient? end @@ -20,9 +23,47 @@ class Newsletter < ActiveRecord::Base sent_at.nil? end + def deliver + run_at = first_batch_run_at + list_of_recipient_emails_in_batches.each do |recipient_emails| + recipient_emails.each do |recipient_email| + if valid_email?(recipient_email) + Mailer.delay(run_at: run_at).newsletter(self, recipient_email) + log_delivery(recipient_email) + end + end + run_at += batch_interval + end + end + + def batch_size + 10000 + end + + def batch_interval + 20.minutes + end + + def first_batch_run_at + Time.current + end + + def list_of_recipient_emails_in_batches + list_of_recipient_emails.in_groups_of(batch_size, false) + end + private def validate_segment_recipient errors.add(:segment_recipient, :invalid) unless valid_segment_recipient? end + + def valid_email?(email) + email.match(/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i) + end + + def log_delivery(recipient_email) + user = User.where(email: recipient_email).first + Activity.log(user, :email, self) + end end diff --git a/app/models/notification.rb b/app/models/notification.rb index dacedb762..f99cbc1ca 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -68,4 +68,29 @@ class Notification < ActiveRecord::Base end end + def self.send_pending + run_at = first_batch_run_at + User.email_digest.find_in_batches(batch_size: batch_size) do |users| + users.each do |user| + email_digest = EmailDigest.new(user) + email_digest.deliver(run_at) + end + run_at += batch_interval + end + end + + private + + def self.batch_size + 10000 + end + + def self.batch_interval + 20.minutes + end + + def self.first_batch_run_at + Time.current + end + end diff --git a/app/models/poll.rb b/app/models/poll.rb index 9f39c709f..c879b3a25 100644 --- a/app/models/poll.rb +++ b/app/models/poll.rb @@ -4,6 +4,11 @@ class Poll < ActiveRecord::Base include ActsAsParanoidAliases include Notifiable + translates :name, touch: true + translates :summary, touch: true + translates :description, touch: true + globalize_accessors + RECOUNT_DURATION = 1.week has_many :booth_assignments, class_name: "Poll::BoothAssignment" diff --git a/app/models/poll/answer.rb b/app/models/poll/answer.rb index 78122188a..851ce2fbc 100644 --- a/app/models/poll/answer.rb +++ b/app/models/poll/answer.rb @@ -9,7 +9,9 @@ class Poll::Answer < ActiveRecord::Base validates :author, presence: true validates :answer, presence: true - validates :answer, inclusion: { in: ->(a) { a.question.question_answers.pluck(:title) }}, + validates :answer, inclusion: { in: ->(a) { a.question.question_answers + .joins(:translations) + .pluck("poll_question_answer_translations.title") }}, unless: ->(a) { a.question.blank? } scope :by_author, ->(author_id) { where(author_id: author_id) } diff --git a/app/models/poll/partial_result.rb b/app/models/poll/partial_result.rb index 0550650a5..72a3372b3 100644 --- a/app/models/poll/partial_result.rb +++ b/app/models/poll/partial_result.rb @@ -10,7 +10,9 @@ class Poll::PartialResult < ActiveRecord::Base validates :question, presence: true validates :author, presence: true validates :answer, presence: true - validates :answer, inclusion: { in: ->(a) { a.question.question_answers.pluck(:title) }}, + validates :answer, inclusion: { in: ->(a) { a.question.question_answers + .joins(:translations) + .pluck("poll_question_answer_translations.title") }}, unless: ->(a) { a.question.blank? } validates :origin, inclusion: { in: VALID_ORIGINS } diff --git a/app/models/poll/question.rb b/app/models/poll/question.rb index 1e6cb8386..35bc56222 100644 --- a/app/models/poll/question.rb +++ b/app/models/poll/question.rb @@ -5,6 +5,9 @@ class Poll::Question < ActiveRecord::Base acts_as_paranoid column: :hidden_at include ActsAsParanoidAliases + translates :title, touch: true + globalize_accessors + belongs_to :poll belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' diff --git a/app/models/poll/question/answer.rb b/app/models/poll/question/answer.rb index 1c10e2b5c..56655e457 100644 --- a/app/models/poll/question/answer.rb +++ b/app/models/poll/question/answer.rb @@ -1,6 +1,11 @@ class Poll::Question::Answer < ActiveRecord::Base include Galleryable include Documentable + + translates :title, touch: true + translates :description, touch: true + globalize_accessors + documentable max_documents_allowed: 3, max_file_size: 3.megabytes, accepted_content_types: [ "application/pdf" ] @@ -15,7 +20,7 @@ class Poll::Question::Answer < ActiveRecord::Base before_validation :set_order, on: :create def description - super.try :html_safe + self[:description].try :html_safe end def self.order_answers(ordered_array) diff --git a/app/models/proposal.rb b/app/models/proposal.rb index 571942099..e96e49592 100644 --- a/app/models/proposal.rb +++ b/app/models/proposal.rb @@ -223,7 +223,7 @@ class Proposal < ActiveRecord::Base end def users_to_notify - (voters + followers).uniq + (voters + followers).uniq - [author] end def self.proposals_orders(user) diff --git a/app/models/proposal_notification.rb b/app/models/proposal_notification.rb index f0a6850f7..686fdc075 100644 --- a/app/models/proposal_notification.rb +++ b/app/models/proposal_notification.rb @@ -37,6 +37,11 @@ class ProposalNotification < ActiveRecord::Base proposal end + def moderate_system_email(moderator) + Notification.where(notifiable_type: 'ProposalNotification', notifiable: self).destroy_all + Activity.log(moderator, :hide, self) + end + def ignore_flag update(ignored_at: Time.current) end diff --git a/app/models/site_customization/content_block.rb b/app/models/site_customization/content_block.rb index c08beb52e..81d624ac3 100644 --- a/app/models/site_customization/content_block.rb +++ b/app/models/site_customization/content_block.rb @@ -1,5 +1,5 @@ class SiteCustomization::ContentBlock < ActiveRecord::Base - VALID_BLOCKS = %w(top_links footer) + VALID_BLOCKS = %w(top_links footer subnavigation_left subnavigation_right) validates :locale, presence: true, inclusion: { in: I18n.available_locales.map(&:to_s) } validates :name, presence: true, uniqueness: { scope: :locale }, inclusion: { in: VALID_BLOCKS } diff --git a/app/models/site_customization/image.rb b/app/models/site_customization/image.rb index e336f6f9a..b10f3799f 100644 --- a/app/models/site_customization/image.rb +++ b/app/models/site_customization/image.rb @@ -1,7 +1,7 @@ class SiteCustomization::Image < ActiveRecord::Base VALID_IMAGES = { "icon_home" => [330, 240], - "logo_header" => [80, 80], + "logo_header" => [260, 80], "social_media_icon" => [470, 246], "social_media_icon_twitter" => [246, 246], "apple-touch-icon-200" => [200, 200] diff --git a/app/models/site_customization/page.rb b/app/models/site_customization/page.rb index 2b60dea81..34e470759 100644 --- a/app/models/site_customization/page.rb +++ b/app/models/site_customization/page.rb @@ -6,11 +6,15 @@ class SiteCustomization::Page < ActiveRecord::Base format: { with: /\A[0-9a-zA-Z\-_]*\Z/, message: :slug_format } validates :title, presence: true validates :status, presence: true, inclusion: { in: VALID_STATUSES } - validates :locale, presence: true + + translates :title, touch: true + translates :subtitle, touch: true + translates :content, touch: true + globalize_accessors scope :published, -> { where(status: 'published').order('id DESC') } scope :with_more_info_flag, -> { where(status: 'published', more_info_flag: true).order('id ASC') } - scope :with_same_locale, -> { where(locale: I18n.locale).order('id ASC') } + scope :with_same_locale, -> { joins(:translations).where("site_customization_page_translations.locale": I18n.locale) } def url "/#{slug}" diff --git a/app/models/user.rb b/app/models/user.rb index 67fe5d104..3a274f056 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -182,6 +182,7 @@ class User < ActiveRecord::Base debates_ids = Debate.where(author_id: id).pluck(:id) comments_ids = Comment.where(user_id: id).pluck(:id) proposal_ids = Proposal.where(author_id: id).pluck(:id) + investment_ids = Budget::Investment.where(author_id: id).pluck(:id) proposal_notification_ids = ProposalNotification.where(author_id: id).pluck(:id) hide @@ -189,6 +190,7 @@ class User < ActiveRecord::Base Debate.hide_all debates_ids Comment.hide_all comments_ids Proposal.hide_all proposal_ids + Budget::Investment.hide_all investment_ids ProposalNotification.hide_all proposal_notification_ids end @@ -327,6 +329,10 @@ class User < ActiveRecord::Base where(conditions.to_hash).where(["username = ?", login]).first end + def self.find_by_manager_login(manager_login) + find_by(id: manager_login.split("_").last) + end + def interests followables = follows.map(&:followable) followables.compact.map { |followable| followable.tags.map(&:name) }.flatten.compact.uniq diff --git a/app/models/widget/card.rb b/app/models/widget/card.rb index b408cc0a9..73bc3eb00 100644 --- a/app/models/widget/card.rb +++ b/app/models/widget/card.rb @@ -1,8 +1,15 @@ class Widget::Card < ActiveRecord::Base include Imageable + # table_name must be set before calls to 'translates' self.table_name = "widget_cards" + translates :label, touch: true + translates :title, touch: true + translates :description, touch: true + translates :link_text, touch: true + globalize_accessors + def self.header where(header: true) end @@ -10,4 +17,4 @@ class Widget::Card < ActiveRecord::Base def self.body where(header: false).order(:created_at) end -end \ No newline at end of file +end diff --git a/app/views/admin/_menu.html.erb b/app/views/admin/_menu.html.erb index f05c5abf9..6e201e2ca 100644 --- a/app/views/admin/_menu.html.erb +++ b/app/views/admin/_menu.html.erb @@ -7,7 +7,7 @@ <%= t("admin.menu.title_polls") %>